/*** * * File: tcp.c * * 27-Aug-93 fr - final cleanup * 28-Aug-92 lr * maybe it's worth resetting s->safetysig on closed sockets. * 09-Jul-92 lr * sometimes, a connection won't start. * check carefully the udp implementations, it's likely to be * broken by variable-size buffers. * *** TCP.C - the true worker of TCP *** contains all opens, closes, major read/write routines and *** basic IP handler for incomming packets *** Much of the TCP/UDP/IP layering is done at the data structure *** level, not in separate routines or tasks *** The network is kept alive by periodically calling tcp_tick(), *** which scans all the sockets. They can be in three 'unhappy' *** states: *** NO means they don't need any activity. *** YES means they need attention at next tick. *** VERY means they need immediate attention. *** ***/ #define WATTCP_KERNEL #define NEWPCTCP #include #include #define NO 0 /*** values for the unhappy flag ***/ #define YES 1 #define VERY 2 #define LAZY 1 /** values for inlist field of tcp sockets **/ #define BUSY 2 /*** *** Debugging stuff *** DB4 always prints a message (these are bugs in this program) *** or in the other party *** DB3 prints a message if debug_on=1 *** DB2 only prints a message if debug is #defined *** ***/ /* * DB_W is a fast output routine for debugging the tcp * state machine -- is currently unused */ #ifndef debug1 # define DB_W(x) #else # define DB_W(x) _db_w(x) void _db_w(char *s) { static char far *vdu_base=(char far *)0xb8000000L; static int vdu_col=0; char c; while(c= *s++){ vdu_base[vdu_col]=c; vdu_col +=2; if (vdu_col>=3200) vdu_col=0; } } #endif /* debug1 */ /*** *** end of debugging stuff ***/ /************************** STATICS **********************************/ /* static void largecheck( void *s, int size ); no more static fr */ static void udp_close( udp_Socket *ds ); static void tcp_close( tcp_Socket *ds ); static void tcp_abort( tcp_Socket *ds ); static void tcp_Retransmitter(void); static void tcp_unthread( tcp_Socket *ds); static int udp_write(register udp_Socket *s, byte *datap, int len); static int udp_read( register udp_Socket *s, byte *datap, int maxlen); static int tcp_read(register tcp_Socket *s, byte *datap, int maxlen); static int tcp_write(register tcp_Socket *s, byte *dp, int len); static void tcp_Flush(tcp_Socket *s); static void udp_handler(in_Header *ip); static void tcp_close_sock(register tcp_Socket *s, char *msg); static void recompute_rto(register tcp_Socket *s); static int acceptable(tcp_Socket *s, in_Header *ip, tcp_Header *tp); static void set_UP(int flags, tcp_Socket *s, tcp_Header *tp); static void tcp_handler1(register tcp_Socket *s,in_Header *ip,tcp_Header *tp,word flags,int len); static void tcp_handler(in_Header *ip); static int tcp_ProcessData(register tcp_Socket *s, tcp_Header *tp, int len); static void tcp_send(register tcp_Socket *s); static void sock_update( tcp_Socket *s ); /*static void (*system_yield)() = NULL; fr put into proto.h */ static unsigned long far *realclock = (unsigned long far *)0x000046cL; static int ip_id = 0; /* packet number */ static unsigned next_tcp_port = 1024; /* auto incremented */ static unsigned next_udp_port = 1024; static udp_Socket *udp_allsocs = NULL; /*static tcp_Socket *tcp_allsocs = NULL; fr traslated to proto.h */ static initialized = 0; /*************** END OF STATICS *******************************/ /*** *** ip user level timer stuff *** void ip_timer_init( void *s, int delayseconds ) *** int ip_timer_expired( void *s ) *** - 0 if not expired ***/ void ip_timer_init( udp_Socket *s , int delayseconds ) { s->usertimer = delayseconds ? set_ttimeout( delayseconds*18 ): 0 ; } int ip_timer_expired( udp_Socket *s) { return( s->usertimer ? chk_timeout( s->usertimer) : 0 ); } longword MsecClock(void) { return( (set_ttimeout(0))*55L); } /*** *** Timer definitions. All of them are in ticks. ***/ #define RETRAN_STRAT_TIME 1 /* how often do we check retrans. tables*/ #define tcp_OPENTIMEOUT 31*18 /* timeout for opens */ #define tcp_CLOSETIMEOUT 31*18 /* timeout for close */ #define tcp_FLUSHTIMEOUT 31*18 /* timeout for flushing data after a close */ #define tcp_TTIMEOUT 31*18 /* timeout during a connection */ #define tcpAbortTimeout 18*13 /* about 5 minutes */ /*** *** Initialization/termination stuff ***/ /*** *** Shut down the card and all services ***/ void tcp_shutdown(void) { while (tcp_allsocs) tcp_abort( tcp_allsocs ); _eth_release(); initialized = 0; } /*** *** tcp_Init - Initialize the tcp implementation *** - may be called more than once without hurting ***/ void tcp_init(void) { /* extern int _arp_last_gateway, _last_nameserver; */ DB2((FDB,"tcp_init():\n")); if (!initialized) { /* initialize ethernet interface */ initialized = 1; _eth_init(); /* in pcpkt.c */ /*ip_id = (int)set_ttimeout( 0 ) ;*/ /* reset the various tables */ _arp_last_gateway = 0; /* reset the gateway table */ _last_nameserver = 0; /* reset the nameserver table */ _last_cookie = 0; /* eat all remaining crumbs */ *_hostname = 0; /* reset the host's name */ if (!my_ip_addr) { /* using our local reverse ethernet address thingamajig */ memcpy( &my_ip_addr, &_eth_addr[2], 4 ); } _eth_free( 0 ); next_udp_port=next_tcp_port=1024+(unsigned)((*realclock>>7)& 0x1ff); } } /*** *** Checks for bugs in large model C compiler ***/ void largecheck( void *s, int size ) { DB2((FDB,"largecheck():\n")); if ( (FP_OFF(s)) > (-size)) { outs("ERROR: C compiler sock size error\n"); exit( 3 ); } } /*** Setting new default socket's buffers dimensions. ***/ void set_tcp_rxbufsize(word size) { RxMaxBufSize = size; } void set_tcp_txbufsize(word size) { TxMaxBufSize = size; } void set_udp_bufsize(word size) { MaxBufSize = size; } int udp_open(udp_Socket *s, word lport, longword ina, word port, procref datahandler) { DB2((FDB,"udp_open():\n")); largecheck( s, sizeof( udp_Socket )); udp_close( s ); memset( s, 0, sizeof( udp_Socket )); s->ip_type = UDP_PROTO; if (lport == 0) lport= ++next_udp_port; /* get a nonzero port */ s->myport = lport; /* check for broadcast */ if ( ina == 0xFFFFFFFF || ina == 0 ) { DB2((FDB,"udp_open(): found a broadcast\n")); memset( s->hisethaddr, 0xff, sizeof( eth_address )); } else { DB2((FDB,"udp_open(): calling _arp_resolve \n")); if ( ! _arp_resolve(ina, &(s->hisethaddr)) ) return( 0 ); DB2((FDB,"udp_open(): _arp_resolve succeded.\n")); } s->hisaddr = ina; s->hisport = port; s->dataHandler = datahandler; s->usr_yield = system_yield; s->next = udp_allsocs; if ((s->rdata = (byte *) malloc((int)MaxBufSize)) == NULL) { DB3((stderr,"Warning: not enough space for rdata buffer")); udp_close(s); return(0); } s->rxbufsize=MaxBufSize; udp_allsocs = s; s->safetysig = SAFETYUDP; return( 1 ); } /*** *** Actively open a TCP connection to a particular destination. *** Return 0 on error */ /*** CHECK THIS ***/ int tcp_open(register tcp_Socket *s, word lport, longword ina, word port, procref datahandler) { DB2((FDB,"tcp_open():\n")); largecheck( s, sizeof( tcp_Socket )); /* stack space warnings */ tcp_unthread(s); /* just in case not totally closed */ memset( s, 0, sizeof( tcp_Socket)); s->ip_type = TCP_PROTO; s->mss = _mss; s->state = tcp_StateSYNSENT; DB2((FDB,"tcp_open(): going to SYNSENT\n")); s->timeout = set_ttimeout( tcp_OPENTIMEOUT ); /*** *** If cannot connect and the timeout expires, the connection *** is aborted. ***/ if (lport==0) lport= ++next_tcp_port; /* get a nonzero port val */ s->myport = lport; DB2((FDB,"Calling _arp_resolve():\n")); if (! _arp_resolve(ina, &(s->hisethaddr))) return( 0 ); DB2((FDB,"_arp_resolve() succeeded.\n")); s->hisaddr = ina; s->hisport = port; /* choose a pseudo-random iss */ /* s->seqnum = intel( set_ttimeout( 0 )) & 0xffff0000 ; */ s->seqnum = (set_ttimeout( 0 ) & 0xfff) << 20 ; s->unacked = s->datalen = 0; /* redundant */ s->flags = tcp_FlagSYN; s->dataHandler = datahandler; s->usr_yield = system_yield; s->next = tcp_allsocs; s->rxbufsize = RxMaxBufSize; if ((s->rdata = (byte *) malloc((int)RxMaxBufSize)) == NULL) { DB3((stderr,"Warning: tcp_open(): not enough room for rdata buffer")); tcp_unthread(s); return(0); } s->txbufsize = TxMaxBufSize; if ((s->data = (byte *) malloc((int)TxMaxBufSize)) == NULL) { DB3((stderr,"Warning: tcp_open(): not enough room for tx data buffer")); tcp_unthread(s); return(0); } tcp_allsocs = s; s->safetysig = SAFETYTCP; DB2((FDB,"Sending first SYN from tcp_open\n")); s->rto= 9; /* start 1/2 sec. rto */ tcp_send(s); return( 1 ); } /*** *** Passive open: listen for a connection on a particular port ***/ /*** CHECK THIS ***/ int tcp_listen(register tcp_Socket *s, word lport, longword ina, word port, procref datahandler, word timeout) { DB2((FDB,"tcp_listen():\n")); largecheck( s, sizeof( tcp_Socket )); tcp_unthread(s); /* just in case not totally closed */ memset( s, 0, sizeof( tcp_Socket)); s->ip_type = TCP_PROTO; s->mss = _mss; s->state = tcp_StateLISTEN; DB2((FDB,"tcp_listen(): going to LISTEN\n")); s->timeout = timeout ? set_timeout( timeout ) : 0; /*** *** Here it's my choice to have or not a timeout *** If there is, and it expires, the connection is aborted. ***/ s->myport = lport; s->hisport = port; s->hisaddr = ina; s->seqnum = intel( (longword)s ); /* not a good idea, but works */ s->datalen = 0; s->flags = 0; s->rto= 9; /* start 1/2 sec. rto */ s->unhappy = NO; /* this is a PASSIVE open */ s->dataHandler = datahandler; s->usr_yield = system_yield; s->safetysig = SAFETYTCP; s->next = tcp_allsocs; s->rxbufsize = RxMaxBufSize; if ((s->rdata = (byte *) malloc((int)RxMaxBufSize)) == NULL) { DB3((stderr,"Warning: tcp_listen(): not enough space for rdata buffer")); tcp_unthread(s); return(0); } s->txbufsize = TxMaxBufSize; if ((s->data = (byte *) malloc((int)TxMaxBufSize)) == NULL) { DB3((stderr,"Warning: tcp_listen(): not enough space for Tx data buffer")); tcp_unthread(s); return(0); } tcp_allsocs = s; return( 1 ); } /*** added by fr ***/ #ifdef unused /* * multi-listen is superseded by select() */ tcp_Socket * multi_listen(int numsock, word lport, longword ina, word port, procref datahandler, word mode) { tcp_Socket *s[20], *sret = NULL, *bsp=NULL, *sldp = NULL; int i; DB2((FDB,"multi_listen():\n")); if ((sldp = (tcp_Socket *) malloc( (int) sizeof( tcp_Socket ) )) == NULL){ DB4((stderr,"No room for any socket!\n")); return(sldp); } memset( sldp, 0, sizeof( tcp_Socket)); sldp->myport = lport; sldp->hisport = port; sldp->hisaddr = ina; sldp->sock_mode = (sldp->sock_mode & 0xfffc) |mode; sldp->dataHandler = datahandler; sldp->next = NULL; for( i=0 ; iip_type = TCP_PROTO; s[i]->mss = _mss; s[i]->state = tcp_StateLISTEN; DB2((FDB,"tcp_listen(): going to LISTEN\n")); s[i]->myport = lport; s[i]->hisport = port; s[i]->hisaddr = ina; s[i]->sock_mode = (s[i]->sock_mode & 0xfffc) | mode; s[i]->seqnum = intel( (longword)s ); /* not a good idea, but works */ s[i]->datalen = 0; s[i]->flags = 0; s[i]->rto= 9; /* start 1/2 sec. rto */ s[i]->unhappy = NO; /* this is a PASSIVE open */ s[i]->dataHandler = datahandler; s[i]->usr_yield = system_yield; s[i]->safetysig = SAFETYTCP; s[i]->next = tcp_allsocs; s[i]->brother = bsp; s[i]->father = sldp; s[i]->inlist = LAZY; s[i]->rxbufsize = RxMaxBufSize; if ((s[i]->rdata = (byte *) malloc((int)RxMaxBufSize)) == NULL) { DB3((stderr,"Warning: not enough space for rdata buffer\n")); DB3((stderr,"%d sockets allocated\n",i)); tcp_unthread(s[i]); break; } s[i]->txbufsize = TxMaxBufSize; if ((s[i]->data = (byte *) malloc((int)TxMaxBufSize)) == NULL) { DB3((stderr,"Warning: not enough space for data buffer")); DB3((stderr,"%d sockets allocated\n",i)); tcp_unthread(s[i]); break; } tcp_allsocs = s[i]; bsp=s[i]; sret=s[i]; if( !(sldp->next) ) sldp->next = sret; /* memorizzo */ }/*end for*/ (sldp->next)->brother = sret; /* chiudo la lista */ sldp->next = sret; return(sldp); } #endif /* unused */ /*** /*** *** simply unlink from socket list *** It's the UDP version of the tcp_unthread ***/ static void udp_close( udp_Socket *ds ) { register udp_Socket *s, **sp; DB2((FDB,"udp_close():\n")); for (sp = &udp_allsocs;; sp = &s->next) { s = *sp; if ( s == ds ) { *sp = s->next; if (s->rdata==NULL) { DB3((stderr,"udp_close: socket was already closed\n")); break; } free(s->rdata); s->rdata=NULL; /* 28-Aug-92 lr you never know... */ break; } if ( !s ) break; if ( ! s->err_msg ) s->err_msg = "UDP Close called"; } } /*** *** Will queue a FIN on a particular port. *** In ESTCL or CLOSWTCL it has already been called. *** Must still allow receives. A timeout becomes active so *** that, if I cannot flush data in a reasonable time, the *** connection is aborted anyway. ***/ static void tcp_close(register tcp_Socket *s){ DB2((FDB,"tcp_close():")); if ( s->ip_type != TCP_PROTO ) return; /* invalid */ switch(s->state) { default: DB3((FDB,"tcp_close(): called in state %s\n",state_names[s->state])); return; case tcp_StateLISTEN: case tcp_StateSYNSENT: tcp_unthread(s); /* check this */ return; /* error: closing */ case tcp_StateCLOSWT: s->state = tcp_StateCLOSWTCL; DB2((FDB,"tcp_close(): going from CLOSWT to CLOSWTCL\n")); s->timeout=set_ttimeout(tcp_CLOSETIMEOUT); case tcp_StateCLOSWTCL: goto tcp_close_1; case tcp_StateSYNRECLIS: case tcp_StateSYNREC: case tcp_StateESTAB: DB2((FDB,"tcp_close(): going from %s to ESTCL\n",state_names[s->state])); s->state = tcp_StateESTCL; s->timeout=set_ttimeout(tcp_CLOSETIMEOUT); case tcp_StateESTCL: tcp_close_1: if ( s->datalen ) { /* must first flush all data */ s->flags |= tcp_FlagPUSH | tcp_FlagACK; if (s->rtt_time==0) { DB4((stderr,"pending data and rtt=0. Ouch!!!\n")); } } else { /* no more outgoing data, really closing */ s->flags = tcp_FlagACK | tcp_FlagFIN; if (!s->err_msg) s->err_msg = "Connection closed normally"; if ( s->state == tcp_StateESTCL ) { DB2((FDB,"tcp_close(): going from %s to FINWT1\n",state_names[s->state])); s->state = tcp_StateFINWT1; } else { DB2((FDB,"tcp_close(): going from %s to LASTACK\n",state_names[s->state])); s->state= tcp_StateLASTACK; } s->timeout = set_ttimeout(tcp_FLUSHTIMEOUT); /*** *** The above timeout is for the upper level protocol, so that *** all the data in the buffers can be flushed. I'm not so sure *** about the actual value. Should be at least rto. ***/ } /* end else */ tcp_send( s ); break; } /* end switch */ } /* tcp_close */ /*** *** Abort a tcp connection. Correct. ***/ static void tcp_abort(register tcp_Socket *s) { DB2((FDB,"tcp_abort():\n")); if (!s->err_msg) s->err_msg = "TCP_ABORT"; if ( s->state != tcp_StateLISTEN && s->state != tcp_StateCLOSED ) { s->flags = tcp_FlagRST | tcp_FlagACK ; tcp_send(s); } s->unhappy = NO; s->datalen = 0; s->state = tcp_StateCLOSED; tcp_unthread(s); } void sock_abort(tcp_Socket *s ) { DB2((FDB,"sock_abort():\n")); if ( s->ip_type == TCP_PROTO ) tcp_abort( s ); else udp_close( (udp_Socket *) s ); } /*** *** make_in_hdr() prepares an internet header *** parameters are in machine format. ***/ void make_in_hdr(in_Header *inp, byte proto, longword dest, word len) { DB2((FDB,"make_in_hdr():\n")); inp->ver = 4; inp->hdrlen = 5; inp->tos = 0; inp->identification = intel16( ++ip_id ); /* was post inc */ inp->frag = 0; inp->ttl = 254; inp->proto = proto; inp->checksum = 0; inp->source = intel( my_ip_addr ); inp->destination = intel( dest ); inp->length = intel16( len ); inp->checksum = ~inchksum((void *)inp, sizeof(in_Header)); } /*** *** Retransmitter - called periodically to perform tcp retransmissions ***/ static void tcp_Retransmitter(void) { static longword retran_strat = 0L; /* timeout retran strategy */ register tcp_Socket *s; int slow=0; #ifdef UNDEF DB2((FDB,"tcp_Retransmitter():\n")); gm_tcp_Retransmitter++; #endif /* UNDEF */ if (slow=chk_timeout(retran_strat)) retran_strat = set_ttimeout( RETRAN_STRAT_TIME ); for ( s = tcp_allsocs; s; s = s->next ) { if (!slow) { if (s->unhappy & VERY) { DB2((FDB,"Retransmitting cause of VERY \n")); gm_tcp_send_very++; tcp_send(s); } } else { DB_W("RT "); /* only do this once per RETRAN_STRAT_TIME milliseconds */ if (s->rtt_time && chk_timeout(s->rtt_time)) { gm_expire++; s->unacked=0; /* have lost some ack, resend */ /*** *** exponential backoff for rto. Should also upper bound it. ***/ if (s->rto) s->rto = (s->rto * 3) / 2; else s->rto = 4; DB3((FDB,"rtt expired, rto now= %d\n",s->rto)); s->unhappy = YES; } /*** *** It is agreed that when, due to a send or an incoming ack, *** new data can be sent, then unhappy is also set. ***/ if (s->unhappy) { /* was also s->datalen>0 */ DB2((FDB,"Sending seqnum %8lx from Retransmitter\n",s->seqnum)); gm_tcp_send_retr++; tcp_send(s); } /*** *** handle inactive tcp timeouts ***/ if ( sock_inactive && s->inactive_to ) { if ( chk_timeout( s->inactive_to)) { /* this baby has timed out */ s->err_msg = "Connection timed out - no activity"; sock_close((sock_type*) s ); } } /*** *** Handle regular timeouts. ***/ if ( s->timeout && chk_timeout( s->timeout)) { if ( s->state == tcp_StateTIMEWT ) { DB2((FDB,"tcp_close(): going from %s to CLOSED\n",state_names[s->state])); s->state=tcp_StateCLOSED; tcp_unthread(s); /* can I still do I/O ? */ } else if (s->state!=tcp_StateESTAB /* && s->state!=tcp_StateCLOSWT */) { s->err_msg = "Timeout, aborting"; DB3((FDB,"Timeout expired for socket in state %s - aborting\n",state_names[s->state])); tcp_abort(s); } else { DB3((FDB,"Timeout expired for active socket in state %s\n",state_names[s->state])); } } } /* end if slow */ } } /* end Retransmitter */ /*** *** Unthread a socket from the socket list, if it's there ***/ void tcp_unthread( tcp_Socket *ds) { register tcp_Socket *s, *fs, **sp; /* eventualm. *pp */ DB2((FDB,"tcp_unthread():\n")); gm_tcp_unthread++; /* if (!ds->rdatalen || (ds->state > tcp_StateESTCL)) *//*always */ ds->ip_type = 0; /* fail io */ ds->state = tcp_StateCLOSED; /* tcp_tick needs this */ if (ds->inlist==BUSY){ fs = ds->father; ds->ip_type = TCP_PROTO; ds->sock_mode = fs->sock_mode; ds->myport = fs->myport; ds->hisport = fs->hisport; ds->dataHandler = fs->dataHandler; ds->usertimer = 0L; memset(&(ds->hisethaddr ),0,sizeof(eth_address)); ds->hisaddr = fs->hisaddr; ds->state = tcp_StateLISTEN; ds->rdatalen = 0; ds->acknum = 0L; ds->seqnum = intel((longword)s); ds->timeout = 0L; ds->unhappy = NO; ds->usr_yield = system_yield; ds->flags = 0; ds->window = 0; ds->datalen = 0; ds->unacked = 0; ds->rto = 9; ds->inlist = LAZY; /* fs->next = ds->brother; i don't think it would be a good idea */ } else { sp = &tcp_allsocs; for (;;) { s = *sp; if ( s == ds ) { *sp = s->next; if (s->rdata==NULL || s->data==NULL) { DB3((stderr,"tcp_unthread: socket already closed\n")); break; } free(s->rdata); free(s->data); s->rdata=s->data=NULL; break; } if ( !s ) break; sp = &s->next; } } /* pp = tcp_allsocs; when multi_listen sockets where discarded at closing for (;;) { s = pp->next; if ( s == ds ) { pp->brother = s->brother; break; } if ( !s ) break; pp = s; } */ } /*** *** tcp_tick(s) - called periodically by user application *** returns 0 if s is NULL or not TCP socket ***/ extern long far lost_packets; short tcp_tick(register sock_type *s ) { register in_Header *ip; word packettype; #ifdef UNDEF DB2((FDB,"tcp_tick():\n")); gm_tcp_tick++; if (previous_lost_packets != lost_packets) { DB2((stderr,"lost packets: %ld\7\n",lost_packets)); previous_lost_packets = lost_packets; } #endif /* UNDEF */ /*** finish off dead sockets ***/ if ( s && ( s->tcp.ip_type == TCP_PROTO ) && ( s->tcp.state==tcp_StateCLOSED ) && ( s->tcp.rdatalen==0 )) { DB3((FDB,"tcp_tick(): unthreading socket\n")); tcp_unthread((tcp_Socket *)s); /* s->tcp.ip_type = 0; /* redundant, already in unthread */ } /*** handle incoming packets ***/ DB2((FDB,"tcp_tick(): testing incoming pkts\n")); while ( ip = (in_Header *)_eth_arrived( &packettype ) ) { DB2((FDB,"tcp_tick(): arrived pkt type %4x\n",packettype)); switch ( packettype ) { case 0x0008 : /* intel16(IP_TYPE) */ if ( inchksum((void *)ip, ip->hdrlen <<2) == 0xffff ) { DB2((FDB,"checksum is OK\n")); if (!my_ip_addr || (intel(ip->destination) == my_ip_addr) #ifdef LR /* 19-dec-91 lr: what do I do with local broadcasts ? */ || (intel(ip->destination)|(~sin_mask) == my_ip_addr| (~sin_mask) ) /* local broadcast */ #endif /* LR */ ) { DB2((FDB,"This pkt is for me\n")); switch ( ip->proto ) { case TCP_PROTO : DB2((FDB,"received TCP packet\n")); tcp_handler(ip); break; case UDP_PROTO : DB2((FDB,"received UDP packet\n")); udp_handler(ip); break; case ICMP_PROTO : DB2((FDB,"received ICMP packet\n")); icmp_handler(ip); break; default: DB3((stderr,"unknown protocol %d\n",ip->proto)); break; } } else { if (intel(ip->destination)|(~sin_mask) == my_ip_addr| (~sin_mask) ) { /* local broadcast */ DB2((FDB, "Received local broadcast\7\n")); } else { word *s; s=(word *)_eth_hardware((byte *)ip); if (s[0]!=0xFFFF || s[1]!=0xffff || s[2]!=0xffff) { DB4((FDB, "This packet is not for me, and not ether broadcast\7\n")); } } } } else { /* bad checksum */ DB2((FDB, "IP packet, bad checksum\7\n")); } break; case 0x0608 : /* intel16(ARP_TYPE) */ DB2((FDB,"received ARP packet\n")); (void)_arp_handler((arp_Header *)ip); break; } /* end switch packettype */ DB2((FDB,"FREE eth arrived\n")); if (ip) _eth_free(ip); } /* end while etharrived */ /*** *** now check for outstanding packets ***/ tcp_Retransmitter(); return( s ? s->tcp.ip_type : 0 ); } void tcp_set_debug_state(int x) { debug_on = x; } /* returns 1 if connection is established */ int tcp_established(tcp_Socket *s) { DB2((FDB,"tcp_established():\n")); return( s->state==tcp_StateESTAB || s->state==tcp_StateESTCL || s->state==tcp_StateCLOSWT || s->state==tcp_StateCLOSWTCL ); } static int udp_write(register udp_Socket *s, byte *datap, int len) { tcp_PseudoHeader ph; struct _pkt { in_Header in; udp_Header udp; int data; } *pkt; int *dp; register in_Header *inp; register udp_Header *udpp; DB2((FDB,"udp_write():\n")); pkt=(struct _pkt *)_eth_formatpacket(&s->hisethaddr[0],/*0x800*/8); dp = &pkt->data; inp = &pkt->in; udpp = &pkt->udp; /* udp header */ udpp->srcPort = intel16( s->myport ); udpp->dstPort = intel16( s->hisport ); udpp->checksum = 0; udpp->length = intel16( UDP_LENGTH + len ); movmem(datap, dp, len ); /* internet header */ make_in_hdr(inp, UDP_PROTO, s->hisaddr, sizeof(in_Header) + UDP_LENGTH + len ); DB2((FDB,"udp inp length = %d\n",intel16(inp->length))); /* compute udp checksum if desired */ if ( s->sock_mode & UDP_MODE_NOCHK ) { udpp->checksum = 0; } else { ph.src = inp->source; /* already INTELled */ ph.dst = inp->destination; ph.mbz = 0; ph.protocol = UDP_PROTO; /* udp */ ph.length = udpp->length; /* already INTELled */ ph.checksum = inchksum((void *)&pkt->udp, intel16(ph.length)); udpp->checksum = ~inchksum((void *)&ph, sizeof(ph)); } /* if (_dbugxmit) (*_dbugxmit)(s,inp,udpp); */ DB2((FDB,"calling _eth_send():\n")); gm_udp_write++; if(_eth_send( intel16( inp->length ))) { gm_udp_sendfailed++; } /* eth_send may fail. But this is udp, so who cares. */ return ( len ); } /* * udp_read - read data from buffer, does large buffering */ static int udp_read( register udp_Socket *s, byte *datap, int maxlen) { int x; DB2((FDB,"udp_read():\n")); if (( x = s->rdatalen ) > 0) { DB2((FDB,"udp_read():datalen = %d maxlen = %d\n",x,maxlen)); gm_udp_read++; if ( x > maxlen ) x = maxlen; if ( x > 0 ) { DB2((FDB,"udp_read():before memcpy\n")); memcpy( datap, s->rdata, x ); if ( s->rdatalen -= x ) DB2((FDB,"udp_read():before memmove\n")); movmem(s->rdata+x ,s->rdata, s->rdatalen); } } return( x ); } void _udp_cancel( in_Header *ip ) { int len; udp_Header *up; register udp_Socket *s; DB2((FDB,"_udp_cancel():\n")); /* match to a udp socket */ len = ip->hdrlen << 2; up = (udp_Header *)((byte *)ip + len); /* udp frame pointer */ /* demux to active sockets */ for ( s = udp_allsocs; s; s = s->next ) if ( s->hisport != 0 && intel16( up->dstPort ) == s->myport && intel16( up->srcPort ) == s->hisport && intel( ip->source ) == s->hisaddr ) break; if ( !s ) { /* demux to passive sockets */ for ( s = udp_allsocs; s; s = s->next ) if ( s->hisport == 0 && intel16( up->dstPort ) == s->myport ) break; } if (s) { s->rdatalen = -1; s->ip_type = 0; } } void _tcp_cancel(in_Header *ip) { int len; register tcp_Socket *s; tcp_Header *tp; DB2((FDB,"_tcp_cancel():\n")); len = ip->hdrlen; /* check work */ tp = (tcp_Header *)((byte *)ip + len); /* tcp frame pointer */ /* demux to active sockets */ for ( s = tcp_allsocs; s; s = s->next ) { if ( s->hisport!=0 && intel16(tp->dstPort) == s->myport && intel16(tp->srcPort) == s->hisport && intel(ip->source) == s->hisaddr ) break; } if ( !s ) { /* demux to passive sockets */ for ( s = tcp_allsocs; s; s = s->next ) if ( s->hisport==0 && intel16(tp->dstPort) == s->myport ) break; } if (s) { /* tcp_unthread should suffice - 27-Jun-92 lr */ s->rdatalen = -1; s->state=tcp_StateCLOSED; s->ip_type=0; /* should be in unthread */ tcp_unthread(s); } } /* end _tcp_cancel */ /*** *** Can always read, even after my close. ***/ int tcp_read(register tcp_Socket *s, byte *datap, int maxlen) { int x; DB2((FDB,"tcp_read(): data pending %d\n",s->rdatalen)); if ( s->ip_type != TCP_PROTO ) return(0); /* invalid */ if (( x = s->rdatalen) > 0) { if ( x > maxlen ) x = maxlen; if ( x > 0 ) { memcpy( datap, s->rdata, x ); if (( s->rdatalen -= x ) > 0 ) movmem(s->rdata+x , s->rdata, s->rdatalen ); /* s->unhappy = VERY ; */ DB2((FDB,"Updating window size in tcp_read\n")); tcp_send(s); /* update window size */ } } else if ( s->state == tcp_StateCLOSWT ) { tcp_close( s ); } return( x ); } /*** *** Write data to a connection. *** Returns number of bytes written, == 0 when connection is not in *** established state. *** Cannot write after my close or other party's FIN. ***/ int tcp_write(register tcp_Socket *s, byte *dp, int len) { int x; DB2((FDB,"tcp_write():\n")); if ( s->ip_type != TCP_PROTO ) return(0); /* invalid */ if ( s->state != tcp_StateESTAB && s->state !=tcp_StateCLOSWT ) len = 0; if ( len > (x = s->txbufsize - s->datalen) ) len = x; if ( len > 0 ) { memcpy(s->data + s->datalen, dp, len ); s->datalen += len; if ( s->sock_mode & TCP_MODE_NONAGLE ) { tcp_send( s ); } else { /* default mode */ /* transmit if first data or reached MTU */ /* not true MTU, but better than nothing */ if ((s->datalen==len )||( s->datalen > (int)(s->mss)/2)) { tcp_send( s ); } else { s->unhappy = YES ; /*send at next tick */ } } } return ( len ); } /* end tcp_write */ /*** *** Send pending data ***/ static void tcp_Flush(tcp_Socket *s) { DB2((FDB,"tcp_flush():\n")); if ( s->datalen > 0 ) { s->flags |= tcp_FlagPUSH; tcp_send(s); } } /*** *** Handler for incoming udp packets. ***/ static void udp_handler(in_Header *ip) { register udp_Header *up; tcp_PseudoHeader ph; word len; byte *dp; register udp_Socket *s; DB2((FDB,"udp_handler():\n")); gm_udp_handler++; len = ip->hdrlen << 2; up = (udp_Header *)((byte *)ip + len); /* udp segment pointer */ len = intel16( up->length ); DB2((FDB,"udp handler = %d\n",len)); /* demux to active sockets */ for ( s = udp_allsocs; s; s = s->next ) { if ( s->safetysig != SAFETYUDP ) { outs("chain error in tcp"); exit(3); } if ( s->hisport != 0 && intel16( up->dstPort ) == s->myport && intel16( up->srcPort ) == s->hisport && intel( ip->source ) == s->hisaddr ) break; } /* end for */ /* if (_dbugrecv) (*_dbugrecv)(s,ip,up); */ if ( !s ) { /* demux to passive sockets */ /* Note: Passive Sockets have their address = 0 */ for ( s = udp_allsocs; s; s = s->next ) if ( s->hisaddr == 0 && intel16( up->dstPort ) == s->myport ) { if (_arp_resolve(intel(ip->source), &(s->hisethaddr))) { s->hisaddr = intel( ip->source ); s->hisport = intel16( up->srcPort ); } break; } } if ( !s ) { /* demux to broadcast sockets */ for ( s = udp_allsocs; s; s = s->next ) if ( s->hisaddr == 0xffffffff && intel16( up->dstPort ) == s->myport ) break; } if ( !s ) { /* maybe can send back an ICMP ? */ DB2((FDB,"Udp_handler: no session seems to exist \n")); return; } if ( up->checksum ) { DB2((FDB,"Udp_handler: filling the pseudoheader \n")); ph.src = ip->source; /* already INTELled */ ph.dst = ip->destination; ph.mbz = 0; ph.protocol = UDP_PROTO; ph.length = up->length; ph.checksum = inchksum((void *)up, len); if (inchksum((void *)&ph, sizeof( tcp_PseudoHeader)) != 0xffff) return; } /* process user data */ /* Remember: UDP_LENGTH = sizeof(udp_Header) */ if ( (len -= UDP_LENGTH ) > 0) { DB2((FDB,"udp handler: data = %d\n",len)); DB2((FDB,"Udp_handler: proc. data \n")); dp = (byte *)( up ); if (s->dataHandler) s->dataHandler( s, &dp[ UDP_LENGTH ], len , &ph); else { if (len > (word)s->rxbufsize) len = s->rxbufsize; memmove( s->rdata,&dp[UDP_LENGTH], len ); s->rdatalen = len; } } } static void tcp_close_sock(register tcp_Socket *s, char *msg) { DB2((FDB,"tcp_close_sock():\n")); s->err_msg= msg; s->state=tcp_StateCLOSED; tcp_unthread(s); } static void recompute_rto(register tcp_Socket *s) { long delta; #define MAXRTO (18*30) /* 30 sec. rto it's a lot */ if(s->vj_last) { if((delta=set_ttimeout(0) - s->vj_last) >=0 ) { /* always enter here apart from midnight case */ s->rto = (word)(delta +(9*(s->rto-delta))/10); if(s->rto > MAXRTO) s->rto = MAXRTO; else if (s->rto <=4 ) s->rto = 4; } } DB2((FDB,"recompute_rto(): rto becomes %d\n",s->rto)); } /*** *** this makes the tests in RFC793, pg.69, and sends the *** ack if needed. ***/ static int acceptable(tcp_Socket *s, in_Header *ip, tcp_Header *tp) { int seglen, good=TRUE; longword seglow, seghigh, winlow, winhigh; word flags = intel16(tp->flags); seglen = intel16( ip->length ) /* IP len including header */ - (ip->hdrlen << 2) /* IP header */ - (tcp_GetDataOffset(tp)<<2); /* TCP header */ /*** *** seglow and seghigh are the bounds of the incoming segment, *** winlow and winhigh are the bounds of the receive window ***/ seglow= intel(tp->seqnum); seghigh= seglow+seglen + (flags&(tcp_FlagACK|tcp_FlagFIN|tcp_FlagSYN) ? 1 : 0); winlow= s->acknum; winhigh= winlow + s->rxbufsize - s->rdatalen; if (winlow==winhigh) winhigh++; /* always room for a Flag */ if ( seghigh<=winlow || winhigh <= seglow ) good=FALSE; if (!good) { DB2((FDB, "acceptable(): state %s accept %s: seglen= %d seg: %8lx %8lx, win: %8lx %8lx\n", state_names[s->state], good ? "T":"F",seglen,seglow,seghigh,winlow,winhigh)); DB2((FDB," send win: %8lx %8lx, tp->ack=%8lx\7\n", s->seqnum,s->seqnum+s->unacked+ (s->flags &(tcp_FlagSYN|tcp_FlagFIN)? 1:0),intel(tp->acknum))); /* should send an ack, unless RST is present */ if (! (intel16(tp->flags) & tcp_FlagRST) ) { s->flags=tcp_FlagACK; s->unhappy=YES ; } } /* else trim packet if needed */ return(good); } static void set_UP(int flags, tcp_Socket *s, tcp_Header *tp) { DB2((FDB,"set_UP():\n")); if( (flags & tcp_FlagURG) && (s->state== tcp_StateESTAB) ) { s->UP= (s->UP >= intel16(tp->urgentPointer)) ? s->UP : intel16(tp->urgentPointer); } } static void tcp_handler1(register tcp_Socket *s,in_Header *ip, tcp_Header *tp,word flags,int len /* tcp hdr+data */) { int acked; /* counters for acked bytes */ long lacked; DB2((FDB,"tcp_handler1(): %d bytes (tcp hdr+data)\n",len)); switch(s->state) { /* break or return is the same */ default: /* just to be sure not to forget... */ DB3((FDB,"Warning: received a segment in state %s\n",state_names[s->state])); return; /*** *** these states should be: *** CLOSING LASTACK TIMEWT CLOSEMSL CLOSED ***/ case tcp_StateLISTEN: DB2((FDB,"State = LISTEN \n")); DB_W("LI "); /*** *** I know nothing about sequence numbers here. *** First check for an RST: incoming RST are ignored ***/ if (flags & tcp_FlagRST) { DB2((FDB,"ignoring incoming RST\n")); return; /* ignore */ } /*** *** Second check for an ack. Any ACK is bad here, an acceptable *** RST segment should be formed with: *** s->seqnum= intel(tp->acknum); *** s->flags= tcp_FlagRST; *** Send it and return. ***/ if (flags & tcp_FlagACK) { DB2((FDB,"reply RST to incoming ACK\n")); tcp_rst(ip,tp); } /*** *** Third, check for a SYN. If set, check security. If ok, set *** s->acknum= intel(tp->seqnum)+1; *** s->flags= tcp_FlagSYN | tcp_FlagACK; *** and send the segment, changing state to SYNRECLIS. ***/ if (flags & tcp_FlagSYN) { DB2((FDB,"good SYN, goto SYNRECLIS\n")); s->acknum= intel(tp->seqnum)+1; s->hisport = intel16(tp->srcPort); s->hisaddr = intel(ip->source); s->window = intel16( tp->window ); /* set new send window size */ DB2((FDB,"LISTEN: window = %d \n",s->window)); tcp_ProcessData(s,tp,len); /* for options */ s->flags= tcp_FlagSYN | tcp_FlagACK; s->state=tcp_StateSYNRECLIS; DB2((FDB,"Sending SYN+ACK to incoming SYN, going to SYNRECLIS\n")); tcp_send(s); /* send right away, no data */ s->timeout=set_ttimeout(tcpAbortTimeout); return; } /*** *** Should never arrive here, in any case drop the segment *** and return. ***/ DB3((FDB,"LISTEN: don't know what to do with a segment\n")); return; case tcp_StateSYNSENT: DB2((FDB,"State = SYNSENT \n")); DB_W("SYs "); /*** *** First, check the ACK bit. If set, but not for our SYN, *** and is not a RST (discard it) send RST with *** s->seqnum= intel(tp->acknum); *** s->flags= tcp_FlagRST; *** discard the segment and return. ***/ if (flags & tcp_FlagACK) { if (intel(tp->acknum) != s->seqnum +1) { DB2((FDB,"bad ACK\n")); if (!(flags & tcp_FlagRST)) { tcp_rst(ip,tp); } return; } /*** else, my fin has been acked, but it's easier *** to check it later. ***/ } DB2((FDB,"good ACK for a SYNSENT\n")); /*** *** Second, check RST bit. If set, and the ACK was acceptable, *** signal "error: connection reset", drop the segment and enter *** the CLOSED state. Otherwise drop the segment and return. *** *** NOTE: apparently, NCSA Telnet sends a RST without ACK. Thus, *** I do not check the ACK here. This is a deviation from standard. ***/ if (flags & tcp_FlagRST) { DB3((FDB,"whoops, received RST in state %s\n",state_names[s->state])); /* if (flags & tcp_FlagACK) */ tcp_close_sock(s,"error: connection reset"); return; } /*** *** At this point have a good segment (maybe with an ACK). *** Third check security. *** Fourth, check the SYN bit. If set, then *** s->acknum= intel(tp->seqnum)+1; *** If our SYN has been acked, go to state ESTAB, and send an ack *** else goto state SYNREC and send SYN,ACK (the SYN is the old one). ***/ if (flags & tcp_FlagSYN) { DB2((FDB,"good SYN in SYNSENT\n")); s->flags=tcp_FlagACK; /* ack remote SYN */ s->window = intel16( tp->window ); /* set new send window size */ DB2((FDB,"SYNSENT: window = %d\n",s->window)); s->acknum=intel(tp->seqnum) +1; if (flags & tcp_FlagACK) { /* seqnum have already been checked */ s->seqnum++; /* my SYN has been acked */ s->state=tcp_StateESTAB; s->timeout=0; /* no more timeout */ s->unhappy = YES; /* is it needed */ DB2((FDB,"going from SYNSENT to ESTAB\n")); goto ESTAB_6; } else { /* maybe there are options, should process them */ s->flags = tcp_FlagSYN | tcp_FlagACK; s->state=tcp_StateSYNREC; DB2((FDB,"going from SYNSENT to SYNREC\n")); tcp_send(s); s->timeout=set_ttimeout(tcp_TTIMEOUT); /* why ?? */ return; } } /*** *** Fifth, if neither SYN or RST, drop the segment and return ***/ return; case tcp_StateSYNREC: DB2((FDB,"State = SYNREC\n")); DB_W("SYr "); goto ESTAB_1; case tcp_StateSYNRECLIS: DB2((FDB,"State = SYNRECLIS\n")); DB_W("SYl "); goto ESTAB_1; case tcp_StateESTAB: DB2((FDB,"State = ESTAB\n")); DB_W("ES "); goto ESTAB_1; case tcp_StateESTCL: DB2((FDB,"State = ESTCL\n")); DB_W("EC "); goto ESTAB_1; case tcp_StateCLOSWT: DB2((FDB,"State = CLOSWT\n")); DB_W("CW "); goto ESTAB_1; case tcp_StateCLOSWTCL: DB2((FDB,"State = CLOSWTCL\n")); DB_W("CWc "); goto ESTAB_1; case tcp_StateFINWT1: DB2((FDB,"State = FINWT1\n")); DB_W("FW1 "); goto ESTAB_1; case tcp_StateFINWT2: DB2((FDB,"State = FINWT2\n")); DB_W("FW2 "); goto ESTAB_1; case tcp_StateLASTACK: DB2((FDB,"State = LASTACK\n")); DB_W("LAk "); goto ESTAB_1; case tcp_StateTIMEWT: DB2((FDB,"State = TIMEWT\n")); DB_W("LAk "); goto ESTAB_1; /*** *** The folloging states are 'connected' states: *** SYNREC SYNRECLIS *** ESTAB ESTCL *** FINWT1 FINWT2 *** CLOSWT *** LASTACK *** TIMEWT *** There is some common processing here. ***/ ESTAB_1: /*** *** First an acceptability test to see if any part of the incoming *** segment falls inside the current receive window. If not, *** RST are ignored, other segments are acked with *** s->flags= tcp_FlagACK; *** and other fields as from the socket descriptor. ***/ if (!acceptable(s,ip,tp)) return; /*** *** Second, check the RST bit. Here processing differs: ***/ if (flags & tcp_FlagRST) { /* valid RST */ DB3((FDB,"oops.. found RST\n")); switch(s->state) { case tcp_StateSYNREC: tcp_close_sock(s, "Connection refused"); return; case tcp_StateSYNRECLIS: s->state=tcp_StateLISTEN; DB2((FDB,"going from SYNRECLIS to LISTEN\n")); s->timeout=0; /* ouch...where's the original timeout! */ return; case tcp_StateESTAB: case tcp_StateESTCL: case tcp_StateFINWT1: case tcp_StateFINWT2: case tcp_StateCLOSWT: case tcp_StateCLOSWTCL: tcp_close_sock(s, "Connection reset"); return; case tcp_StateCLOSING: case tcp_StateLASTACK: case tcp_StateTIMEWT: tcp_close_sock(s,""); return; /*** *** CLOSEMSL and CLOSED are missing ***/ } /* end switch */ return; /*** of course ***/ } /* end if, no RST found */ /*** *** Third, check security. *** Fourth, check the SYN bit: a SYN within the window is an error, *** so send a reset and signal "connection reset". ***/ if (flags & tcp_FlagSYN) { DB3((FDB,"oops.. found SYN\n")); tcp_rst(ip,tp); tcp_close_sock(s, "Connection reset"); return; } /*** *** Fifth, check the ACK field. If off, drop segment and return. *** otherwise, processing differs. ***/ if (!(flags & tcp_FlagACK)) { DB3((FDB,"oops.. missing ACK\n")); return; /* ack is off */ } lacked= intel(tp->acknum) - s->seqnum; acked = (int)lacked; switch(s->state) { case tcp_StateSYNREC: case tcp_StateSYNRECLIS: /*** *** if the ack is acceptable, goto ESTAB and continue processing. *** otherwise form a RST segment *** s->seqnum= intel(tp->acknum); *** s->flags= tcp_FlagRST; *** and send it. I think we can return. ***/ if (lacked==1) { s->seqnum++; /* accept it */ s->window=intel16(tp->window); s->flags= tcp_FlagACK; DB2((FDB,"Setting ACK in SYNREC\n")); s->unhappy=VERY; DB2((FDB,"Setting VERY in SYNREC\n")); s->state=tcp_StateESTAB; s->timeout=0; DB2((FDB,"Received ACK for SYN in SYNRECLIS- going to ESTAB\n")); goto ESTAB_6; } else { DB3((FDB,"Warning: ack for unsent data/flags\n")); tcp_rst(ip,tp); return; } case tcp_StateESTAB: case tcp_StateESTCL: case tcp_StateCLOSWT: case tcp_StateCLOSWTCL: /*** *** If ack is valid, remove data from the retransmission queue. *** The send window should be updated. *** Duplicate acks are ignored, invalid acks are acked, *** then drop segment and return. *** An ack also has a new remote window. If it grows, *** we try to send our data becoming VERY unhappy. ***/ if ( lacked <= 0 ) { if (lacked==0) { /*** *** maybe a window re-opening. Or the other party wants to send *** something. ***/ int oldwindow; oldwindow = (int)s->window; s->window = intel16( tp->window ); /*** *** DOUBLE CHECK THIS !!! ***/ if ( (oldwindow < 1 ) && ( s->window > (word)s->unacked) && ( s->window > (word)oldwindow ) ) { s->unhappy= VERY; DB2((FDB,"Window reopening lacked == 0\n")); } } else { DB2((FDB,"duplicate ack, ignore\n")); } } else { if (lacked <= (long)s-> unacked) { int oldwindow; DB2((FDB,"valid ack\n")); oldwindow = (int)s->window; s->window = intel16( tp->window ); DB2((FDB,"State %d: window = %d \n",s->state,s->window)); s->datalen -= acked; s->unacked -= acked; s->seqnum += lacked; recompute_rto(s); if (s->datalen) { memmove(s->data,s->data+acked,s->datalen); if (( s->window > (word)s->unacked) && (s->window>(word)oldwindow)) { /* window reopening */ s->unhappy=VERY; DB2((FDB,"Setting VERY in State %d\n",s->state)); } } else { /* no more data pending */ s->unhappy=NO; /* as far as I know... */ s->vj_last=0; s->rtt_time=0; /* and remove timeout. */ /*** *** If I had a CLOSE pending, send a FIN. Must call tcp_send() *** directly because after that I'll be in the new state. ***/ if (s->state== tcp_StateESTCL || s->state==tcp_StateCLOSWTCL) { s->flags= tcp_FlagFIN | tcp_FlagACK; tcp_send(s); /* here or later ? */ DB2((FDB,"going from %s",state_names[s->state])); s->state= s->state== tcp_StateESTCL ? tcp_StateFINWT1:tcp_StateLASTACK; DB2((FDB,"to %s\n",state_names[s->state])); } } } else { DB3((FDB,"invalid ack - drop segment\n")); s->flags=tcp_FlagACK; s->unhappy = YES ; return; } } break; /*** *** FINWT1, FINWT2, CLOSING have a similar processing, plus *** something. Checks are simpler, though, because have at *** most one FIN still unacked. *** *** For FINWT1, if FIN is acked, enter FINWT2 and continue *** processing from there. *** *** For FINWT2, if the retransmission queue is empty, the *** user's CLOSE can be acknowledged but the TCB cannot be *** deleted. Note that I choose to empty out the retran.queue *** before sending my FIN. *** *** For CLOSING, if the ACK is for our FIN then enter TIMEWAIT, *** otherwise ignore the segment. ***/ case tcp_StateCLOSING: /***/ case tcp_StateFINWT1: /***/ if (lacked<=0) break; /* old ack, ignore */ if (lacked!=1) return; /* unvalid ack, drop segment */ s->seqnum++; /* ack my fin */ s->flags= tcp_FlagACK; s->rtt_time=0; /* no more timeout */ if (s->state==tcp_StateFINWT1) { DB2((FDB,"going from FINWT1 to FINWT2\n")); s->state=tcp_StateFINWT2; goto FINWT_2; } else { DB2((FDB,"going from CLOSING to TIMEWT\n")); s->state=tcp_StateTIMEWT; s->timeout=set_ttimeout(100); /* 2MSL */ } break; case tcp_StateFINWT2: /***/ if (lacked != 0) return; /* unvalid ack, drop */ break; case tcp_StateLASTACK: /*** *** The only thing can arrive is an ack to our FIN. if it is it, *** delete TCB, enter CLOSED state and return. ***/ if (lacked==1) { tcp_close_sock(s,""); } return; case tcp_StateTIMEWT: /*** *** The only thing can arrive is a retransmission of the remote FIN. *** Acknowledge it and restart the 2MSL timeout. *** To be completed. ***/ break; } /* end switch */ ESTAB_6: DB2((FDB,"we are in ESTAB_6:\n")); /*** *** Sixth, check the urgent bit:... ***/ /* set_UP(s->flags,ip,tp); */ /*** *** Seventh, process the segment text. ***/ if (len>sizeof(tcp_Header)) { /* have data or options */ switch(s->state) { case tcp_StateESTAB: case tcp_StateESTCL: case tcp_StateFINWT1: case tcp_StateFINWT2: /*** *** Text segments can be delivered to user space. I'll ignore *** PUSH requests here. If there are too many data, ProcessData *** returns 0 so that the subsequent FIN is not processed. ***/ FINWT_2: if (!tcp_ProcessData(s,tp,len)) return; break; default: /*** *** data shouldn't arrive. Ignore them. ***/ DB3((FDB,"Unexpected data in state %s\n",state_names[s->state])); break; } /* end switch */ } /*** *** Eight: check the FIN bit. Do not process FIN in CLOSED, *** LISTEN or SYN_SENT because it cannot be validated (drop *** the segment and return in these cases). *** If FIN is set, signal "connection closing" to any *** pending receive. Advance s->acknum over FIN and ack the FIN. *** FIN implies PUSH for any text not yet delivered to the user. *** Then... *** SYNREC, SYNRECLIS, ESTAB: enter CLOSEWAIT *** FINWT1: if our fin has been acked, so enter TIMEWT, start the *** timewait timer and turn off the other timers. otherwise enter *** the CLOSING state. *** FINWT2: as FINWT1 (and assume FIN has been acked). *** TIMEWAIT: restart the 2MSL timeout. *** other cases, remain in the same state. ***/ if (flags & tcp_FlagFIN) { DB2((FDB,"FIN arrived\n")); s->err_msg="Connection closing"; switch(s->state) { case tcp_StateESTAB: case tcp_StateSYNREC: case tcp_StateSYNRECLIS: s->acknum++; DB2((FDB,"fin arrived, going from state %s to CLOSWT\n",state_names[s->state])); s->state=tcp_StateCLOSWT; s->timeout= set_ttimeout(100); /* here they should close soon */ break; case tcp_StateESTCL: /*** *** Here, I don't know what to do yet. Ignore the segment until *** all data have been flushed (i.e. we have reached FINWT1). ***/ DB3((FDB,"Received CLOSE in ESTCL. Check this.\7\n")); return; #ifdef notdef s->acknum++; DB2((FDB,"going from ESTCL to CLOSING\n")); s->state=tcp_StateCLOSING; /* or somewhere else ??? */ break; #endif /* notdef */ case tcp_StateFINWT1: s->acknum++; if (flags & tcp_FlagACK) { DB2((FDB,"going from FINWT1 to TIMEWT\n")); s->state=tcp_StateTIMEWT; s->timeout=set_ttimeout(100); } else { DB2((FDB,"going from FINWT1 to CLOSING\n")); s->state=tcp_StateCLOSING; } break; case tcp_StateFINWT2: s->acknum++; DB2((FDB,"going from FINWT2 to TIMEWT\n")); s->state=tcp_StateTIMEWT; s->timeout=set_ttimeout(100); break; default: DB3((FDB,"Don't want FIN in state %s\n",state_names[ s->state])); return; } DB2((FDB,"Sent ACK\n")); s->flags=tcp_FlagACK; tcp_send(s); /* ack the FIN */ } } /* end main switch */ } /* end tcp_handler1 */ static void tcp_handler(in_Header *ip) { tcp_Header *tp; tcp_PseudoHeader ph; int i, len, found = 0; register tcp_Socket *s, *ps; word flags; DB2((FDB,"tcp_handler():\n")); gm_tcp_handler++; DB_W("h "); len = ip->hdrlen << 2; tp = (tcp_Header *)((byte *)ip + len); /* tcp frame pointer */ len = intel16( ip->length ) - len; /* tcp data including TCP hdr*/ flags = intel16( tp->flags ); /* this saves time later */ DB2((FDB,"entering tcp_handler: %d bytes (tcp hdr+data)\n",len)); /* demux to active sockets */ for ( s = tcp_allsocs; s; s = s->next ) { if ( s->safetysig != SAFETYTCP ) { outs("chain error in tcp"); exit(3); } if ( s->hisport != 0 && intel16( tp->dstPort ) == s->myport && intel16( tp->srcPort ) == s->hisport && intel( ip->source ) == s->hisaddr ) break; } /* end for */ /* demux to passive sockets, must be a new session */ if ( !s && (flags & tcp_FlagSYN)) { #ifdef unused /* because there is no multi_listen() */ /* first look at those sockets created with multi_listen */ for( ps = tcp_allsocs ; ps ; ps = ps->next ){ if( ps->father) break; } if(ps){ ps = (ps->father)->next; s = ps; for(;;){ if ((s->hisport==0)&&(intel16(tp->dstPort)==s->myport)){ found = 1; break; } if ( (s = s->brother) == ps ) break; } } #endif /* unused */ if(!found){ /* then look at those socket created with listen */ for( i=0 ; ibrother ){ s = ps; for(;;){ if ((s->hisport==0)&&(intel16(tp->dstPort)==s->myport)){ found = 1; break; } if( (s = s->brother) == ps ) break; } } if(found) break; } } if(!found){ /* at the end look all the sockets */ for ( s = tcp_allsocs; s; s = s->next ) if ((s->hisport==0)&&(intel16(tp->dstPort)==s->myport)) break; } } if ( !s ) { /* no session seems to exist */ tcp_rst( ip, tp ); return; } if ( sock_inactive ) s->inactive_to = set_timeout( sock_inactive ); /* save his ethernet address */ memcpy( &s->hisethaddr[0], &((((eth_Header *)ip) - 1)->source[0]), sizeof(eth_address)); /*** test checksum ***/ ph.src = ip->source; /* already INTELled */ ph.dst = ip->destination; ph.mbz = 0; ph.protocol = TCP_PROTO; ph.length = intel16( len ); ph.checksum = inchksum((void *)tp, len); if ( inchksum((void *)&ph, sizeof(ph)) != 0xffff ) { DB3((FDB,"bad checksum! in tcp_handler\n")); return; } tcp_handler1(s,ip,tp,flags,len); return; } /*** *** Process the data in an incoming packet. *** Called from all states where incoming data can be received: *** ESTAB, ESTCL, FINWT1, FINWT2 ***/ static int tcp_ProcessData(register tcp_Socket *s, tcp_Header *tp, int len) { int result=TRUE; long lacked; int acked, x; word flags; byte *dp; DB2((FDB,"Enter tcp_ProcessData for %d bytes\n", len)); gm_tcp_ProcessData++; flags = intel16( tp->flags ); lacked = s->acknum - intel( tp->seqnum ); /*** *** s->acknum is the last ack I sent. If all the data have arrived, *** this is also the seqnum of the first byte of the incoming segment, *** thus lacked==0. If the segment is an old copy, tp->seqnum is lower, *** thus lacked>0 (still it might have some good data). *** If I have lost some data, lacked<0 (this I cannot accept). *** Also, if the segment carries a SYN, it is considered the *** first item, so I have to decrease lacked (the first actual data *** byte is at tp->seqnum+1). ***/ if ( flags & tcp_FlagSYN ) lacked--; /* back up to 0 */ acked = (int) lacked; /* shorthand */ /*** find the data portion ***/ x = tcp_GetDataOffset(tp) << 2; /* quadword to byte format */ dp = (byte *)tp + x; /* pointer to data */ /*** check for options (i.e. a longer header) ***/ if ( x > sizeof( tcp_Header )) { byte *op; gm_proc_option++; op = (byte *)(tp) + sizeof(tcp_Header); DB2((FDB,"have %d bytes of options\n",x-sizeof(tcp_Header))); while ( op < dp ) { gm_proc_opt_in++; switch ( *op ) { case 0 : /*** end of options ***/ op = dp; break; case 1 : /*** nop ***/ op++; break; case 2 : /*** we are very liberal on MSS stuff */ op++; /* pointer to option len */ if (*op == 4) { word tmp= intel16( *( (word*)(op+1) ) ); DB2((stderr,"old mss=%d new mss=%d\n",s->mss, tmp)); if (tmp < _mss ) s->mss = tmp; } op += *op - 1; break; default: DB3((stderr,"Warning, unrecognized tcp option %x\n",*op)); op=dp; break; } } /* end while */ } /* done option processing */ len -= x; /* remove the header length */ if ( lacked >= 0 && len>0 ) { /* new data (good ones) */ dp += acked; /* skip already received bytes */ len -= acked; /* now might be len <= 0 */ DB2((FDB,"have %d bytes of new data\n", len)); if (s->dataHandler) { int tmp= s->dataHandler(s, dp, len); s->acknum += tmp; result = (tmp==len); } else { /* no handler, just dump to buffer */ /* limit receive size to our window */ if ( s->rdatalen >= 0 ) { /* fails only after a cancel */ if ( len > ( x = s->rxbufsize - s->rdatalen )) { len = x; result=FALSE; DB3((FDB,"Warning, can't take all the new data\n")); } if ( len > 0 ) { /*** *** This might fail if the first SYN carries data... ***/ s->acknum += len; /* our new ack begins at end of data */ memcpy(s->rdata+s->rdatalen ,dp, len ); s->rdatalen += len; /*** *** Want to send an ack. The strategy to be used may vary. *** Either send it right away, or wait for the input buffer to *** be sufficiently full. ***/ s->unhappy |= s->rdatalen < s->rxbufsize/2 ? YES:VERY; DB2((FDB,"Setting VERY in ProcessData\n")); } } } if (s->timeout) s->timeout=set_ttimeout( tcp_TTIMEOUT ); /* check this ??? */ } else { /* else no data in this packet */ DB2((FDB,"have no new data\n")); } return(result); } /* end tcp_ProcessData */ /* * Format and send an outgoing segment */ static void tcp_send(register tcp_Socket *s) { tcp_PseudoHeader ph; struct _pkt { in_Header in; tcp_Header tcp; word maxsegopt[2]; } *pkt; byte *dp; in_Header *inp; tcp_Header *tcpp; word senddatalen, sendpktlen, maxsend, /* max offset of data to be sent */ sentdata; /* # of bytes actually sent */ register char *r; /* used as a pointer, for efficiency */ DB2((FDB,"tcp_send():\n")); gm_tcp_send++; s->unhappy=FALSE; /* clear it */ pkt = (struct _pkt *)_eth_formatpacket(&s->hisethaddr[0], 8 /*IP */); dp = (byte *)&pkt->maxsegopt; /* dp points to data */ inp = &pkt->in; /* inp points to ip header */ tcpp = &pkt->tcp; /* tcpp points to tcp header */ maxsend=min((word)s->datalen, s->window); /* max possible send size */ if (s->window==0 && s->datalen > s->unacked) { s->flags |= tcp_FlagPUSH; DB2((FDB,"tcp_send(): datalen=%d, window=%d\n",s->datalen,s->window)); } if (s->datalen >0 && s->window ==0 ) s->unhappy=YES; sentdata=0; do { DB2((FDB,"looping in tcp_send - sending %d data bytes\n", maxsend)); /* enter at least once, to send flags */ /* adjust size for each packet */ if( maxsend < (word)s->unacked ) { DB4((stderr,"Error - senddatalen<0\7\n")); } senddatalen= maxsend - (word)s->unacked; if (senddatalen > s->mss) senddatalen = s->mss; DB2((FDB,"tcp_send - truncate to %d data bytes\n", senddatalen)); /*** Prepare tcp header ***/ r=(char *)tcpp; #define tcpp ((tcp_Header *)r) tcpp->srcPort = intel16( s->myport ); tcpp->dstPort = intel16( s->hisport ); tcpp->seqnum = intel(s->seqnum + (long)s->unacked); tcpp->acknum = intel( s->acknum ); tcpp->window = intel16( s->rxbufsize - s->rdatalen ); tcpp->flags = intel16( s->flags | 0x5000 ); /* 5000 is the tcp_data_offset in the tcp header */ tcpp->checksum = 0; tcpp->urgentPointer = 0; if (s->unacked <0 || s->unacked > s->txbufsize) { DB4((FDB,"tcp_send: len=%5d unack=%5d rwin=%5d seq=%10lu\n", senddatalen, s->unacked, s->window, intel(tcpp->seqnum))); DB4((FDB," rdatalen=%5d thiswin=%5d ack=%10lu\n", s->rdatalen, intel16(tcpp->window), s->acknum)); } if ((s->flags & (tcp_FlagSYN | tcp_FlagACK)) == tcp_FlagSYN) { /* do options if this is our first packet */ sendpktlen = sizeof( tcp_Header ) + sizeof( in_Header ) + 4; tcpp->flags = intel16( intel16( tcpp->flags) + 0x1000 ); /*** 1000 means there are some options in the tcp header ***/ pkt->maxsegopt[0] = 0x0402; pkt->maxsegopt[1] = intel16( s->mss ); dp += 4; } else { /* handle packets with data */ if (senddatalen > 0) { memcpy(dp, s->data + s->unacked, senddatalen); dp[ senddatalen ] = 0; /* zero padding is safe */ } sendpktlen=senddatalen+sizeof(tcp_Header)+sizeof(in_Header); } /*** internet header, build IP header ***/ #undef tcpp make_in_hdr(inp, TCP_PROTO, s->hisaddr, sendpktlen); /* compute tcp checksum */ #ifdef notdef r= &ph; #define ph (*(tcp_PseudoHeader *)r) #endif /* notdef */ ph.src = inp->source; /* already INTELled */ ph.dst = inp->destination; ph.mbz = 0; ph.protocol = 6; ph.length = intel16( sendpktlen - sizeof(in_Header)); ph.checksum=inchksum((void *)&pkt->tcp,(sendpktlen-sizeof(in_Header)+1)&0xfffe); tcpp->checksum = ~inchksum((void *)&ph, sizeof(ph)); #ifdef ph #undef ph #endif /* ph */ if (senddatalen==0) { DB2((FDB,"calling _eth_send(): for empty pkt\n")); emptyretr++; } if (senddatalen==0 && (s->flags & tcp_FlagACK)) { DB2((FDB,"Send ACK\n")); emptyeack++; } #ifdef debug if (s->flags & tcp_FlagFIN) { DB2((FDB,"Sending FIN\n")); } if (s->flags & tcp_FlagSYN) { DB2((FDB,"Sending SYN\n")); } #endif /* debug */ if ( _eth_send( intel16( inp->length ))) { /* encounterred error */ s->unhappy=VERY; /* send failed, try later */ DB2((FDB,"Setting VERY cause of failure of eth_send\n")); break; } if (senddatalen==0) break; /* can't send more */ /* do next ip pkt */ sentdata += senddatalen; s->unacked += (int)senddatalen; /* 03-mar-92 lr-gm */ if (s->unacked >s->txbufsize) { DB4((stderr,"Wrong value in s->unacked: %d\n",s->unacked)); } } while ( s->unacked < (int)maxsend ); if (sentdata || (s->flags & (tcp_FlagSYN | tcp_FlagFIN)) ) { s->vj_last = set_ttimeout( 0 ); /* mark send time */ s->rtt_time = set_ttimeout( s->rto ); } else { s->vj_last = 0; /* might be useless */ s->rtt_time = 0; } } /* end tcp_send */ /* * Format and send a reset tcp packet */ void tcp_rst( in_Header *his_ip, tcp_Header *oldtcpp ) { tcp_PseudoHeader ph; struct _pkt { in_Header in; tcp_Header tcp; word maxsegopt[2]; } *pkt, *his_pkt; static longword nextrst = 0L; byte *dp; word oldflags; in_Header *inp; tcp_Header *tcpp; eth_Header *eth; int sendtotlen; /* length of packet */ longword templong; /* see RFC 793 page 65 for details */ DB2((FDB,"tcp_rst():\n")); gm_tcp_rst++; /* * at most one RST per tick */ if ( !chk_timeout( nextrst )) return; nextrst = set_ttimeout( 1 ); oldflags = intel16( oldtcpp->flags ); if (oldflags & tcp_FlagRST ) return; if ( (oldflags & (tcp_FlagACK | tcp_FlagFIN)) == (tcp_FlagACK | tcp_FlagFIN) ){ templong = oldtcpp->seqnum; oldtcpp->seqnum = oldtcpp->acknum; oldtcpp->acknum = templong; oldflags = tcp_FlagACK; } else if ((oldflags & (tcp_FlagSYN | tcp_FlagACK)) == tcp_FlagSYN ) { oldtcpp->acknum = intel( intel( oldtcpp->seqnum ) + 1 ); oldtcpp->seqnum = 0; oldflags = tcp_FlagACK | tcp_FlagRST; } else if ( oldflags & tcp_FlagACK ) { oldtcpp->seqnum = oldtcpp->acknum; oldtcpp->acknum = 0; } else { oldtcpp->acknum = intel( intel(oldtcpp->seqnum) + 1); oldtcpp->seqnum = 0; } if ( (oldflags & ( tcp_FlagFIN | tcp_FlagSYN )) == 0 ) oldflags ^= tcp_FlagACK | tcp_FlagRST; his_pkt = (struct _pkt*)( his_ip ); /* convoluted mechanism - reads his ethernet address or garbage */ eth = _eth_hardware( (byte *)his_ip ); pkt = (struct _pkt *)_eth_formatpacket( eth, 8); dp = (byte *)&pkt->maxsegopt; inp = &pkt->in; tcpp = &pkt->tcp; sendtotlen = sizeof( tcp_Header ) + sizeof( in_Header ); /* tcp header */ tcpp->srcPort = oldtcpp->dstPort; tcpp->dstPort = oldtcpp->srcPort; tcpp->seqnum = oldtcpp->seqnum; tcpp->acknum = oldtcpp->acknum; tcpp->window = 0; tcpp->flags = intel16( oldflags ); tcpp->checksum = 0; tcpp->urgentPointer = 0; /* internet header */ make_in_hdr(inp, TCP_PROTO, intel(his_ip->source) , sendtotlen); /* compute tcp checksum */ ph.src = inp->source; /* already INTELled */ ph.dst = inp->destination; ph.mbz = 0; ph.protocol = 6; ph.length = intel16( sendtotlen - sizeof(in_Header)); ph.checksum = inchksum((void *)&pkt->tcp, (sendtotlen - sizeof(in_Header) +1) & 0xfffe); /* intel16(ph.length));*/ /* 0; *//* watstar */ tcpp->checksum = ~inchksum((void *)&ph, sizeof(ph)); #ifdef DEBUG if (_dbugxmit) (*_dbugxmit)(NULL,inp,tcpp); #endif /* DEBUG */ DB2((FDB,"tcp_rst(): calling _eth_send():\n")); _eth_send( intel16( inp->length )); } /* end tcp_rst() */ /************************************************************* *** socket functions *** *************************************************************/ /*** *** sock_yield - enable user defined yield function ***/ void sock_yield( tcp_Socket *s, void (*fn)()) { if ( s ) s->usr_yield = fn; else system_yield = fn; } /*** *** sock_mode - set binary or ascii - affects sock_gets, sock_dataready *** - set udp checksums ***/ void sock_mode( sock_type *s, word mode ) { s->tcp.sock_mode = (s->tcp.sock_mode & 0xfffc) | mode; } /*** *** sock_read - read a socket with maximum n bytes *** - busywaits until buffer is full but calls s->usr_yield *** - returns count also when connection gets closed ***/ int sock_read(sock_type *s, byte *dp, int len ) { int templen, count=0; DB2((FDB,"sock_read():\n")); while (len) { if ( s->udp.ip_type == UDP_PROTO ) { if (len > (int)_mss) len =_mss; templen = udp_read((udp_Socket *) s, dp, len ); } else templen = tcp_read((tcp_Socket *)s, dp, len); if (s->tcp.usr_yield) (s->tcp.usr_yield)(); if (templen < 1 ) { /* couldn't read */ if (!tcp_tick( s )) { /* closed, or something */ /*** should also check for incoming PUSH ***/ return( count ); } } else { count += templen; dp += templen; len -= templen; } } return( count ); } /*** *** sock_fastread - read a socket with maximum n bytes *** - does not busywait until buffer is full ***/ int sock_fastread(sock_type *s, byte *dp, int len ) { DB2((FDB,"sock_fastread():\n")); return(s->udp.ip_type==UDP_PROTO ? udp_read((udp_Socket*)s,dp,len) : tcp_read((tcp_Socket *)s,dp,len) ); } /*** *** sock_write - writes data and returns length written *** - does not perform flush *** - repeatedly calls s->usr_yield ***/ int sock_write(register sock_type *s, byte *dp, int len) { register int offset=0; int oldmode=0, proto; int oldlen; int len1; DB2((FDB,"sock_write():\n")); oldlen=len; proto = (s->udp.ip_type == TCP_PROTO); if ( proto ) oldmode = s->tcp.flags & tcp_FlagPUSH; while ( len > 0) { len1=len; DB2((FDB,"looping in sock_write():\n")); if (proto) { s->tcp.flags |= oldmode; offset += tcp_write( (tcp_Socket *)s, &dp[ offset ], len); } else { /* 31-mar92 gm Added to warn in case of truncation to mss */ if (len > (int)_mss) { printf("Warning! Transmission truncated to MSS=%d\n",_mss); offset += udp_write(( udp_Socket*)s, &dp[offset], _mss); oldlen=_mss; } else offset += udp_write(( udp_Socket*)s, &dp[offset], len); } len=oldlen-offset; if (s->udp.usr_yield) { (s->udp.usr_yield)(); } if (!tcp_tick(s)) { return( 0 ); } DB2((FDB,"sock_write(): wrote %d, unacked=%d\n",len1,s->tcp.unacked)); DB2((FDB,"end looping in sock_write():\n")); } return( oldlen ); } int sock_fastwrite(sock_type *s, byte *dp, int len) { DB2((FDB,"sock_fastwrite():\n")); tcp_tick(NULL); /* updates our output buffer */ return( ( s->udp.ip_type == UDP_PROTO ) ? udp_write( (udp_Socket *)s, dp, len ) : tcp_write( (tcp_Socket *)s, dp, len) ); } void sock_flush( sock_type *s ) { if ( s->tcp.ip_type == TCP_PROTO ) tcp_Flush((tcp_Socket *) s ); } int sock_waitwrite( sock_type *s, byte *dp, int len ) { int templen; if ( s->udp.ip_type == UDP_PROTO ) { return udp_write( (udp_Socket *)s,dp,len); } else if ( (templen = s->tcp.datalen) + len > (int)(s->tcp.mss / 2)) { return( tcp_write( (tcp_Socket *)s, dp, len ) - templen ); } else { movmem( dp, &s->tcp.data[s->tcp.datalen], len ); s->tcp.datalen += len; return( len ); } } /*** *** sock_flushnext - cause next transmission to have a flush ***/ void sock_flushnext( sock_type *s) { if (s->tcp.ip_type == TCP_PROTO ) s->tcp.flags |= tcp_FlagPUSH; } /*** *** sock_putc - put a character *** - no expansion but flushes on '\n' *** - returns character ***/ byte sock_putc( sock_type *s, byte c ) { if (( c == '\n') || ( c == '\r')) sock_flushnext( s ); sock_write( s, &c, 1 ); return( c ); } word sock_getc( sock_type *s ) { char ch; sock_read( s, &ch, 1 ); return( ch ); } /* * sock_puts - does not append carriage return in binary mode * - returns length */ int sock_puts( sock_type *s, byte *dp ) { int len; len = strlen( dp ); sock_flushnext( s ); sock_write( s, dp, len ); if (s->tcp.sock_mode & TCP_MODE_ASCII ) sock_write( s, "\r\n", 2 ); return( len ); } /* * sock_update - update the socket window size to the other guy */ static void sock_update( tcp_Socket *s ) { if (s->ip_type == TCP_PROTO) { if ( s->rdatalen < ( 3 * s->rxbufsize) / 4 ) { tcp_send( s ); /* update the window */ } else { s->unhappy=YES; } } } /* * sock_gets - read a string from any socket * - return length of returned string * - removes end of line terminator */ word sock_gets( sock_type *s, byte *dp, int n ) { int len, templen; char *src_p, *temp, *temp2; word *np; short BufSize=0; if ( s->udp.ip_type == UDP_PROTO ) { src_p = s->udp.rdata; np = &s->udp.rdatalen; BufSize = s->udp.rxbufsize; } else { src_p = s->tcp.rdata; np = &s->tcp.rdatalen; BufSize = s->tcp.rxbufsize; } if ( n > BufSize) n = BufSize; src_p[ *np ] = 0; /* terminate string */ strncpy( dp, src_p, n ); /* copy everything */ dp[ n-1 ] = 0; /* terminate */ if (temp = strchr( dp, '\n')) *temp = 0; if (temp2= strchr( dp, '\r')) *temp2= 0; len = strlen( dp ); /* skip if there were no crs or lfs ??? */ if (!temp2 && !temp && ( (int)strlen(dp) < n - 1) ) { if ( s->udp.ip_type == TCP_PROTO && s->tcp.state != tcp_StateESTAB && s->tcp.state != tcp_StateESTCL && s->tcp.state != tcp_StateCLOSWT ) /* take what we can get from this connection */; else { *dp = 0; return( 0 ); } } /* skip over \n and \r but stop on end */ #ifndef OLD if ( temp ) templen = FP_OFF( temp ) - FP_OFF( dp ); else if ( temp2 ) templen = FP_OFF( temp2 ) - FP_OFF( dp ); else templen = len + 1; if (templen) { ++templen; movmem( &src_p[ templen ], src_p, *np -= templen); } else * np = 0; #else temp = &src_p[ len + 1 ]; while ( *temp && ( ( *temp == '\n' ) || (*temp == '\r'))) temp++; if (*temp) movmem( temp, src_p, *np = strlen( temp )); else *np = 0; #endif sock_update((tcp_Socket *) s ); /* new window */ return( len ); } /* * sock_dataready - returns number of bytes waiting to be ready * - if in ASCII mode, return 0 until a line is present * or the buffer is full */ int sock_dataready( register sock_type *s ) { int len; char *p; len = (s->tcp.ip_type == TCP_PROTO) ? s->tcp.rdatalen : s->udp.rdatalen; if (len == -1) return( -1 ); /* correction of fr instead of following 1 */ /*if ((len = s->tcp.rdatalen) == -1) return( -1 );*/ if( (s->tcp.ip_type == TCP_PROTO) && (s->tcp.sock_mode & TCP_MODE_ASCII)){ if ( len == s->tcp.rxbufsize ) return ( s->tcp.rxbufsize ); /* check for terminating \n \r */ p = s->tcp.rdata; if ( strchr( p , '\n') || strchr( p, '\r')) return( len ); return( 0 ); } else return( len ); } int sock_established( sock_type *s ) { switch ( s->tcp.ip_type ) { case UDP_PROTO : return( 1 ); case TCP_PROTO : return( s->tcp.state == tcp_StateESTAB || s->tcp.state == tcp_StateESTCL || s->tcp.state == tcp_StateCLOSWT ); default : return( 0 ); } } void sock_close( sock_type *s) { DB2((FDB,"sock_close():\n")); DB2((stderr,"Type of socket [%d]\n", s->udp.ip_type)); switch (s->udp.ip_type) { default: DB3((stderr,"Warning: calling sock_close on closed socket [%d]\n", s->udp.ip_type)); return; case UDP_PROTO : udp_close((udp_Socket *) s ); break; case TCP_PROTO : tcp_close((tcp_Socket *) s ); tcp_tick( s ); break; } } #ifdef unused /* * tcp_accept is superseded by accept() */ tcp_Socket * tcp_accept( tcp_Socket *sl ) { register tcp_Socket *s; s=sl->next; for(;;){ if (!tcp_tick((sock_type *)s)){ DB4((stderr,"Working on non existing socket!\n")); exit(4); } if(!s->brother){ DB4((stderr,"You are in the wrong place!\n")); exit(4); } if( (s->inlist) == LAZY && ( tcp_established((tcp_Socket *) s)) ){ s->inlist = BUSY; sl->next = s->brother; /* fair treatment of client calls */ return(s); } if((s->brother) == (sl->next)){ DB3((stderr,"\t\t\t\t\tNo new-call detected!\r")); return(NULL); } s=s->brother; } } #endif /* unused */ /*** *** _ip_delay0 called by macro sock_wait_established() *** _ip_delay1 called by macro sock_wait_intput() *** _ip_delay2 called by macro sock_wait_closed(); *** ***/ int _ip_delay0(register sock_type *s, int timeoutseconds, procref fn, int *statusptr ) { int status; ip_timer_init((udp_Socket *) s , timeoutseconds ); for (;;) { if ( s->tcp.ip_type == TCP_PROTO ) { if ( tcp_established((tcp_Socket *) s )) { status = 0; break; } } kbhit(); /* permit ^c */ if ( !tcp_tick( s )) { s->tcp.err_msg = "Host refused connection"; status = -1; /* get an early reset */ break; } if ( ip_timer_expired((udp_Socket *) s )) { sock_close( s ); status = -1; break; } if ( fn && (status = fn(s))) break; if ( s->tcp.usr_yield ) (*s->tcp.usr_yield)(); if ( s->tcp.ip_type == UDP_PROTO ) { status = 0; break; } } if (statusptr) *statusptr = status; return( status ); } int _ip_delay1(register sock_type *s, int timeoutseconds, procref fn, int *statusptr) { int status; ip_timer_init((udp_Socket *) s , timeoutseconds ); sock_flush( s ); /* new enhancement */ for (;;) { if ( sock_dataready( s )) { status = 0; break; } kbhit(); /* permit ^c */ if ( !tcp_tick( s )) { status = 1; break; } if ( ip_timer_expired((udp_Socket *) s )) { /* sock_close( s ); */ status = -1; break; } if (s->tcp.state == tcp_StateCLOSWT && s->tcp.rdatalen == 0) { status = 2; /* no more data can arrive */ break; } if (fn && (status = fn(s))) break; if ( s->tcp.usr_yield ) (*s->tcp.usr_yield)(); } if (statusptr) *statusptr = status; return( status ); } int _ip_delay2(register sock_type *s, int timeoutseconds, procref fn, int *statusptr) { int status; ip_timer_init((udp_Socket *)s , timeoutseconds ); if (s->tcp.ip_type != TCP_PROTO ) return( 1 ); for (;;) { kbhit(); /* permit ^c */ if ( (s->tcp.inlist)==LAZY ){ status=1; break; } if ( !tcp_tick( s )) { status = 1; break; } if ( ip_timer_expired((udp_Socket *) s )) { sock_abort((tcp_Socket *) s ); status = -1; break; } if (fn && (status = fn(s))) break; if ( s->tcp.usr_yield ) (*s->tcp.usr_yield)(); }; if (statusptr) *statusptr = status; return( status ); } char *rip( char *s ) { char *temp; if (temp = strchr( s, '\n')) *temp = 0; if (temp = strchr( s, '\r')) *temp = 0; return( s ); } extern long gm_eth_send; void gm_statistics(void) { printf(" TCP statistics \n"); printf("tcp_send: %8ld (eth_send): %8ld\n", gm_tcp_send, gm_eth_send); printf(" from retr: %8ld from very: %8ld\n", gm_tcp_send_retr, gm_tcp_send_very); printf(" empty: %8ld empty and ack: %8ld\n", emptyretr, emptyeack); printf("rtt expired: %8ld\n", gm_expire); printf("tcp_handler: %8ld ProcessData: %8ld\n", gm_tcp_handler, gm_tcp_ProcessData); printf(" from SYNSENT:%8ld from ESTAB: %8ld\n", gm_proces_sent , gm_proces_estab); printf("tcp_tick: %8ld tcp_Retr.: %8ld\n", gm_tcp_tick, gm_tcp_Retransmitter); printf("tcp_rst: %8ld\n", gm_tcp_rst); printf("tcp_unthread: %8ld\n", gm_tcp_unthread); } void gm_udp_statistics(void) { printf(" UDP statistics \n"); printf("udp_write: %8ld (eth_send): %8ld\n", gm_udp_write, gm_eth_send); printf("eth_send failed: %8ld\n", gm_udp_sendfailed); printf("udp_read of data: %8ld\n", gm_udp_read); printf("udp_handler: %8ld\n", gm_udp_handler); printf("tcp_tick: %8ld tcp_Retr.: %8ld\n", gm_tcp_tick, gm_tcp_Retransmitter); printf("tcp_rst: %8ld\n", gm_tcp_rst); } /*** end of file tcp.c ***/