BlinkenArea - GitList
Repositories
Blog
Wiki
flaneth
Code
Commits
Branches
Tags
Search
Tree:
ed647c2
Branches
Tags
master
flaneth
firmware
tcp.c
save memory, to avoid stack hitting data segment
Stefan Schuermans
commited
ed647c2
at 2012-05-06 17:28:25
tcp.c
Blame
History
Raw
/* flaneth - flash and ethernet Copyright (C) 2007-2012 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 50 // maximum idle time of connection before it // is reset (in 200ms steps, max. 255) #define TcpMaxLifeTimeTicks 150 // maximum lifetime of connection before it // is reset (in 200ms steps, max. 255) // TCP connections struct TcpConnection TcpConns[4]; // 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)ConnNo; } void TcpDummyClose(unsigned char ConnNo) { (void)ConnNo; } unsigned short TcpDummySend(unsigned char ConnNo, unsigned long Pos, unsigned char *pBuffer, unsigned short MaxLen) { return 0; (void)ConnNo; (void)Pos; (void)pBuffer; (void)MaxLen; } void TcpDummySent(unsigned char ConnNo, unsigned long Pos) { (void)ConnNo; (void)Pos; } unsigned short TcpDummyReceived(unsigned char ConnNo, unsigned long Pos, unsigned char *pBuffer, unsigned short Len, unsigned short curWnd) { return max(curWnd, 128); (void)ConnNo; (void)Pos; (void)pBuffer; (void)Len; } struct TcpNotify TcpDummyNotify = // (extern) { .Connect = TcpDummyConnect, .Close = TcpDummyClose, .Send = TcpDummySend, .Sent = TcpDummySent, .Received = TcpDummyReceived, };