home *** CD-ROM | disk | FTP | other *** search
- /*
- * TCP 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 *
- * *
- ****************************************************************************
- * Tim Krauskopf Fall 1986
- */
- #include <stdio.h>
- #include <string.h>
-
- #include <Events.h>
-
- #include "protocol.h"
- #include "data.h"
- #include "tools.h"
- #include "mactools.h"
-
- #define UNKNOWN_PORT_TYPE 0 /* BYU 2.4.16 */
- #define MACTCP_PORT_TYPE 1 /* BYU 2.4.16 */
- #define NCSA_PORT_TYPE 2 /* BYU 2.4.16 */
-
- extern short porttype[]; /* BYU 2.4.16 */
-
- extern short slip_connection; /* BYU 2.4.15 */
-
- extern unsigned char SLIP_ip_number[]; /* BYU 2.4.15 */
-
- extern uint16 ipcheck
- (
- void *buf,
- long wdcnt /* BYU 2.4.8 - was "int" */
- );
-
- extern uint16 tcpcheck
- (
- void *phd,
- void *buf,
- long btcnt /* BYU 2.4.8 - was "int" */
- );
-
- static int pnum;
-
- /***************************************************************************/
- /* 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
- (
- struct port *pport,
- int dlen
- )
- {
- struct port *p;
-
- p = pport;
-
- if (p == NULL) {
- netposterr(404);
- return(-1);
- }
-
- if ((p->type == 1) && slip_connection) { /* BYU 2.4.15 */
- movebytes(p->tcpout.i.ipsource,SLIP_ip_number,4); /* BYU 2.4.15 */
- movebytes(p->tcps.source,SLIP_ip_number,4); /* BYU 2.4.15 */
- } /* BYU 2.4.15 */
-
- /*
- * 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(&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 (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(&p->tcps,&p->tcpout.t,dlen+sizeof(struct tcph));
-
- p->out.lasttime = time(NULL);
-
- return(dlayersend(&p->tcpout, /* BYU 2.4.15 */
- (int) (sizeof(DLAYER)+sizeof(IPLAYER)+sizeof(TCPLAYER)+dlen),p->type));/* BYU 2.4.15 */
- } /* tcpsend */
-
- /**********************************************************************/
- /* 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
- (
- struct port *prt,
- TCPKT *p,
- int hlen
- )
- {
- unsigned int i;
- /*
- * check header for maximum segment size option
- */
- if (hlen > 20 && p->x.options[0] == 2 && p->x.options[1] == 4) {
- movebytes(&i,&p->x.options[2],2); /* swapped value of maxseg */
- i = intswap(i);
- if (i < prt->sendsize) /* we have our own limits too */
- prt->sendsize = i;
- }
- } /* checkmss */
-
- /***************************************************************************/
- /* 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
- (
- struct port *p,
- TCPKT *t,
- 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) /* BYU 2.4.15 */
- p->out.lasttime = 0L; /* BYU 2.4.15 */
- else /* BYU 2.4.15 */
- 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 = time(NULL) - p->out.lasttime;
- if (!p->out.contain && /* just now emptied queue */
- rttl < (long)(MAXRTO) && p->rto >= MINRTO) {
- i = (int)(rttl);
- i = ((p->rto-MINRTO)*3 + i + 1) >> 2; /* smoothing function */
- p->rto = i+MINRTO;
- }
-
- if (p->out.size > 0)
- p->out.lasttime = 0L; /* forces xmit */
- return(0);
- }
- return(1);
- } /* ackcheck */
-
- /***************************************************************************/
- /* 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
- (
- 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;
- }
- }
- } /* checkfin */
-
- /***************************************************************************/
- /* estab1986
- * take a packet which has arrived for an established connection and
- * put it where it belongs.
- */
- int estab1986
- (
- struct port *prt,
- TCPKT *pkt,
- int tlen,
- int hlen
- )
- {
- int dlen;
- uint32 sq,want;
-
- dlen = tlen-hlen;
-
- #ifdef OLDM
- /* problem with old way is that dlen==0 is not checked to see if it is in
- seq window */
-
- if (dlen <= 0) { /* only an ACK packet */
- checkfin(prt,pkt); /* might still have FIN */
- return(0);
- }
- #endif
-
- /*
- * 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 += want-sq; /* offset desired */
- dlen -= 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 >= dlen) {
- prt->in.nxt += dlen; /* new ack value */
- prt->tcpout.t.ack = longswap(prt->in.nxt);
- 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->in.lasttime = time(NULL);
-
- }
-
- 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);
- } /* estab1986 */
-
- /**********************************************************************/
- /* tcpdo
- * Looking at the port structure for the destination port, deliver
- * the incoming packet.
- */
- int tcpdo
- (
- struct port *prt,
- TCPKT *p,
- int tlen,
- int hlen
- )
- {
-
- 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);
- #ifdef MAC
- /*
- * look up address in the arp cache if using Atalk 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 */
- }
- #endif
-
- 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 = time(NULL); /* 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 (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) || (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 (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 < time(NULL)))
- prt->state = SCLOSED;
- break;
- case SCLOSED:
- prt->in.port = prt->out.port = 0;
- break;
- default:
- netposterr(403); /* unknown tcp state */
- break;
- }
- return(0);
- } /* tcpdo */
-
- /**********************************************************************/
- /* 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
- (
- TCPKT *t, /* BYU 2.4.15 */
- short fromSLIP /* BYU 2.4.15 */
- )
- {
- 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 */
- t->t.flags = TRESET; /* BYU 2.4.16 */
- }
- else {
- #if 1 /* BYU 2.4.16 */
- t->t.ack = longswap(longswap(t->t.seq) + 1); /* BYU 2.4.16 */
- t->t.flags = TACK | TRESET; /* BYU 2.4.16 */
- #else /* BYU 2.4.16 */
- t->t.ack = longswap(longswap(t->t.seq)+t->i.tlen-sizeof(IPLAYER));
- #endif /* BYU 2.4.16 */
- 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 = 80; /* header len */ /* BYU 2.4.16 - was "20 << 2" */
- 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(&xxx,&t->t,sizeof(struct tcph));
- /*
- * IP and data link layers
- */
- movebytes(t->i.ipdest,t->i.ipsource,4); /* machine it came from */
- if (fromSLIP) /* BYU 2.4.15 */
- movebytes(t->i.ipsource,SLIP_ip_number,4); /* BYU 2.4.15 */
- else { /* BYU 2.4.15 */
- movebytes(t->i.ipsource,nnipnum,4); /* BYU 2.4.15 */
- } /* BYU 2.4.15 */
- t->i.tlen = intswap(sizeof(IPLAYER)+sizeof(TCPLAYER));
- t->i.ident = nnipident++;
- t->i.ttl = 30;
- t->i.check = 0;
- t->i.check = ipcheck(&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(t,(int) (sizeof(DLAYER)+sizeof(IPLAYER)+sizeof(TCPLAYER)),fromSLIP)); /* BYU 2.4.15 */
-
- } /* tcpreset */
-
- /************************************************************************
- * tcpinterpret
- * This is called when a packet comes in, passes IP checksumming and is
- * of the TCP protocol type. Check and see if we are expecting it and
- * where the data should go.
- */
- int tcpinterpret
- (
- TCPKT *p,
- int tlen, /* BYU 2.4.15 */
- short fromSLIP /* BYU 2.4.15 */
- )
- {
- 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(&tcps,&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;
- prt->type = fromSLIP; /* BYU 2.4.15 */
- if (fromSLIP) /* BYU 2.4.16 */
- porttype[pnum] = NCSA_PORT_TYPE; /* BYU 2.4.16 */
- 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;
- prt->type = fromSLIP; /* BYU 2.4.15 */
- if (fromSLIP) { /* BYU 2.4.15 */
- memmove(prt->tcpout.i.ipsource,SLIP_ip_number,4); /* BYU 2.4.15 */
- memmove(prt->tcps.source,SLIP_ip_number,4); /* BYU 2.4.15 */
- porttype[pnum] = NCSA_PORT_TYPE; /* BYU 2.4.16 */
- }
- return(tcpdo(prt,p,tlen,hlen));
- }
-
- }
-
- /*
- * no matching port was found to handle this packet, reject it
- */
-
- tcpreset(p,fromSLIP); /* BYU 2.4.15 - 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 */
-
- return(1); /* no port matches */
-
- } /* tcpinterpret */
-