/*
*   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 "pcdefs.h"
#include "protocol.h"
#include "config.h"
#include "funcdef.h"
#include "tcp.h"

static void do_options(struct tcpport _far *p, TCPLAYER *tcp, int len);
static union rawether *ether;
static void tcpdo(struct tcpport _far *p, TCPLAYER *tcp, u_int len);
static void tcpreset(union rawether *pkt, u_int len);
static void ackcheck(struct tcpport _far *p, TCPLAYER *tcp);
static void estab1986(struct tcpport _far *p, TCPLAYER *tcp, int len);

extern struct tcpport _far *ports[NPORTS];
extern u_short nnipident;
extern char slip_mode;

/* This is the same as a struct tcp except it has no data.
 * It is used only for sending a reset so we don't need to allocate any
 * space for the data.
 */
struct {
    DLAYER d;
    IPLAYER i;
    TCPLAYER t;
} tcpout;

/************************************************************************
*  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.
*/
void
tcpinterpret(pkt, len)
union rawether *pkt;
u_int len;			/* length of data and tcp header */
{
	int	i;
	struct tcpport _far *p;
	int	hlen;
	TCPLAYER *tcp;

	tcp = (TCPLAYER *)((u_char *)&rawip(pkt).i +
	    ((rawip(pkt).i.versionandhdrlen & 0x0f) << 2));
	hlen = (tcp->hlen >> 2) & 0x3c;

	len -= hlen;
	ether = pkt;
/*
*  find the port which is associated with the incoming packet
*  First try open connections, then try listeners
*/
	for (i = 0; i < NPORTS; i++) {
		p = ports[i];
		if (!p || !(p->flag & O_STREAM))
			continue;
		if (p->state <= SLISTEN)
			continue;
		if (p->inport == tcp->dest &&
		    p->outport == tcp->source &&
		    p->outpkt.i.ipdest == rawtcp(pkt).i.ipsource) {
			tcpdo(p, tcp, len);
			return;
		}
	}
/*
*  check to see if the incoming packet should go to a listener
*/
	if (tcp->flags & TRESET)	/* per RFC 793 */
		return;

	for (i = 0; i < NPORTS; i++) {
		p = ports[i];
		if ((p->flag & (O_OPEN|O_STREAM)) != (O_OPEN|O_STREAM))
			continue;
		if (p->state == SLISTEN && tcp->flags & TSYN &&
		   p->inport == tcp->dest) {
			p->outwin = ntohs(tcp->window);
			/* ack the SYN */
			p->outack = ntohl(tcp->seq) + 1L;
			p->outseq = n_clicks();
			p->outport = tcp->source;
			p->outpkt.t.dest = tcp->source;
			p->outpkt.t.flags = TSYN | TACK;

/*
*  initialize all of the low-level transmission stuff (IP and lower)
*/
			p->outpkt.i.ipdest = rawtcp(pkt).i.ipsource;
			if (!slip_mode)
			    memcpy(p->outpkt.d.dest, rawtcp(pkt).d.me, DADDLEN);
			p->state = SSYNRS;	/* syn received */
/*
*  note that the maxmimum segment size is installed by 'SoSocket()'
*/
			/* If the destination host is not on the same network,
			 * send a smaller MSS option.
			 */
			if ((p->outpkt.i.ipdest & Scon.snetmask) !=
			    (Scon.myip & Scon.snetmask)) {
				*(u_short _far *)&p->outpkt.x.options[2] =
				    htons(MSS_SMALL);
			}
			if (hlen > sizeof(struct tcph)) {
				do_options(p, tcp, len);
			}
			if (len)	/* data in first packet */
				estab1986(p, tcp, len);
			tcpsend(p, 4);
			return;
		}
	}
/*
*  no matching port was found to handle this packet, reject it
*/
	tcpreset(pkt, len);
}

/**********************************************************************/
/*  tcpdo
*  Looking at the port structure for the destination port, deliver
*  the incoming packet.
*/
static void
tcpdo(p, tcp, len)
struct tcpport _far *p;
TCPLAYER *tcp;
u_int len;
{
	u_long	seq, ak;
	int i;

	p->resends = 0;
	seq = ntohl(tcp->seq);
	if (tcp->flags & TRESET && p->state != SSYNS &&
	    p->outack >= seq && (u_int)(p->outack - seq) < p->credit) {
		if (p->state == SSYNRS)
			p->state = SLISTEN;
		else {
			p->state = SCLOSED;
			if (p->flag & O_RELEASED)
				free_port((struct port _far *)p);
		}
		return;
	}
	ak = ntohl(tcp->ack);
	switch (p->state) {
	    case SSYNR:
	    case SSYNRS:
		if (tcp->flags & TACK) {
			/* better be for me */
			if (ak != p->outseq + 1) {
				tcpreset(ether, len);
/*
dfputs("SYNR -> reset\r\n");
*/
				break;
			}
		} else {
			tcpsend(p,
			    (p->outpkt.t.hlen >> 2) & 0x3c -
			    sizeof (struct tcph));
/*
dfputs("SYNR -> ignore\r\n");
*/
			break;			/* not the right one */
		}
/*
dfputs("SYNR -> SEST port ");
printd(ntohs(tcp->source));
dfputs("\r\n");
*/
		i = (tcp->hlen >> 2) & 0x3c;
		if (i > sizeof (struct tcph)) {
			do_options(p, tcp, len);
		}
		p->outseq = ak;
		p->outpkt.t.hlen = (sizeof(struct tcph) << 2) & 0xf0;
		p->outwin = ntohs(tcp->window);
		p->outpkt.t.flags = TACK | TPUSH;
		p->state = SEST;
		if (len) {
			estab1986(p, tcp, len);
		}
		break;
	    case SEST:
		if (tcp->flags & TACK) {
			ackcheck(p, tcp);
			if (p->flag & O_CLOSED) {
/*
dfputs("SEST: sending TFIN port ");
printd(ntohs(tcp->source));
dfputs("\r\n");
*/
				p->outpkt.t.flags = TACK | TFIN;
				tcpsend(p, 0);
				p->state = SFW1;
			}
		}
		estab1986(p, tcp, len);
		if (p->flag & O_GOTALL) {
		    if (p->state == SFW1)
			    p->state = SCLOSING;
		    else
			    p->state = SCWAIT;
		}
		break;
	    case SSYNS:	/* check to see if it ACKS correctly */
		if (tcp->flags & TRESET && ak == p->outseq + 1L) {
/*
dfputs("SSYNS:got reset");
*/
			p->state = SCLOSED;
			if (p->flag & O_RELEASED)
				free_port((struct port _far *)p);
			break;
		}
		if (tcp->flags & TSYN) {
			if (tcp->flags & TACK) {
				p->outseq = ak;
				p->outack = seq + 1L;
				p->outpkt.t.flags = TACK | TPUSH;
				p->outwin = ntohs(tcp->window);
				p->outpkt.t.hlen = (sizeof(struct tcph) << 2) &
				    0xf0;
				tcpsend(p, 0);
				p->state = SEST;
			} else {
/*
dfputs("SSYNS: rcvd SYN w/o ACK\r\n");
*/
				p->outack = seq + 1L;
				p->outpkt.t.flags = TACK | TSYN;
				tcpsend(p,
				    (p->outpkt.t.hlen >> 2) & 0x3c -
				    sizeof (struct tcph));
				p->state = SSYNR;
			}
		} else {
/*
dfputs("SSYNS: send reset\r\n");
*/
			tcpreset(ether, len);
		}
		break;
	    case SCWAIT:
		if (tcp->flags & TACK) {
			ackcheck(p, tcp);
		}
		if (p->flag & O_CLOSED) {
/*
dfputs("SCWAIT: LAST\r\n");
*/
			p->outpkt.t.flags = TACK | TFIN;
			tcpsend(p, 0);
			p->state = SLAST;
		}
		break;
	    case SLAST:
		if (ak != p->outseq + 1L) {
/*
dfputs("SLAST: reacking FIN\r\n");
*/
			tcpsend(p, 0);
		} else {
/*
dfputs("SLAST: CLOSED\r\n");
*/
			p->state = SCLOSED;
			if (p->flag & O_RELEASED)
			    free_port((struct port _far *)p);
		}
		break;
	    case SFW1:		/* waiting for ACK of FIN */
		if (tcp->flags & TACK) {
		    if (ak == p->outseq + 1L) {
			p->outseq++;
			p->outpkt.t.flags = TACK;
			p->state = SFW2;
		    }
		}
		/* FALL THROUGH */
	    case SFW2:
		estab1986(p, tcp, len);
		if (p->flag & O_GOTALL) {
		    /* cause last ACK to be sent */
		    tcpsend(p, 0);
		    if (p->state == SFW2)
			p->state = STWAIT;
		    else
			p->state = SCLOSING;
		}
		break;
	    case SCLOSING:		/* want ACK of FIN */
		if (tcp->flags & TACK && ak == p->outseq + 1L) {
/*
dfputs("CLOSING: TWAIT\r\n");
*/
			p->state = STWAIT;	/* time-wait state next */
		} else {
			tcpsend(p, 0);
		}
		break;
	    case STWAIT:		/* ack FIN again? */
		if (tcp->flags & TFIN) {
/*
dfputs("STWAIT: re ACKing\r\n");
*/
		    tcpsend(p, 0);
		    break;
		}
		break;
	    case SCLOSED:
		break;
	}
}

/**********************************************************************/
/* 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 pkxmit it back.
*/
static void
tcpreset(pkt, len)
union rawether *pkt;
u_int	len;
{
    u_long	oack;
    struct pseudotcp itcps;

    if (rawtcp(pkt).t.flags & TRESET)	/* don't reset a reset */
	    return;
/*
*  swap TCP layer portions for sending back
*/
    if (rawtcp(pkt).t.flags & TACK) {
	    tcpout.t.seq = rawtcp(pkt).t.ack; /* ack becomes next seq # */
	    tcpout.t.ack = 0L;		/* ack # is 0 */
	    tcpout.t.flags = TRESET;
    } else {
	    oack = ntohl(rawtcp(pkt).t.seq) + (u_long)len;
	    if (rawtcp(pkt).t.flags & TSYN)
		    oack++;
	    tcpout.t.ack = htonl(oack);
	    tcpout.t.seq = 0L;
	    tcpout.t.flags = TRESET | TACK;
    }
    tcpout.t.source = rawtcp(pkt).t.dest;
    tcpout.t.dest = rawtcp(pkt).t.source;
    tcpout.t.hlen = (sizeof(struct tcph) << 2) & 0xf0; /* header len */
    tcpout.t.window = 0;
    tcpout.t.urgent = 0;

    tcpout.i.ipdest = rawtcp(pkt).i.ipsource;

/*
*  create pseudo header for checksum
*/
    itcps.proto = PROTTCP;
    itcps.tcplen = htons(sizeof(struct tcph));
    itcps.z = 0;
    memcpy(&itcps.source, &tcpout.i.ipsource, 2 * sizeof(u_long));
    tcpout.t.check = 0;
    tcpout.t.check = tcpcheck(&itcps, &tcpout.t, sizeof(struct tcph));
/*
*  IP and data link layers
*/
    tcpout.i.tlen = htons(sizeof(struct iph) + sizeof(struct tcph));
    tcpout.i.ident = htons(nnipident);
    nnipident++;
    tcpout.i.check = 0;
    tcpout.i.check = ipcheck(&tcpout.i, sizeof(struct iph));
    memcpy(tcpout.d.dest, rawtcp(pkt).d.me, DADDLEN);

    (void)pkxmit(&tcpout,
	sizeof(struct ether) + sizeof(struct iph) + sizeof(struct tcph));
}

/***************************************************************************/
/*  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)
*
*/
void
tcpsend(p, dlen)
struct tcpport _far *p;
u_int dlen;
{
    struct pseudotcp otcps;

    p->outpkt.t.window = htons(p->credit);
    p->outpkt.t.seq = htonl(p->outseq);
    p->outpkt.t.ack = htonl(p->outack);
    dlen += sizeof(struct tcph);
    memcpy(&otcps.source, &p->outpkt.i.ipsource, 2 * sizeof(u_long));
    otcps.proto = PROTTCP;
    otcps.z = 0;
    otcps.tcplen = htons(dlen);
    p->outpkt.t.check = 0;
    p->outpkt.t.check = tcpcheck(&otcps, &p->outpkt.t, dlen);

    dlen += sizeof(struct iph);
    p->outpkt.i.ident = htons(nnipident);
    nnipident++;
    p->outpkt.i.tlen = htons(dlen);
    p->outpkt.i.check = 0;			/* install checksum */
    p->outpkt.i.check = iphcheck((u_long)&p->outpkt.i);
    p->lasttime = n_clicks();

    dlen += sizeof (struct ether);
    (void)pkxmit(&p->outpkt, dlen);
}

/***************************************************************************/
/*  ackcheck
*   take an incoming packet and see if there is an ACK for the outgoing
*   side.  Use that ACK to dequeue outgoing data.
*/
static void
ackcheck(p, tcp)
struct tcpport _far *p;
TCPLAYER *tcp;
{
	u_long ak;
	u_long rttl;
	int i;

	i = ntohs(tcp->window); /* allowable xmit size */
	/* If the previous window size was 0, then this packet may
	 * be a window update without actually acking data.  In
	 * this case we should retransmit immediately.
	 */
	if (!p->outwin && i && p->outfree)
		p->wait_tx = 1;
	p->outwin = i;
/*
*  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.
*/
	ak = ntohl(tcp->ack);		/* other side's ACK */
	i = (int)(ak - p->outseq);
	if (i > 0) {
		p->outseq = ak;
		if (i < (int)p->outfree) {
			p->outfree -= i;
			memcpy(p->dataout, &p->dataout[i], p->outfree);
			/* send more */
			p->wait_tx = 1;
		} else {
		    p->outfree = 0;
		    if (p->flag & O_CLOSING)
			p->flag |= O_CLOSED;
/*
*  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->lasttime;
		    if (p->lasttime && rttl < MAXRTO && p->rto >= MINRTO) {
			    /* smoothing function */
			    i = (u_int)(((p->rto-MINRTO)*3L + rttl + 1L) >> 2);
			    p->rto = i+MINRTO;
		    }
		}
	}
	else if (i < 0)
		dfputs("panic acked > 32767 bytes\r\n");
}

/*
*  estab1986
*   take a packet which has arrived for an established connection and
*   put it where it belongs.
*/
static void
estab1986(p, tcp, len)
struct tcpport _far *p;
TCPLAYER *tcp;
int len;
{
	int	slen;
	int	credit;
	u_long	seq;
	u_char	*sp;

	seq = ntohl(tcp->seq);
	if (len > 0) {
		/* slen is number of bytes we have already seen.  If it is
		 * less than zero, then we have missed a packet. */
		slen = (int)(p->outack - seq);
		if (tcp->flags & TSYN)
			--slen;
		if (slen) {
			if (slen > 0) {
				len -= slen;
				if (len <= 0) {
					/* don't need it, retransmit ack */
					if (len == -1)
						p->wait_tx = 50;
					else
						p->wait_tx = 10;
					return;
				}
			} else {
				/* missed packet, retransmit ack */
				p->wait_tx = 10;
				return;
			}
		}
/*
*  If we have room in the window, update the ACK field values
*/
		credit = WINDOWSIZE - p->infree;
		if (credit < len) {
			len = credit;
			credit = 0;
		} else
			credit -= len;

		if (len) {
			sp = (u_char *)(tcp + 1) + slen;
			memcpy(&p->datain[p->infree], sp, len);
			p->infree += len;
			p->outack += (u_long)len;	/* new ack value */
			p->credit = credit;
		}
		/* If we are about to send a small window size, wait a bit
		 * to see if we are going to read the data soon.
		 */
		if (credit < 1024)
			p->wait_tx = 200;
		else if (!p->wait_tx)
			p->wait_tx = 20;
		else if (p->wait_tx > 3)
			p->wait_tx -= 3;
	}
	if (tcp->flags & TFIN) {
		seq += len;	/* set seq to sequence number of FIN */
		if (p->outack - seq == 0) {
			p->flag |= O_GOTALL;
			p->outack++;	/* ack the fin */
		}
else
dfputs("can't ack FIN\r\n");
		p->wait_tx = 2;
	}
}

static void
do_options(p, tcp, len)
struct tcpport _far *p;
TCPLAYER *tcp;
int len;
{
	unsigned i;
	u_char op;
	u_char *cp = (u_char *)(tcp + 1);
	int hlen = (tcp->hlen >> 2) & 0x3c;

	while ((op = *cp++) != 0) {
	    if (op == 2) {
		cp++;	/* skip len == 4 */
		i = ntohs(*(u_short *)cp);
		if (i < p->outsize)	/* we have our own limits too */
		    p->outsize = i;
		cp += 2;
	    }
	}
	if (len > 0)
	    memcpy((u_char *)(tcp + 1), (u_char *)tcp + hlen, len);
}
