/* File MSNTCP.C
 * Main TCP protocol code
 *
 * Copyright (C) 1991, University of Waterloo.
 *	Copyright (C) 1982, 1995, Trustees of Columbia University in the 
 *	City of New York.  The MS-DOS Kermit software may not be, in whole 
 *	or in part, licensed or sold for profit as a software product itself,
 *	nor may it be included in or distributed with commercial products
 *	or otherwise distributed by commercial concerns to their clients 
 *	or customers without written permission of the Office of Kermit 
 *	Development and Distribution, Columbia University.  This copyright 
 *	notice must not be removed, altered, or obscured.
 *
 * Original version created by Erick Engelke of the University of
 *  Waterloo, Waterloo, Ontario, Canada.
 * Adapted and redesigned for MS-DOS Kermit by Joe R. Doupnik, 
 *  Utah State University, jrd@cc.usu.edu, jrd@usu.Bitnet.
 *
 * Last edit
 * 12 Jan 1995 v3.14
 *
 *  PCTCP - the true worker of Waterloo TCP
 *   	  - contains all opens, closes, major read/write routines and
 *	    basic IP handler for incomming packets
 *	  - NOTE: much of the TCP/UDP/IP layering is done at data structure
 *	    level, not in separate routines or tasks
 *
 */

#include "msntcp.h"
#include "msnlib.h"

extern	int arp_handler(in_Header *);
extern	int rarp_handler(void *);
extern	int icmp_handler(void *);
extern	void icmp_noport(void *);
extern	void server_hello(tcp_Socket *s);
extern	void eth_release(void);
extern	void icmp_init(void);
extern	int eth_init(void);
extern	word tcp_status;
extern	void msleep(int);

static	int tcp_handler(void *);
static	int tcp_rst(in_Header *, tcp_Header FAR *);
static	int tcp_read(void *, byte FAR *, int);
static	int tcp_write(void *, byte FAR *, int);
static	int tcp_close(void *);
static	int tcp_processdata(void *, void FAR *, int);
static	int tcp_send(void *);
static	int tcp_sendsoon(void *);
static	int tcp_retransmitter(void);
static	int tcp_unthread(void *);
static	int udp_handler(void *);
static	int udp_read(void *, byte FAR *, int);
static	int udp_write(void *, byte FAR *, int);
static	int udp_close(udp_Socket *);

longword ipbcast = 0xffffffffL;		/* default IP broadcast address */

static	initialized = 0;
static	imposter = 0;			/* ARP for own IP toggle */
extern	byte kdebug;			/* non-zero if debug mode is active */
extern	word ktcpmss;			/* MSS override */
extern	word MAC_len;
	
#ifdef DEBUG
void (*dbugxmit)() = NULL;
void (*dbugrecv)() = NULL;
#endif
/*
 * Local IP address
 */

longword my_ip_addr = 0L;		/* for external references */
longword sin_mask = 0xfffffe00L;
longword sin_gate = 0L;
byte	hostname[MAX_STRING+1] = {0};	/* our full name, from BootP */
/*
 * IP identification numbers
 */

static int ip_id = 0;			/* packet number */
static int next_tcp_port = 1024;	/* auto incremented */
static int next_udp_port = 1024;
static tcp_Socket *tcp_allsocs = NULL;	/* TCP socket linked list head */
static udp_Socket *udp_allsocs = NULL;	/* UDP socket linked list head */

#define tcp_FlagFIN     0x0001
#define tcp_FlagSYN     0x0002
#define tcp_FlagRST     0x0004
#define tcp_FlagPUSH    0x0008
#define tcp_FlagACK     0x0010
#define tcp_FlagURG     0x0020
#define tcp_FlagDO      0xF000
#define tcp_GetDataOffset(tp) (ntohs((tp)->flags) >> 12)

/* Timer definitions, in PC clock tics */
#define tcp_LONGTIMEOUT 13      /* timeout, sec, for opens */
#define tcp_TIMEOUT 13          /* timeout, sec, during a connection */

#define KARN_NEW 0		/* Karn count, new transmission */
#define KARN_PENDING 1		/* Karn count, pending transmission */
#define KARN_TIMEOUT 2		/* Karn count, transmission timed out */

#define TCP_SBUFSIZE 4096 	/* max bytes to buffer in a tcp socket */
#define TCP_RBUFSIZE 4096
#define UDP_BUFSIZE ETH_MSS	/* max bytes to buffer in a udp socket */
#define UDP_LENGTH (sizeof(udp_Header))

#define CONNECTION_REJECTED 9	/* a returnable status */
#define tcp_NOSEND	0	/* for tcp send_kind */
#define tcp_SENDNOW	1
#define tcp_SENDSOON	2

word	debug_on = 0;
word	mss = ETH_MSS;		/* Maximum Segment Size */

#define in_GetVersion(ip) ((ip)->hdrlen_ver >> 4)
#define in_GetHdrlen(ip)  ((ip)->hdrlen_ver & 0xf)  /* 32 bit word size */
#define in_GetHdrlenBytes(ip)  (in_GetHdrlen(ip) << 2) /* 8 bit byte size */

/*	TCP Header (and size in bits)
	Source Port (16)
	Destination Port (16)
	Sequence Number (32)	First octet in this segment. If SYN then
				this is the initial seq num and first data
				octet is one larger.
	Acknowledgment Number (32) Next octet expected to be received
 	Data Offset (4)		number of 32 bit words in this header so we
				know when options are present (and how large)
 	Reserved (6)
 	URG (1)			Urgent
 	ACK (1)			Acknowledgment
 	PSH (1)			Push function (expedite)
 	RST (1)			Reset the connection
 	SYN (1)			Synchronize sequence numbers
 	FIN (1)			Finish, no more data from sender
 	Window (16)		Octets willing to accept after Ack value
 	Checksum (16)		One's complement of header and Pseudo header
 	Urgent Pointer (16)	Points to octet following urgent data <sic>
 	Options & Padding (32)	Only Option is max segment size
 	Data (the rest)
*/
/*
 * tcp_init - Initialize the tcp implementation
 *	    - may be called more than once without hurting
 */
int
tcp_init(void)
{
	if (initialized != 0) return (1);	/* success, inited already */
				/* initialize ethernet interface */
	icmp_init();			/* clear ICMP material */
	last_nameserver = 0;		/* reset the name server table */
	arp_last_gateway = 0;		/* clear old gateway info */
	tcp_allsocs = NULL;		/* zero socket pointers */
	udp_allsocs = NULL;
	ip_id = 0;			/* packet number, convenient */
	imposter = 0;			/* ARP for own IP flag */
	initialized = eth_init();	/* init the hardware, can fail */
					/* next port numbers to choose */
	next_udp_port = next_tcp_port = 1024 + (int)(set_timeout(0) & 0x1ff);
	eth_free (NULL);		/* clear all pkt rcv bufs */
	return (initialized);		/* success (1) or failure (0) */
}

/*
 * Shut down the data link and all services
 */
void
tcp_shutdown(void)
{
	while (tcp_allsocs != NULL)
		tcp_abort(tcp_allsocs);
	eth_release();
	initialized = 0;
}

int 
udp_open(udp_Socket *s, word lport, longword ina, word port)
{
	if (s == NULL) return (0);

	if (s->rdata) free(s->rdata);	/* free any allocated buffer */
	memset(s, 0, sizeof(udp_Socket));
	if (lport == 0) lport = ++next_udp_port;  /* get a nonzero port val */
	s->myport = lport;
	/* check for broadcast */
	if (ina == 0xffffffffL || ina == 0L || ina == ipbcast)
		memset(s->hisethaddr, 0xff, sizeof(eth_address));
	else
		if (arp_resolve(ina, &s->hisethaddr[0]) == 0)
			return (0);			/* fail */
	if ((s->rdata = malloc(UDP_BUFSIZE + sizeof(udp_Header))) == NULL)
		return (0);				/* fail */
	s->frag_identification -= 0x0fff;	 /* move way back */
	s->hisaddr = ina;
	s->hisport = port;
	s->ip_type = UDP_PROTO;
	s->next = udp_allsocs;
	udp_allsocs = s;
	s->sock_mode = UDP_MODE_CHKSUM;		/* turn on checksums */
	s->sisopen = SOCKET_OPEN;
	return (1);					/* success */
}

/*
 * Actively open a TCP connection to a particular destination.
 * return 0 on error
 */
int 
tcp_open(tcp_Socket *s, word lport, longword ina, word port) 
{
	if (s == NULL) return (0);
	if (s->sisopen != SOCKET_CLOSED)
		return (0);

	if (imposter == 0 && arp_resolve(my_ip_addr, NULL))/* imposter check */
		{
		outs("\r\n WARNING: our IP address is used by");
		outs(" another station! Quitting.");
		return(0);			/* fail back to user */
		}
	imposter++;				/* say have done the check */
	if (s->rdata) free(s->rdata);		/* free preallocated buf */
	if (s->sdata) free(s->sdata);		/* free preallocated buf */

	memset(s, 0, sizeof(tcp_Socket));	/* zero everything */
	s->mss = mss;				/* hardware limit */
	if ((ina ^ my_ip_addr) & sin_mask)	/* if not on this network */
		s->mss = (mss > 536)? 536: mss;	/* non-fragmentable max-24 */
	if (ktcpmss < s->mss)			/* user override */
		s->mss = ktcpmss;		/*  is an upper limit */

	if (lport == 0)
		lport = ++next_tcp_port;  /* get a nonzero port value */
	s->myport = lport;
	if (arp_resolve(ina, &s->hisethaddr[0]) == 0)
		return (0);		/* failed to get host Ethernet addr */
						/* create data bufs */
	if ((s->rdata = malloc(TCP_RBUFSIZE + sizeof(tcp_Header))) == NULL)
		return (0);			/* fail */
	if ((s->sdata = malloc(TCP_SBUFSIZE)) == NULL)
		{
		free(s->rdata);
		s->rdata = NULL;
		return (0);
		}
	s->frag_identification -= 0x0fff; 	/* move way back */
	s->next = tcp_allsocs;
	tcp_allsocs = s;
	s->hisaddr = ina;
	s->hisport = port;
	s->seqnum = ntohl(set_timeout(0)) >> 16;
	s->flags = tcp_FlagSYN;
	s->state = tcp_StateSYNSENT;
	s->rptxmit = TRUE;
	s->ip_type = TCP_PROTO;
	s->sisopen = SOCKET_OPEN;
	s->sock_mode = TCP_MODE_NAGLE;		/* Nagle slow start */
	s->rtt_time = set_timeout(4);		/* four seconds, retry */
	s->rto = 18;				/* assume one second */
	s->cwindow = s->mss;			/* slow start VJ algorithm */
	s->timeout = set_timeout(tcp_LONGTIMEOUT); /* seconds */
	return (tcp_send(s));			/* fail if send fails */
}

/*
 * Passive open: listen for a connection on a particular port
 */
tcp_listen(tcp_Socket *s, word lport, longword ina, word port, word timeout)
{
	if (s == NULL) return (0);

	if (s->rdata) free(s->rdata);		/* free preallocated buf */
	if (s->sdata) free(s->sdata);		/* free preallocated buf */
	memset(s, 0, sizeof(tcp_Socket));	/* zero everything */
	s->ip_type = TCP_PROTO;
	s->mss = mss;				/* hardware limit */
	s->cwindow = s->mss;			/* slow start VJ algorithm */
	s->rtt_time = set_timeout(4);		/* four seconds */
	s->rto = 18;				/* assume one second */
	s->state = tcp_StateLISTEN;
	if (timeout != 0)
		s->timeout = set_timeout(timeout);	/* seconds */
	s->frag_identification -= 0x0fff;		/* move way back */
	s->myport = lport;
	s->hisport = port;
	s->hisaddr = ina;
	s->seqnum = ntohl(set_timeout(0)) >> 16;
	s->rptxmit = FALSE;
	if ((s->rdata = malloc(TCP_RBUFSIZE + sizeof(tcp_Header))) == NULL)
		return (0);			/* fail */
	if ((s->sdata = malloc(TCP_SBUFSIZE)) == NULL)
		{
		free(s->rdata);
		s->rdata = NULL;
		return (0);
		}
	s->next = tcp_allsocs;			/* active socket list */
	tcp_allsocs = s;			/* remember active socket */
	s->ip_type = TCP_PROTO;			/* we speak TCP here */
	s->sisopen = SOCKET_OPEN;		/* socket is open */
	s->sock_mode = TCP_MODE_NAGLE;		/* Nagle slow start */
	s->send_kind = tcp_NOSEND;		/* say no pending sends */
	return (1);
}


static int
udp_close(udp_Socket *ds)
{
	register udp_Socket *sp;

	sp = udp_allsocs;		/* ptr to first socket */
	if (sp == NULL) return (0);	/* failure */
	ds->sisopen = SOCKET_CLOSED;	/* say socket is closed, do first */

	if (sp == ds)
		udp_allsocs = ds->next; /* if we are first, unlink */
	while (sp != NULL)
		{
		if (sp->next == ds)	/* if current points to us */
			{
			sp->next = ds->next; /* point it to our successor */
   			break;
			}
		sp = sp->next;		/* look at next socket */
		}
	if (ds->rdata != NULL)
		{
		free(ds->rdata);	/* free received data buffer */
		ds->rdata = NULL;	/* invalidate data buffer address */
		}
        ds->rdatalen = ds->rdatahwater = 0;	/* flush pending reads */
	return (1);			/* success */
}

/*
 * Send a FIN on a particular port -- only works if it is open
 * Must still allow receives
 * Returns 0 for failure
 */
static int 
tcp_close(tcp_Socket *s)
{

	if (s == NULL || s->ip_type != TCP_PROTO)
		return (0);			/* failure */

	s->sdatalen = s->sdatahwater = 0; /* cannot send anything now */
	if (s->state & (tcp_StateESTAB | tcp_StateSYNREC | tcp_StateCLOSWT))
		{
		if (kdebug != 0) outs("\r\nFIN sent, closing");
		if (s->state == tcp_StateCLOSWT)
			s->state = tcp_StateLASTACK;
		else
			s->state = tcp_StateFINWT1;
		s->flags = tcp_FlagACK | tcp_FlagFIN;
		tcp_send(s);
		return (1);			/* success */
		}
	s->rptxmit = FALSE;
	s->state = tcp_StateCLOSED;
	tcp_unthread(s);			/* remove socket from list */
	return (0);
}

/*
 * Abort a tcp connection
 * Returns 0 if failure
 */
int 
tcp_abort(tcp_Socket *s)
{
	if (s == NULL) return (0);		/* failure */

	if (kdebug != 0) outs("\r\nTCP_ABORT");
	if ((s->state != tcp_StateLISTEN) && (s->state != tcp_StateCLOSED))
		{
		s->flags = tcp_FlagRST | tcp_FlagACK;
		tcp_send(s);
		}
	s->rptxmit = FALSE;
	s->sdatalen = 0;
	s->state = tcp_StateCLOSED;
	tcp_unthread(s);
	return (1);				/* success */
}

/*
 * tcp_sendsoon - schedule a transmission pretty soon
 */
int
tcp_sendsoon(tcp_Socket *s)
{
	if (s == NULL || s->ip_type != TCP_PROTO) return (0);

	s->send_kind = tcp_NOSEND;		/* no triggered send yet */
	if (s->karn_count == KARN_PENDING)	/* already have wait time */
		{
		if (chk_timeout(s->rtt_time) == TIMED_OUT)
			tcp_send(s);		/* expired, send now */
		return(1);			/* else wait some more */
		}

	s->karn_count = KARN_PENDING;		/* need to tcp_send 1st time*/
	s->rtt_time = set_ttimeout(s->rto >> 1); /* set waiting time */
	return(1);
}

/*
 * Retransmitter - called periodically to perform tcp retransmissions
 * Returns 0 if failure
 */
static int
tcp_retransmitter(void)
{
	register tcp_Socket *s;

	for (s = tcp_allsocs; s != NULL; s = s->next)
		{ 		/* condx to send or resend a packet */
		if (s->send_kind & tcp_SENDNOW)		/* send now */
			{
			tcp_send(s);
			continue;
			}

		if (s->send_kind & tcp_SENDSOON)	/* send after wait */
			if (s->karn_count != KARN_PENDING)/* if not waiting */
				{
				tcp_sendsoon(s);	/* establish wait */
				continue;
				}

		s->send_kind = tcp_NOSEND;	/* implied, but to be safe */

		if (s->karn_count == KARN_PENDING)	/* not yet sent */
			{
       			if (chk_timeout(s->rtt_time) == TIMED_OUT)
				tcp_send(s);		/* wait expired */
			continue;
			}

		if (s->rptxmit == TRUE || s->sdatalen)	/* ACK needed */
			if (chk_timeout(s->rtt_time) == TIMED_OUT)
				{
				if (s->karn_count == KARN_NEW) /* sent once */
                			s->karn_count = KARN_TIMEOUT;
						/* say really did timeout */
				/* do Van Jacobson congestion reduction */
				/* make all bytes old to resend all */
				s->ssthresh = s->cwindow / 2;
				s->cwindow = s->mss;	/* one full pkt */
				s->sdatahwater = 0;  /* start from beginning*/
				s->loss_count = 0;	/* pkt loss counter */
				s->vj_last = 0;		/* enable timing */
	            		tcp_send(s);
				continue;
	    			}

		if (s->timeout && (chk_timeout(s->timeout) == TIMED_OUT))
			switch (s->state)
			{
			case tcp_StateSYNREC:
				s->vj_last = 0;	/* enable time/seqnum snapshot */
				tcp_close(s);		/* graceful close */
				break;
			case tcp_StateTIMEWT:
				s->state = tcp_StateCLOSED;
				tcp_unthread(s);	/* purge it */
				break;
			default:
				s->vj_last = 0;	/* enable time/seqnum snapshot */
				tcp_abort(s);
				break;
			}		/* end of switch */
		}			/* foot of for loop */
	return (1);			/* success */
}

/*
 * Unthread a socket from the socket list, if it's there. Return 0 on failure
 */
static int
tcp_unthread(tcp_Socket *ds)
{
	register tcp_Socket *sp;

	if (ds == NULL) return (0);		/* failure */

	ds->sisopen = SOCKET_CLOSED;		/* say socket is closed */
	if ((sp = tcp_allsocs) == NULL)		/* no socket in the queue */ 
		return (0);

	if (sp == ds)
		tcp_allsocs = ds->next; /* if we are first, unlink */
	while (sp != NULL)
		{
		if (sp->next == ds)		/* if current points to us */
			{
			sp->next = ds->next; /* point it to our successor */
   			break;
			}
		sp = sp->next;			/* look at next socket */
		}
	return (1);				/* success */
}

/*
 * tcp_tick - called periodically by user application
 *	    - returns 0 when our socket closes
 *	    - called with socket parameter or NULL
 */
tcp_tick(sock_type *s)
{
	register in_Header *ip;
	int packettype;
					/* read a packet */
	while ((ip = (in_Header *)eth_arrived(&packettype)) != NULL)
	{
	switch (packettype)	  	/* network big endian form */
	{
	case TYPE_IP:					/* do IP */
		if ((in_GetVersion(ip) != 4) ||
		(checksum(ip, in_GetHdrlenBytes(ip)) != 0xffff))
			{
			if (debug_on)
			    outs("IP Received BAD Checksum \n\r");
			break;
			}
		if ((my_ip_addr == 0L) || 
			(htonl(ip->destination)	== my_ip_addr))
			{
			switch (ip->proto)
				{
				case TCP_PROTO:
					tcp_handler(ip);
					break;
				case UDP_PROTO:
					udp_handler(ip);
					break;
				case ICMP_PROTO:
					icmp_handler(ip);
					break;
				default:
					break;
				}	/* end of switch (ip->proto) */
			}
		break;

	case TYPE_ARP:					/* do ARP */
		arp_handler(ip);
		break;
	case TYPE_RARP:					/* do RARP */
		rarp_handler(ip);
		break;
	default:				/* unknown type */
		break;
	}				/* end of switch */
	eth_free(ip);			/* free the processed packet */
    }					/* end of while */

	tcp_retransmitter();		/* check on pending sends */
	return ((s != NULL)? s->tcp.sisopen: 0); /* 0 means closed socket */
}

void
tcp_set_debug_state(word x)
{
	debug_on = x;
}

static int
udp_write(udp_Socket *s, byte FAR *datap, int len)
{
	tcp_PseudoHeader ph;
	struct pkt
		{
		in_Header  in;
		udp_Header udp;
		int	   data;
		} register *pkt;

	if (s == NULL || datap == NULL)
		return (0);			/* failure */

	pkt = (struct pkt *)eth_formatpacket(&s->hisethaddr[0], TYPE_IP);
	pkt->in.length = htons(sizeof(in_Header) + UDP_LENGTH + len);
						/* UDP header */
	pkt->udp.srcPort = htons(s->myport);
	pkt->udp.dstPort = htons(s->hisport);
	pkt->udp.length = htons(UDP_LENGTH + len);
	bcopyff(datap, &pkt->data, len);
						/* Internet header */
	pkt->in.hdrlen_ver = 0x45;		/* version 4, hdrlen 5 */
	pkt->in.tos = 0;
	pkt->in.identification = htons(++ip_id);	/* was post inc */
	pkt->in.frag = 0;
#ifdef KERMIT
	pkt->in.ttl = 60;
#else
	pkt->in.ttl = 254;
#endif
	pkt->in.proto = UDP_PROTO;			/* UDP */
	pkt->in.checksum = 0;
	pkt->in.source = htonl(my_ip_addr);
	pkt->in.destination = htonl(s->hisaddr);
	pkt->in.checksum = ~checksum(&pkt->in, sizeof(in_Header));
					/* compute udp checksum if desired */
	if (s->sock_mode & UDP_MODE_CHKSUM == 0)
		pkt->udp.checksum = 0;
	else
		{
		ph.src = pkt->in.source;		/* big endian now */
		ph.dst = pkt->in.destination;
		ph.mbz = 0;
		ph.protocol = UDP_PROTO;		/* UDP */
		ph.length = pkt->udp.length;		/* big endian now */
		ph.checksum = checksum(&pkt->udp, htons(ph.length));
		pkt->udp.checksum = ~checksum(&ph, sizeof(ph));
		}

#ifdef DEBUG
	if (dbugxmit) (*dbugxmit)(s,inp,udpp);
#endif
	if (eth_send(ntohs(pkt->in.length)) != 0)	/* send pkt */
		return (len);
	else	return (0);				/* failed */
}

/*
 * udp_read - read data from buffer, does large buffering.
 * Return 0 on failure.
 */
static int 
udp_read(udp_Socket *s, byte FAR * datap, int maxlen)
{
	register int x;

	if (s == NULL || datap == NULL || s->rdata == NULL || maxlen ==0) 
				return (0);	/* failure */
	if ((x = s->rdatalen) > 0)
		{
		if (x > maxlen) x = maxlen;
		bcopyff(s->rdata, datap, x);
		s->rdatalen -= x;
		if (s->rdatahwater -= x)
			bcopyff(&s->rdata[x], s->rdata, s->rdatahwater);
    		}
	return (x);
}

udp_cancel(in_Header *ip)
{
	int len;
	register udp_Header *up;
	register udp_Socket *s;
						/* match to a udp socket */
	len = in_GetHdrlenBytes(ip);
	up = (udp_Header *)((byte *)ip + len);	/* udp frame pointer */
						/* demux to active sockets */
	for (s = udp_allsocs; s != NULL; s = s->next)
		{
	        if (s->hisport != 0 &&
		     ntohs(up->dstPort) == s->myport &&
		     ntohs(up->srcPort) == s->hisport &&
		     ntohl(ip->source) == s->hisaddr)
		     	break;
        	if (s->hisport != 0 &&		/* ICMP repeat of our pkt */
		     ntohs(up->dstPort) == s->hisport &&
		     ntohs(up->srcPort) == s->myport &&
		     ntohl(ip->source) == my_ip_addr)
			break;		
		}

	if (s == NULL)				/* demux to passive sockets */
		for (s = udp_allsocs; s != NULL; s = s->next)
	    		if (s->hisport == 0 && 
				ntohs(up->dstPort) == s->myport)
	    			break;

	if (s != NULL)
		udp_close(s);
	return (1);				/* success */
}

int
tcp_cancel(in_Header *ip)
{
	int len;
	register tcp_Socket *s;
	register tcp_Header *tp;

	if (ip == NULL) return (0);		/* failure */
	len = in_GetHdrlenBytes(ip);		/* check work */

	tp = (tcp_Header *)((byte *)ip + len);	/* TCP frame pointer */
					    /* demux to active sockets */
	for (s = tcp_allsocs; s != NULL; s = s->next)
		{
        	if (s->hisport != 0 &&		/* them to us */
		     ntohs(tp->dstPort) == s->myport &&
		     ntohs(tp->srcPort) == s->hisport &&
		     ntohl(ip->source) == s->hisaddr)
		     	break;
        	if (s->hisport != 0 &&		/* ICMP repeat of our pkt */
		     ntohs(tp->dstPort) == s->hisport &&
		     ntohs(tp->srcPort) == s->myport &&
		     ntohl(ip->source) == my_ip_addr)
			break;		
		}

	if (s == NULL)			/* demux to passive sockets */
		for (s = tcp_allsocs; s != NULL; s = s->next)
	    		if (s->hisport == 0 &&
				ntohs(tp->dstPort) == s->myport)
	    			break;

	if (s != NULL)
		{
		s->rdatalen = s->rdatahwater = 0;
		s->state = tcp_StateCLOSED;
		tcp_unthread(s);
		}
	return (1);				/* success */
}

static int 
tcp_read(tcp_Socket *s, byte FAR *datap, int maxlen)
{
	register int x;
	int obtained = 0;

	if (s == NULL || datap == NULL || s->rdata == NULL || maxlen <= 0)
			return (0);		/* failure or read nothing */
	if (s->rdatalen > 0)			/* if anything to read */
		{
		/* read from rdata, destuff CR NUL, deliver up to maxlen
		   bytes to output buffer datap, report # obtained in int
		   obtained, remember last read byte for CR NUL state */
		x = destuff(s->rdata, s->rdatalen, datap, maxlen, &obtained,
			&s->last_read);
		if (x > 0)
			{
			s->rdatalen -= x;		/* bytes consumed */
			s->rdatahwater -=x;
	 		 				/* copy down */
			 bcopyff(&s->rdata[x], s->rdata, s->rdatahwater);
			/* send a window update if crossing a boundry */
			if ((s->rdatalen < (TCP_RBUFSIZE / 2) && 
				(s->rdatalen + x) >= TCP_RBUFSIZE / 2))
					tcp_send(s); /* send update */
			}
		}
	return (obtained);			/* return bytes obtained */
}

/*
 * Write data to a connection.
 * Returns number of bytes written, == 0 when connection is not in
 * established state.
 */
static int
tcp_write(tcp_Socket *s, byte FAR *dp, int len)
{
	register int x;

	if (s == NULL || dp == NULL || s->sdata == NULL)
		return (0);				/* failure */
	if (s->state != tcp_StateESTAB) 
		return (0);
	x = TCP_SBUFSIZE - s->sdatalen;		/* space remaining in buf */
	if (x > len) x = len;
	if (x < 0)
		x = 0;				/* catch negative errors */
	if (x > 0)
		{
		bcopyff(dp, &s->sdata[s->sdatalen], x); /* append to buf */
		s->sdatalen += x;		/* un-ACK'd data */
					/* Nagle's algorithm, RFC 896 */
		/* transmit if no previous un-ACK'd data or non-Nagle mode */
		if (((s->sdatalen - s->sdatahwater) <= x) ||
		    ((s->sock_mode & TCP_MODE_NAGLE) != TCP_MODE_NAGLE))
			tcp_send(s);
		}
	return (x);
}

/*
 * Handler for incoming UDP packets. If no socket tell the host via ICMP.
 */
static int
udp_handler(in_Header *ip)
{
	register udp_Header FAR * up;
	register udp_Socket *s;
	tcp_PseudoHeader ph;
	byte	FAR * dp;
	word	len;
	int	ip_hlen, frag;

	ip_hlen = in_GetHdrlenBytes(ip);
	up = (udp_Header FAR *)((byte *)ip + ip_hlen); /* UDP dgram pointer */
	len = ntohs(up->length);
	frag = ntohs(ip->frag) & 0x3fff;	/* frag flags and offset */

/* If not first frag then match IP ident with that stored in the UDP header
   so we can identify active reassemblies, and thence to choose the proper
   session data buffer for frag storage. Rdata will hold the UDP header and
   frag data just after rdatalen (uses rdatahwater-rdatalen bytes).
*/
	if (frag && (frag != 0x2000))		/* not first frag */
		{
		for (s = udp_allsocs; s != NULL; s = s->next)
			if (s->frag_identification == ip->identification)
       				goto dofrag;
		return (0);		/* reject stray fragment piece */
		}


				/* demux to active sockets */
	for (s = udp_allsocs; s != NULL; s = s->next)
		if (s->sisopen == SOCKET_OPEN && s->hisport != 0 && 
		    ntohs(up->dstPort) == s->myport &&
		    ntohs(up->srcPort) == s->hisport
		/*  && ntohl(ip->source) == s->hisaddr */)
				break;

#ifdef DEBUG
	if (dbugrecv) (*dbugrecv)(s,ip,up);
#endif
	if (s == NULL)			/* demux to passive sockets */
		for (s = udp_allsocs; s != NULL; s = s->next)
			if (s->sisopen == SOCKET_OPEN && s->hisaddr == 0 &&
				ntohs(up->dstPort) == s->myport)
				{
				if (arp_resolve(htonl(ip->source),
					&s->hisethaddr[0]))
					{
		   			s->hisaddr = ntohl(ip->source);
		    			s->hisport = ntohs(up->srcPort);
					}
				break;
	    			}

	if (s == NULL)		/* demux to broadcast sockets */
		for (s = udp_allsocs; s != NULL; s = s->next)
			if (s->sisopen == SOCKET_OPEN && 
				s->hisaddr == ipbcast &&
				ntohs(up->dstPort) == s->myport)
					break;

	if (s == NULL)
		{
		icmp_noport(ip);	/* tell host port is unreachable */
		if (debug_on) outs(" UDP discarding...");
		return (0);			/* say no socket */
		}

	if (s->sisopen == SOCKET_CLOSED) return (0);

dofrag:	if (frag != 0)				/* fragmented packet */
		{
		int frag_offset;

		if (frag == 0x2000)	/* if first fragment, remember IP id*/
			s->frag_identification = ip->identification;

		frag_offset = (frag & 0x1fff) << 3;	/* frag offset */
		if (s->rdatalen + frag_offset + len > 
			UDP_BUFSIZE + sizeof(udp_Header))
			{			/* insufficient space */
			s->rdatahwater = s->rdatalen;	/* clear saved bytes*/
			s->frag_identification -= 0x0fff; /* move way back */
			return (0);			/* discard packet */
			}
			/* set highwater mark to cover new information */
		s->rdatahwater = s->rdatalen +  frag_offset;
		bcopyff((byte *)ip + ip_hlen, &s->rdata[s->rdatahwater], len);
		s->rdatahwater += len;

		if (frag & 0x2000)		/* if More Frags bit set */
			return (0);		/* wait for remainder */

		/* Else last fragment. UDP header (up) is in data buffer */
		up = (udp_Header FAR *)&s->rdata[s->rdatalen];
		len = s->rdatahwater - s->rdatalen;	/* UDP header+data */
		s->rdatahwater = s->rdatalen;		/* remove save area */
		s->frag_identification -= 0x0fff; 	/* move way back */
	}

	if (up->checksum)			/* if checksum field used */
		{
		ph.src = ip->source;		/* already network order */
		ph.dst = ip->destination;
		ph.mbz = 0;
		ph.protocol = UDP_PROTO;
		ph.length = up->length;
		ph.checksum = checksum(up, len);
		if (checksum(&ph, sizeof(tcp_PseudoHeader)) != 0xffff)
			return (0);		/* failure */
		}

					    /* process user data */
	if ((len -= UDP_LENGTH) > 0)
		{
		dp = (byte FAR *)(up);
		if (len > UDP_BUFSIZE) len = UDP_BUFSIZE;
		bcopyff(&dp[UDP_LENGTH], s->rdata, len); /* write to buf */
		s->rdatalen = s->rdatahwater = len;
		bcopyff(&ip->source, &s->hisaddr, 4); /* sender's IP */
		}
	return (1);				/* success */
}

/* Handle TCP packets. If no socket send an RST pkt. */

static 
tcp_handler(in_Header *ip)
{
	tcp_Header FAR * tp;
	register tcp_Socket *s;
	tcp_PseudoHeader ph;

	int ip_hlen, len, diff, diffticks, rtt_error;	/* signed, please */
	int frag, old_window;
	word flags;
	long ldiff;				/* must be signed */

	ip_hlen = in_GetHdrlenBytes(ip);
	tp = (tcp_Header FAR *)((byte *)ip + ip_hlen); /* tcp frame pointer */
	len = ntohs(ip->length) - ip_hlen;	/* len of tcp material */
	flags = ntohs(tp->flags) & 0x003f;	/* tcp flags from pkt */
	frag = ntohs(ip->frag) & 0x3fff;	/* frag flags and offset */


/* If not first frag then match IP ident with that stored in the TCP header
   so we can identify active reassemblies, and thence to choose the proper
   session data buffer for frag storage. Rdata will hold the TCP header and
   frag data just after rdatalen (uses rdatahwater-rdatalen bytes).
*/
	if (frag && (frag != 0x2000))		/* not first frag */
		{
		for (s = tcp_allsocs; s != NULL; s = s->next)
			if (s->frag_identification == ip->identification)
       				goto dofrag;
		if (s != NULL)
			tcp_send(s);		/* rpt last pkt */
		return (0);		/* reject stray fragment piece */
		}
				/* demux to active sockets */
	for (s = tcp_allsocs; s != NULL; s = s->next)
		if (s->hisport != 0 &&
		    ntohs(tp->dstPort) == s->myport &&
		    ntohs(tp->srcPort) == s->hisport &&
		    ntohl(ip->source) == s->hisaddr)
			break;

	if (s == NULL)	/* demux to passive sockets, must be a new session */
		for (s = tcp_allsocs; s != NULL; s = s->next)
			if ((s->hisport == 0) &&
				(ntohs(tp->dstPort) == s->myport))
				break;

#ifdef DEBUG
	if (dbugrecv) (*dbugrecv)(s, ip, tp);
#endif

	if (s == NULL)
		{     /* no matching session exists so we must send a reset */
		tcp_rst(ip, tp);
		return (0);		/* 0 to say socket is closed */
		}
	if (s->sisopen == SOCKET_CLOSED) return (0);

				/* save remote Ethernet address to socket */
	bcopy(&((((eth_Header *)ip) - 1)->source[0]), &s->hisethaddr[0],
    		sizeof(eth_address));

				/* start reassembly of TCP segment */
dofrag:	if (frag != 0)				/* if fragmented segment */
		{
		int frag_offset;

		if (frag == 0x2000)	/* if first fragment, remember IP id*/
			{
			if (ntohl(tp->seqnum) > s->acknum)
				return (0);	/* out of order, discard */
			s->frag_identification = ip->identification;
			}

		frag_offset = (frag & 0x1fff) << 3;	/* frag offset */
		if (s->rdatalen + frag_offset + len > 
			TCP_RBUFSIZE + sizeof(tcp_Header))
			{			/* insufficient space */
			s->rdatahwater = s->rdatalen;	/* clear saved bytes*/
			s->frag_identification -= 0x0fff; /* move way back */
			return (0);			/* discard packet */
			}
			/* set highwater mark to cover new information */
		s->rdatahwater = s->rdatalen +  frag_offset;
		bcopyff((byte *)ip + ip_hlen, &s->rdata[s->rdatahwater], len);
		s->rdatahwater += len;

		if (frag & 0x2000)		/* if More Frags bit set */
			return (0);		/* wait for remainder */

		/* Else last fragment. TCP header (tp) is in data buffer */
		tp = (tcp_Header FAR *)&s->rdata[s->rdatalen];
		len = s->rdatahwater - s->rdatalen;	/* TCP header+data */
		flags = ntohs(tp->flags);		/* flags from hdr */
		s->rdatahwater = s->rdatalen;		/* remove save area */
		s->frag_identification -= 0x0fff; 	/* move way back */
	}
					/* end of reassembly of TCP segment */
	/* pseudo header checking */
	ph.src = ip->source;			/* still in network order */
	ph.dst = ip->destination;
	ph.mbz = 0;
	ph.protocol = TCP_PROTO;
	ph.length = htons(len);
	ph.checksum =  checksum(tp, len);
	if (checksum(&ph, sizeof(ph)) != 0xffff)
		{
		if (debug_on) outs("\r\nBad TCP checksum ");
		return (1);
		}

	if (flags & tcp_FlagRST)	/* reset code */
		{
		outs("\r\n Connection refused by host");
		s->rdatalen = s->rdatahwater = 0;
		s->state = tcp_StateCLOSED;
		tcp_unthread(s);
		tcp_status = CONNECTION_REJECTED;
		if (kdebug != 0)
			outs("\r\nRST received");
		return (0);			/* say session is closed */
		}

	    /* Update retransmission timeout, Van Jacobson's algorithm */
	if ((s->karn_count == KARN_NEW)		 /* just sent something */
		&& (s->vj_last != 0)	/* and we have armed the timer */
		&& (s->vj_seqnum < ntohl(tp->acknum)) /* pkt of interest */
		&& ((diffticks = (int)(set_ttimeout(0) - s->vj_last)) >= 0))
		{
		s->vj_last = 0;				/* disarm timer */
		rtt_error = (diffticks << 3) - s->vj_sa;/* 8*error from avg */
		s->vj_sa += rtt_error;		/* 8*smoothed avg resp time */
		if (rtt_error < 0) rtt_error = -rtt_error;
		rtt_error -= s->vj_sd >> 2;
		s->vj_sd += rtt_error;	 	/* 8 * smoothed std dev */
						/* round trip timeout */
		s->rto = ((s->vj_sa >> 3) + s->vj_sd) >> 3;
		if (s->rto > 5 * 18)		/* PC clock ticks, 18.2/sec */
			{
			s->rto = 5 * 18; 	/* 5 sec cap */
			s->vj_sd = 0; 		/* clear high variance */
			}
		}

        s->rptxmit = FALSE;			/* no repeated xmissions */

    /* Here starts the main TCP state machine */
    switch (s->state) {

    case tcp_StateLISTEN:			/* accepting SYNs */
    /* Passive open. Sit and wait for an arriving SYN, sync to it and */
    /* respond with an ACK to their SYN plus a SYN of our own. */
    /* Reset the session if SYN is absent */
        if (flags & tcp_FlagSYN)
		{
		if (kdebug != 0) outs("\r\nLISTEN");
		s->hisport = ntohs(tp->srcPort);
		s->hisaddr = ntohl(ip->source);
					/* if not on this network */
		if ((s->hisaddr ^ my_ip_addr) & sin_mask)
			s->mss = (mss > 536)? 536: mss;	/* non-fragmentable */
		s->flags = tcp_FlagSYN | tcp_FlagACK;
		s->acknum = ntohl(tp->seqnum) + 1;   /* sync to their SYN */
		s->send_kind = tcp_SENDNOW;	     /* reply immediately */
		s->state = tcp_StateSYNREC;
		s->timeout = set_timeout(tcp_TIMEOUT);
		}
	else
        	tcp_rst(ip, tp);		/* send a RST */
        break;

    case tcp_StateSYNSENT:			/* we sent a SYN */
    /* We have sent a SYN from either active or passive opens. */
    /* Our s->seqnum points at it (no displacement yet). No SYN */
    /* has been received yet so s->acknum means nothing so far. */
    /* Get SYN+ACK to our SYN or get just SYN from the remote host. */
   
	if (kdebug != 0) outs("\r\nSYN_SENT");
	if ((flags & tcp_FlagSYN) == 0)		/* they did not send SYN */
		{
        	tcp_rst(ip, tp);		/* send a RST */
		break;
		}

	if (flags & tcp_FlagACK)		/* if they included an ACK */
		if (ntohl(tp->acknum) == s->seqnum + 1)
			{ 				/* for our SYN */
			s->state = tcp_StateESTAB;
			s->timeout = 0;			/* do not time out */
			s->seqnum++;			/* count our SYN */
			s->acknum = ntohl(tp->seqnum) + 1; /* sync to them */
			s->flags = tcp_FlagACK;		/* ACK their SYN */
			s->send_kind = tcp_SENDNOW;
			tcp_processdata(s, tp, len);	/* if sent data */
			break;
			}
		else			/* simultaneous open or wrong ACK */
			{
			tcp_rst(ip, tp);	/* reset the connection */
			break;
			}

	/* bare SYN arrived, no ACK yet */
	s->acknum = ntohl(tp->seqnum) + 1;		/* sync to them */
	s->flags = tcp_FlagSYN  + tcp_FlagACK;		/* ACK their SYN */
	s->timeout = set_timeout(tcp_TIMEOUT);
	s->state = tcp_StateSYNREC;
	s->send_kind |= tcp_SENDNOW;	/* reply immediately */
	break;

    case tcp_StateSYNREC:
    /* Received a SYN from active or passive opens, and it has been ACK'd. */
    /* Our s->acknum points one beyond it. We have sent a SYN but we are */
    /* awaiting an ACK to it. */

	if (kdebug != 0) outs("\r\nSYN_RECVD");
	if ((flags & tcp_FlagACK) && 			/* an ACK */
		(ntohl(tp->acknum) == (s->seqnum + 1)))	/* to our SYN */
		{
		s->flags = tcp_FlagACK;
		s->seqnum++;			/* move beyond our SYN */
		tcp_processdata(s, tp, len);	/* gather sent data */
		s->send_kind |= tcp_SENDNOW;	/* reply immediately */
		s->state = tcp_StateESTAB;
        	s->timeout = 0;     		/* do not timeout */
		}
	break;


    case tcp_StateESTAB:
    /* Both sides have exchanged and ACK's their SYNs. Proceed to exchange */
    /* data in both directions. An ACK is required on each packet. */

	if ((flags & tcp_FlagACK) == 0)
		break;			 /* they must ack somthing*/

				/* process their ack value in packet */
	/* ldiff is their ack number minus our oldest sent seq number */
	/* ldiff < 0 preceeds this window and is an old packet. */
	/* ldiff == 0 is typically an ACK and might represent their timeout.*/
	/* ldiff > 0 exceeding the window (our oldest seqnum + sent data */
	/* s-sdatalen) means pkt is out of order or a window probe (likely)*/
	/* just ACK to keep their timers happy. */
	/* However, in all cases grab any useful data for us. */		
	ldiff = ntohl(tp->acknum) - s->seqnum; /* current ACK - oldest sent */
	if (ldiff > 0 && ldiff <= s->sdatalen)
		{ 			/* their ack is in our window*/
		s->seqnum += ldiff;	/* update ACK'd file pointer */
		diff = (int)ldiff;	/* 16 bits, bigger than our window */
		s->sdatalen -= diff;	/* deduct amount ACK'd data */
		if ((s->sdatahwater -= diff) < 0) /* less old, sent data */
			s->sdatahwater = 0;
					/* move down residual in send buf */
 		bcopyff(&s->sdata[diff], s->sdata, s->sdatalen);

		/* if we have enough data to test a larger burst */
		if ((s->sdatalen - s->sdatahwater) > (int)s->cwindow)
			{
			if (s->cwindow < s->ssthresh)	/* VJ congestion */
				s->cwindow += s->mss;	/* fast opening */
			else
			 	s->cwindow += s->mss / (s->cwindow / s->mss);
			if (s->cwindow > TCP_SBUFSIZE)
				s->cwindow = TCP_SBUFSIZE;
			}
		s->loss_count = 0;
		}

	s->flags = tcp_FlagACK;			/* tell them thanks */
	old_window = s->window;			/* their previous window */
			/* process incoming data, get new s->window */
	tcp_processdata(s, tp, len);		/* get data for us */

	/* set old_window to zero if no window opening from shut */
	old_window = (old_window == 0 && s->window > 0)? 1: 0;

	/* they ACK'd some of our data or it's a window opening up, */
	/* and we have unsent data, then send our data now */
	if ( ((ldiff > 0) || old_window) && 
			(s->sdatalen - s->sdatahwater) > 0)
		s->send_kind |= tcp_SENDNOW;	/* send any new stuff */

	/* Congestion loss, they timed out, window update without data. */
	/* Can also be just an ordinary ACK from some time ago */
	/* They did not ACK more of our data && we have data for them. */
	/* Acknum at our oldest seqnum && we have data && not a win update */
	if (ldiff == 0 && s->sdatalen && old_window == 0)
		{
		s->loss_count++;		/* count lost packet */
		if (s->loss_count > 2)
			{
			s->ssthresh = s->cwindow / 2;
			s->cwindow = s->mss;	/* open with one packet */
			s->sdatahwater = 0;	/* retransmit all data, */
			s->loss_count = 0;	/* pkt loss counter */
			s->vj_last = 0;		/* enable timing */
			s->send_kind = tcp_SENDSOON;
						/* don't add to congestion */
			}
		}

	/* Case of ldiff < 0 means it's an old packet so our last response
	   must have been lost. ACK it to reinform the remote host.
	   ACKing condition recognised by tcp_processdata(). */

	if (flags & tcp_FlagFIN)		/* they said FIN */
		{
		if (kdebug != 0) outs("\r\nFIN received");
		if (s->sdatalen)		/* we have more to send */
			s->state = tcp_StateCLOSWT;
		else
			{		/* we have nothing more to send */
 					/* say FIN to them too */
 			s->flags |= tcp_FlagFIN;
			s->state = tcp_StateLASTACK; /* nothing more from us*/
			}
		s->acknum++;			/* count their FIN */
		s->rptxmit = TRUE;
		s->send_kind |= tcp_SENDNOW;	/* reply immediately */
		}
	break;

    case tcp_StateCLOSWT:
    	/* Have received and ACK'd their FIN. Our s->acknum points at it. */
	/* Obtain last minute ACKs of our data, send our FIN with our */
	/* s->seqnum pointing one byte before our FIN. */

	if (kdebug != 0) outs("\r\nCLOSEWT");
	s->sdatahwater = 0;			/* amount not sent yet */
	ldiff = ntohl(tp->acknum) - s->seqnum; /* current - prev ack */
	if (ldiff > 0 && ldiff <= s->sdatalen) /* their ack is in our window*/
		{
		s->seqnum += ldiff;	/* update ACK'd file counter */
		diff = (int)ldiff;	/* 16 bits, more than our window */
		s->sdatalen -= diff;	/* deduct amount ACK'd */
		if ((s->sdatahwater -= diff) < 0) /* less old, sent data */
			s->sdatahwater = 0;
		bcopyff(&s->sdata[diff], s->sdata, s->sdatalen);
		}				/* move residual */
	s->send_kind |= tcp_SENDNOW;	/* reply immediately */
	s->flags = tcp_FlagACK;		/* ACK the reception */
	if (s->sdatalen <= 0)
		{
		s->flags |= tcp_FlagFIN;	/* say no more data */
		s->state = tcp_StateLASTACK;	/* get their ACK */
		s->rptxmit = TRUE;		/* need an ACK to our FIN */
		}
	break;

    case tcp_StateFINWT1:
	/* Have sent our FIN, our s->seqnum points at it. */
	/* Expect to receive either an ACK for the FIN, or the remote FIN, */
	/* or a remote FIN and and ACK for our FIN. */

	if (kdebug != 0) outs("\r\nFINWAIT-1");
	s->sdatalen = s->sdatahwater = 0; /* we can't send new from here */
	if (flags & tcp_FlagACK)
		{
		ldiff = ntohl(tp->acknum) - s->seqnum; /* current - prev ack*/
		if (ldiff > 0 && ldiff <= s->sdatalen)
			{		/* their ack is in our window */
			s->seqnum += ldiff;	/* update ACK'd file counter */
			diff = (int)ldiff;	/* 16 bits, more than our window */
			s->sdatalen -= diff;	/* deduct amount ACK'd */
			if ((s->sdatahwater -= diff) < 0) /* less old */
				s->sdatahwater = 0;
			bcopyff(&s->sdata[diff], s->sdata, s->sdatalen);
			}				/* move residual */
				/* process our data in the packet */
		tcp_processdata(s, tp, len);
		
				/* if this ACKs our FIN */
		if (ntohl(tp->acknum) == s->seqnum + 1)
			{
			s->seqnum++;		/* count our FIN */
			s->state = tcp_StateFINWT2;
			}
		}		/* fall through to look at their FIN bit */


	if (flags & (tcp_FlagFIN || tcp_FlagACK) == 
					(tcp_FlagFIN || tcp_FlagACK))
		{
		if (kdebug != 0) outs("\r\nFIN received");
		s->acknum++;		/* ACK their FIN */
		s->flags = tcp_FlagACK;
		s->send_kind |= tcp_NOSEND;

		/* if our FIN has been ACK'd then do timed wait */
		/* else go to CLOSING to await that ACK */
		if (ntohl(tp->acknum) < s->seqnum)	/* not ACK'd yet */
			{
			s->timeout = set_timeout(3);
			s->state = tcp_StateCLOSING;
			tcp_send(s);
			}
		else
			{	/* FIN have been ACK'd, we are done */
			/* state TIMEWT would be next if there were
			   something to drive it, but there isn't */
			tcp_send(s);
			s->state = tcp_StateCLOSED;	 /* no 2 msl */
			tcp_unthread(s);
			}
		}
	break;

    case tcp_StateFINWT2:
	/* Have sent our FIN and received an ACK to it. We have not */
	/* yet received a FIN from the remote side so wait for it here. */
	/* When that FIN arrives send and ACK and do TIMEWT (empty here).*/
	/* Our s->seqnum points at our FIN, their tp->acknum points to our */
	/* FIN too, so these two are equal */

	if (kdebug != 0) outs("\r\nFINWAIT-2");
	s->sdatalen = s->sdatahwater = 0;	/* cannot send from here */
	if (flags & tcp_FlagFIN)
		if ((ntohl(tp->acknum) == s->seqnum) &&
			(ntohl(tp->seqnum) >= s->acknum))
			{
			s->acknum++;		/* ACK their FIN */
			s->flags = tcp_FlagACK;
			tcp_send(s);		/* send last ACK */
			/* state TIMEWT would be next if there were
			   something to drive it, but there isn't */
			s->state = tcp_StateCLOSED;	 /* no 2 msl */
			tcp_unthread(s);
			}
	break;

    case tcp_StateCLOSING:
    	/* Have sent our FIN, our s->seqnum points at it, but it has */
	/* not been ACK'd. Thus their tp->acknum < our s->seqnum */
	/* Have received their FIN, our s->acknum points at it, and */
	/* we have sent an ACK for their FIN. */
	/* Get an ACK to our FIN and then go to TIMEWT (empty here). */

	if (kdebug != 0) outs("\r\nCLOSING");
    	if (flags & tcp_FlagACK)
		if (ntohl(tp->acknum) == s->seqnum)
			{
			/* state TIMEWT would be next if there were
			   something to drive it, but there isn't */
			s->state = tcp_StateCLOSED;	 /* no 2 msl */
			tcp_unthread(s);
			}
	break;

    case tcp_StateLASTACK:
    /* Have received and ACK'd their FIN, so our s->acknum points at it. */
    /* Have sent a FIN to them with our s->seqnum one less than it. */
    /* Here we get their ACK to our FIN and exit the protocol stack. */

	if (kdebug != 0) outs("\r\nLAST_ACK");
	if (flags & tcp_FlagACK)		/* an ACK to our FIN */
		if (ntohl(tp->acknum) == s->seqnum + 1)
			{
			s->state = tcp_StateCLOSED;     /* no 2 msl */
			tcp_unthread(s);
			break;
			}
	/* Get here if they lost our ACK + FIN pkts. Resend both */
	if (flags & tcp_FlagFIN)    
		{
		s->flags = tcp_FlagACK | tcp_FlagFIN;
		s->send_kind |= tcp_SENDNOW;	/* reply immediately */
		s->rptxmit = TRUE;		/* need an ACK to our FIN */
		}
	break;

    case tcp_StateTIMEWT:
	if (kdebug != 0) outs("\r\nTIMED_WAIT");
	s->state = tcp_StateCLOSED;		 /* no 2 msl */
	tcp_unthread(s);
	break;
    }						/* end switch */
    return (1);					/* success */
}

/*
 * Process the data for us in an incoming packet.
 * Called from all states where incoming data can be received: established,
 * fin-wait-1, fin-wait-2
 * len is the length of TCP header + data
 * Sets s->send_kind if transmission is needed.
 * Returns int 0 for nothing needs doing, > 0 for data accepted,
 * and < 0 for data rejected.
 */
static int
tcp_processdata(tcp_Socket *s, tcp_Header FAR *tp, int len)
{
	register int diff, x;
	long ldiff;					/* signed */
	word numoptions, opt_temp;
	byte FAR * dp, FAR * options;

	if (s == NULL || tp == NULL) return (0);	/* failure */

	s->window = ntohs(tp->window);
	if (s->window > 0x7fff)			/* SGI 64KB window nonsense?*/
		s->window = 0x7fff;		/* yes, cut window to 32KB */

	ldiff = s->acknum - ntohl(tp->seqnum);	/* new data, signed long */

	if (ntohs(tp->flags) & tcp_FlagSYN)
		ldiff--;			/* SYN counts as one unit */
	diff = (int)ldiff;			/* 16 bit version */
						/* find the data portion */
	x = tcp_GetDataOffset(tp) << 2;		/* quadword to byte count */
	dp = (byte FAR *)tp + x;		/* points to data */

						/* process those options */
	if (numoptions = x - sizeof(tcp_Header))
		{
		options = (byte FAR *)tp + sizeof(tcp_Header);
		while (numoptions-- > 0)
			switch (*(options++) & 0xff)
				{
				case 0: numoptions = 0;	/* end of options */
				case 1:	break;		/* nop */

				  /* we are very liberal on MSS stuff */
				case 2: if (*options == 4)	/* length */
						{
				opt_temp = ntohs(* (word FAR *)(&options[1]));
						if (opt_temp < s->mss)
							s->mss = opt_temp;
						}
					numoptions -= *options - 1;
					options += *options - 1;
			  		break;
				default: break;
				}	/* end of switch and while */
		}			/* end of if */
				    	/* done option processing */

	len -= x;			/* remove the TCP header */

	if (len <= 0)			/* amount of data in this packet */
		return (0);		/* no data, must be an ACK */

	s->send_kind |= tcp_SENDNOW;	/* send an ACK for any data */
				/* skip already received bytes */
				/* diff = last rcv'd byte - new start*/
	if (diff < 0)		/* if data is beyond our last byte */
		return (0);	/*  then ignore the data */	
	dp += diff;		/* move to new data in packet */
	len -= diff;		/* length of new data in packet */
	 			/* limit receive size to our window */
	if (s->rdatalen < 0)
		s->rdatalen = s->rdatahwater = 0; /* sanity check */
	if (len > (x = TCP_RBUFSIZE - s->rdatalen))
			len = x;		/* space available only */
	if (len <= 0)				/* old packet */
		return (len);

	bcopyff(dp, &s->rdata[s->rdatalen], len);
	s->rdatalen += len;
	s->acknum += len;		 /* new ack begins at end of data */
	if (s->rdatahwater < s->rdatalen)	/* sanity check again */
		s->rdatahwater = s->rdatalen;
	return (len);				/* success */
}

/*
 * Format and send an outgoing segment.
 */
static int
tcp_send(tcp_Socket *s)
{
	tcp_PseudoHeader ph;
	struct pkt
		{
		in_Header in;
		tcp_Header tcp;
		word maxsegopt[2];
		} register *pkt;
	byte *dp;
	register int senddatalen, temp;
	int sendtotlen, sendpktlen, their_window;

	if (s == NULL) return (0);		/* failure */

						/* just unsent new stuff */
	senddatalen = s->sdatalen - s->sdatahwater;

	if (senddatalen < 0)
		senddatalen = 0;

	/* space remaining in other side's receive buffer */
	/* their announced window minus bytes sent (unACK'd) from here */
	their_window = s->window - s->sdatahwater;

	if (their_window < 0)
		their_window = 0;		/* sanity check, wrap too */

	if (their_window == 0 && senddatalen)	/* excuses our empty ACKs */
		{				/* closed in our face */
		s->send_kind = tcp_NOSEND;
		s->karn_count = KARN_NEW;
		return (1);
		}

	if (senddatalen > their_window)	    /* do not exceed their window */
		senddatalen = their_window;

	/* 
	   While s->cwindow increases either fast by s->mss or slow
	   by fractional s->mss units we want to transmit full sized
	   frames whenever possible to deal with routers which think
	   in frames rather than frame length. Thus we recompute the
	   congestion window by rounding up to a full frame.
	   For byte-sized adjustments we would have used instead:
		if (senddatalen > (int)s->cwindow)
			senddatalen = s->cwindow;
	*/

/*#define BYTE*/
#ifdef BYTE					/* adjust to the byte */
	if (senddatalen > (int)s->cwindow)
		senddatalen = s->cwindow;
#else						/* adjust to whole MSS */

	if (senddatalen > (int)s->mss)		/* worry about small pkts */
		{				/*  after the full ones */
		if (temp = (s->cwindow) % s->mss) /* fraction above mss */
			temp = s->cwindow + (s->mss - temp); /* round up */
		else
			temp = s->cwindow;	/* no fraction */
		if (senddatalen > temp)		/* apply s->cwindow limit */
			senddatalen = temp;
		}
#endif	/* BYTE */

	if (senddatalen > (int)(s->ssthresh + 2 * s->mss))
		s->ssthresh += (int)s->mss;	 /* lots of success */

	s->karn_count = KARN_NEW;	/* say is a fresh transmission */

	sendtotlen = 0;			/* running count of what we've sent */

	pkt = (struct pkt *)eth_formatpacket(&s->hisethaddr[0], TYPE_IP);
	pkt->in.hdrlen_ver = 0x45;	/* version 4, hdrlen 5 */
	pkt->in.tos = 0;		/* type of service, normal */
	pkt->in.frag = 0;		/* fragment, none, allowed */

#ifdef KERMIT
        pkt->in.ttl = 60;		/* seconds */
#else
        pkt->in.ttl = 254;
#endif
	pkt->in.proto = TCP_PROTO;
       	pkt->in.source = htonl(my_ip_addr);
       	pkt->in.destination = htonl(s->hisaddr);
       	ph.src = pkt->in.source;	/* already in network order */
       	ph.dst = pkt->in.destination;
       	ph.mbz = 0;
       	ph.protocol = TCP_PROTO;
	pkt->tcp.srcPort = htons(s->myport);
	pkt->tcp.dstPort = htons(s->hisport);
	pkt->tcp.acknum = htonl(s->acknum);
	pkt->tcp.window = htons(TCP_RBUFSIZE - s->rdatalen);
	pkt->tcp.urgentPointer = 0;  /* not used in this program */

	while (1 == 1)
		{
		dp = (byte *)pkt->maxsegopt;
		if (senddatalen) 		/* set PSH bit on data pkts */
			s->flags |= tcp_FlagPUSH;
		else
			s->flags &= ~tcp_FlagPUSH;

					/* tcp header material */
        	pkt->tcp.seqnum = htonl(s->seqnum + sendtotlen + 
							s->sdatahwater);
        	pkt->tcp.checksum = 0;
       		pkt->tcp.flags = htons(s->flags | 0x5000); /* 5 quadbytes */
                sendpktlen = sizeof(tcp_Header) + sizeof(in_Header);

				/* do options if this is our first packet */
        	if (s->flags & tcp_FlagSYN)
			{				/* 5 + 1 quadbytes */
			pkt->tcp.flags = htons(s->flags | (0x5000 + 0x1000));
			pkt->maxsegopt[0] = 0x0402;
			pkt->maxsegopt[1] = htons(s->mss);
			sendpktlen += 4;
			dp += 4;
			}
		else
			{			/*  not a SYN packet */
			if (senddatalen > 0)	/* handle packets with data */
				{		/* limit pkt to mss */
	        		if (senddatalen > (int)s->mss)
						temp = (int)s->mss;
				else
						temp = senddatalen;
				bcopyff(&s->sdata[sendtotlen + s->sdatahwater], 
								dp, temp);
                		sendpktlen += temp; /* len of this pkt */
	        		sendtotlen += temp; /* amount sent so far */
				senddatalen -= temp; /* qty left to send */
				}
        		}

					/* Internet Packet header */
        	pkt->in.identification = htons(++ip_id); /* pre-inc req'd */
        	pkt->in.checksum = 0;
        	pkt->in.length = htons(sendpktlen);
        	pkt->in.checksum = ~checksum(&pkt->in, sizeof(in_Header));

        	/* compute TCP checksum, ph is pseudo header pointer */
        	ph.length = htons(sendpktlen - sizeof(in_Header));
        	ph.checksum = checksum(&pkt->tcp,
				(sendpktlen - sizeof(in_Header) + 1) & 0xfffe);
        	pkt->tcp.checksum = ~checksum(&ph, sizeof(ph));

	        if (eth_send(htons(pkt->in.length)) == 0)
			{
			s->rptxmit = TRUE;
			return (0);			/* sending failed */
			}

		if (senddatalen <= 0)		/* if have sent all data */
			break;
	}					/* do next IP packet */

	/* if timer available and either sent data or sent SYN or FIN */
	if (s->vj_last == 0 && (sendtotlen || 
		(s->flags & (tcp_FlagSYN | tcp_FlagFIN))))
		{
		s->vj_seqnum = s->seqnum + s->sdatahwater;  /* 1st byte */
		s->vj_last = set_ttimeout(0);	/* time of now */
		temp = s->rto - 1;
		if (temp < 1)
			temp = 1;
		s->rtt_time = set_ttimeout(temp);
		}

	s->sdatahwater += sendtotlen;	/* move have-sent highwater mark */
	s->send_kind = tcp_NOSEND;	/* clear kind-of sending flag */
	return (1);				/* success */
}

/*
 * Format and send a reset tcp packet
 */
int
tcp_rst(in_Header *his_ip, tcp_Header FAR * oldtcpp)
{
	tcp_PseudoHeader ph;
	struct pkt
		{
        	in_Header in;
        	tcp_Header tcp;
		word maxsegopt[2];
		} register *pkt;

	eth_Header *eth;
	register int sendtotlen;			/* length of packet */

	if (his_ip == NULL || oldtcpp == NULL) return (0);	/* failure */

					/* see RFC 793 page 65 for details */
	oldtcpp->flags = ntohs(oldtcpp->flags);		/* net to local */

	if (oldtcpp->flags & tcp_FlagRST)
		return (0);  
	if ((oldtcpp->flags & tcp_FlagACK) == 0)
	        oldtcpp->flags = tcp_FlagACK;
	else
        	oldtcpp->flags = 0;

				/* reads his Ethernet address or garbage */
	eth = eth_hardware((byte *)his_ip);

	pkt = (struct pkt *)eth_formatpacket(eth, TYPE_IP);
	sendtotlen = sizeof(tcp_Header) + sizeof(in_Header);
	pkt->in.length = htons(sendtotlen);
					/* TCP header */
	pkt->tcp.srcPort = oldtcpp->dstPort;
	pkt->tcp.dstPort = oldtcpp->srcPort;
	pkt->tcp.seqnum = oldtcpp->acknum;
	pkt->tcp.acknum = htonl(ntohl(oldtcpp->seqnum) + 1);
	pkt->tcp.window = 0;
	pkt->tcp.flags = htons(tcp_FlagRST | oldtcpp->flags | 0x5000);
	pkt->tcp.checksum = 0;
	pkt->tcp.urgentPointer = 0;
					/* Internet header */
	pkt->in.hdrlen_ver = 0x45;		/* version 4, hdrlen 5 */
	pkt->in.tos = 0;
	pkt->in.identification = htons(++ip_id); /* use pre-inc here */
	pkt->in.frag = 0;
#ifdef KERMIT
	pkt->in.ttl = 60;			/* time to live */
#else
	pkt->in.ttl = 254;
#endif
	pkt->in.proto = TCP_PROTO;
	pkt->in.checksum = 0;
	pkt->in.source = his_ip->destination;
	pkt->in.destination = his_ip->source;
	pkt->in.checksum = ~checksum(&pkt->in, sizeof(in_Header));
						/* compute TCP checksum */
	ph.src = pkt->in.source;		/* already big endian */
	ph.dst = pkt->in.destination;
	ph.mbz = 0;
	ph.protocol = TCP_PROTO;
	ph.length = htons(sendtotlen - sizeof(in_Header));
	ph.checksum = checksum(&pkt->tcp, (sendtotlen - sizeof(in_Header) + 1)
								& 0xfffe);
	pkt->tcp.checksum = ~checksum(&ph, sizeof(ph));

#ifdef DEBUG
	if (dbugxmit) (*dbugxmit)(NULL, &pkt->in, &pkt->tcp);
#endif
	return (eth_send(htons(pkt->in.length)));
}

/* Handle ICMP Redirects. ICMP processor yields new gateway IP number
   in "gateway" and pointer to offending IP header which we have sent.
   Find socket with destination IP address matching the gateway
   sending us the Redirect message, replace the socket's destination
   Ethernet address with that of the new gateway (use arp_resolve).
   For TCP resend the packet, for UDP give up all hope.
*/
void
do_redirect(longword gateway, in_Header *ip)
{
	register udp_Socket *s_udp;
	register tcp_Socket *s_tcp;
	tcp_Header *tp;
	int len;
	longword host;

	len = in_GetHdrlenBytes(ip);
	tp = (tcp_Header *)((byte *)ip + len);	/* tcp frame pointer */
	host = ntohl(ip->destination);		/* destination IP */

	switch(ip->proto)
		{
		case TCP_PROTO:		/* active TCP sockets */
		for (s_tcp = tcp_allsocs; s_tcp != NULL; s_tcp = s_tcp->next)
			if ((host == s_tcp->hisaddr) &&	/* if same dest IP */
				(ntohs(tp->srcPort) == s_tcp->myport))
								/* & port */
				{
				arp_resolve(gateway, &s_tcp->hisethaddr[0]);
				tcp_send(s_tcp);	/* resend the packet */
				}
			break;
		case UDP_PROTO:			/* active UDP sockets */
		for (s_udp = udp_allsocs; s_udp != NULL; s_udp = s_udp->next)
			if ((host == s_udp->hisaddr) &&
				(ntohs(tp->srcPort) == s_udp->myport))
				arp_resolve(gateway, &s_udp->hisethaddr[0]);
			break;
		}
}

/* Handle ICMP Redirects. ICMP processor yields new gateway IP number
   in "gateway" and pointer to offending IP header which we have sent.
   Find socket with destination IP address matching the gateway
   sending us the Redirect message, replace the socket's destination
   Ethernet address with that of the new gateway (use arp_resolve).
   For TCP resend the packet, for UDP give up all hope.
*/
void
do_quench(in_Header *ip)
{
	register tcp_Socket *s_tcp;
	register tcp_Header *tp;
	int len;
	longword host;

	len = in_GetHdrlenBytes(ip);
	tp = (tcp_Header *)((byte *)ip + len);	/* tcp frame pointer */
	host = ntohl(ip->destination);		/* destination IP */

	if (ip->proto != TCP_PROTO)
		return;

	for (s_tcp = tcp_allsocs; s_tcp != NULL; s_tcp = s_tcp->next)
		if ((host == s_tcp->hisaddr) &&		/* if same dest IP */
		    (ntohs(tp->srcPort) == s_tcp->myport))	/* & port */
			{
			s_tcp->rto++;		/* one Bios clock tick */
			s_tcp->ssthresh = 0;
			s_tcp->cwindow = s_tcp->mss;
			break;
			}
}

/**********************************************************************
 * socket functions
 **********************************************************************/

/* socket based procedures */

/*
 * sock_setmode - set mode bit to true or false
 */
int
sock_setmode(tcp_Socket *s, word mode, word value)
{
	if (s == NULL)
		return (0);
	if (value == TRUE)
		s->sock_mode |= mode;
	else
		s->sock_mode &= ~mode;
	return (1);		/* success */
}

/*
 * sock_read - read a socket with maximum n bytes
 *	     - returns count also when connection gets closed
 */
int
sock_read(sock_type *s, byte FAR *dp, int len)
{
	register int templen, count;
	count = 0;

	if (s == NULL || dp == NULL) return (0);		/* failure */
	do    	{
		switch (s->tcp.ip_type)
			{
			case UDP_PROTO:
				templen = udp_read((udp_Socket *)s, dp, len);
				break;
			case TCP_PROTO:
				templen = tcp_read((tcp_Socket *)s, dp, len);
				if (templen == 0 && 
					s->tcp.sisopen == SOCKET_CLOSED)
						return (count); /*quit early*/
				break;
			default: return (0);
			}
		count += templen;
		len -= templen;
    		}
	while (len > 0);
   	return (count);
}

/*
 * sock_fastread - read a socket with maximum n bytes
 *	     - does not busywait until buffer is full
 */
int
sock_fastread(sock_type *s, byte FAR *dp, int len)
{
	if (s == NULL)  return (0);		/* failure */
	switch(s->tcp.ip_type)
		{
		case UDP_PROTO:
			return (udp_read((udp_Socket *)s, dp, len));
		case TCP_PROTO:
			return (tcp_read((tcp_Socket *)s, dp, len));
		default: return (0);
		}
}


/*
 * sock_write - writes data and returns length written
 *	      - does not perform flush
 */
int
sock_write(sock_type *s, byte FAR *dp, int len)
{
	register int offset, howmuch;
	int dolen, retry;
	longword temptime;

	dolen = len;
	offset = 0;
	retry = 9 ;				/* ~0.5 sec retry counter */
	if (s == NULL || dp == NULL) return (0);	/* failure */

	while (dolen > 0)
		{
		switch (s->tcp.ip_type)
			{
			case UDP_PROTO:
				howmuch = 
				udp_write((udp_Socket *)s, &dp[offset],dolen);
				break;
			case TCP_PROTO:
				howmuch = 
				tcp_write((tcp_Socket *)s, &dp[offset],dolen);
				break;
			default: return (0);
			}
		dolen -= howmuch;
		offset += howmuch;
		if (howmuch == 0)		/* if unable to write */
			{
			if (retry-- > 0) 	 /* try again */
				{		/* pause 1 Bios clock tick */
				temptime = set_ttimeout(1);
				while (chk_timeout(temptime) != TIMED_OUT)
					if (tcp_tick(s) == SOCKET_CLOSED)
						return (-1); /* no session */
				if (retry <= 1)
				((tcp_Socket *)s)->send_kind |= tcp_SENDNOW;

				continue;
				}
			else
				return (offset);
			}
		}
	return (offset);
}

/*
 * sock_dataready - returns number of bytes waiting to be read
 */
word 
sock_dataready(sock_type *s)
{
	if (s == NULL) 
		return (0);
        return (s->tcp.rdatalen);
}

int
sock_established(sock_type *s)
{
	if (s == NULL) return (0);			/* failure */
	switch (s->tcp.ip_type)
		{
		case UDP_PROTO:
			return (1);
		case TCP_PROTO:
			return (s->tcp.state == tcp_StateESTAB);
		default:
			return (0);
		}
}

int
sock_close(sock_type *s)
{
	register int status;

	if (s == NULL) return (0);			/* failure */
	tcp_tick(s);			/* process any last packets */
	switch (s->tcp.ip_type)
		{
		case UDP_PROTO:
			status = udp_close((udp_Socket *)s);
			break;
		case TCP_PROTO:
			status = tcp_close((tcp_Socket *)s);
			while ((tcp_Socket *)s->tcp.sisopen != SOCKET_CLOSED)
				tcp_tick(s);	/* do protocol close */
			break;
		default:
			return (0);
		}
	return (status);					/* success */
}

void 
sock_abort(sock_type *s)
{
	if (s == NULL) return;			/* do nothing */
	switch (s->tcp.ip_type)
		{
		case TCP_PROTO:
			tcp_abort((tcp_Socket *)s);
			break;
		case UDP_PROTO:
			udp_close((udp_Socket *)s);
			break;
		}
}

/*
 * ip_delay0 called by macro sock_wait_established()
 * ip_delay1 called by macro sock_wait_input()
 * ip_delay2 called by macro sock_wait_closed();
 *
 */

ip_delay0(sock_type *s, int timeoutseconds, procref fn, int *statusptr)
{
	register int status;
	longword timeout;

	if (s == NULL)
		{
		status = -1;		/* failure */
    		if (statusptr != NULL) *statusptr = status;
		return (status);
		}
	timeout = set_timeout(timeoutseconds);
	do
		{
		if (s->tcp.ip_type == TCP_PROTO)
			if (s->tcp.state == tcp_StateESTAB)
				{
				status = 0;
				break;
				}
		if (tcp_tick(s) == SOCKET_CLOSED)
			{
			status = -1;	/* get an early reset */
			break;
			}

		if (chk_timeout(timeout) == TIMED_OUT)
			{
			sock_close(s);
			status = -1;
			break;
			}
		if (fn != NULL) 
			if (status = fn(s, NULL, 0)) break;
		if (s->tcp.ip_type == UDP_PROTO)
			{
			status = 0;
			break;
			}
		if (chkcon())			/* check console for ^C */
			{
			status = -1;		/* a failure */
			while (chkcon()) ;	/* soak up ^C's */
			break;
			}
		} while (1 == 1);
	if (statusptr != NULL) *statusptr = status;
	return (status);
}

int
ip_delay1(sock_type *s, int timeoutseconds, procref fn, int *statusptr)
{
	register int status;
	longword timeout;

	if (s == NULL)
		{
		status = -1;		/* failure */
    		if (statusptr != NULL) *statusptr = status;
		return (status);
		}

	timeout = set_timeout(timeoutseconds);

	do
		{
		if (sock_dataready(s))
			{
			status = 0;
			break;
			}

		if (tcp_tick(s) == SOCKET_CLOSED)
			{
			status = 1;
			break;
			}
		if (chk_timeout(timeout) == TIMED_OUT)
			{
			sock_close(s);
			status = -1;
			break;
			}
		if (fn != NULL)
			if (status = fn(s, NULL, 0)) break;
		} while (1 == 1);

	if (statusptr != NULL) *statusptr = status;
	return (status);
}

int
ip_delay2(sock_type *s, int timeoutseconds, procref fn, int *statusptr)
{
	register int status;
	longword timeout;

	if (s == NULL)
		{
		status = 0;		/* failure */
    		if (statusptr != NULL) *statusptr = status;
		return (status);
		}

	timeout = set_timeout(timeoutseconds);

	if (s->tcp.ip_type != TCP_PROTO) return (1);

	do
		{
		if (tcp_tick(s) == SOCKET_CLOSED) 	/*  no socket */
			{
			status = 1;
			break;
			}
		if (chk_timeout(timeout) == TIMED_OUT)
			{
			sock_abort(s);
			status = 0;
			break;
			}
		if (fn != NULL)
			if (status = fn(s, NULL, 0)) break;
		} while (1 == 1);

	if (statusptr != NULL) *statusptr = status;
	return (status);
}
