/* tcp.c */ #define SCKTMASTER #define TCP_UDP /* * Includes */ #include #include "vtcpip.h" #include "dfault.h" #include "evtdef.h" #include "hstdef.h" #include "prodef.h" #include "prodat.h" #include "tcpdat.h" #include "mapskt.h" #include "bytprc.h" #include "hdrchk.h" #include "detach.h" #include "cticks.h" /* * tcpinterpret * * Called when a packet comes in and passes the IP checksum and is of * TCP protocol type. Check to see if we have an open connection on * the appropriate socket and stuff it in the right buffer */ int tcpinterpret(pkt,tlen) int tlen; register TCPKT *pkt; { int i,j,sknum,myport,hlen,hisport; register struct socket *skt; register struct service *sp; #ifdef DEBUGOPTION if(debug&0x104) { dmpfil("tcpinp",(DLAYER *)pkt,sizeof(DLAYER)+sizeof(IPLAYER)+tlen); } #endif /* * First, fill the pseudo header with its fields, * then run our checksum to confirm it. */ if(pkt->t.check) { /* * move both addresses */ movebytes(tcps.source,pkt->i.ipsource,8); tcps.z = 0; tcps.proto = pkt->i.protocol; tcps.tcplen = intswap(tlen); /* * compute checksum */ if(tcpcheck((char *)&tcps,(char *)&pkt->t,tlen)) { ntposterr(400); return(2); } } /* * bytes offset to data */ hlen = pkt->t.hlen >> 2; /* * find the port which is associated with the incoming packet * First try open connections, listeners, and then services */ myport = intswap(pkt->t.dest); hisport = intswap(pkt->t.source); #ifdef DEBUGOPTION if(debug&0x02) { printf("\nmyport = %d, hisport = %d\n", myport,hisport); } #endif /* * Try open connections */ for (sknum=0; sknumi.ipsource,4)) { #ifdef DEBUGOPTION if(debug&0x02) { printf("open: inport = %d, outport = %d\n", sktlist[sknum].inport,sktlist[sknum].outport); } #endif return(tcpdo(sknum,pkt,tlen,hlen)); } } /* * Try listeners */ for (sknum=0; sknumt.flags & TSYN) { #ifdef DEBUGOPTION if(debug&0x02) { printf("listener: inport = %d, outport = %d\n", sktlist[sknum].inport,sktlist[sknum].outport); } #endif return(tcpdo(sknum,pkt,tlen,hlen)); } } /* * Try services */ if(pkt->t.flags & TSYN) { sp = Scon.srvc; while(sp != NULL) { if(sp->port == myport) { /* * Check session count */ for (i=0,j=0; istate>SLISTEN) j++; } } if(j >= sp->maxses) break; /* * Create a socket */ sknum = makesocket(0); if(sknum < 0) break; skt = (struct socket *) mapskt(sknum); /* * Try to start up service */ j = strtdj(sp->cmdfil); if(j) { sktlist[sknum].jobnum = -j; } else { skt->state = SCLOSED; break; } /* * Set socket for listening */ skt->sktport = myport; ntlisten(sknum); /* * Set an abort timer event incase of failure */ Stmrset(SRVCABORT,myport,-j,ATTACHTIMEOUT); /* * Now allow connection to be made */ #ifdef DEBUGOPTION if(debug&0x02) { printf("services: inport = %d, outport = %d\n", sktlist[sknum].inport,sktlist[sknum].outport); } #endif return(tcpdo(sknum,pkt,tlen,hlen)); } sp = sp->next; } } /* * no matching port was found to handle this packet, reject it */ tcpreset(pkt); /* * no error message if it is a SYN */ if(!(pkt->t.flags & TSYN)) /* * invalid port for incoming packet */ ntposterr(407); return(1); } /* * tcpdo * * Deliver the incoming packet. */ static int tcpdo(sknum,pkt,tlen,hlen) int sknum,tlen,hlen; register TCPKT *pkt; { register struct socket *skt; register int ret; ret = 0; skt = (struct socket *) mapskt(sknum); #ifdef DEBUGOPTION if(debug&0x02) { printf("sknum = %d, skt->state(in) = %d", sknum, skt->state); } #endif switch(skt->state) { case SLISTEN: /* waiting for remote connection */ if(pkt->t.flags & TSYN) { /* * receive SYN * remember anything important * from the incoming TCP header */ skt->out.size = intswap(pkt->t.window); sktlist[sknum].outport = skt->out.port = intswap(pkt->t.source); skt->in.nxt = longswap(pkt->t.seq)+1; /* * set the necessary fields * in the outgoing TCP packet */ skt->tcpout.t.dest = pkt->t.source; skt->tcpout.t.ack = longswap(skt->in.nxt); skt->tcpout.t.flags = TSYN|TACK; /* * initialize all of the low-level * transmission stuff(IP and lower) */ movebytes(sktlist[sknum].remoteip, pkt->i.ipsource,4); movebytes(skt->tcps.dest, pkt->i.ipsource,4); movebytes(skt->tcpout.i.ipdest, pkt->i.ipsource,4); movebytes(skt->tcpout.d.dest, pkt->d.me,DADDLEN); tcpsend(skt,4); /* * syn received */ skt->state = SSYNR; } break; case SSYNR: if(!(pkt->t.flags & TACK)) { /* * not the right one */ tcpsend(skt,4); break; } /* * response not needed */ skt->tcpout.t.hlen = 20<<2; skt->out.lasttime = cticks(NULL); /* * count SYN as sent */ skt->out.nxt++; /* * starting ACK value */ skt->out.ack = longswap(pkt->t.ack); /* * allowed window */ skt->out.size = intswap(pkt->t.window); /* * starting ACK flag */ skt->tcpout.t.flags = TACK; /* * see if MSS option is there */ checkmss(sknum,pkt,hlen); /* * drop through to established */ skt->state = SEST; ntptevent(CONCLASS,CONOPEN,sknum); case SEST: /* normal data transmission */ /* * check and accept a possible piggybacked ack */ ackcheck(sknum,pkt); estabcon(sknum,pkt,tlen,hlen); break; case SSYNS: /* check to see if it ACKS correctly */ /* remember that tcpout is pre-set-up */ if(pkt->t.flags & TACK) { if(longswap(pkt->t.ack) != skt->out.nxt) { ntposterr(401); ret = 1; break; } } if(pkt->t.flags & TRESET) { ntposterr(507); skt->state = SCLOSED; ntptuev(CONCLASS,CONCLOSE,sknum); ret = 1; break; } if(pkt->t.flags & TSYN) { skt->in.nxt = longswap(pkt->t.seq) + 1; skt->tcpout.t.ack = longswap(skt->in.nxt); skt->out.ack = longswap(pkt->t.ack); skt->out.size = intswap(pkt->t.window); skt->out.lasttime = 0L; if(pkt->t.flags & TACK) { /* * need to send ACK */ skt->tcpout.t.flags = TACK; skt->state = SEST; ntptevent(CONCLASS,CONOPEN,sknum); checkmss(sknum,pkt,hlen); } else { /* * need to send SYN & ACK */ skt->tcpout.t.flags = TSYN|TACK; skt->state = SSYNR; } } break; case SCWAIT: ackcheck(sknum,pkt); if(skt->in.rdptr==skt->in.wtptr) { skt->tcpout.t.flags = TFIN|TACK; skt->out.lasttime = 0L; skt->state = SLAST; } break; case SLAST: /* check ack of FIN, */ /* or reset to see if we are done */ if((pkt->t.flags & TRESET) || (longswap(pkt->t.ack) == skt->out.nxt+1)) skt->state = SCLOSED; break; case SFW1: /* waiting for ACK of FIN */ /* throw away data */ skt->in.nxt = longswap(pkt->t.seq)+tlen-hlen; if(pkt->t.flags & TRESET) { skt->state = SCLOSED; } else if(longswap(pkt->t.ack) != skt->out.nxt+1) { if(pkt->t.flags & TFIN) { /* * got FIN,no ACK for mine * account for FIN byte */ skt->in.nxt++; skt->tcpout.t.ack = longswap(skt->in.nxt); /* * cause last ACK to be sent */ skt->tcpout.t.flags = TACK; skt->out.lasttime = 0L; skt->state = SCLOSING; } else { skt->tcpout.t.ack = longswap(skt->in.nxt); /* * cause FIN & ACK to be sent */ skt->tcpout.t.flags = TACK|TFIN; skt->out.lasttime = 0L; } } else if(pkt->t.flags & TFIN) { /* * ACK and FIN * * account for his FIN flag */ skt->in.nxt++; /* * account for my FIN */ skt->out.nxt++; skt->tcpout.t.ack = longswap(skt->in.nxt); /* * cause last ACK to be sent */ skt->tcpout.t.flags = TACK; skt->out.lasttime = 0L; skt->state = STWAIT; } else { /* * got ACK, no FIN * account for my FIN byte */ skt->out.nxt++; /* * final pkt has no FIN flag */ skt->tcpout.t.flags = TACK; skt->state = SFW2; } break; case SFW2: /* want FIN */ skt->in.nxt = longswap(pkt->t.seq)+tlen-hlen; if(pkt->t.flags & TRESET) { skt->state = SCLOSED; } else if(pkt->t.flags & TFIN) { /* * we got FIN * count his FIN byte */ skt->in.nxt++; skt->tcpout.t.ack = longswap(skt->in.nxt); /* * cause last ACK to be sent */ skt->out.lasttime = 0L; skt->state = STWAIT; } break; case SCLOSING: /* want ACK of FIN */ if(pkt->t.flags & TRESET) { skt->state = SCLOSED; } else if(!ackcheck(sknum,pkt)) { /* * account for my FIN byte */ skt->out.nxt++; /* * time-wait state next */ skt->state = STWAIT; } break; case STWAIT: /* ack FIN again? */ if(pkt->t.flags & TRESET) skt->state = SCLOSED; /* * only if he wants it */ if(pkt->t.flags & TFIN) skt->out.lasttime = 0L; if(skt->out.lasttime != 0L) { if(elapsed(skt->out.lasttime) > WAITTIME) skt->state = SCLOSED; } break; case SCLOSED: sktlist[sknum].inport = skt->in.port = 0; sktlist[sknum].outport = skt->out.port = 0; break; default: /* unknown tcp state */ ntposterr(403); break; } #ifdef DEBUGOPTION if(debug&0x02) { printf(", skt->state(out) = %d\r\n", skt->state); } #endif return(ret); } /* * checkmss * * Look at incoming SYN,ACK packet and check for the options field * containing a TCP Maximum segment size option. If it has one, * then set the port's internal value to make sure that it never * exceeds that segment size. */ static void checkmss(sknum,pkt,hlen) int sknum; register TCPKT *pkt; int hlen; { unsigned int i; register struct socket *skt; skt = (struct socket *) mapskt(sknum); /* * check header for maximum segment size option */ if(hlen > 20 && pkt->x.options[0] == 2 && pkt->x.options[1] == 4) { /* * swapped value of maxseg */ movebytes((char *)&i,(char *)&pkt->x.options[2],2); i = intswap(i); /* * we have our own limits too */ if(i < skt->sendsize) skt->sendsize = i; } } /* * tcpreset * * Send a reset packet back to sender * Use the packet which just came in as a template to return to * sender. Fill in all of the fields necessary and dlsend it back. */ static int tcpreset(pkt) register TCPKT *pkt; { unsigned int tport; int rstlen; struct pseudotcp xxx; /* * don't reset a reset */ if(pkt->t.flags & TRESET) return(1); /* * swap TCP layer portions for sending back */ if(pkt->t.flags & TACK) { /* * ack becomes next seq # */ pkt->t.seq = pkt->t.ack; /* * ack # is 0 */ pkt->t.ack = 0L; } else { pkt->t.ack = longswap( longswap(pkt->t.seq)+pkt->i.tlen-sizeof(IPLAYER)); pkt->t.seq = 0L; } pkt->t.flags = TRESET; /* * swap port #'s */ tport = pkt->t.source; pkt->t.source = pkt->t.dest; pkt->t.dest = tport; pkt->t.hlen = 20<<2; pkt->t.window = 0; /* * create pseudo header for checksum */ xxx.z = 0; xxx.proto = pkt->i.protocol; xxx.tcplen = intswap(20); movebytes(xxx.source,pkt->i.ipsource,4); movebytes(xxx.dest,pkt->i.ipdest,4); pkt->t.check = 0; pkt->t.check = tcpcheck((char *)&xxx, (char *)&pkt->t,sizeof(struct tcph)); /* * IP and data link layers */ /* * machine it came from */ movebytes(pkt->i.ipdest,pkt->i.ipsource,4); movebytes(pkt->i.ipsource,nnipnum,4); pkt->i.tlen = intswap(sizeof(IPLAYER)+sizeof(TCPLAYER)); pkt->i.identity = nnipident++; pkt->i.ttl = TCPTTL; pkt->i.check = 0; pkt->i.check = ipcheck((char *)&pkt->i,10); rstlen = sizeof(DLAYER) + sizeof(IPLAYER) + sizeof(TCPLAYER); /* * data link address */ movebytes(pkt->d.dest,pkt->d.me,DADDLEN); /* * my address */ movebytes(pkt->d.me,dblank.me,DADDLEN); #ifdef DEBUGOPTION if(debug&0x104) { dmpfil("tcprst",(DLAYER *)pkt,rstlen); } #endif return(dlsend((DLAYER *)pkt,rstlen)); } /* * tcpsend * * transmits a TCP packet. * * For IP: * sets identity,check,totallen * For TCP: * sets seq and window from port information, * fills in the pseudo header and computes the checksum. * Assumes that all fields not filled in here are filled in by the * calling proc or were filled in by makesocket(). * (see all inits in protinit) */ int tcpsend(skt,dlen) register struct socket *skt; int dlen; { int tcplen; /* * set current in.size */ skt->in.size = inroom(skt); /* * do IP header first */ skt->tcpout.i.identity = intswap(nnipident++); skt->tcpout.i.tlen = intswap(sizeof(struct iph)+sizeof(struct tcph) + dlen); /* * install checksum */ skt->tcpout.i.check = 0; skt->tcpout.i.check = ipcheck((char *)&skt->tcpout.i,10); /* * do TCP header */ skt->tcpout.t.seq = longswap(skt->out.nxt); /* * if the port has some credit limit, use it instead of large * window buffer. Generally demanded by hardware limitations. */ if(skt->credit < skt->in.size) skt->tcpout.t.window = intswap(skt->credit); else skt->tcpout.t.window = intswap(skt->in.size); /* * prepare pseudo-header for checksum */ skt->tcps.tcplen = intswap(dlen+sizeof(TCPLAYER)); skt->tcpout.t.check = 0; skt->tcpout.t.check = tcpcheck((char *)&skt->tcps, (char *)&skt->tcpout.t,dlen+sizeof(struct tcph)); skt->out.lasttime = cticks(NULL); tcplen = sizeof(DLAYER) + sizeof(IPLAYER) + sizeof(TCPLAYER) + dlen; #ifdef DEBUGOPTION if(debug&0x104) { dmpfil("tcpout",(DLAYER *)&skt->tcpout,tcplen); } #endif return(dlsend((DLAYER *)&skt->tcpout,tcplen)); } /* * ackcheck * * take an incoming packet and see if there is an ACK for the outgoing * side. Use that ACK to dequeue outgoing data. */ static int ackcheck(sknum,pkt) int sknum; register TCPKT *pkt; { register struct socket *skt; register int i,j; long ak,rttl; skt = (struct socket *) mapskt(sknum); if((pkt->t.flags & TRESET) && (pkt->t.seq == skt->tcpout.t.ack)) { ntposterr(405); skt->state = SCLOSED; ntptuev(CONCLASS,CONCLOSE,sknum); return(1); } /* * check ACK flag */ if(!(pkt->t.flags & TACK)) return(1); /* * allowable transmission size */ skt->out.size = intswap(pkt->t.window); /* * rmqueue any bytes which have been ACKed, update skt->out.nxt * to the new next seq number for outgoing. Update send window. */ /* * other side's ACK */ ak = longswap(pkt->t.ack); /* * If ak is not increasing (above skt->out.nxt) then we should assume * that it is a duplicate packet or one of those stupid keepalive * packets that 4.2 sends out. * * ((a) - (b)) handles sequence space wrap-around. * Overflow/Underflow for the operation makes the result * below correct (-, 0, +) for any a, b in the sequence space. * Results: result implies * - a < b * 0 a = b * + a > b */ i = ((ak) - (skt->out.nxt)); if(i > 0) { /* * take off of queue */ rmqueue(skt,i); skt->out.nxt = ak; skt->out.ack = ak; /* * Check to see if this acked our most recent transmission. * If so, adjust the RTO value to reflect the newly measured RTT. * This formula reduces the RTO value so that it gradually * approaches twice the most recent round trip measurement. * This essentially allows a variance equal to the round trip time. * When a packet is retransmitted, this value is doubled * (exponential backoff). */ if(skt->out.lasttime!=0L && skt->out.rdptr==skt->out.wtptr) { rttl = elapsed(skt->out.lasttime); if(rttl>1)) i = MINRTO>>1; i <<= 1; j = skt->rto; skt->rto = (i + j + j + j)>>2; } } else { if(skt->out.size>0) /* * forces xmit */ skt->out.lasttime = 0L; } return(0); } return(1); } /* * estabcon * * take a packet which has arrived for an established connection * and put it where it belongs. */ static int estabcon(sknum,pkt,tlen,hlen) int sknum; register TCPKT *pkt; int tlen,hlen; { int dlen,i,j; long sq,want; register struct socket *skt; skt = (struct socket *) mapskt(sknum); skt->in.size = inroom(skt); dlen = tlen-hlen; /* * see if we want this packet, or is it a duplicate? */ sq = longswap(pkt->t.seq); want = skt->in.nxt; if(sq != want) { /* * overlap */ i = ((sq) - (want)); j = ((sq + dlen) - (want)); if(i < 0 && j >= 0) { /* * offset desired (i < 0) */ hlen -= i; /* * skip this much (i < 0) */ dlen += i; } else { /* * make the ACK time out */ skt->out.lasttime = 0L; return(-1); } } else { if(dlen <= 0) { /* * only an ACK packet * * might still have FIN */ checkfin(sknum,pkt); return(0); } } /* * If we have room in the window, update the ACK field values */ if(skt->in.size >= dlen) { /* * new ack value */ skt->in.nxt += dlen; skt->tcpout.t.ack = longswap(skt->in.nxt); /* * new window size */ skt->in.size -= dlen; /* * move data to queue */ enqueue(skt,pkt->x.data+hlen-20,dlen); /* * urgent flag set ? */ if(pkt->t.flags & TURG) skt->in.urgent = (int) skt->in.wtptr; /* * tell user about it */ ntptuev(CONCLASS,CONDATA,sknum); /* * force timeout for ACK */ skt->out.lasttime = 0L; skt->in.lasttime = cticks(NULL); } else { /* * no room in input buffer * * re-ack old sequence value */ skt->out.lasttime = 0L; } /* * Check the FIN bit to see if this connection is closing */ checkfin(sknum,pkt); return(0); } /* * checkfin * * Check the FIN bit of an incoming packet to see if the connection * should be closing, ACK it if we need to. * Half open connections immediately, automatically close. We do * not support them. As soon as the incoming data is delivered, the * connection will close. */ static void checkfin(sknum,pkt) int sknum; register TCPKT *pkt; { register struct socket *skt; skt = (struct socket *) mapskt(sknum); if(pkt->t.flags & TFIN) { /* * fin bit found * * count the FIN byte */ skt->in.nxt++; /* * close-wait */ skt->state = SCWAIT; /* * set ACK in packet */ skt->tcpout.t.ack = longswap(skt->in.nxt); skt->credit = 0; /* * cause ACK to be sent */ skt->out.lasttime = 0L; ntptuev(CONCLASS,CONCLOSE,sknum); /* * At this point, we know that we have received all data * that the other side is allowed to send. Some of that data * may still be in the incoming queue. As soon as that queue * empties, finish off the TCP close sequence. We are not * allowing the user to utilize a half-open connection, but * we cannot close before the user has received all of the * data from the incoming queue. */ /* * data remaining ? */ if(skt->in.rdptr==skt->in.wtptr) { skt->tcpout.t.flags = TFIN|TACK; tcpsend(skt,0); skt->state = SLAST; } } }