home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
GRIPS 2: Government Rast…rocessing Software & Data
/
GRIPS_2.cdr
/
dos
/
ncsa_tel
/
tel_2_2_
/
source
/
tcp.c
< prev
next >
Wrap
C/C++ Source or Header
|
1988-07-15
|
17KB
|
624 lines
/*
* 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 *
* *
****************************************************************************
* Tim Krauskopf Fall 1986
* mods for int16/int32 2/88
*/
#include "stdio.h"
#include "protocol.h"
#include "data.h"
static int pnum;
/************************************************************************
* 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.
*/
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(&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;
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 */
return(1); /* no port matches */
}
/**********************************************************************/
/* tcpdo
* Looking at the port structure for the destination port, deliver
* the incoming packet.
*/
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);
#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);
}
/**********************************************************************/
/* 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.
*/
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(&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;
}
}
/**********************************************************************/
/* 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.
*/
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(&xxx,&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(&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,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)
*
*/
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(&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,
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.
*/
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 = 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);
}
/***************************************************************************/
/* estab1986
* take a packet which has arrived for an established connection and
* put it where it belongs.
*/
estab1986(prt,pkt,tlen,hlen)
struct port *prt;
TCPKT *pkt;
int tlen,hlen;
{
int dlen;
uint32 sq,want;
dlen = tlen-hlen;
if (dlen <= 0) { /* only an ACK packet */
checkfin(prt,pkt); /* might still have FIN */
return(0);
}
/*
* 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);
}
}
/*
* 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);
}
/***************************************************************************/
/* 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.
*/
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;
}
}
}