V9/sys/inet/tcp_device.c

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

/*
 * tcp_device.c
 */

#include "tcp.h"
#if NTCP
#include "../h/param.h"
#include "../h/systm.h"
#include "../h/stream.h"
#include "../h/inio.h"
#include "../h/ttyio.h"
#include "../h/ttyld.h"
#include "../h/buf.h"
#include "../h/conf.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/inet/in.h"
#include "../h/inet/mbuf.h"
#include "../h/inet/tcp.h"
#include "../h/inet/tcp_timer.h"
#include "../h/inet/tcp_seq.h"
#include "../h/inet/tcp_var.h"
#include "../h/inet/tcp_user.h"
#include "../h/inet/tcp_fsm.h"
#include "../h/inet/socket.h"

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

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

int Ntcp = NTCP;	/* for netstat */
struct tcpcb tcpcb[NTCP];
struct socket tcpsocks[NTCP];

tcpdopen(q, dev)
register struct queue *q;
dev_t dev;
{
	struct socket *so;

	dev = minor(dev);
	if(dev >= NTCP)
		return(0);
	so = &tcpsocks[dev];
	if((dev&01) == 0 && (so->so_state&SS_ACTIVE) == 0)
		return(0);
	if(so->so_state&SS_WAITING)
		return(0);
	if((so->so_options&SO_ACCEPTCONN) && (so->so_state&SS_OPEN))
		return(0);
	if((so->so_state & (SS_ACTIVE|SS_OPEN|SS_PLEASEOPEN)) == SS_ACTIVE)
		return(0);
	if(q->ptr && dev&01)	/* re-opening outgoing port */
		return(0);
	if(q->ptr)
		return(1);
	tcp_busy++;
	if((so->so_state & SS_PLEASEOPEN) == 0){
		bzero(so, sizeof(struct socket));
		so->so_state |= SS_WAITING;
	}
	so->so_state |= SS_OPEN;
	so->so_dev = dev;
	so->so_rq = q;
	so->so_wq = WR(q);
	q->ptr = (caddr_t)so;
	WR(q)->flag |= QNOENB|QBIGB;
	WR(q)->ptr = (caddr_t)so;
	--tcp_busy;
	if(so->so_state & SS_PLEASEOPEN){
		so->so_state &= ~SS_PLEASEOPEN;
		qenable(WR(q));		/* to force out rcv wnd update */
	}
	return(1);
}

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

	so = (struct socket *)q->ptr;
	tcp_busy++;
	so->so_state &= ~(SS_OPEN|SS_WAITING);
	so->so_rq = so->so_wq = (struct queue *) 0;
	so->so_wcount = so->so_rcount = 0;
	tp = so->so_tcpcb;
	if(tp == 0){
		--tcp_busy;
		return;
	}
	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;
{
	struct socket *so;
	int s, x;
	register union stmsg *sp;
	struct tcpcb *tp;
	struct block *nbp;
	struct foo {
		int com;
		struct tcpuser rep;
	};
	struct foo *fp;

	so = (struct socket *)q->ptr;
	switch(bp->type){
	case M_IOCTL:
		sp = (union stmsg *)bp->rptr;
		bp->type = M_IOCACK;
		switch(sp->ioc0.com){
		case TIOCSETP:
		case TIOCSETN:
			x = sp->ioc1.sb.sg_ispeed;
			bp->wptr = bp->rptr;
			bp->type = M_IOCACK;
			qreply(q, bp);
			if(x == 0)
				tcp_putctl(OTHERQ(q), M_HANGUP);
			return;
		case TIOCGETP:
			sp->ioc1.sb.sg_ispeed =
			  sp->ioc1.sb.sg_ospeed = B9600;
			break;
		case TCPGETADDR:
			nbp = allocb(sizeof(struct foo));
			if (nbp == NULL) {
				bp->type = M_IOCNAK;
				printf("tcpdput: out of blocks\n");
				break;
			}
			nbp->type = M_IOCACK;
			fp = (struct foo *)nbp->rptr;
			fp->com = sp->ioc1.com;
			fp->rep.lport = so->so_lport;
			fp->rep.fport = so->so_fport;
			fp->rep.laddr = so->so_laddr;
			fp->rep.faddr = so->so_faddr;
			nbp->wptr = nbp->rptr + sizeof(struct foo);
			freeb(bp);
			bp = nbp;
			break;
		case TCPIOHUP:
			so->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(so)){
			freeb(bp);
			return;
		}
		s = spl6();
		so->so_delimcnt = 0;
		so->so_wcount += BLEN(bp);	/* BEFORE the putq */
		putq(q, bp);
		splx(s);
		if((so->so_options&SO_ACCEPTCONN) == 0
		   && (so->so_state&SS_WAITING) == 0
		   && (tp = so->so_tcpcb)){
			/* so as to invoke tcp_output() */
			if(tp->t_state > TCPS_CLOSE_WAIT)
				printf("data after CLOSE_WAIT?\n");
			qenable(q);
		}
		break;
	case M_DELIM:
		if(so->so_options&SO_ACCEPTCONN){
			printf("DELIM on listener\n");
		} else if(so->so_state & SS_WAITING){
			putq(q, bp);
			/* to invoke tcpduser */
			qenable(q);
		} else {
			/* two back to back delims constitute logical eof */
			freeb(bp);
			s = spl6();
			if(socantsendmore(so)){
				splx(s);
				return;
			}
			if(++(so->so_delimcnt) > 1){
				splx(s);
				qenable(q);
			} else
				splx(s);
		}
		break;
	default:
		freeb(bp);
		break;
	}
}

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

	so = (struct socket *)q->ptr;
	if (so->so_state&SS_WCLOSED)
		return;
	tcp_busy++;
	if(so->so_delimcnt > 1){
		so->so_state |= SS_WCLOSED;
		if((tp = so->so_tcpcb) == 0){
			printf("delimcnt but no tp\n");
			--tcp_busy;
			return;
		}
		tp = tcp_usrclosed(tp);
		if(tp)
			tcp_output(tp);
	} else {
		if((so->so_options&SO_ACCEPTCONN) == 0
		   && (so->so_state&SS_WAITING) == 0
		   && (tp = so->so_tcpcb)){
			tcp_output(tp);
		} else {
			tcpduser(so);
		}
	}
	--tcp_busy;
}

tcpdrint(bp, so)
register struct block *bp;
struct socket *so;
{
	register struct block *bp1;
	register struct queue *q;

	q = so->so_rq;
	if(q){
		while(bp){
			bp1 = bp->next;
			so->so_rcount += bp->wptr - bp->rptr;
			if(bp->wptr == bp->rptr)
				freeb(bp);
			else
				putq(q, bp);
			bp = bp1;
		}
	} else {
		printf("tcpdrint but no so->so_rq\n");
		bp_free(bp);
	}
}

tcpdisrv(q)
struct queue *q;
{
	struct socket *so = (struct socket *)(q->ptr);
	struct block *bp;

	while((q->next->flag&QFULL) == 0){
		if(bp = getq(q)){
			if(bp->type == M_DATA)
				so->so_rcount -= bp->wptr - bp->rptr;
			if(so->so_rcount < 0)
				panic("so_rcount");
			(*q->next->qinfo->putp)(q->next, bp);
		} else
			break;
	}
	if(q->count <= q->qinfo->lolimit)
		qenable(OTHERQ(q));	/* update remote send window */
}

/*
 * imitation tcp_usrreq
 */
tcpduser(so)
register struct socket *so;
{
	extern struct ipif *ip_ifwithaddr();
	struct tcpuser *tu;
	struct block *bp, *bp1, *head;
	register struct tcpcb *tp;

	bp = bp1 = head = NULL;
	while(bp = getq(so->so_wq)){
		if(bp->type != M_DATA){
			freeb(bp);
		} else if(bp1 == NULL){
			bp1 = head = bp;
			bp->next = NULL;
		} else {
			bp1->next = bp;
			bp1 = bp;
			bp->next = NULL;
		}
	}
	if(head == NULL)
		return;
	so->so_wcount = 0;
	bp = head;
	if(BLEN(bp) < sizeof(struct tcpuser)){
/*
		printf("tcpuser short\n");
*/
		bp_free(bp);
		return;
	}
	tu = (struct tcpuser *)bp->rptr;
	if(so->so_tcpcb)
		printf("%d: tcpduser w/ tcpcb\n", so->so_dev);
	switch(tu->code){
	case TCPC_CONNECT:
		if(so->so_state&SS_ACTIVE)
			goto bad;
		if (tu->laddr != INADDR_ANY) {
			/* has the user has specified a legal local address? */
			if (ip_ifwithaddr(tu->laddr) == 0)
				goto bad;
		} else {
			/* pick a local address related to the destination */
			tu->laddr = ip_hoston(tu->faddr);
			if(tu->laddr == INADDR_ANY)
				goto bad;
		}
		if(tcp_attach(so))
			goto bad;
		if(sobind(so, tu->laddr, tu->lport))
			goto bad;
		tp = so->so_tcpcb;
		so->so_fport = tu->fport;
		so->so_faddr = tu->faddr;
		so->so_options = tu->param;
                if (so->so_options & SO_KEEPALIVE)
			tcp_timers(tp, TCPT_KEEP);
		tp->t_template = tcp_template(tp);
		if(tp->t_template == 0)
			goto bad;
		soisconnecting(so);
		tp->t_state = TCPS_SYN_SENT;
		tp->t_timer[TCPT_KEEP] = TCPTV_KEEP;
		tp->iss = tcp_iss; tcp_iss += TCP_ISSINCR/2;
		tcp_sendseqinit(tp);
		so->so_state &= ~SS_WAITING;
		tcp_output(tp);
		break;
	case TCPC_LISTEN:
		if(so->so_state&SS_ACTIVE)
			goto bad;
		if (tu->laddr != INADDR_ANY) {
			/* has the user has specified a legal local address? */
			if (ip_ifwithaddr(tu->laddr) == 0)
				goto bad;
		}
		if(tcp_attach(so))
			goto bad;
		if(sobind(so, tu->laddr, tu->lport))
			goto bad;
		tp = so->so_tcpcb;
		tp->t_state = TCPS_LISTEN;
		so->so_options |= SO_ACCEPTCONN;
		so->so_fport = tu->fport==0 ? TCPPORT_ANY : tu->fport;
		so->so_faddr = tu->faddr;
		so->so_state &= ~SS_WAITING;
		tcp_isconnected(so);
		break;
	default:
		goto bad;
	}
	bp_free(bp);
	return;
bad:
	bp_free(bp);
	tcp_hungup(so);
}

tcp_attach(so)
struct socket *so;
{
	register struct tcpcb *tp;
	extern struct tcpcb *tcp_newtcpcb();

	tp = tcp_newtcpcb(so);
	if(tp == 0)
		return(1);
	tp->t_socket = so;
	tp->t_state = TCPS_CLOSED;
	return(0);
}

struct tcpcb *
tcp_disconnect(tp)
register struct tcpcb *tp;
{
	struct socket *so = tp->t_socket;

	if(tp->t_state < TCPS_ESTABLISHED)
		tp = tcp_close(tp);
	else {
		soisdisconnecting(so);
		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->t_socket->so_options |= SO_KEEPALIVE;
		tcp_timers(tp, TCPT_KEEP);
		break;

	case TCPS_CLOSE_WAIT:
		tp->t_state = TCPS_LAST_ACK;
		tp->t_socket->so_options |= SO_KEEPALIVE;
		tcp_timers(tp, TCPT_KEEP);
		break;
	}
	if(tp && tp->t_state >= TCPS_FIN_WAIT_2)
		soisdisconnected(tp->t_socket);
	return(tp);
}

tcp_isconnected(so)
struct socket *so;
{
	struct block *bp;
	struct tcpuser *tu;
	struct socket *rso;

	if(so->so_head)
		rso = so->so_head;
	else
		rso = so;
	if((rso->so_state & SS_OPEN) == 0){
		printf("isconnected, no fd ref\n");
		return;
	}
	bp = allocb(64);
	if(bp == 0)
		return;
	bp->next = NULL;
	bp->type = M_DATA;
	bp->wptr += sizeof(struct tcpuser);
	tu = (struct tcpuser *)bp->rptr;
	tu->code = TCPC_OK;
	tu->fport = so->so_fport;
	tu->faddr = so->so_faddr;
	tu->lport = so->so_lport;
	tu->laddr = so->so_laddr;
	tu->param = so->so_dev;
	tcpdrint(bp, rso);
}

tcp_hungup(so)
register struct socket *so;
{
	register struct queue *q;

	q = so->so_rq;
	if(q == 0)
		return;
	tcp_putctl(q, M_HANGUP);
}

/*
 * find a spare odd tcp device for a new passive-end
 * connection.
 */
struct socket *
tcp_newconn(so)
struct socket *so;
{
	struct socket *nso;

	if(so->so_rq && (so->so_rq->flag&QFULL)){
		printf("listen %d q full\n", so->so_lport);
		return(0);
	}
	for(nso = &tcpsocks[0]; nso < &tcpsocks[NTCP]; nso += 2){
		if((nso->so_state & (SS_OPEN|SS_ACTIVE)) == 0){
			bzero(nso, sizeof(struct socket));
			nso->so_head = so;
			nso->so_dev = nso - tcpsocks;
			return(nso);
		}
	}
	return(0);
}

struct socket *
so_lookup(faddr, fport, laddr, lport)
in_addr faddr, laddr;
tcp_port fport, lport;
{
	register struct socket *so, *match = 0;
	int highscore = 0, score;

	for(so = &tcpsocks[0]; so < &tcpsocks[NTCP]; so++){
		if(so->so_tcpcb == 0)
			continue;
		if((so->so_state&(SS_OPEN|SS_ACTIVE)) == 0)
			continue;
		if(so->so_state & SS_WAITING)
			continue;
		score = 11;
		if(so->so_faddr != faddr) {
			if(so->so_faddr != INADDR_ANY)
				continue;
			else
				score -=4;
		}
		if(so->so_fport != fport) {
			if(so->so_fport != TCPPORT_ANY)
				continue;
			else
				score -=4;
		}
		if(so->so_laddr != laddr) {
			if(so->so_laddr != INADDR_ANY)
				continue;
			else
				score -=1;
		}
		if(so->so_lport != lport) {
			if(so->so_lport != TCPPORT_ANY)
				continue;
			else
				score -=2;
		}
		if (score==11)
			return so;
		if (score<highscore)
			continue;
		match = so;
		highscore = score;
	}
	return(match);
}

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

	q = so->so_wq;
	if(q == 0)
		return;
	bp = 0;
	while(n > 0 && (bp = getq(q))){
		i = MIN(BLEN(bp), n);
		bp->rptr += i;
		n -= i;
		so->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 };

sobind(so, addr, port)
register struct socket *so;
register in_addr addr;
register tcp_port port;
{
	register struct socket *sp;
	register int i;

	so->so_lport = 0;
	if(port){
		/* what about, for instance, restarting rlogind when
		 * people are rlogin'd here?
		 */
		for(sp = &tcpsocks[0]; sp < &tcpsocks[NTCP]; sp++){
			if(sp->so_tcpcb == 0)
				continue;
			if((sp->so_state&(SS_OPEN|SS_ACTIVE)) == 0)
				continue;
			if(sp->so_lport == port && sp->so_laddr == addr) {
				return(1);
			}
		}
		so->so_lport = port;
		so->so_laddr = addr;
		return(0);
	}
	/* pick one for him */
	i = u.u_uid==0 ? 0 : 1;
	if(portnext[i] >= porthigh[i])
		portnext[i] = portlow[i];
	port = portnext[i];
	while(1){
		if(sobind(so, 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(so)
register struct socket *so;
{
	register struct queue *q;

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

tcp_putctl(q, c)
register struct queue *q;
{
	register struct block *bp;

	if ((bp = allocb(0)) == NULL) {
		printf("tcp_putctl: no more blocks\n");
		return(0);
	}
	bp->type = c;
	putq(q, bp);
	return(1);
}
#endif