BlinkenArea - GitList
Repositories
Blog
Wiki
flaneth
Code
Commits
Branches
Tags
Search
Tree:
e8658d5
Branches
Tags
master
flaneth
firmware.dartboard
tcp.c
initial commit after making CF identify work
Stefan Schuermans
commited
e8658d5
at 2012-04-15 19:57:57
tcp.c
Blame
History
Raw
/* flaneth - flash and ethernet - dartboard mod * version 0.1 date 2008-11-09 * Copyright (C) 2007-2008 Stefan Schuermans <stefan@schuermans.info> * Copyleft: GNU public license V2 - http://www.gnu.org/copyleft/gpl.html * a BlinkenArea project - http://www.blinkenarea.org/ */ #include <stdio.h> #include "config.h" #include "checksum.h" #include "debug.h" #include "ethernet.h" #include "http.h" #include "ip.h" #include "macros.h" #include "nethelp.h" #include "random.h" #include "tcp.h" #define TCP_URG 0x20 #define TCP_ACK 0x10 #define TCP_PSH 0x08 #define TCP_RST 0x04 #define TCP_SYN 0x02 #define TCP_FIN 0x01 #define TCP_FLAGS 0x3F #define TcpResendTicks 5 // time after which to resend a packet not ACKed (in 200ms steps, max. 255) #define TcpTimeWaitTicks 20 // time to wait in TIME_WAIT state (in 200ms steps, max. 255) #define TcpTimeoutTicks 250 // maximum idle time of connection before it is reset (in 200ms steps, max. 255) #define TcpMaxLifeTimeTicks 250 // maximum lifetime of connection before it is reset (in 200ms steps, max. 255) // TCP connections struct TcpConnection TcpConns[8]; // initialize void TcpInit( void ) // (extern) { unsigned char i; // set all connections to closed for( i = 0; i < count( TcpConns ); i++ ) TcpConns[i].State = TCP_CLOSED; } // send a TCP packet // pData must point to a struct TcpPacket with TcpHdr.SrcPort, TcpHdr.DestPort, // TcpHdr.SeqNo, TcpHdr.AckNo, TcpHdr.WndSz and IpHdr.Dest already initialized static void TcpSendPacket( unsigned char * pData, unsigned short Length, unsigned char optLen, unsigned char flags ) { struct TcpPacket * pTcpPack; unsigned int chk; // packet too short if( Length < sizeof( struct TcpPacket ) + optLen ) return; // convert pointer to UDP packet // (this saves us from always casting pData) pTcpPack = (struct TcpPacket *)pData; debug_tcp_printf( "send src=%u dest=%u flags=%s%s%s%s%s%s len=%u", ntohs( pTcpPack->TcpHdr.SrcPort ), ntohs( pTcpPack->TcpHdr.DestPort ), flags & TCP_URG ? "U" : "", flags & TCP_ACK ? "A" : "", flags & TCP_PSH ? "P" : "", flags & TCP_RST ? "R" : "", flags & TCP_SYN ? "S" : "", flags & TCP_FIN ? "F" : "", Length ); // fill in header values pTcpPack->TcpHdr.Ofs_Flags = htons( (unsigned short)((optLen + 23) & 0x3C) << 10 | (unsigned short)(flags & TCP_FLAGS) ); pTcpPack->TcpHdr.Chk = 0x0000; pTcpPack->TcpHdr.UrgentPtr = 0x0000; ip_cpy( pTcpPack->IpHdr.Src, ConfigIp ); // put IP already here into IP header // because it is needed for calculation of UDP checksum // generate checksum chk = Checksum( (unsigned char *)&pTcpPack->IpHdr.Src, Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) + 8, 0x0006, Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) ); pTcpPack->TcpHdr.Chk = htons( chk ); // send TCP packet pTcpPack->IpHdr.Proto = 0x06; // TCP IpSend( pData, Length ); } // send an empty segment static void TcpEmptySegment( struct TcpConnection * pConn, unsigned long seq, unsigned long ack, unsigned char flags ) { struct TcpPacket EsPack; // send empty TCP segment EsPack.TcpHdr.SrcPort = htons( pConn->LocalPort ); EsPack.TcpHdr.DestPort = htons( pConn->RemotePort ); EsPack.TcpHdr.SeqNo = htonl( seq ); EsPack.TcpHdr.AckNo = htonl( ack ); EsPack.TcpHdr.WndSz = htons( pConn->RcvWnd ); ip_cpy( EsPack.IpHdr.Dest, pConn->RemoteIp ); TcpSendPacket( (unsigned char *)&EsPack, sizeof( EsPack ), 0, flags ); return; } // send a SYN segment (empty with MSS option set) static void TcpSynSegment( struct TcpConnection * pConn, unsigned long seq, unsigned long ack, unsigned char flags ) { struct { struct TcpPacket Tcp; struct { unsigned char Kind, Len; unsigned short Mss; } MssOpt; } Pack; // send empty TCP segment with MSS Pack.Tcp.TcpHdr.SrcPort = htons( pConn->LocalPort ); Pack.Tcp.TcpHdr.DestPort = htons( pConn->RemotePort ); Pack.Tcp.TcpHdr.SeqNo = htonl( seq ); Pack.Tcp.TcpHdr.AckNo = htonl( ack ); Pack.Tcp.TcpHdr.WndSz = htons( pConn->RcvWnd ); ip_cpy( Pack.Tcp.IpHdr.Dest, pConn->RemoteIp ); Pack.MssOpt.Kind = 2; Pack.MssOpt.Len = 4; Pack.MssOpt.Mss = htons( 256 ); TcpSendPacket( (unsigned char *)&Pack, sizeof( Pack ), sizeof( Pack.MssOpt ), flags ); return; } // send a data segment static void TcpSendDataSegment( unsigned char connNo, struct TcpConnection * pConn, char needSendAck ) { unsigned long sendDataLenL; unsigned short sendDataLen, len; struct { struct TcpPacket Tcp; unsigned char Data[256]; // do not use larger segments to save stack memory } Packet; // if outbound connection is not closed yet if( pConn->State == TCP_ESTAB || pConn->State == TCP_CLOSE_WAIT ) { // get maximum number of bytes that might be sent in this segment sendDataLenL = (unsigned long)pConn->SndWnd - (pConn->SndNxt - pConn->SndUna); if( (signed long)sendDataLenL < 0 ) // just to be on the safe side - this should never happen sendDataLen = 0; sendDataLen = (unsigned short)min( sendDataLenL, (unsigned long)pConn->Mss ); sendDataLen = min( sendDataLen, sizeof( Packet.Data ) ); // get data to send from user if( sendDataLen > 0 ) { debug_tcp_printf( "notify send no=%u pos=%lu max_len=%u", connNo, pConn->SndNxt - (pConn->Iss + 1), sendDataLen ); len = pConn->Notify->Send( connNo, pConn->SndNxt - (pConn->Iss + 1), Packet.Data, sendDataLen ); if( len > sendDataLen ) // returned 0xFFFF (or any number too large) to close connection { debug_tcp_printf( "notify send no=%u close", connNo ); TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_FIN | TCP_ACK ); // send TCP FIN pConn->SndNxt++; pConn->State = TCP_FIN_WAIT_1; // go to state FIN-WAIT-1 debug_tcp_printf( "notify close no=%u", connNo ); pConn->Notify->Close( connNo ); // signal "connection closed" to user return; } debug_tcp_printf( "notify send no=%u len=%u", connNo, len ); sendDataLen = len; } } // if outbound connection is already closed else // do not send data sendDataLen = 0; // send a segment if data is available or an ACK needs to be sent if( sendDataLen > 0 || needSendAck ) { // send TCP segment Packet.Tcp.TcpHdr.SrcPort = htons( pConn->LocalPort ); Packet.Tcp.TcpHdr.DestPort = htons( pConn->RemotePort ); Packet.Tcp.TcpHdr.SeqNo = htonl( pConn->SndNxt ); Packet.Tcp.TcpHdr.AckNo = htonl( pConn->RcvNxt ); Packet.Tcp.TcpHdr.WndSz = htons( pConn->RcvWnd ); ip_cpy( Packet.Tcp.IpHdr.Dest, pConn->RemoteIp ); TcpSendPacket( (unsigned char *)&Packet, sizeof( struct TcpPacket ) + sendDataLen, 0, sendDataLen > 0 ? TCP_ACK | TCP_PSH : TCP_ACK ); pConn->SndNxt += sendDataLen; } } // tick procedure - call every 200ms void TcpTick200( void ) // (extern) { unsigned char i, MaxTicks; // for all active connections for( i = 0; i < count( TcpConns ); i++ ) { if( TcpConns[i].State != TCP_CLOSED ) { // increase normal timer TcpConns[i].Ticks++; // get maximum value for normal timer MaxTicks = TcpConns[i].State == TCP_TIME_WAIT ? TcpTimeWaitTicks : TcpResendTicks; // normal timer elapsed if( TcpConns[i].Ticks >= MaxTicks ) { // reset normal timer TcpConns[i].Ticks = 0; // different behaviour in different states switch( TcpConns[i].State ) { case TCP_SYN_RCVD: // resend SYN,ACK segment TcpSynSegment( &TcpConns[i], TcpConns[i].SndUna, TcpConns[i].RcvNxt, TCP_SYN | TCP_ACK ); TcpConns[i].SndNxt = TcpConns[i].SndUna + 1; break; case TCP_SYN_SENT: // resend SYN segment TcpSynSegment( &TcpConns[i], TcpConns[i].SndUna, 0, TCP_SYN ); TcpConns[i].SndNxt = TcpConns[i].SndUna + 1; break; case TCP_ESTAB: case TCP_CLOSE_WAIT: // if something is not yet ACKed if( (long)(TcpConns[i].SndUna - TcpConns[i].SndNxt) < 0 ) { // resend data segment TcpConns[i].SndNxt = TcpConns[i].SndUna; // some kind of "go back N" // BUG: this is not really "go back N" // according to RFC793, every segment sent and not acknowledged has to be stored // in the resend queue until is is acknowledged // but this is not possible on a microcontroller with 4kB of RAM TcpSendDataSegment( i, &TcpConns[i], 0 ); } break; case TCP_FIN_WAIT_1: case TCP_CLOSING: case TCP_LAST_ACK: // resend FIN segment TcpEmptySegment( &TcpConns[i], TcpConns[i].SndUna, TcpConns[i].RcvNxt, TCP_FIN | TCP_ACK ); TcpConns[i].SndNxt = TcpConns[i].SndUna + 1; break; case TCP_TIME_WAIT: // close connection, free TCB TcpConns[i].State = TCP_CLOSED; break; } // switch( TcpConns[i].State ) } // if( TcpConns[i].Ticks >= ... } // if( TcpConns[i].State != ... if( TcpConns[i].State != TCP_CLOSED ) { // increase timeout timer TcpConns[i].Timeout++; // timeout timer elapsed if( TcpConns[i].Timeout >= TcpTimeoutTicks ) { // send a RST segment TcpEmptySegment( &TcpConns[i], TcpConns[i].SndUna, TcpConns[i].RcvNxt, TCP_RST ); // depending on state ... switch( TcpConns[i].State ) { case TCP_SYN_RCVD: case TCP_ESTAB: case TCP_CLOSE_WAIT: TcpConns[i].State = TCP_CLOSED; // close connection debug_tcp_printf( "notify close no=%u", i ); TcpConns[i].Notify->Close( i ); // tell user that connection was closed break; default: TcpConns[i].State = TCP_CLOSED; // close connection } } // if( TcpConns[i].Timeout >= ... } // if( TcpConns[i].State != ... if( TcpConns[i].State != TCP_CLOSED ) { // increase lifetime timer TcpConns[i].LifeTime++; // lifetime timer elapsed // - connections may not last forever - even not with traffic on them if( TcpConns[i].LifeTime >= TcpMaxLifeTimeTicks ) { // send a RST segment TcpEmptySegment( &TcpConns[i], TcpConns[i].SndUna, TcpConns[i].RcvNxt, TCP_RST ); // depending on state ... switch( TcpConns[i].State ) { case TCP_SYN_RCVD: case TCP_ESTAB: case TCP_CLOSE_WAIT: TcpConns[i].State = TCP_CLOSED; // close connection debug_tcp_printf( "notify close no=%u", i ); TcpConns[i].Notify->Close( i ); // tell user that connection was closed break; default: TcpConns[i].State = TCP_CLOSED; // close connection } } // if( TcpConns[i].LifeTime >= ... } // if( TcpConns[i].State != ... } // for( i ... } // process a received TCP packet void TcpRecv( unsigned char * pData, unsigned short Length ) // (extern) { struct TcpPacket * pTcpPack; unsigned long seq, ack, seqEnd; unsigned short localPort, remotePort, wnd, mss, ofs, len, tmp, rcvWnd; unsigned char i, flags, * optPtr, optLen, connNo; struct TcpConnection * pConn; char accept, sendAck; struct TcpNotify * pNotify; // packet too short if( Length < sizeof( struct TcpPacket ) ) return; // convert pointer to TCP packet // (this saves us from always casting pData) pTcpPack = (struct TcpPacket *)pData; // test checksum if( Checksum( (unsigned char*)&pTcpPack->IpHdr.Src, Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) + 8, 0x0006, Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) ) != 0 ) return; // get local and remote port localPort = ntohs( pTcpPack->TcpHdr.DestPort ); remotePort = ntohs( pTcpPack->TcpHdr.SrcPort ); // ignore packets sent from or to port 0 // - this might be some attack if( localPort == 0 || remotePort == 0 ) return; // get sequence number, acknowledge number and window size seq = ntohl( pTcpPack->TcpHdr.SeqNo ); ack = ntohl( pTcpPack->TcpHdr.AckNo ); wnd = ntohs( pTcpPack->TcpHdr.WndSz ); // maximum segment size: liberal default according to RFC879 mss = 536; // get flags flags = ntohs( pTcpPack->TcpHdr.Ofs_Flags ) & 0x003F; // get data offset and segment length in bytes ofs = (ntohs( pTcpPack->TcpHdr.Ofs_Flags ) & 0xF000) >> 10; if( ofs < 20 || ofs > Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) ) // invalid offset return; // remote side is unable to build valid TCP packets - ignore // get segment length (length of data in segment) len = Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) - ofs; debug_tcp_printf( "recv src=%u dest=%u flags=%s%s%s%s%s%s len=%u", ntohs( pTcpPack->TcpHdr.SrcPort ), ntohs( pTcpPack->TcpHdr.DestPort ), flags & TCP_URG ? "U" : "", flags & TCP_ACK ? "A" : "", flags & TCP_PSH ? "P" : "", flags & TCP_RST ? "R" : "", flags & TCP_SYN ? "S" : "", flags & TCP_FIN ? "F" : "", Length ); // process options optLen = (unsigned char)(ofs - 20); optPtr = (unsigned char *)&pTcpPack->TcpHdr + sizeof( struct TcpHeader ); while( optLen > 0 ) { switch( *optPtr ) { // end of options case 0: optLen = 0; break; // no operation case 1: optLen--; optPtr++; break; // maximum segment size case 2: if( optLen < 4 || optPtr[1] != 4 ) { optPtr = NULL; // error optLen = 0; break; } mss = ntohs( *(unsigned short *)(optPtr+2) ); optLen -= 4; optPtr += 4; break; // unknown option default: if( optLen < 2 || optPtr[1] > optLen ) { optPtr = NULL; // error optLen = 0; } optLen -= optPtr[1]; // ignore this option optPtr += optPtr[1]; } // switch( *optPtr ) } // while( optLen > 0 ) if( optPtr == NULL ) // some error during option parsing return; // remote side is unable to build valid TCP packets - ignore // get sequence number at end of this segment seqEnd = seq + len; if( flags & TCP_SYN ) // TCP SYN counts as 1 in sequence number space seqEnd++; if( flags & TCP_FIN ) // TCP FIN counts as 1 in sequence number space seqEnd++; // search connection for( i = 0; i < count( TcpConns ); i++ ) if( TcpConns[i].State != TCP_CLOSED && ip_eq( TcpConns[i].RemoteIp, pTcpPack->IpHdr.Src ) && TcpConns[i].LocalPort == localPort && TcpConns[i].RemotePort == remotePort ) break; // connection not found, only SYN flag set, no data if( i >= count( TcpConns ) && flags == TCP_SYN && len == 0 ) { // accept connections on some ports (note: localPort cannot be 0 because of check above) rcvWnd = 0; pNotify = NULL; // HTTP if( localPort == ConfigHttpPort ) { rcvWnd = 256; // connection shall be accepted pNotify = &HttpNotify; } // connection shall be accepted if( pNotify != NULL ) { // search empty connection slot for( i = 0; i < count( TcpConns ); i++ ) if( TcpConns[i].State == TCP_CLOSED ) break; // free connection slot found if( i < count( TcpConns ) ) { // create new connection (passive) in this slot ip_cpy( TcpConns[i].RemoteIp, pTcpPack->IpHdr.Src ); TcpConns[i].LocalPort = localPort; TcpConns[i].RemotePort = remotePort; TcpConns[i].State = TCP_LISTEN; TcpConns[i].Ticks = 0; TcpConns[i].Timeout = 0; TcpConns[i].LifeTime = 0; TcpConns[i].RcvWnd = rcvWnd; TcpConns[i].Notify = pNotify; } } } // connection still not found if( i >= count( TcpConns ) ) { struct TcpPacket EsPack; unsigned char EsFlags; // do nothing if RST flag is set if( flags & TCP_RST ) return; // send TCP RST EsPack.TcpHdr.SrcPort = htons( localPort ); EsPack.TcpHdr.DestPort = htons( remotePort ); EsPack.TcpHdr.WndSz = htons( 0 ); ip_cpy( EsPack.IpHdr.Dest, pTcpPack->IpHdr.Src ); if( flags & TCP_ACK ) { EsPack.TcpHdr.SeqNo = htonl( ack ); EsPack.TcpHdr.AckNo = htonl( 0 ); EsFlags = TCP_RST; } else { EsPack.TcpHdr.SeqNo = htonl( 0 ); EsPack.TcpHdr.AckNo = htonl( seqEnd ); EsFlags = TCP_RST | TCP_ACK; } TcpSendPacket( (unsigned char *)&EsPack, sizeof( EsPack ), 0, EsFlags ); return; } // a connection was found - save number and pointer to it connNo = i; pConn = &TcpConns[i]; // reset connection on reception of urgent data (URG flag set) // BUG: urgent data must be supported according to RFC793 // but urgent data is not used in protocols we use, so leave this out here to save time and memory if( flags & TCP_URG ) { // send TCP RST if( flags & TCP_ACK ) TcpEmptySegment( pConn, ack, 0, TCP_RST ); else TcpEmptySegment( pConn, 0, seqEnd, TCP_RST | TCP_ACK ); return; } // different behaviour in different states according to RFC793 switch( pConn->State ) { case TCP_LISTEN: if( flags & TCP_RST ) // An incoming RST should be ignored. return; if( flags & TCP_ACK ) // Any acknowledgment is bad if it arrives on a connection still in the LISTEN state. { TcpEmptySegment( pConn, ack, 0, TCP_RST ); // An acceptable reset segment should be formed for any arriving ACK-bearing segment. pConn->Ticks = 0; // restart timer return; } if( flags & TCP_SYN ) // third check for a SYN { pConn->RcvNxt = seq + 1; // Set RCV.NXT to SEG.SEQ+1, pConn->Irs = seq; // IRS is set to SEG.SEQ RandomGetData( (unsigned char *)&pConn->Iss, sizeof( pConn->Iss ) ); // ISS should be selected (randomly!!!) TcpSynSegment( pConn, pConn->Iss, pConn->RcvNxt, TCP_SYN | TCP_ACK ); // and a SYN segment sent pConn->SndNxt = pConn->Iss + 1; // SND.NXT is set to ISS+1 pConn->SndUna = pConn->Iss; // and SND.UNA to ISS pConn->State = TCP_SYN_RCVD; // The connection state should be changed to SYN-RECEIVED. pConn->SndWnd = wnd; // initialize send window from packet pConn->SndWl1 = seq; pConn->SndWl2 = ack; pConn->Mss = mss; // save maximum segment size pConn->Ticks = 0; // restart timer return; } // fourth other text or control // So you are unlikely to get here, but if you do, drop the segment, and return. return; case TCP_SYN_SENT: accept = 0; if( flags & TCP_ACK ) // If the ACK bit is set { if( (long)(ack - pConn->Iss) <= 0 || (long)(ack - pConn->SndNxt) > 0 ) // If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, { if( ! (flags & TCP_RST) ) // (unless the RST bit is set) TcpEmptySegment( pConn, ack, 0, TCP_RST ); // send a reset return; // and discard the segment. Return. } accept = (long)(pConn->SndUna - ack) <= 0 && (long)(ack - pConn->SndNxt) <= 0; // If SND.UNA =< SEG.ACK =< SND.NXT then the ACK is acceptable. } if( flags & TCP_RST ) // If the RST bit is set { if( accept ) // If the ACK was acceptable pConn->State = TCP_CLOSED; // enter CLOSED state, delete TCB, return; // drop the segment and return. } if( (accept || ! (flags & TCP_ACK)) && // This step should be reached only if the ACK is ok, or there is no ACK, flags & TCP_SYN ) // If the SYN bit is on { pConn->RcvNxt = seq + 1; // RCV.NXT is set to SEG.SEQ+1, pConn->Irs = seq; // IRS is set to SEG.SEQ. pConn->SndWl2 = pConn->RcvNxt; // last acknowledge number when updating the window size is first acknowledge number at all if( accept ) // (if there is an ACK) pConn->SndUna = ack; // SND.UNA should be advanced to equal SEG.ACK pConn->SndWnd = wnd; // initialize send window from packet pConn->SndWl1 = seq; pConn->SndWl2 = ack; pConn->Mss = mss; // save maximum segment size if( (long)(pConn->SndUna - pConn->Iss) > 0 ) // If SND.UNA > ISS (our SYN has been ACKed), { pConn->State = TCP_ESTAB; // change the connection state to ESTABLISHED, debug_tcp_printf( "notify connect no=%u", connNo ); pConn->Notify->Connect( connNo ); // signal "connected" TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_ACK ); // form an ACK segment and send it. } else // Otherwise { pConn->State = TCP_SYN_RCVD; // enter SYN-RECEIVED, TcpSynSegment( pConn, pConn->Iss, pConn->RcvNxt, TCP_SYN | TCP_ACK ); // form a SYN,ACK segment and send it. } pConn->Ticks = 0; // restart timer } return; } // switch( pConn->State ) // Otherwise, // first check sequence number if( len == 0 ) if( pConn->RcvWnd == 0 ) accept = seq == pConn->RcvNxt; // SEG.SEQ = RCV.NXT else accept = (long)(pConn->RcvNxt - seq) <= 0 && // RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND (long)(seq - (pConn->RcvNxt + pConn->RcvWnd)) < 0; else if( pConn->RcvWnd == 0 ) accept = 0; // not acceptable else accept = ((long)(pConn->RcvNxt - seq) <= 0 && // RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND (long)(seq - (pConn->RcvNxt + pConn->RcvWnd)) < 0) || ((long)(pConn->RcvNxt - (seq + len - 1) <= 0) && // or RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND (long)((seq + len - 1) - (pConn->RcvNxt + pConn->RcvWnd)) < 0); // because there is not enough memory to store segments needed later, // we only accept segments with SEG.SEQ <= RCV.NXT // thus, we reject the segment if SEG.SEQ > RCV.NXT // BAD PERFORMANCE: this is _not_ a bug, but a major impact on performace when packets arrive out of order // - however, we cannot do something against it, // because there is not enough memory available on the controller // to store segements for later processing if( accept && (long)(seq - pConn->RcvNxt) > 0 ) accept = 0; // If an incoming segment is not acceptable if( ! accept ) { /* disabled this - sometimes it ssems to generates endless ACKs being exchanged with remote host) if( ! (flags & TCP_RST) ) // (unless the RST bit is set) TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_ACK ); // an acknowledgment should be sent in reply */ return; // drop the unacceptable segment and return. } // if segment contains duplicate data tmp = pConn->RcvNxt - seq; // cannot be negative because of checks above if( tmp > 0 ) { // remove duplicate SYN flag from segment if( flags & TCP_SYN ) { flags &= ~TCP_SYN; tmp--; } // remove duplicate data from segment ofs += tmp; len -= tmp; // length cannot become negative here because of checks above // now this segments starts with the expected sequence number seq = pConn->RcvNxt; } // if segment extends beyond end of receive window if( len > pConn->RcvWnd ) { // remove data behind receive window seqEnd -= len - pConn->RcvWnd; len = pConn->RcvWnd; } // restart timer, because this was an acceptable segment pConn->Ticks = 0; // second check the RST bit, if( flags & TCP_RST ) { switch( pConn->State ) { case TCP_SYN_RCVD: pConn->State = TCP_CLOSED; // enter the CLOSED state and delete the TCB, an return. debug_tcp_printf( "notify close no=%u", connNo ); pConn->Notify->Close( connNo ); // signal "connection refused" return; case TCP_ESTAB: case TCP_FIN_WAIT_1: case TCP_FIN_WAIT_2: case TCP_CLOSE_WAIT: pConn->State = TCP_CLOSED; // Enter the CLOSED state, delete the TCB, and return. debug_tcp_printf( "notify close no=%u", connNo ); pConn->Notify->Close( connNo ); // signal "connection reset" return; case TCP_CLOSING: case TCP_LAST_ACK: case TCP_TIME_WAIT: pConn->State = TCP_CLOSED; // enter the CLOSED state, delete the TCB, and return. return; } // switch( pConn->State ) } // if( flags & TCP_RST ) // third check security and precedence // - this does not apply to our implementation because we do not support security and precedence on TCP layer // fourth, check the SYN bit if( flags & TCP_SYN ) { switch( pConn->State ) { case TCP_SYN_RCVD: case TCP_ESTAB: case TCP_FIN_WAIT_1: case TCP_FIN_WAIT_2: case TCP_CLOSE_WAIT: // If the SYN is in the window it is an error, // - this is always the case here, because SYN not in window would habe been removed above TcpEmptySegment( pConn, ack, 0, TCP_RST ); // send a reset, pConn->State = TCP_CLOSED; // enter the CLOSED state, delete the TCB, and return. debug_tcp_printf( "notify close no=%u", connNo ); pConn->Notify->Close( connNo ); // signal "connection reset" return; case TCP_CLOSING: case TCP_LAST_ACK: case TCP_TIME_WAIT: // If the SYN is in the window it is an error, // - this is always the case here, because SYN not in window would habe been removed above TcpEmptySegment( pConn, ack, 0, TCP_RST ); // send a reset, pConn->State = TCP_CLOSED; // enter the CLOSED state, delete the TCB, and return. return; } // switch( pConn->State ) } // if( flags & TCP_SYN ) // fifth check the ACK field, if( ! (flags & TCP_ACK) ) // if the ACK bit is off return; // drop the segment and return switch( pConn->State ) { case TCP_SYN_RCVD: if( (long)(pConn->SndUna - ack) <= 0 && // If SND.UNA =< SEG.ACK =< SND.NXT (long)(ack - pConn->SndNxt) <= 0 ) { pConn->State = TCP_ESTAB; // then enter ESTABLISHED state debug_tcp_printf( "notify connect no=%u", connNo ); pConn->Notify->Connect( connNo ); // signal "connected" // and continue processing. } else // If the segment acknowledgment is not acceptable, { TcpEmptySegment( pConn, ack, 0, TCP_RST ); // form a reset segment, and send it. return; // drop this segment } // no break here case TCP_ESTAB: case TCP_FIN_WAIT_1: case TCP_FIN_WAIT_2: case TCP_CLOSE_WAIT: case TCP_CLOSING: if( (long)(ack - pConn->SndUna) <= 0 ) // If the ACK is a duplicate (SEG.ACK <= SND.UNA), break; // it can be ignored. if( (long)(ack - pConn->SndNxt) > 0 ) // If the ACK acks something not yet sent (SEG.ACK > SND.NXT) { TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_ACK ); // then send an ACK, return; // drop the segment, and return. } // here: SND.UNA < SEG.ACK =< SND.NXT pConn->SndUna = ack; // set SND.UNA <- SEG.ACK. if( (long)(pConn->SndWl1 - seq) < 0 || // If SND.WL1 < SEG.SEQ (pConn->SndWl1 == seq && (long)(pConn->SndWl2 - ack) <= 0) ) // or (SND.WL1 = SEG.SEQ and SND.WL2 =< SEG.ACK), { pConn->SndWnd = wnd; // set SND.WND <- SEG.WND, pConn->SndWl1 = seq; // set SND.WL1 <- SEG.SEQ, pConn->SndWl2 = ack; // and set SND.WL2 <- SEG.ACK. } pConn->Timeout = 0; // restart timeout timer debug_tcp_printf( "notify sent no=%u pos=%lu", connNo, pConn->SndNxt - (pConn->Iss + 1) ); pConn->Notify->Sent( connNo, pConn->SndUna - (pConn->Iss + 1) ); // signal "send completed" (up to ack) // additional processing switch( pConn->State ) { case TCP_FIN_WAIT_1: if( pConn->SndUna == pConn->SndNxt ) // if our FIN is now acknowledged pConn->State = TCP_FIN_WAIT_2; // then enter FIN-WAIT-2 and continue processing in that state. // no break here case TCP_FIN_WAIT_2: if( pConn->SndUna == pConn->SndNxt ) // if the retransmission queue is empty, { debug_tcp_printf( "notify close no=%u", connNo ); pConn->Notify->Close( connNo ); // the user's CLOSE can be acknowledged } break; case TCP_CLOSING: if( pConn->SndUna == pConn->SndNxt ) // if the ACK acknowledges our FIN pConn->State = TCP_TIME_WAIT; // then enter the TIME-WAIT state, else return; // otherwise ignore the segment. break; } break; case TCP_LAST_ACK: // The only thing that can arrive in this state is an acknowledgment of our FIN. if( pConn->SndUna == pConn->SndNxt ) // If our FIN is now acknowledged, { pConn->State = TCP_CLOSED; // delete the TCB, enter the CLOSED state, return; // and return. } break; case TCP_TIME_WAIT: // The only thing that can arrive in this state is a retransmission of the remote FIN. TcpEmptySegment( pConn, ack, seqEnd, TCP_ACK ); // Acknowledge it, break; } // switch( pConn->State ) // sixth, check the URG bit, // - this cannot occur, because we have alredy reset the connection if URG was set // no ACK needs to be sent yet sendAck = 0; // seventh, process the segment text, if( len > 0 ) { switch( pConn->State ) { case TCP_ESTAB: case TCP_FIN_WAIT_1: case TCP_FIN_WAIT_2: pConn->RcvWnd -= len; // make receive window smaller (len <= pConn->RcvWnd) pConn->RcvNxt += len; // advance sequence number of next data to receive pConn->Timeout = 0; // restart timeout timer debug_tcp_printf( "notify recv no=%u pos=%lu len=%u, min_wnd=%u", connNo, pConn->RcvNxt - (pConn->Irs + 1) - len, len, pConn->RcvWnd ); pConn->RcvWnd = pConn->Notify->Received( connNo, pConn->RcvNxt - (pConn->Irs + 1) - len, // give received data to user (unsigned char *)&pTcpPack->TcpHdr + ofs, len, // (update receive window size) pConn->RcvWnd ); debug_tcp_printf( "notify recv no=%u wnd=%u", connNo, pConn->RcvWnd ); sendAck = 1; // remember to send an ACK break; } // switch( pConn->State ) } // if( len > 0 ) // eighth, check the FIN bit, if( flags & TCP_FIN ) { switch( pConn->State ) { case TCP_SYN_RCVD: case TCP_ESTAB: sendAck = 1; // remember to send an ACK pConn->RcvNxt++; // FIN counts as one in sequence number space pConn->State = TCP_CLOSE_WAIT; // Enter the CLOSE-WAIT state. // close outbound part of the connection // i.e. do an automatic call to close TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_FIN | TCP_ACK ); // send a FIN pConn->SndNxt++; pConn->State = TCP_CLOSING; debug_tcp_printf( "notify close no=%u", connNo ); pConn->Notify->Close( connNo ); // signal "connection closed" to user return; // we do not need to send an ack, becaue we already sent it with our FIN case TCP_FIN_WAIT_1: sendAck = 1; // remember to send an ACK pConn->RcvNxt++; // FIN counts as one in sequence number space pConn->State = TCP_CLOSING; // Enter the CLOSING state. break; case TCP_FIN_WAIT_2: sendAck = 1; // remember to send an ACK pConn->RcvNxt++; // FIN counts as one in sequence number space pConn->State = TCP_TIME_WAIT; // Enter the TIME-WAIT state. break; case TCP_CLOSING: case TCP_CLOSE_WAIT: case TCP_LAST_ACK: case TCP_TIME_WAIT: sendAck = 1; // remember to send an ACK break; } // switch( pConn->State ) } // send data / ACK segment TcpSendDataSegment( connNo, pConn, sendAck ); } // open a TCP connection // must not be called from a TCP notification function // returns the connection number of the new connection of 0xFF in case of error unsigned char TcpOpen( unsigned char * remoteIp, unsigned short remotePort, // (extern) unsigned short initialWnd, struct TcpNotify * Notify ) #define TcpOpenLocalPortMin 32768 #define TcpOpenLocalPortRange 16384 { static unsigned short nextLocalPort = TcpOpenLocalPortMin; // local port to use for next TCP connection unsigned short localPort; unsigned char i; debug_tcp_printf( "open ip=%u.%u.%u.%u port=%u", remoteIp[0], remoteIp[1], remoteIp[2], remoteIp[3], remotePort ); // search an unused local port for( localPort = nextLocalPort; ; localPort++ ) { for( i = 0; i < count( TcpConns ); i++ ) if( TcpConns[i].State != TCP_CLOSED && TcpConns[i].LocalPort == localPort ) break; if( i >= count( TcpConns ) ) break; } // save next local port to use nextLocalPort = localPort + 1; if( nextLocalPort >= TcpOpenLocalPortMin + TcpOpenLocalPortRange ) nextLocalPort = TcpOpenLocalPortMin; // search empty connection slot for( i = 0; i < count( TcpConns ); i++ ) if( TcpConns[i].State == TCP_CLOSED ) break; // no free connection slot found if( i >= count( TcpConns ) ) return 0xFF; // create new connection in this slot ip_cpy( TcpConns[i].RemoteIp, remoteIp ); TcpConns[i].LocalPort = localPort; TcpConns[i].RemotePort = remotePort; TcpConns[i].RcvNxt = 0; TcpConns[i].Irs = 0; RandomGetData( (unsigned char *)&TcpConns[i].Iss, sizeof( TcpConns[i].Iss ) ); // An initial send sequence number (ISS) is selected. TcpSynSegment( &TcpConns[i], TcpConns[i].Iss, 0, TCP_SYN ); // A SYN segment of the form <SEQ=ISS><CTL=SYN> is sent. TcpConns[i].SndUna = TcpConns[i].Iss; // Set SND.UNA to ISS, TcpConns[i].SndNxt = TcpConns[i].Iss + 1; // SND.NXT to ISS+1, TcpConns[i].State = TCP_SYN_SENT; // enter SYN-SENT state, TcpConns[i].SndWnd = 0; // not allowed to send data for now TcpConns[i].SndWl1 = 0; // window size was never updated TcpConns[i].SndWl2 = 0; TcpConns[i].Ticks = 0; // restart timers TcpConns[i].Timeout = 0; TcpConns[i].LifeTime = 0; TcpConns[i].RcvWnd = initialWnd; TcpConns[i].Notify = Notify; debug_tcp_printf( "open no=%u", i ); // return connection number return i; } // close a TCP connection // must not be called from a TCP notification function void TcpClose( unsigned char connNo ) // (extern) { struct TcpConnection * pConn; debug_tcp_printf( "close no=%u", connNo ); // connection does not exist if( connNo >= count( TcpConns ) || TcpConns[connNo].State == TCP_CLOSED ) return; // get connection pConn = &TcpConns[connNo]; // different actions in different states accroding to RFC793 switch( pConn->State ) { case TCP_LISTEN: case TCP_SYN_SENT: pConn->State = TCP_CLOSED; // Delete TCB, enter CLOSED state, break; // and return. case TCP_SYN_RCVD: case TCP_ESTAB: TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_FIN | TCP_ACK ); // form a FIN segment and send it, pConn->SndNxt++; pConn->State = TCP_FIN_WAIT_1; // and enter FIN-WAIT-1 state; debug_tcp_printf( "notify close no=%u", connNo ); pConn->Notify->Close( connNo ); // signal "connection closed" to user break; case TCP_CLOSE_WAIT: TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_FIN | TCP_ACK ); // send a FIN segment, pConn->SndNxt++; pConn->State = TCP_CLOSING; // enter CLOSING state. debug_tcp_printf( "notify close no=%u", connNo ); pConn->Notify->Close( connNo ); // signal "connection closed" to user break; } // switch( pConn->State ) } // request sending on a TCP connection // must not be called from a TCP notification function // this makes the send notification to be called if possible void TcpSend( unsigned char connNo ) // (extern) { struct TcpConnection * pConn; debug_tcp_printf( "send no=%u", connNo ); // connection does not exist if( connNo >= count( TcpConns ) || TcpConns[connNo].State == TCP_CLOSED ) return; // get connection pConn = &TcpConns[connNo]; // different behaviour in different states switch( pConn->State ) { case TCP_ESTAB: case TCP_CLOSE_WAIT: // if nothing is sent and not yet ACKed if( pConn->SndUna == pConn->SndNxt ) // send a data segment TcpSendDataSegment( connNo, pConn, 0 ); break; } // switch( TcpConns[i].State ) } // dummy notification functions void TcpDummyConnect( unsigned char ConnNo ) { } void TcpDummyClose( unsigned char ConnNo ) { } unsigned short TcpDummySend( unsigned char ConnNo, unsigned long Pos, unsigned char * pBuffer, unsigned short MaxLen ) { return 0; } void TcpDummySent( unsigned char ConnNo, unsigned long Pos ) { } unsigned short TcpDummyReceived( unsigned char ConnNo, unsigned long Pos, unsigned char * pBuffer, unsigned short Len, unsigned short curWnd ) { return max( curWnd, 128 ); } struct TcpNotify TcpDummyNotify = // (extern) { .Connect = TcpDummyConnect, .Close = TcpDummyClose, .Send = TcpDummySend, .Sent = TcpDummySent, .Received = TcpDummyReceived, };