V10/sys/inet/tcp_device.c

Compare this file to the similar file:
Show the results in this format:

/*
 * tcp_device.c
 */

#include "sys/param.h"
#include "sys/stream.h"
#include "sys/inio.h"
#include "sys/ttyio.h"
#include "sys/conf.h"
#include "sys/user.h"
#include "sys/inet/in.h"
#include "sys/inet/ip_var.h"
#include "sys/inet/tcp.h"
#include "sys/inet/tcp_timer.h"
#include "sys/inet/tcp_seq.h"
#include "sys/inet/tcp_var.h"
#include "sys/inet/tcpip.h"
#include "sys/inet/tcp_user.h"
#include "sys/inet/tcp_fsm.h"

extern int tcp_busy;	/* set to discourage timers & ensuing panics */
extern int tcp_maxseg;

long	tcpdopen();
int	tcpdclose(), tcpdput();
int	tcpdosrv(), tcpdisrv();
static	struct qinit tcpdrinit = { noput, tcpdisrv, tcpdopen, tcpdclose, 2048, 64 };
	struct qinit tcpdwinit = { tcpdput, tcpdosrv, tcpdopen, tcpdclose, 2048, 64};
struct	streamtab tcpdinfo = { &tcpdrinit, &tcpdwinit };

struct cdevsw tcpcdev = cstrinit(&tcpdinfo);

extern int tcpcnt;
extern struct tcpcb tcpcb[];

long
tcpdopen(q, dev)
register struct queue *q;
dev_t dev;
{
	struct tcpcb *tp;

	dev = minor(dev);
	if(dev >= tcpcnt)
		return(0);
	tp = &tcpcb[dev];
	if(dev&01){
		/* outgoing channel */
		/*
		 *  disallow if active
		 */
		if(tp->t_state != TCPS_CLOSED)
			return(0);
		/*
		 *  only one opener at a time for outgoing channels
		 */
		if(q->ptr)
			return(0);
	} else {
		/* incoming channel */
		/*
		 *  disallow if inactive
		 */
		if(tp->t_state == TCPS_CLOSED) {
printf("tcp%d closed\n", dev);
			return(0);
		}
		/*
		 *  disallow if it's not open or waiting to be opened
		 */
		if((tp->so_state&(SS_OPEN|SS_PLEASEOPEN))==0) {
printf("tcp%d state %x\n", dev, tp->so_state);
			return(0);
		}
		/*
		 *  if already open, just accept
		 */
		if(q->ptr)
			return(1);
	}
	tcp_busy++;
	if((tp->so_state & SS_PLEASEOPEN) == 0){
		tp->so_rcount = tp->so_wcount = tp->so_options = 0;
		tp->so_laddr = tp->so_faddr = (in_addr)0;
		tp->so_lport = tp->so_fport = (tcp_port)0;
		tp->so_oobmark = tp->so_delimcnt = 0;
		tp->so_head = (struct tcpcb *)0;
		tp->so_state = SS_WAITING;
		tp->t_maxseg = TCP_DEFMAXSEG;	/* until changed by other end */
	}
	tp->so_state |= SS_OPEN;
	tp->so_dev = dev;
	tp->so_rq = q;
	tp->so_wq = WR(q);
	q->ptr = (caddr_t)tp;
	WR(q)->flag |= QBIGB;
	WR(q)->ptr = (caddr_t)tp;
	--tcp_busy;
	if(tp->so_state & SS_PLEASEOPEN){
		tp->so_state &= ~SS_PLEASEOPEN;
		qenable(WR(q));		/* to force out rcv wnd update */
	}
	return(1);
}

tcpdclose(q)
register struct queue *q;
{
	struct tcpcb *tp;

	tp = (struct tcpcb *)q->ptr;
	tcp_busy++;
	if(tp == 0){
		--tcp_busy;
		return;
	}
	tp->so_state &= ~(SS_OPEN|SS_WAITING);
	tp->so_rq = tp->so_wq = (struct queue *) 0;
	tp->so_wcount = tp->so_rcount = 0;
	if(tp->t_state > TCPS_LISTEN)
		tcp_disconnect(tp);
	else
		tcp_close(tp);
	--tcp_busy;
}

tcpdput(q, bp)
register struct queue *q;
register struct block *bp;
{
	int s, x;
	struct tcpcb *tp;
	struct block *nbp;
	struct foo {
		int com;
		struct tcpuser rep;
	};
	struct foo *fp;

	tp = (struct tcpcb *)q->ptr;
	switch(bp->type){
	case M_IOCTL:
		bp->type = M_IOCACK;
		switch(stiocom(bp)){
		case TIOCSETP:
		case TIOCSETN:
			x = ((struct ttydevb *)stiodata(bp))->ispeed;
			bp->wptr = bp->rptr;
			bp->type = M_IOCACK;
			qreply(q, bp);
			if(x == 0)
				qpctl(OTHERQ(q), M_HANGUP);
			return;
		case TIOCGETP:
			((struct ttydevb *)stiodata(bp))->ispeed =
			  ((struct ttydevb *)stiodata(bp))->ospeed = B9600;
			break;
		case TCPGETADDR:
			nbp = allocb(sizeof(struct foo));
			if (nbp == NULL) {
				bp->type = M_IOCNAK;
				break;
			}
			nbp->type = M_IOCACK;
			fp = (struct foo *)nbp->rptr;
			fp->com = stiocom(bp);
			fp->rep.lport = tp->so_lport;
			fp->rep.fport = tp->so_fport;
			fp->rep.laddr = tp->so_laddr;
			fp->rep.faddr = tp->so_faddr;
			nbp->wptr = nbp->rptr + sizeof(struct foo);
			freeb(bp);
			bp = nbp;
			break;
		case TCPIOHUP:
			tp->so_state |= SS_HANGUP;
			bp->wptr = bp->rptr;
			bp->type = M_IOCACK;
			qreply(q, bp);
			return;
		default:
			bp->type = M_IOCNAK;
		}
		qreply(q, bp);
		return;
	case M_DATA:
		if(socantsendmore(tp)){
			freeb(bp);
			return;
		}
		s = spl6();
		if (bp->wptr!=bp->rptr)
			tp->so_delimcnt = 0;
		if (bp->class&S_DELIM)
			tp->so_delimcnt++;	/* 2 delims are logical EOF */
		if (bp->wptr==bp->rptr){
			freeb(bp);
		} else {
			tp->so_wcount += BLEN(bp);	/* BEFORE the putq */
			putq(q, bp);
		}
		splx(s);
		if(tp->so_delimcnt
		 || (tp->so_options&(SO_ACCEPTCONN|SS_WAITING)) == 0)
			qenable(q);
		break;
	default:
		freeb(bp);
		break;
	}
}

tcpdosrv(q)
struct queue *q;
{
	register struct tcpcb *tp;

	if((tp = (struct tcpcb *)q->ptr) == 0)
		return;
	if (tp->so_state&SS_WCLOSED)
		return;
	tcp_busy++;
	if(tp->so_delimcnt > 1){
		tp->so_state |= SS_WCLOSED;
		tp = tcp_usrclosed(tp);
		if(tp)
			tcp_output(tp);
	} else if ((tp->so_options & SO_ACCEPTCONN) == 0
	       &&  (tp->so_state & SS_WAITING) == 0)
		tcp_output(tp);
	else
		tcpduser(tp);
	--tcp_busy;
}

tcpdrint(bp, tp)
register struct block *bp;
register struct tcpcb *tp;
{
	register struct block *bp1;

	if (tp->so_rq == NULL) {
		printf("tcpdrint but no tp->so_rq\n");
		bp_free(bp);
		return -1;
	}
	while(bp){
		bp1 = bp->next;
		tp->so_rcount += bp->wptr - bp->rptr;
		if(bp->wptr <= bp->rptr)
			freeb(bp);
		else
			putq(tp->so_rq, bp);
		bp = bp1;
	}
	return 0;
}

tcpdisrv(q)
register struct queue *q;
{
	register struct tcpcb *tp = (struct tcpcb *)(q->ptr);
	register struct block *bp;

	while ((q->next->flag&QFULL) == 0 && (bp = getq(q)) != NULL) {
		if(bp->type == M_DATA)
			tp->so_rcount -= bp->wptr - bp->rptr;
		if(tp->so_rcount < 0){
			printf("so_rcount %d\n", tp->so_rcount);
			if(tp->so_state & SS_OPEN)
				panic("so_rcount");
		}
		(*q->next->qinfo->putp)(q->next, bp);
	}
	if(q->count <= q->qinfo->lolimit)
		qenable(OTHERQ(q));	/* update remote send window */
}

/*
 * here if channel isn't connected yet:
 * output `data' is a user command
 * grab all the data written so far,
 * even though we'll discard the part we don't use
 */
tcpduser(tp)
register struct tcpcb *tp;
{
	extern struct ipif *ip_ifwithaddr();
	struct tcpuser *tu;
	struct block *bp, *tail, *head;

	tail = head = NULL;
	while(bp = getq(tp->so_wq)){
		if(bp->type != M_DATA){
			freeb(bp);
			continue;
		}
		bp->next = NULL;
		if (head == NULL)
			head = bp;
		else
			tail->next = bp;
		tail = bp;
	}
	if(head == NULL)
		return;
	tp->so_wcount = 0;
	bp = head;
	if(BLEN(bp) < sizeof(struct tcpuser)){
		bp_free(bp);
		return;
	}

	/*
	 *  tu->code is a request to bind this channel
	 */
	tu = (struct tcpuser *)bp->rptr;
	switch(tu->code){
	case TCPC_CONNECT:
		if(tp->t_state != TCPS_CLOSED){
			tu->code = TCPC_BADDEV;
			goto bad;
		}
		if (tu->laddr != INADDR_ANY) {
			/* has the user specified a legal local address? */
			if (ip_ifwithaddr(tu->laddr) == 0) {
				tu->code = TCPC_BADLOCAL;
				goto bad;
			}
		} else {
			/* pick a local address related to the destination */
			tu->laddr = ip_hoston(tu->faddr);
			if(tu->laddr == INADDR_ANY) {
				tu->code = TCPC_NOROUTE;
				goto bad;
			}
		}
		tp->so_options = tu->param & ~SO_ACCEPTCONN;
		if(tcpcb_bind(tp, tu->laddr, tu->lport)) {
			tu->code = TCPC_BOUND;
			goto bad;
		}
		tp->so_fport = tu->fport;
		tp->so_faddr = tu->faddr;
		tcp_template(tp);
		if(tp->t_template == (struct block *)0) {
			printf("no template - ");
			tu->code = TCPC_BADDEV;
			goto bad;
		}
		if (tp->so_options & SO_KEEPALIVE)
			tcp_timers(tp, TCPT_KEEP);
		tp->t_state = TCPS_SYN_SENT;
		tp->iss = tcp_iss; tcp_iss += TCP_ISSINCR/2;
		tcp_sendseqinit(tp);
		tp->so_state &= ~SS_WAITING;
		tcp_output(tp);
		break;
	case TCPC_LISTEN:
		if(tp->t_state != TCPS_CLOSED){
			tu->code = TCPC_BADDEV;
			goto bad;
		}
		if (tu->laddr != INADDR_ANY) {
			/* has the user specified a legal local address? */
			if (ip_ifwithaddr(tu->laddr) == 0) {
				tu->code = TCPC_BADLOCAL;
				goto bad;
			}
		}
		tp->so_options = tu->param & ~SO_ACCEPTCONN;
		if(tcpcb_bind(tp, tu->laddr, tu->lport)) {
			tu->code = TCPC_BOUND;
			goto bad;
		}
		tp->t_state = TCPS_LISTEN;
		tp->so_options |= SO_ACCEPTCONN;
		tp->so_fport = tu->fport==0 ? TCPPORT_ANY : tu->fport;
		tp->so_faddr = tu->faddr;
		tcp_template(tp);
		if(tp->t_template == 0) {
			tu->code = TCPC_BADDEV;
			printf("no template - ");
			goto bad;
		}
		tp->so_state &= ~SS_WAITING;
		if (tcp_isconnected(tp)<0) {
			tu->code = TCPC_BADDEV;
			goto bad;
		}
		break;
	default:
		tu->code = TCPC_SORRY;
		goto bad;
	}
	bp_free(bp);
	return;
bad:
	/*
	 *  send reason for failure back to user.
	 */
	if(bp->next) {
		bp_free(bp->next);
		bp->next = 0;
	}
	(*tp->so_rq->next->qinfo->putp)(tp->so_rq->next, bp);
	tcp_hungup(tp);
}

struct tcpcb *
tcp_disconnect(tp)
register struct tcpcb *tp;
{

	if(tp->t_state < TCPS_ESTABLISHED)
		tp = tcp_close(tp);
	else {
		tp->so_state &= ~SS_PLEASEOPEN;
		tcp_hungup(tp);	/* sends M_HANGUP */
		tp = tcp_usrclosed(tp);
		if(tp)
			tcp_output(tp);
	}
	return(tp);
}

struct tcpcb *
tcp_usrclosed(tp)
register struct tcpcb *tp;
{

	switch(tp->t_state){

	case TCPS_CLOSED:
	case TCPS_LISTEN:
	case TCPS_SYN_SENT:
		tp->t_state = TCPS_CLOSED;
		tp = tcp_close(tp);
		break;

	case TCPS_SYN_RECEIVED:
	case TCPS_ESTABLISHED:
		tp->t_state = TCPS_FIN_WAIT_1;
		tp->so_options |= SO_KEEPALIVE;
		tcp_timers(tp, TCPT_KEEP);
		break;

	case TCPS_CLOSE_WAIT:
		tp->t_state = TCPS_LAST_ACK;
		tp->so_options |= SO_KEEPALIVE;
		tcp_timers(tp, TCPT_KEEP);
		break;
	}
	if(tp && tp->t_state >= TCPS_FIN_WAIT_2) {
		tp->so_state &= ~(SS_PLEASEOPEN|SS_RCVATMARK);
		if(tp->so_state&SS_OPEN && !(tp->so_state&SS_HUNGUP)) {
			tp->so_state |= SS_HUNGUP;
			tcp_hungup(tp);
		}
	}
	return(tp);
}

tcp_isconnected(tp)
struct tcpcb *tp;
{
	struct block *bp;
	struct tcpuser *tu;
	struct tcpcb *rtp;

	if(tp->so_head)
		rtp = tp->so_head;
	else
		rtp = tp;
	if((rtp->so_state & SS_OPEN) == 0){
		printf("isconnected, no fd ref\n");
		return -1;
	}
	bp = allocb(sizeof(struct tcpuser));
	if(bp == 0)
		return -1;
	bp->wptr += sizeof(struct tcpuser);
	tu = (struct tcpuser *)bp->rptr;
	tu->code = TCPC_OK;
	tu->fport = tp->so_fport;
	tu->faddr = tp->so_faddr;
	tu->lport = tp->so_lport;
	tu->laddr = tp->so_laddr;
	tu->param = tp->so_dev;
	bp->class |= S_DELIM;
	return tcpdrint(bp, rtp);
}

tcp_hungup(tp)
register struct tcpcb *tp;
{
	register struct queue *q;

	q = tp->so_rq;
	if(q == 0)
		return;
	qpctl(q, M_HANGUP);
}

/*
 * find a spare even-numbered tcp device for a new passive-end
 * connection.
 */
struct tcpcb *
tcp_newconn(tp)
register struct tcpcb *tp;
{
	register struct tcpcb *ntp;
	register struct tcpcb *tend;

	if(tp->so_rq && (tp->so_rq->flag&QFULL)){
		printf("listen %d q full\n", tp->so_lport);
		return(0);
	}
	tend = &tcpcb[tcpcnt];
	for(ntp = &tcpcb[0]; ntp < tend; ntp += 2){
		if(ntp->t_state == TCPS_CLOSED){
			bzero(ntp, sizeof(struct tcpcb));
			ntp->t_maxseg = TCP_DEFMAXSEG;
			ntp->so_options = tp->so_options & SO_KEEPALIVE;
			ntp->so_head = tp;
			ntp->so_dev = ntp - tcpcb;
			return(ntp);
		}
	}
	return(0);
}

struct tcpcb *
tcpcb_lookup(faddr, fport, laddr, lport)
register in_addr faddr, laddr;
register tcp_port fport, lport;
{
	register struct tcpcb *tp;
	int highscore = 0, score;
	struct tcpcb *match = 0;
	struct tcpcb *tend;

	tend = &tcpcb[tcpcnt];
	for(tp = &tcpcb[0]; tp < tend; tp++){
		if(tp->t_state == TCPS_CLOSED)
			continue;
		score = 22;
		if(tp->so_faddr != faddr) {
			if(tp->so_faddr != INADDR_ANY)
				continue;
			else
				score -=8;
		}
		if(tp->so_fport != fport) {
			if(tp->so_fport != TCPPORT_ANY)
				continue;
			else
				score -=8;
		}
		if(tp->so_laddr != laddr) {
			if(tp->so_laddr == in_netof(laddr))
				score -=1;
			else if(tp->so_laddr != INADDR_ANY)
				continue;
			else
				score -=2;
		}
		if(tp->so_lport != lport) {
			if(tp->so_lport != TCPPORT_ANY)
				continue;
			else
				score -=4;
		}
		if (score==22)
			return tp;
		if (score<highscore)
			continue;
		match = tp;
		highscore = score;
	}
	return(match);
}

/* n chars were acked; drop them now */
sbsnddrop(tp, n)
register struct tcpcb *tp;
register int n;
{
	register struct queue *q;
	register int i;
	register struct block *bp;

	q = tp->so_wq;
	if(q == 0)
		return;
	bp = 0;
	while(n > 0 && (bp = getq(q))){
		i = MIN(BLEN(bp), n);
		bp->rptr += i;
		n -= i;
		tp->so_wcount -= i;
		if(bp->rptr >= bp->wptr){
			freeb(bp);
			bp = 0;
		} else if(n > 0){
			panic("sbsnddrop");
		}
	}
	if(bp)
		putbq(q, bp);
}

static tcp_port portnext[] = { 600, 1024 };
static tcp_port portlow[] = { 600, 1024 };
static tcp_port porthigh[] = { 1024, 2048 };

tcpcb_bind(tp, addr, port)
register struct tcpcb *tp;
register in_addr addr;
register tcp_port port;
{
	register int i;

	tp->so_lport = 0;
	if(port){
		/*
		 *  Don't let just anyone get a trusted port.
		 */
		if(port<porthigh[0] && u.u_uid!=0)
			return(1);
		tp->so_lport = port;
		tp->so_laddr = addr;
		return(0);
	}
	/*
	 *  No specific port, pick one.  Root only gets a trusted
	 *  port if explicitly requested, so privileged programs
	 *  can make non-privileged calls.
	 */
	i = (u.u_uid==0 && (tp->so_options&SO_TRUSTED)) ? 0 : 1;
	if(portnext[i] >= porthigh[i])
		portnext[i] = portlow[i];
	port = portnext[i];
	while(1){
		if(tcpcb_bind(tp, addr, portnext[i]) == 0){
			portnext[i]++;
			return(0);
		}
		portnext[i]++;
		if(portnext[i] >= porthigh[i])
			portnext[i] = portlow[i];
		if(portnext[i] == port)	/* tried them all */
			break;
	}
	return(1);
}

tcp_cantrcvmore(tp)
register struct tcpcb *tp;
{
	register struct queue *q;

	q = tp->so_rq;
	if(q == NULL)
		return;
	if(tp->so_state & SS_HANGUP)
		qpctl(q, M_HANGUP);
	else {
		/* two delims ensure a zero length read at the process */
		qpctld(q, M_DATA);
		qpctld(q, M_DATA);
	}
}