shithub: riscv

ref: 2e369f0d1fad5f38c44ed13109f74cdd6ab535f2
dir: /sys/src/9/ip/tcp.c/

View raw version
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"../port/error.h"

#include	"ip.h"

enum
{
	QMAX		= 64*1024-1,
	IP_TCPPROTO	= 6,

	TCP4_IPLEN	= 8,
	TCP4_PHDRSIZE	= 12,
	TCP4_HDRSIZE	= 20,
	TCP4_TCBPHDRSZ	= 40,
	TCP4_PKT	= TCP4_IPLEN+TCP4_PHDRSIZE,

	TCP6_IPLEN	= 0,
	TCP6_PHDRSIZE	= 40,
	TCP6_HDRSIZE	= 20,
	TCP6_TCBPHDRSZ	= 60,
	TCP6_PKT	= TCP6_IPLEN+TCP6_PHDRSIZE,

	TcptimerOFF	= 0,
	TcptimerON	= 1,
	TcptimerDONE	= 2,
	MAX_TIME 	= (1<<20),	/* Forever */
	TCP_ACK		= 50,		/* Timed ack sequence in ms */
	MAXBACKMS	= 9*60*1000,	/* longest backoff time (ms) before hangup */

	URG		= 0x20,		/* Data marked urgent */
	ACK		= 0x10,		/* Acknowledge is valid */
	PSH		= 0x08,		/* Whole data pipe is pushed */
	RST		= 0x04,		/* Reset connection */
	SYN		= 0x02,		/* Pkt. is synchronise */
	FIN		= 0x01,		/* Start close down */

	EOLOPT		= 0,
	NOOPOPT		= 1,
	MSSOPT		= 2,
	MSS_LENGTH	= 4,		/* Maximum segment size */
	WSOPT		= 3,
	WS_LENGTH	= 3,		/* Bits to scale window size by */
	MSL2		= 10,
	MSPTICK		= 50,		/* Milliseconds per timer tick */
	DEF_MSS		= 1460,		/* Default maximum segment */
	DEF_MSS6	= 1220,		/* Default maximum segment (min) for v6 */
	DEF_RTT		= 500,		/* Default round trip */
	DEF_KAT		= 120000,	/* Default time (ms) between keep alives */
	TCP_LISTEN	= 0,		/* Listen connection */
	TCP_CONNECT	= 1,		/* Outgoing connection */
	SYNACK_RXTIMER	= 250,		/* ms between SYNACK retransmits */

	TCPREXMTTHRESH	= 3,		/* dupack threshhold for rxt */

	FORCE		= 1,
	CLONE		= 2,
	RETRAN		= 4,
	ACTIVE		= 8,
	SYNACK		= 16,

	LOGAGAIN	= 3,
	LOGDGAIN	= 2,

	Closed		= 0,		/* Connection states */
	Listen,
	Syn_sent,
	Syn_received,
	Established,
	Finwait1,
	Finwait2,
	Close_wait,
	Closing,
	Last_ack,
	Time_wait,

	Maxlimbo	= 1000,		/* maximum procs waiting for response to SYN ACK */
	NLHT		= 256,		/* hash table size, must be a power of 2 */
	LHTMASK		= NLHT-1,

	/*
	 * window is 64kb · 2ⁿ
	 * these factors determine the ultimate bandwidth-delay product.
	 * 64kb · 2⁵ = 2mb, or 2x overkill for 100mbps · 70ms.
	 */
	Maxqscale	= 4,		/* maximum queuing scale */
	Defadvscale	= 4,		/* default advertisement */
};

/* Must correspond to the enumeration above */
char *tcpstates[] =
{
	"Closed", 	"Listen", 	"Syn_sent", "Syn_received",
	"Established", 	"Finwait1",	"Finwait2", "Close_wait",
	"Closing", 	"Last_ack", 	"Time_wait"
};

typedef struct Tcptimer Tcptimer;
struct Tcptimer
{
	Tcptimer	*next;
	Tcptimer	*prev;
	Tcptimer	*readynext;
	int	state;
	int	start;
	int	count;
	void	(*func)(void*);
	void	*arg;
};

/*
 *  v4 and v6 pseudo headers used for
 *  checksuming tcp
 */
typedef struct Tcp4hdr Tcp4hdr;
struct Tcp4hdr
{
	uchar	vihl;		/* Version and header length */
	uchar	tos;		/* Type of service */
	uchar	length[2];	/* packet length */
	uchar	id[2];		/* Identification */
	uchar	frag[2];	/* Fragment information */
	uchar	Unused;
	uchar	proto;
	uchar	tcplen[2];
	uchar	tcpsrc[4];
	uchar	tcpdst[4];
	uchar	tcpsport[2];
	uchar	tcpdport[2];
	uchar	tcpseq[4];
	uchar	tcpack[4];
	uchar	tcpflag[2];
	uchar	tcpwin[2];
	uchar	tcpcksum[2];
	uchar	tcpurg[2];
	/* Options segment */
	uchar	tcpopt[1];
};

typedef struct Tcp6hdr Tcp6hdr;
struct Tcp6hdr
{
	uchar	vcf[4];
	uchar	ploadlen[2];
	uchar	proto;
	uchar	ttl;
	uchar	tcpsrc[IPaddrlen];
	uchar	tcpdst[IPaddrlen];
	uchar	tcpsport[2];
	uchar	tcpdport[2];
	uchar	tcpseq[4];
	uchar	tcpack[4];
	uchar	tcpflag[2];
	uchar	tcpwin[2];
	uchar	tcpcksum[2];
	uchar	tcpurg[2];
	/* Options segment */
	uchar	tcpopt[1];
};

/*
 *  this represents the control info
 *  for a single packet.  It is derived from
 *  a packet in ntohtcp{4,6}() and stuck into
 *  a packet in htontcp{4,6}().
 */
typedef struct Tcp Tcp;
struct	Tcp
{
	ushort	source;
	ushort	dest;
	ulong	seq;
	ulong	ack;
	uchar	flags;
	uchar	update;
	ushort	ws;	/* window scale option */
	ulong	wnd;	/* prescaled window*/
	ushort	urg;
	ushort	mss;	/* max segment size option (if not zero) */
	ushort	len;	/* size of data */
};

/*
 *  this header is malloc'd to thread together fragments
 *  waiting to be coalesced
 */
typedef struct Reseq Reseq;
struct Reseq
{
	Reseq	*next;
	Tcp	seg;
	Block	*bp;
	ushort	length;
};

/*
 *  the qlock in the Conv locks this structure
 */
typedef struct Tcpctl Tcpctl;
struct Tcpctl
{
	uchar	state;			/* Connection state */
	uchar	type;			/* Listening or active connection */
	uchar	code;			/* Icmp code */
	struct {
		ulong	una;		/* Unacked data pointer */
		ulong	nxt;		/* Next sequence expected */
		ulong	ptr;		/* Data pointer */
		ulong	wnd;		/* Tcp send window */
		ulong	urg;		/* Urgent data pointer */
		ulong	wl2;
		uint	scale;		/* how much to right shift window in xmitted packets */
		/* to implement tahoe and reno TCP */
		ulong	dupacks;	/* number of duplicate acks rcvd */
		ulong	partialack;
		int	recovery;	/* loss recovery flag */
		int	retransmit;	/* retransmit 1 packet @ una flag */
		int	rto;
		ulong	rxt;		/* right window marker for recovery "recover" rfc3782 */
	} snd;
	struct {
		ulong	nxt;		/* Receive pointer to next uchar slot */
		ulong	wnd;		/* Receive window incoming */
		ulong	wsnt;		/* Last wptr sent.  important to track for large bdp */
		ulong	wptr;
		ulong	urg;		/* Urgent pointer */
		ulong	ackptr;		/* last acked sequence */
		int	blocked;
		uint	scale;		/* how much to left shift window in rcv'd packets */
	} rcv;
	ulong	iss;			/* Initial sequence number */
	ulong	cwind;			/* Congestion window */
	ulong	abcbytes;		/* appropriate byte counting rfc 3465 */
	uint	scale;			/* desired snd.scale */
	ulong	ssthresh;		/* Slow start threshold */
	int	resent;			/* Bytes just resent */
	int	irs;			/* Initial received squence */
	ushort	mss;			/* Maximum segment size */
	int	rerecv;			/* Overlap of data rerecevived */
	ulong	window;			/* Our receive window (queue) */
	uint	qscale;			/* Log2 of our receive window (queue) */
	uchar	backoff;		/* Exponential backoff counter */
	int	backedoff;		/* ms we've backed off for rexmits */
	uchar	flags;			/* State flags */
	Reseq	*reseq;			/* Resequencing queue */
	int	nreseq;
	int	reseqlen;
	Tcptimer	timer;			/* Activity timer */
	Tcptimer	acktimer;		/* Acknowledge timer */
	Tcptimer	rtt_timer;		/* Round trip timer */
	Tcptimer	katimer;		/* keep alive timer */
	ulong	rttseq;			/* Round trip sequence */
	int	srtt;			/* Smoothed round trip */
	int	mdev;			/* Mean deviation of round trip */
	int	kacounter;		/* count down for keep alive */
	uint	sndsyntime;		/* time syn sent */
	ulong	time;			/* time Finwait2 or Syn_received was sent */
	ulong	timeuna;			/* snd.una when time was set */
	int	nochecksum;		/* non-zero means don't send checksums */
	int	flgcnt;			/* number of flags in the sequence (FIN,SEQ) */

	union {
		Tcp4hdr	tcp4hdr;
		Tcp6hdr	tcp6hdr;
	} protohdr;		/* prototype header */
};

/*
 *  New calls are put in limbo rather than having a conversation structure
 *  allocated.  Thus, a SYN attack results in lots of limbo'd calls but not
 *  any real Conv structures mucking things up.  Calls in limbo rexmit their
 *  SYN ACK every SYNACK_RXTIMER ms up to 4 times, i.e., they disappear after 1 second.
 *
 *  In particular they aren't on a listener's queue so that they don't figure
 *  in the input queue limit.
 *
 *  If 1/2 of a T3 was attacking SYN packets, we'ld have a permanent queue
 *  of 70000 limbo'd calls.  Not great for a linear list but doable.  Therefore
 *  there is no hashing of this list.
 */
typedef struct Limbo Limbo;
struct Limbo
{
	Limbo	*next;

	uchar	laddr[IPaddrlen];
	uchar	raddr[IPaddrlen];
	ushort	lport;
	ushort	rport;
	ulong	irs;		/* initial received sequence */
	ulong	iss;		/* initial sent sequence */
	ushort	mss;		/* mss from the other end */
	ushort	rcvscale;	/* how much to scale rcvd windows */
	ushort	sndscale;	/* how much to scale sent windows */
	ulong	lastsend;	/* last time we sent a synack */
	uchar	version;	/* v4 or v6 */
	uchar	rexmits;	/* number of retransmissions */
};

int	tcp_irtt = DEF_RTT;	/* Initial guess at round trip time */

enum {
	/* MIB stats */
	MaxConn,
	Mss,
	ActiveOpens,
	PassiveOpens,
	EstabResets,
	CurrEstab,
	InSegs,
	OutSegs,
	RetransSegs,
	RetransSegsSent,
	RetransTimeouts,
	InErrs,
	OutRsts,

	/* non-MIB stats */
	CsumErrs,
	HlenErrs,
	LenErrs,
	Resequenced,
	OutOfOrder,
	ReseqBytelim,
	ReseqPktlim,
	Delayack,
	Wopenack,

	Recovery,
	RecoveryDone,
	RecoveryRTO,
	RecoveryNoSeq,
	RecoveryCwind,
	RecoveryPA,

	Nstats
};

static char *statnames[Nstats] =
{
[MaxConn]	"MaxConn",
[Mss]		"MaxSegment",
[ActiveOpens]	"ActiveOpens",
[PassiveOpens]	"PassiveOpens",
[EstabResets]	"EstabResets",
[CurrEstab]	"CurrEstab",
[InSegs]	"InSegs",
[OutSegs]	"OutSegs",
[RetransSegs]	"RetransSegs",
[RetransSegsSent]	"RetransSegsSent",
[RetransTimeouts]	"RetransTimeouts",
[InErrs]	"InErrs",
[OutRsts]	"OutRsts",
[CsumErrs]	"CsumErrs",
[HlenErrs]	"HlenErrs",
[LenErrs]	"LenErrs",
[OutOfOrder]	"OutOfOrder",
[Resequenced]	"Resequenced",
[ReseqBytelim]	"ReseqBytelim",
[ReseqPktlim]	"ReseqPktlim",
[Delayack]	"Delayack",
[Wopenack]	"Wopenack",

[Recovery]	"Recovery",
[RecoveryDone]	"RecoveryDone",
[RecoveryRTO]	"RecoveryRTO",

[RecoveryNoSeq]	"RecoveryNoSeq",
[RecoveryCwind]	"RecoveryCwind",
[RecoveryPA]	"RecoveryPA",
};

typedef struct Tcppriv Tcppriv;
struct Tcppriv
{
	/* List of active timers */
	QLock 	tl;
	Tcptimer *timers;

	/* hash table for matching conversations */
	Ipht	ht;

	/* calls in limbo waiting for an ACK to our SYN ACK */
	int	nlimbo;
	Limbo	*lht[NLHT];

	/* for keeping track of tcpackproc */
	QLock	apl;
	int	ackprocstarted;

	uvlong	stats[Nstats];
};

/*
 *  Setting tcpporthogdefense to non-zero enables Dong Lin's
 *  solution to hijacked systems staking out port's as a form
 *  of DoS attack.
 *
 *  To avoid stateless Conv hogs, we pick a sequence number at random.  If
 *  that number gets acked by the other end, we shut down the connection.
 *  Look for tcpporthogdefense in the code.
 */
int tcpporthogdefense = 0;

static	int	addreseq(Fs*, Tcpctl*, Tcppriv*, Tcp*, Block*, ushort);
static	int	dumpreseq(Tcpctl*);
static	void	getreseq(Tcpctl*, Tcp*, Block**, ushort*);
static	void	limbo(Conv*, uchar*, uchar*, Tcp*, int);
static	void	limborexmit(Proto*);
static	void	localclose(Conv*, char*);
static	void	procsyn(Conv*, Tcp*);
static	void	tcpacktimer(void*);
static	void	tcpiput(Proto*, Ipifc*, Block*);
static	void	tcpkeepalive(void*);
static	void	tcpoutput(Conv*);
static	void	tcprcvwin(Conv*);
static	void	tcprxmit(Conv*);
static	void	tcpsetkacounter(Tcpctl*);
static	void	tcpsetscale(Conv*, Tcpctl*, ushort, ushort);
static	void	tcpsettimer(Tcpctl*);
static	void	tcpsndsyn(Conv*, Tcpctl*);
static	void	tcpstart(Conv*, int);
static	void	tcpsynackrtt(Conv*);
static	void	tcptimeout(void*);
static	int	tcptrim(Tcpctl*, Tcp*, Block**, ushort*);

static void
tcpsetstate(Conv *s, uchar newstate)
{
	Tcpctl *tcb;
	uchar oldstate;
	Tcppriv *tpriv;

	tpriv = s->p->priv;

	tcb = (Tcpctl*)s->ptcl;

	oldstate = tcb->state;
	if(oldstate == newstate)
		return;

	if(oldstate == Established)
		tpriv->stats[CurrEstab]--;
	if(newstate == Established)
		tpriv->stats[CurrEstab]++;

	switch(newstate) {
	case Closed:
		qclose(s->rq);
		qclose(s->wq);
		qclose(s->eq);
		break;

	case Close_wait:		/* Remote closes */
		qhangup(s->rq, nil);
		break;
	}

	tcb->state = newstate;

	if(oldstate == Syn_sent && newstate != Closed)
		Fsconnected(s, nil);
}

static char*
tcpconnect(Conv *c, char **argv, int argc)
{
	char *e;
	Tcpctl *tcb;

	tcb = (Tcpctl*)(c->ptcl);
	if(tcb->state != Closed)
		return Econinuse;

	e = Fsstdconnect(c, argv, argc);
	if(e != nil)
		return e;
	tcpstart(c, TCP_CONNECT);

	return nil;
}

static int
tcpstate(Conv *c, char *state, int n)
{
	Tcpctl *s;

	s = (Tcpctl*)(c->ptcl);

	return snprint(state, n,
		"%s qin %d qout %d rq %d.%d srtt %d mdev %d sst %lud cwin %lud swin %lud>>%d rwin %lud>>%d qscale %d timer.start %d timer.count %d rerecv %d katimer.start %d katimer.count %d\n",
		tcpstates[s->state],
		c->rq ? qlen(c->rq) : 0,
		c->wq ? qlen(c->wq) : 0,
		s->nreseq, s->reseqlen,
		s->srtt, s->mdev, s->ssthresh,
		s->cwind, s->snd.wnd, s->rcv.scale, s->rcv.wnd, s->snd.scale,
		s->qscale,
		s->timer.start, s->timer.count, s->rerecv,
		s->katimer.start, s->katimer.count);
}

static int
tcpinuse(Conv *c)
{
	Tcpctl *s;

	s = (Tcpctl*)(c->ptcl);
	return s->state != Closed;
}

static char*
tcpannounce(Conv *c, char **argv, int argc)
{
	char *e;
	Tcpctl *tcb;

	tcb = (Tcpctl*)(c->ptcl);
	if(tcb->state != Closed)
		return Econinuse;

	e = Fsstdannounce(c, argv, argc);
	if(e != nil)
		return e;
	tcpstart(c, TCP_LISTEN);
	Fsconnected(c, nil);

	return nil;
}

/*
 *  tcpclose is always called with the q locked
 */
static void
tcpclose(Conv *c)
{
	Tcpctl *tcb;

	tcb = (Tcpctl*)c->ptcl;

	qhangup(c->rq, nil);
	qhangup(c->wq, nil);
	qhangup(c->eq, nil);
	qflush(c->rq);

	switch(tcb->state) {
	case Listen:
		/*
		 *  reset any incoming calls to this listener
		 */
		Fsconnected(c, "Hangup");

		localclose(c, nil);
		break;
	case Closed:
	case Syn_sent:
		localclose(c, nil);
		break;
	case Syn_received:
	case Established:
		tcb->flgcnt++;
		tcb->snd.nxt++;
		tcpsetstate(c, Finwait1);
		tcpoutput(c);
		break;
	case Close_wait:
		tcb->flgcnt++;
		tcb->snd.nxt++;
		tcpsetstate(c, Last_ack);
		tcpoutput(c);
		break;
	}
}

static void
tcpkick(void *x)
{
	Conv *s = x;
	Tcpctl *tcb;

	tcb = (Tcpctl*)s->ptcl;

	if(waserror()){
		qunlock(s);
		nexterror();
	}
	qlock(s);

	switch(tcb->state) {
	case Syn_sent:
	case Syn_received:
	case Established:
	case Close_wait:
		/*
		 * Push data
		 */
		tcpoutput(s);
		break;
	default:
		localclose(s, "Hangup");
		break;
	}

	qunlock(s);
	poperror();
}

static int seq_lt(ulong, ulong);

static void
tcprcvwin(Conv *s)				/* Call with tcb locked */
{
	int w;
	Tcpctl *tcb;

	tcb = (Tcpctl*)s->ptcl;
	w = tcb->window - qlen(s->rq);
	if(w < 0)
		w = 0;
	/* RFC 1122 § 4.2.2.17 do not move right edge of window left */
	if(seq_lt(tcb->rcv.nxt + w, tcb->rcv.wptr))
		w = tcb->rcv.wptr - tcb->rcv.nxt;
	if(w != tcb->rcv.wnd)
	if(w>>tcb->rcv.scale == 0 || tcb->window > 4*tcb->mss && w < tcb->mss/4){
		tcb->rcv.blocked = 1;
		netlog(s->p->f, Logtcp, "tcprcvwin: window %lud qlen %d ws %ud lport %d\n",
			tcb->window, qlen(s->rq), tcb->rcv.scale, s->lport);
	}
	tcb->rcv.wnd = w;
	tcb->rcv.wptr = tcb->rcv.nxt + w;
}

static void
tcpacktimer(void *v)
{
	Tcpctl *tcb;
	Conv *s;

	s = v;
	tcb = (Tcpctl*)s->ptcl;

	if(waserror()){
		qunlock(s);
		nexterror();
	}
	qlock(s);
	if(tcb->state != Closed){
		tcb->flags |= FORCE;
		tcpoutput(s);
	}
	qunlock(s);
	poperror();
}

static void
tcpcongestion(Tcpctl *tcb)
{
	ulong inflight;

	inflight = tcb->snd.nxt - tcb->snd.una;
	if(inflight > tcb->cwind)
		inflight = tcb->cwind;
	tcb->ssthresh = inflight / 2;
	if(tcb->ssthresh < 2*tcb->mss)
		tcb->ssthresh = 2*tcb->mss;
}

enum {
	L		= 2,		/* aggressive slow start; legal values ∈ (1.0, 2.0) */
};

static void
tcpabcincr(Tcpctl *tcb, uint acked)
{
	uint limit;

	tcb->abcbytes += acked;
	if(tcb->cwind < tcb->ssthresh){
		/* slow start */
		if(tcb->snd.rto)
			limit = 1*tcb->mss;
		else
			limit = L*tcb->mss;
		tcb->cwind += MIN(tcb->abcbytes, limit);
		tcb->abcbytes = 0;
	}
	else{
		tcb->snd.rto = 0;
		/* avoidance */
		if(tcb->abcbytes >= tcb->cwind){
			tcb->abcbytes -= tcb->cwind;
			tcb->cwind += tcb->mss;
		}
	}
}

static void
tcpcreate(Conv *c)
{
	c->rq = qopen(QMAX, Qcoalesce, tcpacktimer, c);
	c->wq = qopen(QMAX, Qkick, tcpkick, c);
}

static void
timerstate(Tcppriv *priv, Tcptimer *t, int newstate)
{
	if(newstate != TcptimerON){
		if(t->state == TcptimerON){
			/* unchain */
			if(priv->timers == t){
				priv->timers = t->next;
				if(t->prev != nil)
					panic("timerstate1");
			}
			if(t->next)
				t->next->prev = t->prev;
			if(t->prev)
				t->prev->next = t->next;
			t->next = t->prev = nil;
		}
	} else {
		if(t->state != TcptimerON){
			/* chain */
			if(t->prev != nil || t->next != nil)
				panic("timerstate2");
			t->prev = nil;
			t->next = priv->timers;
			if(t->next)
				t->next->prev = t;
			priv->timers = t;
		}
	}
	t->state = newstate;
}

static void
tcpackproc(void *a)
{
	Tcptimer *t, *tp, *timeo;
	Proto *tcp;
	Tcppriv *priv;
	int loop;

	tcp = a;
	priv = tcp->priv;

	while(waserror())
		;

	for(;;) {
		tsleep(&up->sleep, return0, 0, MSPTICK);

		qlock(&priv->tl);
		timeo = nil;
		loop = 0;
		for(t = priv->timers; t != nil; t = tp) {
			if(loop++ > 10000)
				panic("tcpackproc1");
			tp = t->next;
 			if(t->state == TcptimerON) {
				t->count--;
				if(t->count == 0) {
					timerstate(priv, t, TcptimerDONE);
					t->readynext = timeo;
					timeo = t;
				}
			}
		}
		qunlock(&priv->tl);

		loop = 0;
		for(t = timeo; t != nil; t = t->readynext) {
			if(loop++ > 10000)
				panic("tcpackproc2");
			if(t->state == TcptimerDONE && t->func != nil && !waserror()){
				(*t->func)(t->arg);
				poperror();
			}
		}

		limborexmit(tcp);
	}
}

static void
tcpgo(Tcppriv *priv, Tcptimer *t)
{
	if(t == nil || t->start == 0)
		return;

	qlock(&priv->tl);
	t->count = t->start;
	timerstate(priv, t, TcptimerON);
	qunlock(&priv->tl);
}

static void
tcphalt(Tcppriv *priv, Tcptimer *t)
{
	if(t == nil)
		return;

	qlock(&priv->tl);
	timerstate(priv, t, TcptimerOFF);
	qunlock(&priv->tl);
}

static int
backoff(int n)
{
	return 1 << n;
}

static void
localclose(Conv *s, char *reason)	/* called with tcb locked */
{
	Tcpctl *tcb;
	Tcppriv *tpriv;

	tpriv = s->p->priv;
	tcb = (Tcpctl*)s->ptcl;

	iphtrem(&tpriv->ht, s);

	tcphalt(tpriv, &tcb->timer);
	tcphalt(tpriv, &tcb->rtt_timer);
	tcphalt(tpriv, &tcb->acktimer);
	tcphalt(tpriv, &tcb->katimer);

	/* Flush reassembly queue; nothing more can arrive */
	dumpreseq(tcb);

	if(tcb->state == Syn_sent)
		Fsconnected(s, reason);
	if(s->state == Announced)
		wakeup(&s->listenr);

	qhangup(s->rq, reason);
	qhangup(s->wq, reason);

	tcpsetstate(s, Closed);
}

/* mtu (- TCP + IP hdr len) of 1st hop */
static int
tcpmtu(Route *r, int version, uint *scale)
{
	Ipifc *ifc;
	int mtu;

	/*
	 * set the ws.  it doesn't commit us to anything.
	 * ws is the ultimate limit to the bandwidth-delay product.
	 */
	*scale = Defadvscale;

	/*
	 * currently we do not implement path MTU discovery
	 * so use interface MTU *only* if directly reachable
	 * or when we use V4 which allows routers to fragment.
	 * otherwise, we use the default MSS which assumes a
	 * safe minimum MTU of 1280 bytes for V6.
	 */  
	if(r != nil && (ifc = r->ifc) != nil){
		mtu = ifc->maxtu - ifc->m->hsize;
		if(version == V4)
			return mtu - (TCP4_PKT + TCP4_HDRSIZE);
		mtu -= TCP6_PKT + TCP6_HDRSIZE;
		if((r->type & (Rifc|Runi)) != 0 || mtu <= DEF_MSS6)
			return mtu;
	}
	if(version == V6)
		return DEF_MSS6;
	else
		return DEF_MSS;
}

static void
inittcpctl(Conv *s, int mode)
{
	Tcpctl *tcb;
	Tcp4hdr* h4;
	Tcp6hdr* h6;
	Tcppriv *tpriv;
	int mss;

	tcb = (Tcpctl*)s->ptcl;

	memset(tcb, 0, sizeof(Tcpctl));

	tcb->ssthresh = QMAX;			/* reset by tcpsetscale() */
	tcb->srtt = tcp_irtt<<LOGAGAIN;
	tcb->mdev = 0;

	/* setup timers */
	tcb->timer.start = tcp_irtt / MSPTICK;
	tcb->timer.func = tcptimeout;
	tcb->timer.arg = s;
	tcb->rtt_timer.start = MAX_TIME;
	tcb->acktimer.start = TCP_ACK / MSPTICK;
	tcb->acktimer.func = tcpacktimer;
	tcb->acktimer.arg = s;
	tcb->katimer.start = DEF_KAT / MSPTICK;
	tcb->katimer.func = tcpkeepalive;
	tcb->katimer.arg = s;

	mss = DEF_MSS;

	/* create a prototype(pseudo) header */
	if(mode != TCP_LISTEN){
		if(ipcmp(s->laddr, IPnoaddr) == 0)
			findlocalip(s->p->f, s->laddr, s->raddr);

		switch(s->ipversion){
		case V4:
			h4 = &tcb->protohdr.tcp4hdr;
			memset(h4, 0, sizeof(*h4));
			h4->proto = IP_TCPPROTO;
			hnputs(h4->tcpsport, s->lport);
			hnputs(h4->tcpdport, s->rport);
			v6tov4(h4->tcpsrc, s->laddr);
			v6tov4(h4->tcpdst, s->raddr);
			break;
		case V6:
			h6 = &tcb->protohdr.tcp6hdr;
			memset(h6, 0, sizeof(*h6));
			h6->proto = IP_TCPPROTO;
			hnputs(h6->tcpsport, s->lport);
			hnputs(h6->tcpdport, s->rport);
			ipmove(h6->tcpsrc, s->laddr);
			ipmove(h6->tcpdst, s->raddr);
			mss = DEF_MSS6;
			break;
		default:
			panic("inittcpctl: version %d", s->ipversion);
		}
	}

	tcb->mss = tcb->cwind = mss;
	tcb->abcbytes = 0;
	tpriv = s->p->priv;
	tpriv->stats[Mss] = tcb->mss;

	/* default is no window scaling */
	tcpsetscale(s, tcb, 0, 0);
}

/*
 *  called with s qlocked
 */
static void
tcpstart(Conv *s, int mode)
{
	Tcpctl *tcb;
	Tcppriv *tpriv;
	char kpname[KNAMELEN];

	tpriv = s->p->priv;

	if(tpriv->ackprocstarted == 0){
		qlock(&tpriv->apl);
		if(tpriv->ackprocstarted == 0){
			snprint(kpname, sizeof(kpname), "#I%dtcpack", s->p->f->dev);
			kproc(kpname, tcpackproc, s->p);
			tpriv->ackprocstarted = 1;
		}
		qunlock(&tpriv->apl);
	}

	tcb = (Tcpctl*)s->ptcl;

	inittcpctl(s, mode);

	iphtadd(&tpriv->ht, s);
	switch(mode) {
	case TCP_LISTEN:
		tpriv->stats[PassiveOpens]++;
		tcb->flags |= CLONE;
		tcpsetstate(s, Listen);
		break;

	case TCP_CONNECT:
		tpriv->stats[ActiveOpens]++;
		tcb->flags |= ACTIVE;
		tcpsndsyn(s, tcb);
		tcpsetstate(s, Syn_sent);
		tcpoutput(s);
		break;
	}
}

static char*
tcpflag(char *buf, char *e, ushort flag)
{
	char *p;

	p = seprint(buf, e, "%d", flag>>10);	/* Head len */
	if(flag & URG)
		p = seprint(p, e, " URG");
	if(flag & ACK)
		p = seprint(p, e, " ACK");
	if(flag & PSH)
		p = seprint(p, e, " PSH");
	if(flag & RST)
		p = seprint(p, e, " RST");
	if(flag & SYN)
		p = seprint(p, e, " SYN");
	if(flag & FIN)
		p = seprint(p, e, " FIN");
	USED(p);
	return buf;
}

static Block*
htontcp6(Tcp *tcph, Block *data, Tcp6hdr *ph, Tcpctl *tcb)
{
	int dlen;
	Tcp6hdr *h;
	ushort csum;
	ushort hdrlen, optpad = 0;
	uchar *opt;

	hdrlen = TCP6_HDRSIZE;
	if(tcph->flags & SYN){
		if(tcph->mss)
			hdrlen += MSS_LENGTH;
		if(tcph->ws)
			hdrlen += WS_LENGTH;
		optpad = hdrlen & 3;
		if(optpad)
			optpad = 4 - optpad;
		hdrlen += optpad;
	}

	if(data) {
		dlen = blocklen(data);
		data = padblock(data, hdrlen + TCP6_PKT);
	}
	else {
		dlen = 0;
		data = allocb(hdrlen + TCP6_PKT + 64);	/* the 64 pad is to meet mintu's */
		data->wp += hdrlen + TCP6_PKT;
	}

	/* copy in pseudo ip header plus port numbers */
	h = (Tcp6hdr *)(data->rp);
	memmove(h, ph, TCP6_TCBPHDRSZ);

	/* compose pseudo tcp header, do cksum calculation */
	hnputl(h->vcf, hdrlen + dlen);
	h->ploadlen[0] = h->ploadlen[1] = h->proto = 0;
	h->ttl = ph->proto;

	/* copy in variable bits */
	hnputl(h->tcpseq, tcph->seq);
	hnputl(h->tcpack, tcph->ack);
	hnputs(h->tcpflag, (hdrlen<<10) | tcph->flags);
	hnputs(h->tcpwin, tcph->wnd>>(tcb != nil ? tcb->snd.scale : 0));
	hnputs(h->tcpurg, tcph->urg);

	if(tcph->flags & SYN){
		opt = h->tcpopt;
		if(tcph->mss != 0){
			*opt++ = MSSOPT;
			*opt++ = MSS_LENGTH;
			hnputs(opt, tcph->mss);
			opt += 2;
		}
		if(tcph->ws != 0){
			*opt++ = WSOPT;
			*opt++ = WS_LENGTH;
			*opt++ = tcph->ws;
		}
		while(optpad-- > 0)
			*opt++ = NOOPOPT;
	}

	if(tcb != nil && tcb->nochecksum){
		h->tcpcksum[0] = h->tcpcksum[1] = 0;
	} else {
		csum = ptclcsum(data, TCP6_IPLEN, hdrlen+dlen+TCP6_PHDRSIZE);
		hnputs(h->tcpcksum, csum);
	}

	/* move from pseudo header back to normal ip header */
	memset(h->vcf, 0, 4);
	h->vcf[0] = IP_VER6;
	hnputs(h->ploadlen, hdrlen+dlen);
	h->proto = ph->proto;

	return data;
}

static Block*
htontcp4(Tcp *tcph, Block *data, Tcp4hdr *ph, Tcpctl *tcb)
{
	int dlen;
	Tcp4hdr *h;
	ushort csum;
	ushort hdrlen, optpad = 0;
	uchar *opt;

	hdrlen = TCP4_HDRSIZE;
	if(tcph->flags & SYN){
		if(tcph->mss)
			hdrlen += MSS_LENGTH;
		if(1)
			hdrlen += WS_LENGTH;
		optpad = hdrlen & 3;
		if(optpad)
			optpad = 4 - optpad;
		hdrlen += optpad;
	}

	if(data) {
		dlen = blocklen(data);
		data = padblock(data, hdrlen + TCP4_PKT);
	}
	else {
		dlen = 0;
		data = allocb(hdrlen + TCP4_PKT + 64);	/* the 64 pad is to meet mintu's */
		data->wp += hdrlen + TCP4_PKT;
	}

	/* copy in pseudo ip header plus port numbers */
	h = (Tcp4hdr *)(data->rp);
	memmove(h, ph, TCP4_TCBPHDRSZ);

	/* copy in variable bits */
	hnputs(h->tcplen, hdrlen + dlen);
	hnputl(h->tcpseq, tcph->seq);
	hnputl(h->tcpack, tcph->ack);
	hnputs(h->tcpflag, (hdrlen<<10) | tcph->flags);
	hnputs(h->tcpwin, tcph->wnd>>(tcb != nil ? tcb->snd.scale : 0));
	hnputs(h->tcpurg, tcph->urg);

	if(tcph->flags & SYN){
		opt = h->tcpopt;
		if(tcph->mss != 0){
			*opt++ = MSSOPT;
			*opt++ = MSS_LENGTH;
			hnputs(opt, tcph->mss);
			opt += 2;
		}
		/* always offer.  rfc1323 §2.2 */
		if(1){
			*opt++ = WSOPT;
			*opt++ = WS_LENGTH;
			*opt++ = tcph->ws;
		}
		while(optpad-- > 0)
			*opt++ = NOOPOPT;
	}

	if(tcb != nil && tcb->nochecksum){
		h->tcpcksum[0] = h->tcpcksum[1] = 0;
	} else {
		csum = ptclcsum(data, TCP4_IPLEN, hdrlen+dlen+TCP4_PHDRSIZE);
		hnputs(h->tcpcksum, csum);
	}

	return data;
}

static int
ntohtcp6(Tcp *tcph, Block **bpp)
{
	Tcp6hdr *h;
	uchar *optr;
	ushort hdrlen;
	ushort optlen;
	int n;

	*bpp = pullupblock(*bpp, TCP6_PKT+TCP6_HDRSIZE);
	if(*bpp == nil)
		return -1;

	h = (Tcp6hdr *)((*bpp)->rp);
	tcph->source = nhgets(h->tcpsport);
	tcph->dest = nhgets(h->tcpdport);
	tcph->seq = nhgetl(h->tcpseq);
	tcph->ack = nhgetl(h->tcpack);
	hdrlen = (h->tcpflag[0]>>2) & ~3;
	if(hdrlen < TCP6_HDRSIZE) {
		freeblist(*bpp);
		return -1;
	}

	tcph->flags = h->tcpflag[1];
	tcph->wnd = nhgets(h->tcpwin);
	tcph->urg = nhgets(h->tcpurg);
	tcph->mss = 0;
	tcph->ws = 0;
	tcph->update = 0;
	tcph->len = nhgets(h->ploadlen) - hdrlen;

	*bpp = pullupblock(*bpp, hdrlen+TCP6_PKT);
	if(*bpp == nil)
		return -1;

	optr = h->tcpopt;
	n = hdrlen - TCP6_HDRSIZE;
	while(n > 0 && *optr != EOLOPT) {
		if(*optr == NOOPOPT) {
			n--;
			optr++;
			continue;
		}
		optlen = optr[1];
		if(optlen < 2 || optlen > n)
			break;
		switch(*optr) {
		case MSSOPT:
			if(optlen == MSS_LENGTH)
				tcph->mss = nhgets(optr+2);
			break;
		case WSOPT:
			if(optlen == WS_LENGTH && *(optr+2) <= 14)
				tcph->ws = *(optr+2);
			break;
		}
		n -= optlen;
		optr += optlen;
	}
	return hdrlen;
}

static int
ntohtcp4(Tcp *tcph, Block **bpp)
{
	Tcp4hdr *h;
	uchar *optr;
	ushort hdrlen;
	ushort optlen;
	int n;

	*bpp = pullupblock(*bpp, TCP4_PKT+TCP4_HDRSIZE);
	if(*bpp == nil)
		return -1;

	h = (Tcp4hdr *)((*bpp)->rp);
	tcph->source = nhgets(h->tcpsport);
	tcph->dest = nhgets(h->tcpdport);
	tcph->seq = nhgetl(h->tcpseq);
	tcph->ack = nhgetl(h->tcpack);

	hdrlen = (h->tcpflag[0]>>2) & ~3;
	if(hdrlen < TCP4_HDRSIZE) {
		freeblist(*bpp);
		return -1;
	}

	tcph->flags = h->tcpflag[1];
	tcph->wnd = nhgets(h->tcpwin);
	tcph->urg = nhgets(h->tcpurg);
	tcph->mss = 0;
	tcph->ws = 0;
	tcph->update = 0;
	tcph->len = nhgets(h->length) - (hdrlen + TCP4_PKT);

	*bpp = pullupblock(*bpp, hdrlen+TCP4_PKT);
	if(*bpp == nil)
		return -1;

	optr = h->tcpopt;
	n = hdrlen - TCP4_HDRSIZE;
	while(n > 0 && *optr != EOLOPT) {
		if(*optr == NOOPOPT) {
			n--;
			optr++;
			continue;
		}
		optlen = optr[1];
		if(optlen < 2 || optlen > n)
			break;
		switch(*optr) {
		case MSSOPT:
			if(optlen == MSS_LENGTH)
				tcph->mss = nhgets(optr+2);
			break;
		case WSOPT:
			if(optlen == WS_LENGTH && *(optr+2) <= 14)
				tcph->ws = *(optr+2);
			break;
		}
		n -= optlen;
		optr += optlen;
	}
	return hdrlen;
}

/*
 *  For outgoing calls, generate an initial sequence
 *  number and put a SYN on the send queue
 */
static void
tcpsndsyn(Conv *s, Tcpctl *tcb)
{
	Tcppriv *tpriv;

	tcb->iss = (nrand(1<<16)<<16)|nrand(1<<16);
	tcb->rttseq = tcb->iss;
	tcb->snd.wl2 = tcb->iss;
	tcb->snd.una = tcb->iss;
	tcb->snd.rxt = tcb->iss;
	tcb->snd.ptr = tcb->rttseq;
	tcb->snd.nxt = tcb->rttseq;
	tcb->flgcnt++;
	tcb->flags |= FORCE;
	tcb->sndsyntime = NOW;

	/* set desired mss and scale */
	tcb->mss = tcpmtu(v6lookup(s->p->f, s->raddr, s->laddr, s), s->ipversion, &tcb->scale);
	tpriv = s->p->priv;
	tpriv->stats[Mss] = tcb->mss;
}

void
sndrst(Proto *tcp, uchar *source, uchar *dest, ushort length, Tcp *seg, uchar version, char *reason)
{
	Block *hbp;
	uchar rflags;
	Tcppriv *tpriv;
	Tcp4hdr ph4;
	Tcp6hdr ph6;

	netlog(tcp->f, Logtcp, "sndrst: %s\n", reason);

	tpriv = tcp->priv;

	if(seg->flags & RST)
		return;

	/* make pseudo header */
	switch(version) {
	case V4:
		memset(&ph4, 0, sizeof(ph4));
		ph4.vihl = IP_VER4;
		v6tov4(ph4.tcpsrc, dest);
		v6tov4(ph4.tcpdst, source);
		ph4.proto = IP_TCPPROTO;
		hnputs(ph4.tcplen, TCP4_HDRSIZE);
		hnputs(ph4.tcpsport, seg->dest);
		hnputs(ph4.tcpdport, seg->source);
		break;
	case V6:
		memset(&ph6, 0, sizeof(ph6));
		ph6.vcf[0] = IP_VER6;
		ipmove(ph6.tcpsrc, dest);
		ipmove(ph6.tcpdst, source);
		ph6.proto = IP_TCPPROTO;
		hnputs(ph6.ploadlen, TCP6_HDRSIZE);
		hnputs(ph6.tcpsport, seg->dest);
		hnputs(ph6.tcpdport, seg->source);
		break;
	default:
		panic("sndrst: version %d", version);
	}

	tpriv->stats[OutRsts]++;
	rflags = RST;

	/* convince the other end that this reset is in band */
	if(seg->flags & ACK) {
		seg->seq = seg->ack;
		seg->ack = 0;
	}
	else {
		rflags |= ACK;
		seg->ack = seg->seq;
		seg->seq = 0;
		if(seg->flags & SYN)
			seg->ack++;
		seg->ack += length;
		if(seg->flags & FIN)
			seg->ack++;
	}
	seg->flags = rflags;
	seg->wnd = 0;
	seg->urg = 0;
	seg->mss = 0;
	seg->ws = 0;
	switch(version) {
	case V4:
		hbp = htontcp4(seg, nil, &ph4, nil);
		if(hbp == nil)
			return;
		ipoput4(tcp->f, hbp, 0, MAXTTL, DFLTTOS, nil);
		break;
	case V6:
		hbp = htontcp6(seg, nil, &ph6, nil);
		if(hbp == nil)
			return;
		ipoput6(tcp->f, hbp, 0, MAXTTL, DFLTTOS, nil);
		break;
	default:
		panic("sndrst2: version %d", version);
	}
}

/*
 *  send a reset to the remote side and close the conversation
 *  called with s qlocked
 */
static char*
tcphangup(Conv *s)
{
	Tcp seg;
	Tcpctl *tcb;
	Block *hbp;

	tcb = (Tcpctl*)s->ptcl;
	if(waserror())
		return commonerror();
	if(ipcmp(s->raddr, IPnoaddr) != 0) {
		if(!waserror()){
			memset(&seg, 0, sizeof seg);
			seg.flags = RST | ACK;
			seg.ack = tcb->rcv.nxt;
			tcb->rcv.ackptr = seg.ack;
			seg.seq = tcb->snd.ptr;
			seg.wnd = 0;
			seg.urg = 0;
			seg.mss = 0;
			seg.ws = 0;
			switch(s->ipversion) {
			case V4:
				tcb->protohdr.tcp4hdr.vihl = IP_VER4;
				hbp = htontcp4(&seg, nil, &tcb->protohdr.tcp4hdr, tcb);
				ipoput4(s->p->f, hbp, 0, s->ttl, s->tos, s);
				break;
			case V6:
				tcb->protohdr.tcp6hdr.vcf[0] = IP_VER6;
				hbp = htontcp6(&seg, nil, &tcb->protohdr.tcp6hdr, tcb);
				ipoput6(s->p->f, hbp, 0, s->ttl, s->tos, s);
				break;
			default:
				panic("tcphangup: version %d", s->ipversion);
			}
			poperror();
		}
	}
	localclose(s, nil);
	poperror();
	return nil;
}

/*
 *  (re)send a SYN ACK
 */
static int
sndsynack(Proto *tcp, Limbo *lp)
{
	Block *hbp;
	Tcp4hdr ph4;
	Tcp6hdr ph6;
	Tcp seg;
	uint scale;

	/* make pseudo header */
	switch(lp->version) {
	case V4:
		memset(&ph4, 0, sizeof(ph4));
		ph4.vihl = IP_VER4;
		v6tov4(ph4.tcpsrc, lp->laddr);
		v6tov4(ph4.tcpdst, lp->raddr);
		ph4.proto = IP_TCPPROTO;
		hnputs(ph4.tcplen, TCP4_HDRSIZE);
		hnputs(ph4.tcpsport, lp->lport);
		hnputs(ph4.tcpdport, lp->rport);
		break;
	case V6:
		memset(&ph6, 0, sizeof(ph6));
		ph6.vcf[0] = IP_VER6;
		ipmove(ph6.tcpsrc, lp->laddr);
		ipmove(ph6.tcpdst, lp->raddr);
		ph6.proto = IP_TCPPROTO;
		hnputs(ph6.ploadlen, TCP6_HDRSIZE);
		hnputs(ph6.tcpsport, lp->lport);
		hnputs(ph6.tcpdport, lp->rport);
		break;
	default:
		panic("sndrst: version %d", lp->version);
	}

	memset(&seg, 0, sizeof seg);
	seg.seq = lp->iss;
	seg.ack = lp->irs+1;
	seg.flags = SYN|ACK;
	seg.urg = 0;
	seg.mss = tcpmtu(v6lookup(tcp->f, lp->raddr, lp->laddr, nil), lp->version, &scale);
	seg.wnd = QMAX;

	/* if the other side set scale, we should too */
	if(lp->rcvscale){
		seg.ws = scale;
		lp->sndscale = scale;
	} else {
		seg.ws = 0;
		lp->sndscale = 0;
	}

	switch(lp->version) {
	case V4:
		hbp = htontcp4(&seg, nil, &ph4, nil);
		if(hbp == nil)
			return -1;
		ipoput4(tcp->f, hbp, 0, MAXTTL, DFLTTOS, nil);
		break;
	case V6:
		hbp = htontcp6(&seg, nil, &ph6, nil);
		if(hbp == nil)
			return -1;
		ipoput6(tcp->f, hbp, 0, MAXTTL, DFLTTOS, nil);
		break;
	default:
		panic("sndsnack: version %d", lp->version);
	}
	lp->lastsend = NOW;
	return 0;
}

#define hashipa(a, p) ( ( (a)[IPaddrlen-2] + (a)[IPaddrlen-1] + p )&LHTMASK )

/*
 *  put a call into limbo and respond with a SYN ACK
 *
 *  called with proto locked
 */
static void
limbo(Conv *s, uchar *source, uchar *dest, Tcp *seg, int version)
{
	Limbo *lp, **l;
	Tcppriv *tpriv;
	int h;

	tpriv = s->p->priv;
	h = hashipa(source, seg->source);

	for(l = &tpriv->lht[h]; *l != nil; l = &lp->next){
		lp = *l;
		if(lp->lport != seg->dest || lp->rport != seg->source || lp->version != version)
			continue;
		if(ipcmp(lp->raddr, source) != 0)
			continue;
		if(ipcmp(lp->laddr, dest) != 0)
			continue;

		/* each new SYN restarts the retransmits */
		lp->irs = seg->seq;
		break;
	}
	lp = *l;
	if(lp == nil){
		if(tpriv->nlimbo >= Maxlimbo && tpriv->lht[h]){
			lp = tpriv->lht[h];
			tpriv->lht[h] = lp->next;
			lp->next = nil;
		} else {
			lp = malloc(sizeof(*lp));
			if(lp == nil)
				return;
			tpriv->nlimbo++;
		}
		*l = lp;
		lp->version = version;
		ipmove(lp->laddr, dest);
		ipmove(lp->raddr, source);
		lp->lport = seg->dest;
		lp->rport = seg->source;
		lp->mss = seg->mss;
		lp->rcvscale = seg->ws;
		lp->irs = seg->seq;
		lp->iss = (nrand(1<<16)<<16)|nrand(1<<16);
	}

	if(sndsynack(s->p, lp) < 0){
		*l = lp->next;
		tpriv->nlimbo--;
		free(lp);
	}
}

/*
 *  resend SYN ACK's once every SYNACK_RXTIMER ms.
 */
static void
limborexmit(Proto *tcp)
{
	Tcppriv *tpriv;
	Limbo **l, *lp;
	int h;
	int seen;
	ulong now;

	tpriv = tcp->priv;

	if(!canqlock(tcp))
		return;
	seen = 0;
	now = NOW;
	for(h = 0; h < NLHT && seen < tpriv->nlimbo; h++){
		for(l = &tpriv->lht[h]; *l != nil && seen < tpriv->nlimbo; ){
			lp = *l;
			seen++;
			if(now - lp->lastsend < (lp->rexmits+1)*SYNACK_RXTIMER)
				continue;

			/* time it out after 1 second */
			if(++(lp->rexmits) > 5){
				tpriv->nlimbo--;
				*l = lp->next;
				free(lp);
				continue;
			}

			/* if we're being attacked, don't bother resending SYN ACK's */
			if(tpriv->nlimbo > 100)
				continue;

			if(sndsynack(tcp, lp) < 0){
				tpriv->nlimbo--;
				*l = lp->next;
				free(lp);
				continue;
			}

			l = &lp->next;
		}
	}
	qunlock(tcp);
}

/*
 *  lookup call in limbo.  if found, throw it out.
 *
 *  called with proto locked
 */
static void
limborst(Conv *s, Tcp *segp, uchar *src, uchar *dst, uchar version)
{
	Limbo *lp, **l;
	int h;
	Tcppriv *tpriv;

	tpriv = s->p->priv;

	/* find a call in limbo */
	h = hashipa(src, segp->source);
	for(l = &tpriv->lht[h]; *l != nil; l = &lp->next){
		lp = *l;
		if(lp->lport != segp->dest || lp->rport != segp->source || lp->version != version)
			continue;
		if(ipcmp(lp->laddr, dst) != 0)
			continue;
		if(ipcmp(lp->raddr, src) != 0)
			continue;

		/* RST can only follow the SYN */
		if(segp->seq == lp->irs+1){
			tpriv->nlimbo--;
			*l = lp->next;
			free(lp);
		}
		break;
	}
}

static void
initialwindow(Tcpctl *tcb)
{
	/* RFC 3390 initial window */
	if(tcb->mss < 1095)
		tcb->cwind = 4*tcb->mss;
	else if(tcb->mss < 2190)
		tcb->cwind = 4380;
	else
		tcb->cwind = 2*tcb->mss;
}

/*
 *  come here when we finally get an ACK to our SYN-ACK.
 *  lookup call in limbo.  if found, create a new conversation
 *
 *  called with proto locked
 */
static Conv*
tcpincoming(Conv *s, Tcp *segp, uchar *src, uchar *dst, uchar version)
{
	Conv *new;
	Tcpctl *tcb;
	Tcppriv *tpriv;
	Tcp4hdr *h4;
	Tcp6hdr *h6;
	Limbo *lp, **l;
	int h;

	/* unless it's just an ack, it can't be someone coming out of limbo */
	if((segp->flags & SYN) || (segp->flags & ACK) == 0)
		return nil;

	tpriv = s->p->priv;

	/* find a call in limbo */
	h = hashipa(src, segp->source);
	for(l = &tpriv->lht[h]; (lp = *l) != nil; l = &lp->next){
		netlog(s->p->f, Logtcp, "tcpincoming s %I!%ud/%I!%ud d %I!%ud/%I!%ud v %d/%d\n",
			src, segp->source, lp->raddr, lp->rport,
			dst, segp->dest, lp->laddr, lp->lport,
			version, lp->version
 		);

		if(lp->lport != segp->dest || lp->rport != segp->source || lp->version != version)
			continue;
		if(ipcmp(lp->laddr, dst) != 0)
			continue;
		if(ipcmp(lp->raddr, src) != 0)
			continue;

		/* we're assuming no data with the initial SYN */
		if(segp->seq != lp->irs+1 || segp->ack != lp->iss+1){
			netlog(s->p->f, Logtcp, "tcpincoming s %lux/%lux a %lux %lux\n",
				segp->seq, lp->irs+1, segp->ack, lp->iss+1);
			lp = nil;
		} else {
			tpriv->nlimbo--;
			*l = lp->next;
		}
		break;
	}
	if(lp == nil)
		return nil;

	new = Fsnewcall(s, src, segp->source, dst, segp->dest, version);
	if(new == nil)
		return nil;

	memmove(new->ptcl, s->ptcl, sizeof(Tcpctl));
	tcb = (Tcpctl*)new->ptcl;
	tcb->flags &= ~CLONE;
	tcb->timer.arg = new;
	tcb->timer.state = TcptimerOFF;
	tcb->acktimer.arg = new;
	tcb->acktimer.state = TcptimerOFF;
	tcb->katimer.arg = new;
	tcb->katimer.state = TcptimerOFF;
	tcb->rtt_timer.arg = new;
	tcb->rtt_timer.state = TcptimerOFF;

	tcb->irs = lp->irs;
	tcb->rcv.nxt = tcb->irs+1;
	tcb->rcv.wptr = tcb->rcv.nxt;
	tcb->rcv.wsnt = 0;
	tcb->rcv.urg = tcb->rcv.nxt;

	tcb->iss = lp->iss;
	tcb->rttseq = tcb->iss;
	tcb->snd.wl2 = tcb->iss;
	tcb->snd.una = tcb->iss+1;
	tcb->snd.ptr = tcb->iss+1;
	tcb->snd.nxt = tcb->iss+1;
	tcb->snd.rxt = tcb->iss+1;
	tcb->flgcnt = 0;
	tcb->flags |= SYNACK;

	/* set desired mss and scale */
	tcb->mss = tcpmtu(v6lookup(s->p->f, src, dst, s), version, &tcb->scale);

	/* our sending max segment size cannot be bigger than what he asked for */
	if(lp->mss != 0 && lp->mss < tcb->mss)
		tcb->mss = lp->mss;
	tpriv->stats[Mss] = tcb->mss;

	/* window scaling */
	tcpsetscale(new, tcb, lp->rcvscale, lp->sndscale);

	/* congestion window */
	tcb->snd.wnd = segp->wnd;
	initialwindow(tcb);

	/* set initial round trip time */
	tcb->sndsyntime = lp->lastsend+lp->rexmits*SYNACK_RXTIMER;
	tcpsynackrtt(new);

	free(lp);

	/* set up proto header */
	switch(version){
	case V4:
		h4 = &tcb->protohdr.tcp4hdr;
		memset(h4, 0, sizeof(*h4));
		h4->proto = IP_TCPPROTO;
		hnputs(h4->tcpsport, new->lport);
		hnputs(h4->tcpdport, new->rport);
		v6tov4(h4->tcpsrc, dst);
		v6tov4(h4->tcpdst, src);
		break;
	case V6:
		h6 = &tcb->protohdr.tcp6hdr;
		memset(h6, 0, sizeof(*h6));
		h6->proto = IP_TCPPROTO;
		hnputs(h6->tcpsport, new->lport);
		hnputs(h6->tcpdport, new->rport);
		ipmove(h6->tcpsrc, dst);
		ipmove(h6->tcpdst, src);
		break;
	default:
		panic("tcpincoming: version %d", new->ipversion);
	}

	tcpsetstate(new, Established);

	iphtadd(&tpriv->ht, new);

	return new;
}

static int
seq_within(ulong x, ulong low, ulong high)
{
	if(low <= high){
		if(low <= x && x <= high)
			return 1;
	}
	else {
		if(x >= low || x <= high)
			return 1;
	}
	return 0;
}

static int
seq_lt(ulong x, ulong y)
{
	return (int)(x-y) < 0;
}

static int
seq_le(ulong x, ulong y)
{
	return (int)(x-y) <= 0;
}

static int
seq_gt(ulong x, ulong y)
{
	return (int)(x-y) > 0;
}

static int
seq_ge(ulong x, ulong y)
{
	return (int)(x-y) >= 0;
}

/*
 *  use the time between the first SYN and it's ack as the
 *  initial round trip time
 */
static void
tcpsynackrtt(Conv *s)
{
	Tcpctl *tcb;
	int delta;
	Tcppriv *tpriv;

	tcb = (Tcpctl*)s->ptcl;
	tpriv = s->p->priv;

	delta = NOW - tcb->sndsyntime;
	tcb->srtt = delta<<LOGAGAIN;
	tcb->mdev = delta<<LOGDGAIN;

	/* halt round trip timer */
	tcphalt(tpriv, &tcb->rtt_timer);
}

static void
update(Conv *s, Tcp *seg)
{
	int rtt, delta;
	Tcpctl *tcb;
	ulong acked;
	Tcppriv *tpriv;

	if(seg->update)
		return;
	seg->update = 1;

	tpriv = s->p->priv;
	tcb = (Tcpctl*)s->ptcl;

	/* catch zero-window updates, update window & recover */
	if(tcb->snd.wnd == 0 && seg->wnd > 0)
	if(seq_lt(seg->ack,  tcb->snd.ptr)){
		netlog(s->p->f, Logtcp, "tcp: zwu ack %lud una %lud ptr %lud win %lud\n",
			seg->ack,  tcb->snd.una, tcb->snd.ptr, seg->wnd);
		tcb->snd.wnd = seg->wnd;
		goto recovery;
	}

	/* newreno fast retransmit */
	if(seg->ack == tcb->snd.una)
	if(tcb->snd.una != tcb->snd.nxt)
	if(++tcb->snd.dupacks == 3){
recovery:
		if(tcb->snd.recovery){
			tpriv->stats[RecoveryCwind]++;
			tcb->cwind += tcb->mss;
		}else if(seq_le(tcb->snd.rxt, seg->ack)){
			tpriv->stats[Recovery]++;
			tcb->abcbytes = 0;
			tcb->snd.recovery = 1;
			tcb->snd.partialack = 0;
			tcb->snd.rxt = tcb->snd.nxt;
			tcpcongestion(tcb);
			tcb->cwind = tcb->ssthresh + 3*tcb->mss;
			netlog(s->p->f, Logtcpwin, "recovery inflate %ld ss %ld @%lud\n",
				tcb->cwind, tcb->ssthresh, tcb->snd.rxt);
			tcprxmit(s);
		}else{
			tpriv->stats[RecoveryNoSeq]++;
			netlog(s->p->f, Logtcpwin, "!recov %lud not ≤ %lud %ld\n",
				tcb->snd.rxt, seg->ack, tcb->snd.rxt - seg->ack);
			/* do not enter fast retransmit */
			/* do not change ssthresh */
		}
	}else if(tcb->snd.recovery){
		tpriv->stats[RecoveryCwind]++;
		tcb->cwind += tcb->mss;
	}

	/*
	 *  update window
	 */
	if(seq_gt(seg->ack, tcb->snd.wl2)
	|| (tcb->snd.wl2 == seg->ack && seg->wnd > tcb->snd.wnd)){
		/* clear dupack if we advance wl2 */
		if(tcb->snd.wl2 != seg->ack)
			tcb->snd.dupacks = 0;
		tcb->snd.wnd = seg->wnd;
		tcb->snd.wl2 = seg->ack;
	}

	if(!seq_gt(seg->ack, tcb->snd.una)){
		/*
		 *  don't let us hangup if sending into a closed window and
		 *  we're still getting acks
		 */
		if((tcb->flags&RETRAN) && tcb->snd.wnd == 0)
			tcb->backedoff = MAXBACKMS/4;
		return;
	}

	/* Compute the new send window size */
	acked = seg->ack - tcb->snd.una;

	/* avoid slow start and timers for SYN acks */
	if((tcb->flags & SYNACK) == 0) {
		tcb->flags |= SYNACK;
		acked--;
		tcb->flgcnt--;
		goto done;
	}

	/*
	 *  congestion control
	 */
	if(tcb->snd.recovery){
		if(seq_ge(seg->ack, tcb->snd.rxt)){
			/* recovery finished; deflate window */
			tpriv->stats[RecoveryDone]++;
			tcb->snd.dupacks = 0;
			tcb->snd.recovery = 0;
			tcb->cwind = (tcb->snd.nxt - tcb->snd.una) + tcb->mss;
			if(tcb->ssthresh < tcb->cwind)
				tcb->cwind = tcb->ssthresh;
			netlog(s->p->f, Logtcpwin, "recovery deflate %ld %ld\n",
				tcb->cwind, tcb->ssthresh);
		} else {
			/* partial ack; we lost more than one segment */
			tpriv->stats[RecoveryPA]++;
			if(tcb->cwind > acked)
				tcb->cwind -= acked;
			else{
				netlog(s->p->f, Logtcpwin, "partial ack neg\n");
				tcb->cwind = tcb->mss;
			}
			netlog(s->p->f, Logtcpwin, "partial ack %ld left %ld cwind %ld\n",
				acked, tcb->snd.rxt - seg->ack, tcb->cwind);

			if(acked >= tcb->mss)
				tcb->cwind += tcb->mss;
			tcb->snd.partialack++;
		}
	} else
		tcpabcincr(tcb, acked);

	/* Adjust the timers according to the round trip time */
	/* todo: fix sloppy treatment of overflow cases here. */
	if(tcb->rtt_timer.state == TcptimerON && seq_ge(seg->ack, tcb->rttseq)) {
		tcphalt(tpriv, &tcb->rtt_timer);
		if((tcb->flags&RETRAN) == 0) {
			tcb->backoff = 0;
			tcb->backedoff = 0;
			rtt = tcb->rtt_timer.start - tcb->rtt_timer.count;
			if(rtt == 0)
				rtt = 1;	/* otherwise all close systems will rexmit in 0 time */
			rtt *= MSPTICK;
			if(tcb->srtt == 0) {
				tcb->srtt = rtt << LOGAGAIN;
				tcb->mdev = rtt << LOGDGAIN;
			} else {
				delta = rtt - (tcb->srtt>>LOGAGAIN);
				tcb->srtt += delta;
				if(tcb->srtt <= 0)
					tcb->srtt = 1;

				delta = abs(delta) - (tcb->mdev>>LOGDGAIN);
				tcb->mdev += delta;
				if(tcb->mdev <= 0)
					tcb->mdev = 1;
			}
			tcpsettimer(tcb);
		}
	}

done:
	if(qdiscard(s->wq, acked) < acked)
		tcb->flgcnt--;
	tcb->snd.una = seg->ack;

	/* newreno fast recovery */
	if(tcb->snd.recovery)
		tcprxmit(s);

	if(seq_gt(seg->ack, tcb->snd.urg))
		tcb->snd.urg = seg->ack;

	if(tcb->snd.una != tcb->snd.nxt){
		/* “impatient” variant */
		if(!tcb->snd.recovery || tcb->snd.partialack == 1){
			tcb->time = NOW;
			tcb->timeuna = tcb->snd.una;
			tcpgo(tpriv, &tcb->timer);
		}
	}
	else
		tcphalt(tpriv, &tcb->timer);

	if(seq_lt(tcb->snd.ptr, tcb->snd.una))
		tcb->snd.ptr = tcb->snd.una;

	if(!tcb->snd.recovery)
		tcb->flags &= ~RETRAN;
	tcb->backoff = 0;
	tcb->backedoff = 0;
}

static void
tcpiput(Proto *tcp, Ipifc*, Block *bp)
{
	Tcp seg;
	Tcp4hdr *h4;
	Tcp6hdr *h6;
	int hdrlen;
	Tcpctl *tcb;
	ushort length, csum;
	uchar source[IPaddrlen], dest[IPaddrlen];
	Conv *s;
	Fs *f;
	Tcppriv *tpriv;
	uchar version;

	f = tcp->f;
	tpriv = tcp->priv;

	tpriv->stats[InSegs]++;

	h4 = (Tcp4hdr*)(bp->rp);
	h6 = (Tcp6hdr*)(bp->rp);

	if((h4->vihl&0xF0)==IP_VER4) {
		version = V4;
		length = nhgets(h4->length);
		v4tov6(dest, h4->tcpdst);
		v4tov6(source, h4->tcpsrc);

		h4->Unused = 0;
		hnputs(h4->tcplen, length-TCP4_PKT);
		if(!(bp->flag & Btcpck) && (h4->tcpcksum[0] || h4->tcpcksum[1]) &&
			ptclcsum(bp, TCP4_IPLEN, length-TCP4_IPLEN)) {
			tpriv->stats[CsumErrs]++;
			tpriv->stats[InErrs]++;
			netlog(f, Logtcp, "bad tcp proto cksum\n");
			freeblist(bp);
			return;
		}

		hdrlen = ntohtcp4(&seg, &bp);
		if(hdrlen < 0){
			tpriv->stats[HlenErrs]++;
			tpriv->stats[InErrs]++;
			netlog(f, Logtcp, "bad tcp hdr len\n");
			return;
		}

		/* trim the packet to the size claimed by the datagram */
		length -= hdrlen+TCP4_PKT;
		bp = trimblock(bp, hdrlen+TCP4_PKT, length);
		if(bp == nil){
			tpriv->stats[LenErrs]++;
			tpriv->stats[InErrs]++;
			netlog(f, Logtcp, "tcp len < 0 after trim\n");
			return;
		}
	}
	else {
		int ttl = h6->ttl;
		int proto = h6->proto;

		version = V6;
		length = nhgets(h6->ploadlen);
		ipmove(dest, h6->tcpdst);
		ipmove(source, h6->tcpsrc);

		h6->ploadlen[0] = h6->ploadlen[1] = h6->proto = 0;
		h6->ttl = proto;
		hnputl(h6->vcf, length);
		if((h6->tcpcksum[0] || h6->tcpcksum[1]) &&
		    (csum = ptclcsum(bp, TCP6_IPLEN, length+TCP6_PHDRSIZE)) != 0) {
			tpriv->stats[CsumErrs]++;
			tpriv->stats[InErrs]++;
			netlog(f, Logtcp,
			    "bad tcpv6 proto cksum: got %#ux, computed %#ux\n",
				h6->tcpcksum[0]<<8 | h6->tcpcksum[1], csum);
			freeblist(bp);
			return;
		}
		h6->ttl = ttl;
		h6->proto = proto;
		hnputs(h6->ploadlen, length);

		hdrlen = ntohtcp6(&seg, &bp);
		if(hdrlen < 0){
			tpriv->stats[HlenErrs]++;
			tpriv->stats[InErrs]++;
			netlog(f, Logtcp, "bad tcpv6 hdr len\n");
			return;
		}

		/* trim the packet to the size claimed by the datagram */
		length -= hdrlen;
		bp = trimblock(bp, hdrlen+TCP6_PKT, length);
		if(bp == nil){
			tpriv->stats[LenErrs]++;
			tpriv->stats[InErrs]++;
			netlog(f, Logtcp, "tcpv6 len < 0 after trim\n");
			return;
		}
	}

	/* lock protocol while searching for a conversation */
	qlock(tcp);

	/* Look for a matching conversation */
	s = iphtlook(&tpriv->ht, source, seg.source, dest, seg.dest);
	if(s == nil){
		netlog(f, Logtcp, "iphtlook(src %I!%d, dst %I!%d) failed\n",
			source, seg.source, dest, seg.dest);
reset:
		qunlock(tcp);
		sndrst(tcp, source, dest, length, &seg, version, "no conversation");
		freeblist(bp);
		return;
	}

	/* if it's a listener, look for the right flags and get a new conv */
	tcb = (Tcpctl*)s->ptcl;
	if(tcb->state == Listen){
		if(seg.flags & RST){
			limborst(s, &seg, source, dest, version);
			qunlock(tcp);
			freeblist(bp);
			return;
		}

		/* if this is a new SYN, put the call into limbo */
		if((seg.flags & SYN) && (seg.flags & ACK) == 0){
			limbo(s, source, dest, &seg, version);
			qunlock(tcp);
			freeblist(bp);
			return;
		}

		/*
		 *  if there's a matching call in limbo, tcpincoming will
		 *  return it in state Syn_received
		 */
		s = tcpincoming(s, &seg, source, dest, version);
		if(s == nil)
			goto reset;
	}

	/* The rest of the input state machine is run with the control block
	 * locked and implements the state machine directly out of the RFC.
	 * Out-of-band data is ignored - it was always a bad idea.
	 */
	tcb = (Tcpctl*)s->ptcl;
	if(waserror()){
		qunlock(s);
		nexterror();
	}
	qlock(s);
	qunlock(tcp);

	/* fix up window */
	seg.wnd <<= tcb->rcv.scale;

	/* every input packet in puts off the keep alive time out */
	tcpsetkacounter(tcb);

	switch(tcb->state) {
	case Closed:
		sndrst(tcp, source, dest, length, &seg, version, "sending to Closed");
		goto raise;
	case Syn_sent:
		if(seg.flags & ACK) {
			if(!seq_within(seg.ack, tcb->iss+1, tcb->snd.nxt)) {
				sndrst(tcp, source, dest, length, &seg, version,
					 "bad seq in Syn_sent");
				goto raise;
			}
		}
		if(seg.flags & RST) {
			if(seg.flags & ACK)
				localclose(s, Econrefused);
			goto raise;
		}

		if(seg.flags & SYN) {
			procsyn(s, &seg);
			if(seg.flags & ACK){
				update(s, &seg);
				tcpsynackrtt(s);
				tcpsetstate(s, Established);
				tcpsetscale(s, tcb, seg.ws, tcb->scale);
			}
			else {
				tcb->time = NOW;
				tcpsetstate(s, Syn_received);	/* DLP - shouldn't this be a reset? */
			}

			if(length != 0 || (seg.flags & FIN))
				break;

			freeblist(bp);
			goto output;
		}
		else
			freeblist(bp);

		qunlock(s);
		poperror();
		return;
	case Syn_received:
		/* doesn't matter if it's the correct ack, we're just trying to set timing */
		if(seg.flags & ACK)
			tcpsynackrtt(s);
		break;
	}

	/*
	 *  One DOS attack is to open connections to us and then forget about them,
	 *  thereby tying up a conv at no long term cost to the attacker.
	 *  This is an attempt to defeat these stateless DOS attacks.  See
	 *  corresponding code in tcpsendka().
	 */
	if(tcb->state != Syn_received && (seg.flags & RST) == 0){
		if(tcpporthogdefense
		&& seq_within(seg.ack, tcb->snd.una-(1<<31), tcb->snd.una-(1<<29))){
			print("stateless hog %I.%d->%I.%d f %ux %lux - %lux - %lux\n",
				source, seg.source, dest, seg.dest, seg.flags,
				tcb->snd.una-(1<<31), seg.ack, tcb->snd.una-(1<<29));
			localclose(s, "stateless hog");
		}
	}

	/* Cut the data to fit the receive window */
	tcprcvwin(s);
	if(tcptrim(tcb, &seg, &bp, &length) == -1) {
		if(seg.seq+1 != tcb->rcv.nxt || length != 1)
		netlog(f, Logtcp, "tcp: trim: !inwind: seq %lud-%lud win %lud-%lud l %d from %I\n", 
			seg.seq, seg.seq + length - 1, 
			tcb->rcv.nxt, tcb->rcv.nxt + tcb->rcv.wnd-1, length, s->raddr);
		update(s, &seg);
		if(qlen(s->wq)+tcb->flgcnt == 0 && tcb->state == Closing) {
			tcphalt(tpriv, &tcb->rtt_timer);
			tcphalt(tpriv, &tcb->acktimer);
			tcphalt(tpriv, &tcb->katimer);
			tcpsetstate(s, Time_wait);
			tcb->timer.start = MSL2*(1000 / MSPTICK);
			tcpgo(tpriv, &tcb->timer);
		}
		if(!(seg.flags & RST)) {
			tcb->flags |= FORCE;
			goto output;
		}
		qunlock(s);
		poperror();
		return;
	}

	/* Cannot accept so answer with a rst */
	if(length && tcb->state == Closed) {
		sndrst(tcp, source, dest, length, &seg, version, "sending to Closed");
		goto raise;
	}

	/* The segment is beyond the current receive pointer so
	 * queue the data in the resequence queue
	 */
	if(seg.seq != tcb->rcv.nxt)
	if(length != 0 || (seg.flags & (SYN|FIN))) {
		update(s, &seg);
		if(addreseq(f, tcb, tpriv, &seg, bp, length) < 0)
			print("reseq %I.%d -> %I.%d\n", s->raddr, s->rport, s->laddr, s->lport);
		tcb->flags |= FORCE;		/* force duplicate ack; RFC 5681 §3.2 */
		goto output;
	}

	if(tcb->nreseq > 0)
		tcb->flags |= FORCE;		/* filled hole in sequence space; RFC 5681 §3.2 */

	/*
	 *  keep looping till we've processed this packet plus any
	 *  adjacent packets in the resequence queue
	 */
	for(;;) {
		if(seg.flags & RST) {
			if(tcb->state == Established) {
				tpriv->stats[EstabResets]++;
				if(tcb->rcv.nxt != seg.seq)
					print("out of order RST rcvd: %I.%d -> %I.%d, rcv.nxt %lux seq %lux\n", s->raddr, s->rport, s->laddr, s->lport, tcb->rcv.nxt, seg.seq);
			}
			localclose(s, Econrefused);
			goto raise;
		}

		if((seg.flags&ACK) == 0)
			goto raise;

		switch(tcb->state) {
		case Syn_received:
			if(!seq_within(seg.ack, tcb->snd.una+1, tcb->snd.nxt)){
				sndrst(tcp, source, dest, length, &seg, version,
					"bad seq in Syn_received");
				goto raise;
			}
			update(s, &seg);
			tcpsetstate(s, Established);
		case Established:
		case Close_wait:
			update(s, &seg);
			break;
		case Finwait1:
			update(s, &seg);
			if(qlen(s->wq)+tcb->flgcnt == 0){
				tcphalt(tpriv, &tcb->rtt_timer);
				tcphalt(tpriv, &tcb->acktimer);
				tcpsetkacounter(tcb);
				tcb->time = NOW;
				tcpsetstate(s, Finwait2);
				tcb->katimer.start = MSL2 * (1000 / MSPTICK);
				tcpgo(tpriv, &tcb->katimer);
			}
			break;
		case Finwait2:
			update(s, &seg);
			break;
		case Closing:
			update(s, &seg);
			if(qlen(s->wq)+tcb->flgcnt == 0) {
				tcphalt(tpriv, &tcb->rtt_timer);
				tcphalt(tpriv, &tcb->acktimer);
				tcphalt(tpriv, &tcb->katimer);
				tcpsetstate(s, Time_wait);
				tcb->timer.start = MSL2*(1000 / MSPTICK);
				tcpgo(tpriv, &tcb->timer);
			}
			break;
		case Last_ack:
			update(s, &seg);
			if(qlen(s->wq)+tcb->flgcnt == 0) {
				localclose(s, nil);
				goto raise;
			}
		case Time_wait:
			tcb->flags |= FORCE;
			if(tcb->timer.state != TcptimerON)
				tcpgo(tpriv, &tcb->timer);
		}

		if((seg.flags&URG) && seg.urg) {
			if(seq_gt(seg.urg + seg.seq, tcb->rcv.urg)) {
				tcb->rcv.urg = seg.urg + seg.seq;
				pullblock(&bp, seg.urg);
			}
		}
		else
		if(seq_gt(tcb->rcv.nxt, tcb->rcv.urg))
			tcb->rcv.urg = tcb->rcv.nxt;

		if(length == 0) {
			if(bp != nil)
				freeblist(bp);
		}
		else {
			switch(tcb->state){
			default:
				/* Ignore segment text */
				if(bp != nil)
					freeblist(bp);
				break;

			case Syn_received:
			case Established:
			case Finwait1:
				/* If we still have some data place on
				 * receive queue
				 */
				if(bp) {
					qpassnolim(s->rq, packblock(bp));
					bp = nil;
				}
				tcb->rcv.nxt += length;

				/*
				 *  turn on the acktimer if there's something
				 *  to ack
				 */
				if(tcb->acktimer.state != TcptimerON)
					tcpgo(tpriv, &tcb->acktimer);

				break;
			case Finwait2:
				/* no process to read the data, send a reset */
				if(bp != nil)
					freeblist(bp);
				sndrst(tcp, source, dest, length, &seg, version,
					"send to Finwait2");
				qunlock(s);
				poperror();
				return;
			}
		}

		if(seg.flags & FIN) {
			tcb->flags |= FORCE;

			switch(tcb->state) {
			case Syn_received:
			case Established:
				tcb->rcv.nxt++;
				tcpsetstate(s, Close_wait);
				break;
			case Finwait1:
				tcb->rcv.nxt++;
				if(qlen(s->wq)+tcb->flgcnt == 0) {
					tcphalt(tpriv, &tcb->rtt_timer);
					tcphalt(tpriv, &tcb->acktimer);
					tcphalt(tpriv, &tcb->katimer);
					tcpsetstate(s, Time_wait);
					tcb->timer.start = MSL2*(1000/MSPTICK);
					tcpgo(tpriv, &tcb->timer);
				}
				else
					tcpsetstate(s, Closing);
				break;
			case Finwait2:
				tcb->rcv.nxt++;
				tcphalt(tpriv, &tcb->rtt_timer);
				tcphalt(tpriv, &tcb->acktimer);
				tcphalt(tpriv, &tcb->katimer);
				tcpsetstate(s, Time_wait);
				tcb->timer.start = MSL2 * (1000/MSPTICK);
				tcpgo(tpriv, &tcb->timer);
				break;
			case Close_wait:
			case Closing:
			case Last_ack:
				break;
			case Time_wait:
				tcpgo(tpriv, &tcb->timer);
				break;
			}
		}

		/*
		 *  get next adjacent segment from the resequence queue.
		 *  dump/trim any overlapping segments
		 */
		for(;;) {
			if(tcb->reseq == nil)
				goto output;

			if(seq_ge(tcb->rcv.nxt, tcb->reseq->seg.seq) == 0)
				goto output;

			getreseq(tcb, &seg, &bp, &length);

			tcprcvwin(s);
			if(tcptrim(tcb, &seg, &bp, &length) == 0){
				tcb->flags |= FORCE;
				break;
			}
		}
	}
output:
	tcpoutput(s);
	qunlock(s);
	poperror();
	return;
raise:
	qunlock(s);
	poperror();
	freeblist(bp);
	tcpkick(s);
}

/*
 *  always enters and exits with the s locked.  We drop
 *  the lock to ipoput the packet so some care has to be
 *  taken by callers.
 */
static void
tcpoutput(Conv *s)
{
	Tcp seg;
	uint msgs;
	Tcpctl *tcb;
	Block *hbp, *bp;
	int sndcnt;
	ulong ssize, dsize, sent;
	Fs *f;
	Tcppriv *tpriv;
	uchar version;

	f = s->p->f;
	tpriv = s->p->priv;
	version = s->ipversion;

	tcb = (Tcpctl*)s->ptcl;

	/* force ack every 2*mss */
	if((tcb->flags & FORCE) == 0)
	if(tcb->rcv.nxt - tcb->rcv.ackptr >= 2*tcb->mss){
		tpriv->stats[Delayack]++;
		tcb->flags |= FORCE;
	}

	/* force ack if window opening */
	if(0)
	if((tcb->flags & FORCE) == 0){
		tcprcvwin(s);
		if((int)(tcb->rcv.wptr - tcb->rcv.wsnt) >= 2*tcb->mss){
			tpriv->stats[Wopenack]++;
			tcb->flags |= FORCE;
		}
	}

	for(msgs = 0; msgs < 100; msgs++) {
		switch(tcb->state) {
		case Listen:
		case Closed:
		case Finwait2:
			return;
		}

		/* Don't send anything else until our SYN has been acked */
		if(tcb->snd.ptr != tcb->iss && (tcb->flags & SYNACK) == 0)
			break;

		/* force an ack when a window has opened up */
		tcprcvwin(s);
		if(tcb->rcv.blocked && tcb->rcv.wnd > 0){
			tcb->rcv.blocked = 0;
			tcb->flags |= FORCE;
		}

		sndcnt = qlen(s->wq)+tcb->flgcnt;
		sent = tcb->snd.ptr - tcb->snd.una;
		ssize = sndcnt;
		if(tcb->snd.wnd == 0){
			/* zero window probe */
			if(sent > 0)
			if(!(tcb->flags & FORCE))
				break;	/* already probing, rto re-probes */
			if(ssize < sent)
				ssize = 0;
			else{
				ssize -= sent;
				if(ssize > 0)
					ssize = 1;
			}
		} else {
			/* calculate usable segment size */
			if(ssize > tcb->cwind)
				ssize = tcb->cwind;
			if(ssize > tcb->snd.wnd)
				ssize = tcb->snd.wnd;

			if(ssize < sent)
				ssize = 0;
			else {
				ssize -= sent;
				if(ssize > tcb->mss)
					ssize = tcb->mss;
			}
		}

		dsize = ssize;
		seg.urg = 0;

		if(!(tcb->flags & FORCE)){
			if(ssize == 0)
				break;
			if(ssize < tcb->mss)
			if(tcb->snd.nxt == tcb->snd.ptr)
			if(sent > TCPREXMTTHRESH*tcb->mss)
				break;
		}

		tcb->flags &= ~FORCE;

		/* By default we will generate an ack */
		tcphalt(tpriv, &tcb->acktimer);
		seg.source = s->lport;
		seg.dest = s->rport;
		seg.flags = ACK;
		seg.mss = 0;
		seg.ws = 0;
		seg.update = 0;
		switch(tcb->state){
		case Syn_sent:
			seg.flags = 0;
			if(tcb->snd.ptr == tcb->iss){
				seg.flags |= SYN;
				dsize--;
				seg.mss = tcb->mss;
				seg.ws = tcb->scale;
			}
			break;
		case Syn_received:
			/*
			 *  don't send any data with a SYN/ACK packet
			 *  because Linux rejects the packet in its
			 *  attempt to solve the SYN attack problem
			 */
			if(tcb->snd.ptr == tcb->iss){
				seg.flags |= SYN;
				dsize = 0;
				ssize = 1;
				seg.mss = tcb->mss;
				seg.ws = tcb->scale;
			}
			break;
		}
		seg.seq = tcb->snd.ptr;
		seg.ack = tcb->rcv.nxt;
		seg.wnd = tcb->rcv.wnd;

		/* Pull out data to send */
		bp = nil;
		if(dsize != 0) {
			bp = qcopy(s->wq, dsize, sent);
			if(BLEN(bp) != dsize) {
				seg.flags |= FIN;
				dsize--;
			}
		}

		if(sent+dsize == sndcnt && dsize)
			seg.flags |= PSH;

		tcb->snd.ptr += ssize;

		/* Pull up the send pointer so we can accept acks
		 * for this window
		 */
		if(seq_gt(tcb->snd.ptr,tcb->snd.nxt))
			tcb->snd.nxt = tcb->snd.ptr;

		/* Build header, link data and compute cksum */
		switch(version){
		case V4:
			tcb->protohdr.tcp4hdr.vihl = IP_VER4;
			hbp = htontcp4(&seg, bp, &tcb->protohdr.tcp4hdr, tcb);
			if(hbp == nil) {
				freeblist(bp);
				return;
			}
			break;
		case V6:
			tcb->protohdr.tcp6hdr.vcf[0] = IP_VER6;
			hbp = htontcp6(&seg, bp, &tcb->protohdr.tcp6hdr, tcb);
			if(hbp == nil) {
				freeblist(bp);
				return;
			}
			break;
		default:
			hbp = nil;	/* to suppress a warning */
			panic("tcpoutput: version %d", version);
		}

		/* Start the transmission timers if there is new data and we
		 * expect acknowledges
		 */
		if(ssize != 0){
			if(tcb->timer.state != TcptimerON){
				tcb->time = NOW;
				tcb->timeuna = tcb->snd.una;
				tcpgo(tpriv, &tcb->timer);
			}

			/*  If round trip timer isn't running, start it.
			 *  measure the longest packet only in case the
			 *  transmission time dominates RTT
			 */
			if(tcb->snd.retransmit == 0)
			if(tcb->rtt_timer.state != TcptimerON)
			if(ssize == tcb->mss) {
				tcpgo(tpriv, &tcb->rtt_timer);
				tcb->rttseq = tcb->snd.ptr;
			}
		}

		tpriv->stats[OutSegs]++;
		if(tcb->snd.retransmit)
			tpriv->stats[RetransSegsSent]++;
		tcb->rcv.ackptr = seg.ack;
		tcb->rcv.wsnt = tcb->rcv.wptr;

		/* put off the next keep alive */
		tcpgo(tpriv, &tcb->katimer);

		switch(version){
		case V4:
			if(ipoput4(f, hbp, 0, s->ttl, s->tos, s) < 0){
				/* a negative return means no route */
				localclose(s, "no route");
			}
			break;
		case V6:
			if(ipoput6(f, hbp, 0, s->ttl, s->tos, s) < 0){
				/* a negative return means no route */
				localclose(s, "no route");
			}
			break;
		default:
			panic("tcpoutput2: version %d", version);
		}
		if((msgs%4) == 3){
			qunlock(s);
			qlock(s);
		}
	}
}

/*
 *  the BSD convention (hack?) for keep alives.  resend last uchar acked.
 */
static void
tcpsendka(Conv *s)
{
	Tcp seg;
	Tcpctl *tcb;
	Block *hbp,*dbp;

	tcb = (Tcpctl*)s->ptcl;

	dbp = nil;
	memset(&seg, 0, sizeof seg);
	seg.urg = 0;
	seg.source = s->lport;
	seg.dest = s->rport;
	seg.flags = ACK|PSH;
	seg.mss = 0;
	seg.ws = 0;
	if(tcpporthogdefense)
		seg.seq = tcb->snd.una-(1<<30)-nrand(1<<20);
	else
		seg.seq = tcb->snd.una-1;
	seg.ack = tcb->rcv.nxt;
	tcb->rcv.ackptr = seg.ack;
	tcprcvwin(s);
	seg.wnd = tcb->rcv.wnd;
	if(tcb->state == Finwait2){
		seg.flags |= FIN;
	} else {
		dbp = allocb(1);
		dbp->wp++;
	}

	if(isv4(s->raddr)) {
		/* Build header, link data and compute cksum */
		tcb->protohdr.tcp4hdr.vihl = IP_VER4;
		hbp = htontcp4(&seg, dbp, &tcb->protohdr.tcp4hdr, tcb);
		if(hbp == nil) {
			freeblist(dbp);
			return;
		}
		ipoput4(s->p->f, hbp, 0, s->ttl, s->tos, s);
	}
	else {
		/* Build header, link data and compute cksum */
		tcb->protohdr.tcp6hdr.vcf[0] = IP_VER6;
		hbp = htontcp6(&seg, dbp, &tcb->protohdr.tcp6hdr, tcb);
		if(hbp == nil) {
			freeblist(dbp);
			return;
		}
		ipoput6(s->p->f, hbp, 0, s->ttl, s->tos, s);
	}
}

/*
 *  set connection to time out after 12 minutes
 */
static void
tcpsetkacounter(Tcpctl *tcb)
{
	tcb->kacounter = (12 * 60 * 1000) / (tcb->katimer.start*MSPTICK);
	if(tcb->kacounter < 3)
		tcb->kacounter = 3;
}

/*
 *  if we've timed out, close the connection
 *  otherwise, send a keepalive and restart the timer
 */
static void
tcpkeepalive(void *v)
{
	Tcpctl *tcb;
	Conv *s;

	s = v;
	tcb = (Tcpctl*)s->ptcl;
	if(waserror()){
		qunlock(s);
		nexterror();
	}
	qlock(s);
	if(tcb->state != Closed){
		if(--(tcb->kacounter) <= 0) {
			localclose(s, Etimedout);
		} else {
			tcpsendka(s);
			tcpgo(s->p->priv, &tcb->katimer);
		}
	}
	qunlock(s);
	poperror();
}

/*
 *  start keepalive timer
 */
static char*
tcpstartka(Conv *s, char **f, int n)
{
	Tcpctl *tcb;
	int x;

	tcb = (Tcpctl*)s->ptcl;
	if(tcb->state != Established)
		return "connection must be in Establised state";
	if(n > 1){
		x = atoi(f[1]);
		if(x >= MSPTICK)
			tcb->katimer.start = x/MSPTICK;
	}
	tcpsetkacounter(tcb);
	tcpgo(s->p->priv, &tcb->katimer);

	return nil;
}

/*
 *  turn checksums on/off
 */
static char*
tcpsetchecksum(Conv *s, char **f, int)
{
	Tcpctl *tcb;

	tcb = (Tcpctl*)s->ptcl;
	tcb->nochecksum = !atoi(f[1]);

	return nil;
}

/*
 *  retransmit (at most) one segment at snd.una.
 *  preserve cwind & snd.ptr
 */
static void
tcprxmit(Conv *s)
{
	Tcpctl *tcb;
	Tcppriv *tpriv;
	ulong tcwind, tptr;

	tcb = (Tcpctl*)s->ptcl;
	tcb->flags |= RETRAN|FORCE;

	tptr = tcb->snd.ptr;
	tcwind = tcb->cwind;
	tcb->snd.ptr = tcb->snd.una;
	tcb->cwind = tcb->mss;
	tcb->snd.retransmit = 1;
	tcpoutput(s);
	tcb->snd.retransmit = 0;
	tcb->cwind = tcwind;
	tcb->snd.ptr = tptr;

	tpriv = s->p->priv;
	tpriv->stats[RetransSegs]++;
}

/*
 *  todo: RFC 4138 F-RTO
 */
static void
tcptimeout(void *arg)
{
	Conv *s;
	Tcpctl *tcb;
	int maxback;
	Tcppriv *tpriv;

	s = (Conv*)arg;
	tpriv = s->p->priv;
	tcb = (Tcpctl*)s->ptcl;

	if(waserror()){
		qunlock(s);
		nexterror();
	}
	qlock(s);
	switch(tcb->state){
	default:
		tcb->backoff++;
		if(tcb->state == Syn_sent)
			maxback = MAXBACKMS/2;
		else
			maxback = MAXBACKMS;
		tcb->backedoff += tcb->timer.start * MSPTICK;
		if(tcb->backedoff >= maxback) {
			localclose(s, Etimedout);
			break;
		}
		netlog(s->p->f, Logtcprxmt, "rxm %d/%d %ldms %lud rto %d %lud %s\n",
			tcb->srtt, tcb->mdev, NOW-tcb->time,
			tcb->snd.una-tcb->timeuna, tcb->snd.rto, tcb->snd.ptr,
			tcpstates[s->state]);
		tcpsettimer(tcb);
		if(tcb->snd.rto == 0)
			tcpcongestion(tcb);
		tcprxmit(s);
		tcb->snd.ptr = tcb->snd.una;
		tcb->cwind = tcb->mss;
		tcb->snd.rto = 1;
		tpriv->stats[RetransTimeouts]++;

		if(tcb->snd.recovery){
			tcb->snd.dupacks = 0;			/* reno rto */
			tcb->snd.recovery = 0;
			tpriv->stats[RecoveryRTO]++;
			tcb->snd.rxt = tcb->snd.nxt;
			netlog(s->p->f, Logtcpwin,
				"rto recovery rxt @%lud\n", tcb->snd.nxt);
		}

		tcb->abcbytes = 0;
		break;
	case Time_wait:
		localclose(s, nil);
		break;
	case Closed:
		break;
	}
	qunlock(s);
	poperror();
}

static int
inwindow(Tcpctl *tcb, int seq)
{
	return seq_within(seq, tcb->rcv.nxt, tcb->rcv.nxt+tcb->rcv.wnd-1);
}

/*
 *  set up state for a received SYN (or SYN ACK) packet
 */
static void
procsyn(Conv *s, Tcp *seg)
{
	Tcpctl *tcb;
	Tcppriv *tpriv;

	tcb = (Tcpctl*)s->ptcl;
	tcb->flags |= FORCE;

	tcb->rcv.nxt = seg->seq + 1;
	tcb->rcv.wptr = tcb->rcv.nxt;
	tcb->rcv.wsnt = 0;
	tcb->rcv.urg = tcb->rcv.nxt;
	tcb->irs = seg->seq;

	/* our sending max segment size cannot be bigger than what he asked for */
	if(seg->mss != 0 && seg->mss < tcb->mss) {
		tcb->mss = seg->mss;
		tpriv = s->p->priv;
		tpriv->stats[Mss] = tcb->mss;
	}

	tcb->snd.wnd = seg->wnd;
	initialwindow(tcb);
}

static int
dumpreseq(Tcpctl *tcb)
{
	Reseq *r, *next;

	for(r = tcb->reseq; r != nil; r = next){
		next = r->next;
		freeblist(r->bp);
		free(r);
	}
	tcb->reseq = nil;
	tcb->nreseq = 0;
	tcb->reseqlen = 0;
	return -1;
}

static void
logreseq(Fs *f, Reseq *r, ulong n)
{
	char *s;

	for(; r != nil; r = r->next){
		s = nil;
		if(r->next == nil && r->seg.seq != n)
			s = "hole/end";
		else if(r->next == nil)
			s = "end";
		else if(r->seg.seq != n)
			s = "hole";
		if(s != nil)
			netlog(f, Logtcp, "%s %lud-%lud (%ld) %#ux\n", s,
				n, r->seg.seq, r->seg.seq-n, r->seg.flags);
		n = r->seg.seq + r->seg.len;
	}
}

static int
addreseq(Fs *f, Tcpctl *tcb, Tcppriv *tpriv, Tcp *seg, Block *bp, ushort length)
{
	Reseq *rp, **rr;
	int qmax;

	rp = malloc(sizeof(Reseq));
	if(rp == nil){
		freeblist(bp);	/* bp always consumed by addreseq */
		return 0;
	}

	rp->seg = *seg;
	rp->bp = bp;
	rp->length = length;

	tcb->reseqlen += length;
	tcb->nreseq++;

	/* Place on reassembly list sorting by starting seq number */
	for(rr = &tcb->reseq;; rr = &(*rr)->next)
		if(*rr == nil || seq_lt(seg->seq, (*rr)->seg.seq)){
			rp->next = *rr;
			*rr = rp;
			tpriv->stats[Resequenced]++;
			if(rp->next != nil)
				tpriv->stats[OutOfOrder]++;
			break;
		}

	qmax = tcb->window;
	if(tcb->reseqlen > qmax){
		netlog(f, Logtcp, "tcp: reseq: queue > window: %d > %d; %d packets\n", tcb->reseqlen, qmax, tcb->nreseq);
		logreseq(f, tcb->reseq, tcb->rcv.nxt);
		tpriv->stats[ReseqBytelim]++;
		return dumpreseq(tcb);
	}
	qmax = tcb->window / tcb->mss;		/* ~190 for qscale==2, 390 for qscale=3 */
	if(tcb->nreseq > qmax){
		netlog(f, Logtcp, "resequence queue > packets: %d %d; %d bytes\n", tcb->nreseq, qmax, tcb->reseqlen);
		logreseq(f, tcb->reseq, tcb->rcv.nxt);
		tpriv->stats[ReseqPktlim]++;
		return dumpreseq(tcb);
	}

	return 0;
}

static void
getreseq(Tcpctl *tcb, Tcp *seg, Block **bp, ushort *length)
{
	Reseq *rp;

	rp = tcb->reseq;
	if(rp == nil)
		return;

	tcb->reseq = rp->next;

	*seg = rp->seg;
	*bp = rp->bp;
	*length = rp->length;

	tcb->nreseq--;
	tcb->reseqlen -= rp->length;

	free(rp);
}

static int
tcptrim(Tcpctl *tcb, Tcp *seg, Block **bp, ushort *length)
{
	ushort len;
	uchar accept;
	int dupcnt, excess;

	accept = 0;
	len = *length;
	if(seg->flags & SYN)
		len++;
	if(seg->flags & FIN)
		len++;

	if(tcb->rcv.wnd == 0) {
		if(len == 0 && seg->seq == tcb->rcv.nxt)
			return 0;
	}
	else {
		/* Some part of the segment should be in the window */
		if(inwindow(tcb,seg->seq))
			accept++;
		else
		if(len != 0) {
			if(inwindow(tcb, seg->seq+len-1) ||
			seq_within(tcb->rcv.nxt, seg->seq,seg->seq+len-1))
				accept++;
		}
	}
	if(!accept) {
		freeblist(*bp);
		return -1;
	}
	dupcnt = tcb->rcv.nxt - seg->seq;
	if(dupcnt > 0){
		tcb->rerecv += dupcnt;
		if(seg->flags & SYN){
			seg->flags &= ~SYN;
			seg->seq++;

			if(seg->urg > 1)
				seg->urg--;
			else
				seg->flags &= ~URG;
			dupcnt--;
		}
		if(dupcnt > 0){
			pullblock(bp, (ushort)dupcnt);
			seg->seq += dupcnt;
			*length -= dupcnt;

			if(seg->urg > dupcnt)
				seg->urg -= dupcnt;
			else {
				seg->flags &= ~URG;
				seg->urg = 0;
			}
		}
	}
	excess = seg->seq + *length - (tcb->rcv.nxt + tcb->rcv.wnd);
	if(excess > 0) {
		tcb->rerecv += excess;
		*length -= excess;
		*bp = trimblock(*bp, 0, *length);
		if(*bp == nil)
			panic("presotto is a boofhead");
		seg->flags &= ~FIN;
	}
	return 0;
}

static void
tcpadvise(Proto *tcp, Block *bp, char *msg)
{
	Tcp4hdr *h4;
	Tcp6hdr *h6;
	Tcpctl *tcb;
	uchar source[IPaddrlen];
	uchar dest[IPaddrlen];
	ushort psource, pdest;
	Conv *s, **p;

	h4 = (Tcp4hdr*)(bp->rp);
	h6 = (Tcp6hdr*)(bp->rp);

	if((h4->vihl&0xF0)==IP_VER4) {
		v4tov6(dest, h4->tcpdst);
		v4tov6(source, h4->tcpsrc);
		psource = nhgets(h4->tcpsport);
		pdest = nhgets(h4->tcpdport);
	} else {
		ipmove(dest, h6->tcpdst);
		ipmove(source, h6->tcpsrc);
		psource = nhgets(h6->tcpsport);
		pdest = nhgets(h6->tcpdport);
	}

	/* Look for a connection */
	qlock(tcp);
	for(p = tcp->conv; (s = *p) != nil; p++) {
		tcb = (Tcpctl*)s->ptcl;
		if(s->rport == pdest)
		if(s->lport == psource)
		if(tcb->state != Closed)
		if(ipcmp(s->raddr, dest) == 0)
		if(ipcmp(s->laddr, source) == 0){
			if(s->ignoreadvice)
				break;
			qlock(s);
			qunlock(tcp);
			switch(tcb->state){
			case Syn_sent:
				localclose(s, msg);
				break;
			}
			qunlock(s);
			freeblist(bp);
			return;
		}
	}
	qunlock(tcp);
	freeblist(bp);
}

static char*
tcpporthogdefensectl(char *val)
{
	if(strcmp(val, "on") == 0)
		tcpporthogdefense = 1;
	else if(strcmp(val, "off") == 0)
		tcpporthogdefense = 0;
	else
		return "unknown value for tcpporthogdefense";
	return nil;
}

/* called with c qlocked */
static char*
tcpctl(Conv* c, char** f, int n)
{
	if(n == 1 && strcmp(f[0], "close") == 0)
		return tcpclose(c), nil;
	if(n == 1 && strcmp(f[0], "hangup") == 0)
		return tcphangup(c);
	if(n >= 1 && strcmp(f[0], "keepalive") == 0)
		return tcpstartka(c, f, n);
	if(n >= 1 && strcmp(f[0], "checksum") == 0)
		return tcpsetchecksum(c, f, n);
	if(n >= 1 && strcmp(f[0], "tcpporthogdefense") == 0)
		return tcpporthogdefensectl(f[1]);
	return "unknown control request";
}

static int
tcpstats(Proto *tcp, char *buf, int len)
{
	Tcppriv *priv;
	char *p, *e;
	int i;

	priv = tcp->priv;
	p = buf;
	e = p+len;
	for(i = 0; i < Nstats; i++)
		p = seprint(p, e, "%s: %llud\n", statnames[i], priv->stats[i]);
	return p - buf;
}

/*
 *  garbage collect any stale conversations:
 *	- SYN received but no SYN-ACK after 5 seconds (could be the SYN attack)
 *	- Finwait2 after 5 minutes
 *
 *  this is called whenever we run out of channels.  Both checks are
 *  of questionable validity so we try to use them only when we're
 *  up against the wall.
 */
static int
tcpgc(Proto *tcp)
{
	Conv *c, **pp, **ep;
	int n;
	Tcpctl *tcb;


	n = 0;
	ep = &tcp->conv[tcp->nc];
	for(pp = tcp->conv; pp < ep; pp++) {
		c = *pp;
		if(c == nil)
			break;
		if(!canqlock(c))
			continue;
		tcb = (Tcpctl*)c->ptcl;
		switch(tcb->state){
		case Syn_received:
			if(NOW - tcb->time > 5000){
				localclose(c, Etimedout);
				n++;
			}
			break;
		case Finwait2:
			if(NOW - tcb->time > 5*60*1000){
				localclose(c, Etimedout);
				n++;
			}
			break;
		}
		qunlock(c);
	}
	return n;
}

static void
tcpsettimer(Tcpctl *tcb)
{
	int x;

	/* round trip dependency */
	x = backoff(tcb->backoff) *
		(tcb->mdev + (tcb->srtt>>LOGAGAIN) + MSPTICK) / MSPTICK;

	/* bounded twixt 0.3 and 64 seconds */
	if(x < 300/MSPTICK)
		x = 300/MSPTICK;
	else if(x > (64000/MSPTICK))
		x = 64000/MSPTICK;
	tcb->timer.start = x;
}

void
tcpinit(Fs *fs)
{
	Proto *tcp;
	Tcppriv *tpriv;

	tcp = smalloc(sizeof(Proto));
	tpriv = tcp->priv = smalloc(sizeof(Tcppriv));
	tcp->name = "tcp";
	tcp->connect = tcpconnect;
	tcp->announce = tcpannounce;
	tcp->ctl = tcpctl;
	tcp->state = tcpstate;
	tcp->create = tcpcreate;
	tcp->close = tcpclose;
	tcp->rcv = tcpiput;
	tcp->advise = tcpadvise;
	tcp->stats = tcpstats;
	tcp->inuse = tcpinuse;
	tcp->gc = tcpgc;
	tcp->ipproto = IP_TCPPROTO;
	tcp->nc = scalednconv();
	tcp->ptclsize = sizeof(Tcpctl);
	tpriv->stats[MaxConn] = tcp->nc;

	Fsproto(fs, tcp);
}

static void
tcpsetscale(Conv *s, Tcpctl *tcb, ushort rcvscale, ushort sndscale)
{
	/*
	 * guess at reasonable queue sizes.  there's no current way 
	 * to know how many nic receive buffers we can safely tie up in the
	 * tcp stack, and we don't adjust our queues to maximize throughput
	 * and minimize bufferbloat.  n.b. the offer (rcvscale) needs to be
	 * respected, but we still control our own buffer commitment by
	 * keeping a seperate qscale.
	 */
	tcb->rcv.scale = rcvscale & 0xff;
	tcb->snd.scale = sndscale & 0xff;
	tcb->qscale = rcvscale & 0xff;
	if(rcvscale > Maxqscale)
		tcb->qscale = Maxqscale;

	if(rcvscale != tcb->rcv.scale)
		netlog(s->p->f, Logtcp, "tcpsetscale: window %lud qlen %d >> window %ud lport %d\n",
			tcb->window, qlen(s->rq), QMAX<<tcb->qscale, s->lport);
	tcb->window = QMAX<<tcb->qscale;
	tcb->ssthresh = tcb->window;

	/*
	 * it's important to set wq large enough to cover the full
	 * bandwidth-delay product.  it's possible to be in loss
	 * recovery with a big window, and we need to keep sending
	 * into the inflated window.  the difference can be huge
	 * for even modest (70ms) ping times.
	 */
	qsetlimit(s->rq, QMAX<<tcb->qscale);
	qsetlimit(s->wq, QMAX<<tcb->qscale);
	tcprcvwin(s);
}