home *** CD-ROM | disk | FTP | other *** search
- /*
- * TCP.C
- *
- * TCP level routines
- *
- ***************************************************************************
- * *
- * part of: *
- * TCP/UDP/ICMP/IP Network kernel for NCSA Telnet *
- * by Tim Krauskopf *
- * *
- * National Center for Supercomputing Applications *
- * 152 Computing Applications Building *
- * 605 E. Springfield Ave. *
- * Champaign, IL 61820 *
- * *
- * Copyright (c) 1987, Board of Trustees of the University of Illinois *
- * *
- ****************************************************************************
- *
- * Revision history:
- *
- * 10/86 started
- * 2/88 mods for int16/int32
- * 5/89 clean up for 2.3 release, JKM
- *
- */
-
- /*
- * Includes
- */
- #include <stdio.h>
- #ifdef MEMORY_DEBUG
- #include "memdebug.h"
- #endif
- #include "protocol.h"
- #include "data.h"
- #include "externs.h"
-
- /*
- * Semi-Global Vars
- */
- static int pnum; /* port number */
-
- /************************************************************************
- *
- * tcpinterpret ( p, tlen )
- *
- * Called when a packet comes in and passes the IP checksum and is ov
- * TCP protocol type. Check to see if we have an open connection on
- * the appropriate port and stuff it in the right buffer
- *
- */
- int tcpinterpret(p,tlen)
- int tlen;
- TCPKT *p;
- {
- uint i,myport,hlen,hisport;
- struct port *prt;
- /*
- * checksum
- * First, fill the pseudo header with its fields, then run our
- * checksum to confirm it.
- *
- */
- if(p->t.check) {
- movebytes(tcps.source,p->i.ipsource,8); /* move both addresses */
- tcps.z=0;
- tcps.proto=p->i.protocol;
- tcps.tcplen=intswap(tlen); /* byte-swapped length */
- if(tcpcheck((char *)&tcps,(char *)&p->t,tlen)) { /* compute checksum */
- netposterr(400);
- return(2);
- }
- }
- /*
- * find the port which is associated with the incoming packet
- * First try open connections, then try listeners
- */
- myport=intswap(p->t.dest);
- hisport=intswap(p->t.source);
- hlen=p->t.hlen >> 2; /* bytes offset to data */
- for (i=0; i<NPORTS; i++) {
- prt=portlist[i];
- if(prt!= NULL&&prt->in.port==myport&&prt->out.port==hisport) {
- pnum=i;
- return(tcpdo(prt,p,tlen,hlen));
- }
-
- }
- /*
- * check to see if the incoming packet should go to a listener
- */
- for(i=0; i< NPORTS; i++) {
- prt=portlist[i];
- if(prt!=NULL&&!prt->out.port&&prt->in.port==myport&&(p->t.flags & TSYN)) {
- pnum=i;
- return(tcpdo(prt,p,tlen,hlen));
- }
- }
- /*
- * no matching port was found to handle this packet, reject it
- */
- tcpreset(p); /* tell them they are crazy */
- if(!(p->t.flags&TSYN)){ /* no error message if it is a SYN */
- netposterr(407); /* invalid port for incoming packet */
-
- inv_port_err(1,myport,p->i.ipdest);
- }
- return(1); /* no port matches */
- }
-
- /**********************************************************************/
- /*
- * tcpdo ( prt, p, tlen, hlen )
- *
- * Deliver the incoming packet.
- *
- */
- int tcpdo(prt,p,tlen,hlen)
- int tlen,hlen;
- struct port *prt;
- TCPKT *p;
- {
- switch(prt->state) {
- case SLISTEN: /* waiting for remote connection */
- if(p->t.flags&TSYN) { /* receive SYN */
- /*
- * remember anything important from the incoming TCP header
- */
- prt->out.size=intswap(p->t.window); /* credit window */
- prt->out.port=intswap(p->t.source);
- prt->in.nxt=longswap(p->t.seq)+1;
- /*
- * set the necessary fields in the outgoing TCP packet
- */
- prt->tcpout.t.dest=p->t.source;
- prt->tcpout.t.ack=longswap(prt->in.nxt);
- prt->tcpout.t.flags=TSYN|TACK;
- prt->tcpout.t.hlen=24<<2;
- /*
- * note that the maxmimum segment size is installed by 'netlisten()'
- * hence the header length is 24, not 20
- */
- /*
- * initialize all of the low-level transmission stuff(IP and lower)
- */
- movebytes(prt->tcps.dest,p->i.ipsource,4);
- movebytes(prt->tcpout.i.ipdest,p->i.ipsource,4);
- movebytes(prt->tcpout.d.dest,p->d.me,DADDLEN);
- /*
- * look up address in the arp cache if using Localtalk encapsulation
- */
- if(!nnemac) {
- unsigned char *pc;
-
- pc=getdlayer(p->i.ipsource);
- if(pc!= NULL)
- movebytes(prt->tcpout.d.dest,pc,DADDLEN);
- else
- return(0); /* no hope this time */
- }
- tcpsend(prt,4);
- prt->state=SSYNR; /* syn received */
- }
- break;
-
- case SSYNR:
- if(!(p->t.flags&TACK)) {
- tcpsend(prt,4);
- break; /* not the right one */
- }
- prt->tcpout.t.hlen=20<<2;
- prt->out.lasttime=n_clicks(); /* don't need response */
- prt->out.nxt++; /* count SYN as sent */
- prt->out.ack=longswap(p->t.ack); /* starting ACK value */
- prt->out.size=intswap(p->t.window); /* allowed window */
- prt->tcpout.t.flags=TACK; /* starting ACK flag */
- prt->state=SEST; /* drop through to established */
- netputevent(CONCLASS,CONOPEN,pnum);
- checkmss(prt,p,hlen); /* see if MSS option is there */
- /* fall through */
-
- case SEST: /* normal data transmission */
- /*
- * check and accept a possible piggybacked ack
- */
- ackcheck(prt,p,pnum);
- estab1986(prt,p,tlen,hlen);
- return(0);
-
- case SSYNS: /* check to see if it ACKS correctly */
- /* remember that tcpout is pre-set-up */
- if(p->t.flags&TACK) { /* It is ACKING us */
- if((uint32)longswap(p->t.ack)!= (prt->out.nxt)) {
- netposterr(401);
- return(1);
- }
- }
- if(p->t.flags&TRESET) {
- netposterr(507);
- prt->state=SCLOSED;
- netputuev(CONCLASS,CONCLOSE,pnum);
- return(1);
- }
- if(p->t.flags&TSYN) { /* need to send ACK */
- prt->tcpout.t.flags=TACK;
- prt->in.nxt=longswap(p->t.seq) + 1;
- prt->tcpout.t.ack=longswap(prt->in.nxt);
- prt->out.ack=longswap(p->t.ack);
- prt->out.size=intswap(p->t.window); /* credit window */
- prt->out.lasttime=0L;
- if(p->t.flags&TACK) {
- prt->state=SEST;
- netputevent(CONCLASS,CONOPEN,pnum);
- checkmss(prt,p,hlen);
- }
- else
- prt->state=SSYNR; /* syn received */
- }
- break;
-
- case SCWAIT:
- ackcheck(prt,p,pnum);
- if(!prt->in.contain) {
- prt->tcpout.t.flags=TFIN|TACK;
- prt->out.lasttime=0L;
- prt->state=SLAST;
- }
- break;
-
- case SLAST: /* check ack of FIN, or reset to see if we are done */
- if((p->t.flags&TRESET)||((uint32)longswap(p->t.ack)==(prt->out.nxt+1)))
- prt->state=SCLOSED;
- break;
-
- case SFW1: /* waiting for ACK of FIN */
- /* throw away data */
- prt->in.nxt=longswap(p->t.seq)+tlen-hlen;
- if(p->t.flags&TRESET)
- prt->state=SCLOSED;
- else
- if((uint32)longswap(p->t.ack)!=(prt->out.nxt+1)) {
- if(p->t.flags&TFIN) { /* got FIN, no ACK for mine */
- prt->in.nxt++; /* account for FIN byte */
- prt->tcpout.t.ack=longswap(prt->in.nxt);
- prt->tcpout.t.flags=TACK; /* final byte has no FIN flag */
- prt->out.lasttime=0L; /* cause last ACK to be sent */
- prt->state=SCLOSING;
- }
- else {
- prt->tcpout.t.ack=longswap(prt->in.nxt);
- prt->tcpout.t.flags=TACK|TFIN;
- prt->out.lasttime=0L;
- }
- }
- else
- if(p->t.flags&TFIN) { /* ACK and FIN */
- prt->in.nxt++; /* account for his FIN flag */
- prt->out.nxt++; /* account for my FIN */
- prt->tcpout.t.ack=longswap(prt->in.nxt);
- prt->tcpout.t.flags=TACK; /* final byte has no FIN flag */
- prt->out.lasttime=0L; /* cause last ACK to be sent */
- prt->state=STWAIT; /* we are done */
- }
- else { /* got ACK, no FIN */
- prt->out.nxt++; /* account for my FIN byte */
- prt->tcpout.t.flags=TACK; /* final pkt has no FIN flag */
- prt->state=SFW2;
- }
- break;
-
- case SFW2: /* want FIN */
- prt->in.nxt=longswap(p->t.seq)+tlen-hlen;
- if(p->t.flags&TRESET)
- prt->state=SCLOSED;
- else
- if(p->t.flags&TFIN) { /* we got FIN */
- prt->in.nxt++; /* count his FIN byte */
- prt->tcpout.t.ack=longswap(prt->in.nxt);
- prt->out.lasttime=0L; /* cause last ACK to be sent */
- prt->state=STWAIT;
- }
- break;
-
- case SCLOSING: /* want ACK of FIN */
- if(p->t.flags&TRESET)
- prt->state=SCLOSED;
- else
- if(!ackcheck(prt,p,pnum)) {
- prt->out.nxt++; /* account for my FIN byte */
- prt->state=STWAIT; /* time-wait state next */
- }
- break;
-
- case STWAIT: /* ack FIN again? */
- if(p->t.flags&TRESET)
- prt->state=SCLOSED;
- if(p->t.flags&TFIN) /* only if he wants it */
- prt->out.lasttime=0L;
- if(prt->out.lasttime&&
- (prt->out.lasttime+WAITTIME<n_clicks()))
- prt->state=SCLOSED;
- break;
-
- case SCLOSED:
- prt->in.port=prt->out.port=0;
- break;
-
- default:
- netposterr(403); /* unknown tcp state */
- break;
- }
- return(0);
- }
-
- /**********************************************************************/
- /* 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.
- */
- void checkmss(prt,p,hlen)
- int hlen;
- struct port *prt;
- TCPKT *p;
- {
- unsigned int i;
- /*
- * check header for maximum segment size option
- */
- if(hlen>20&&p->x.options[0]==2&&p->x.options[1]==4) {
- movebytes((char *)&i,(char *)&p->x.options[2],2); /* swapped value of maxseg */
- i=intswap(i);
- if(i< (unsigned int)(prt->sendsize)) /* we have our own limits too */
- prt->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 dlayersend it back.
- */
- int tcpreset(t)
- TCPKT *t;
- {
- uint tport;
- struct pseudotcp xxx;
-
- if(t->t.flags&TRESET) /* don't reset a reset */
- return(1);
- /*
- * swap TCP layer portions for sending back
- */
- if(t->t.flags&TACK) {
- t->t.seq=t->t.ack; /* ack becomes next seq # */
- t->t.ack=0L; /* ack # is 0 */
- }
- else {
- t->t.ack=longswap(longswap(t->t.seq)+t->i.tlen-sizeof(IPLAYER));
- t->t.seq=0L;
- }
- t->t.flags=TRESET;
- tport=t->t.source; /* swap port #'s */
- t->t.source=t->t.dest;
- t->t.dest=tport;
- t->t.hlen=20 << 2; /* header len */
- t->t.window=0;
- /*
- * create pseudo header for checksum
- */
- xxx.z=0;
- xxx.proto=t->i.protocol;
- xxx.tcplen=intswap(20);
- movebytes(xxx.source,t->i.ipsource,4);
- movebytes(xxx.dest,t->i.ipdest,4);
- t->t.check=0;
- t->t.check=tcpcheck((char *)&xxx,(char *)&t->t,sizeof(struct tcph));
- /*
- * IP and data link layers
- */
- movebytes(t->i.ipdest,t->i.ipsource,4); /* machine it came from */
- movebytes(t->i.ipsource,nnipnum,4);
- t->i.tlen=intswap(sizeof(IPLAYER)+sizeof(TCPLAYER));
- t->i.ident=nnipident++;
- t->i.ttl=30;
- t->i.check=0;
- t->i.check=ipcheck((char *)&t->i,10);
- movebytes(t->d.dest,t->d.me,DADDLEN); /* data link address */
- movebytes(t->d.me,blankd.me,DADDLEN); /* my address */
- return(dlayersend((DLAYER *)t,sizeof(DLAYER)+sizeof(IPLAYER)+sizeof(TCPLAYER)));
- }
-
- /***************************************************************************/
- /* tcpsend
- * transmits a TCP packet.
- *
- * For IP:
- * sets ident,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 makeport().
- * (see all inits in protinit)
- *
- */
- int tcpsend(pport,dlen)
- int dlen;
- struct port *pport;
- {
- struct port *p;
-
- p=pport;
-
- if(p==NULL) {
- netposterr(404);
- return(-1);
- }
- /*
- * do IP header first
- */
- p->tcpout.i.ident=intswap(nnipident++);
- p->tcpout.i.tlen=intswap(sizeof(struct iph)+sizeof(struct tcph) + dlen);
- p->tcpout.i.check=0; /* install checksum */
- p->tcpout.i.check=ipcheck((char *)&p->tcpout.i,10);
- /*
- * do TCP header
- */
- p->tcpout.t.seq=longswap(p->out.nxt); /* bytes swapped */
- /*
- * if the port has some credit limit, use it instead of large
- * window buffer. Generally demanded by hardware limitations.
- */
- if((uint)(p->credit) < (p->in.size))
- p->tcpout.t.window=intswap(p->credit);
- else
- p->tcpout.t.window=intswap(p->in.size); /* window size */
- /*
- * prepare pseudo-header for checksum
- */
- p->tcps.tcplen=intswap(dlen+sizeof(TCPLAYER));
- p->tcpout.t.check=0;
- p->tcpout.t.check=tcpcheck((char *)&p->tcps,(char *)&p->tcpout.t,dlen+sizeof(struct tcph));
- p->out.lasttime=n_clicks();
- return(dlayersend((DLAYER *)&p->tcpout,sizeof(DLAYER)+sizeof(IPLAYER)+sizeof(TCPLAYER)+dlen));
- }
-
- /***************************************************************************/
- /* ackcheck
- * take an incoming packet and see if there is an ACK for the outgoing
- * side. Use that ACK to dequeue outgoing data.
- */
- int ackcheck(p,t,pnum)
- TCPKT *t;
- struct port *p;
- int pnum;
- {
- uint32 ak;
- int32 rttl;
- int i;
-
- if((t->t.flags&TRESET)&&(t->t.seq==p->tcpout.t.ack)) {
- netposterr(405);
- p->state=SCLOSED;
- netputuev(CONCLASS,CONCLOSE,pnum);
- return(1);
- }
- if(!(t->t.flags&TACK)) /* check ACK flag */
- return(1); /* if no ACK, no go */
- p->out.size=intswap(t->t.window); /* allowable transmission size */
- /*
- * rmqueue any bytes which have been ACKed, update p->out.nxt to the
- * new next seq number for outgoing. Update send window.
- *
- */
- ak=longswap(t->t.ack); /* other side's ACK */
- /*
- * Need to add code to check for wrap-around of sequence space
- * for ak. ak - p->out.ack may be affected by sequence wraparound.
- * If you have good, efficient code for this, please send it to me.
- *
- * If ak is not increasing (above p->out.nxt) then we should assume
- * that it is a duplicate packet or one of those stupid keepalive
- * packets that 4.2 sends out.
- */
- if(ak>p->out.nxt) {
- rmqueue(&p->out,(int)(ak - p->out.ack)); /* take off of queue */
- p->out.nxt=ak;
- p->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 the most recent round
- * trip measurement. When a packet is retransmitted, this value is
- * doubled (exponential backoff).
- */
- rttl=n_clicks()-p->out.lasttime;
- if(!p->out.contain && rttl<(long)(MAXRTO) && p->rto>=MINRTO) { /* just now emptied queue */
- i=(int)(rttl);
- i=((p->rto-MINRTO)*3+i+1)>>2; /* smoothing function */
- p->rto=i+MINRTO;
- }
- if(!p->out.contain && p->out.push) /* if the queue emptied and push was set, clear push */
- p->out.push=0;
- if(p->out.contain>0)
- p->out.lasttime=0L; /* forces xmit */
- return(0);
- }
- /* the following line was added by QAK in an attempt to get ftpbin working again. */
- if(p->out.size>0)
- p->out.lasttime=0L; /* forces xmit */
- return(1);
- }
-
- /***************************************************************************/
- /* estab1986
- * take a packet which has arrived for an established connection and
- * put it where it belongs.
- */
- int estab1986(prt,pkt,tlen,hlen)
- struct port *prt;
- TCPKT *pkt;
- int tlen,hlen;
- {
- int dlen;
- uint32 sq,want;
-
- dlen=tlen-hlen;
- /*
- * see if we want this packet, or is it a duplicate?
- */
- sq=longswap(pkt->t.seq);
- want=prt->in.nxt;
- if(sq!= want) { /* we may want it, may not */
- if(sq<want&&sq+dlen>=want) { /* overlap */
- hlen+=(int)(want-sq); /* offset desired */
- dlen-=(int)(want-sq); /* skip this much */
- }
- else { /* tough it */
- prt->out.lasttime=0L; /* make the ACK time out */
- return(-1);
- }
- }
- else
- if(dlen<=0) { /* only an ACK packet */
- checkfin(prt,pkt); /* might still have FIN */
- return(0);
- }
- /*
- * If we have room in the window, update the ACK field values
- */
- if((prt->in.size) >= (uint)dlen) {
- prt->in.nxt+=dlen; /* new ack value */
- prt->in.size-=dlen; /* new window size */
- prt->out.lasttime=0L; /* force timeout for ACK */
- enqueue(&prt->in,pkt->x.data+hlen-20,dlen);
- netputuev(CONCLASS,CONDATA,pnum); /* tell user about it */
- prt->tcpout.t.ack=longswap(prt->in.nxt);
- prt->in.lasttime=n_clicks();
- }
- else { /* no room in input buffer */
- prt->out.lasttime=0L; /* re-ack old sequence value */
- }
- /*
- * Check the FIN bit to see if this connection is closing
- */
- checkfin(prt,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.
- */
- void checkfin(prt,pkt)
- struct port *prt;
- TCPKT *pkt;
- {
- if(pkt->t.flags&TFIN) { /* fin bit found */
- prt->in.nxt++; /* count the FIN byte */
- prt->state=SCWAIT; /* close-wait */
- prt->tcpout.t.ack=longswap(prt->in.nxt); /* set ACK in packet */
- prt->credit=0;
- prt->out.lasttime=0L; /* cause ACK to be sent */
- netputuev(CONCLASS,CONCLOSE,pnum);
- /*
- * 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.
- */
- if(!prt->in.contain) { /* data remaining? */
- prt->tcpout.t.flags=TFIN|TACK;
- tcpsend(prt,0);
- prt->state=SLAST;
- }
- }
- }
-