#include "global.h" #include "tcp.h" #include "socket.h" #include "usock.h" static void s_trcall __ARGS((struct tcb *tcb,int cnt)); static void s_tscall __ARGS((struct tcb *tcb,int old,int new)); static void s_ttcall __ARGS((struct tcb *tcb,int cnt)); static void trdiscard __ARGS((struct tcb *tcb,int cnt)); static void autobind __ARGS((struct usock *up)); int16 Lport = 1024; int so_tcp(up,protocol) struct usock *up; int protocol; { up->type = TYPE_TCP; strcpy(up->eol,INET_EOL); return 0; } int so_tcp_listen(up,backlog) struct usock *up; int backlog; { int s; struct sockaddr_in *local; struct socket lsock; s = up - Usock + SOCKBASE; if(up->name == NULLCHAR) autobind(up); local = (struct sockaddr_in *)up->name; lsock.address = local->sin_addr.s_addr; lsock.port = local->sin_port; up->cb.tcb = open_tcp(&lsock,NULLSOCK, backlog ? TCP_SERVER:TCP_PASSIVE,0, s_trcall,s_ttcall,s_tscall,up->tos,s); return 0; } int so_tcp_conn(up) struct usock *up; { int s; struct tcb *tcb; struct socket lsock,fsock; struct sockaddr_in *local,*remote; if(up->name == NULLCHAR) { autobind(up); } if(checkipaddr(up->peername,up->namelen) == -1){ errno = EAFNOSUPPORT; return -1; } s = up - Usock + SOCKBASE; /* Construct the TCP-style ports from the sockaddr structs */ local = (struct sockaddr_in *)up->name; remote = (struct sockaddr_in *)up->peername; if(local->sin_addr.s_addr == INADDR_ANY) /* Choose a local address */ local->sin_addr.s_addr = locaddr(remote->sin_addr.s_addr); lsock.address = local->sin_addr.s_addr; lsock.port = local->sin_port; fsock.address = remote->sin_addr.s_addr; fsock.port = remote->sin_port; /* Open the TCB in active mode */ up->cb.tcb = open_tcp(&lsock,&fsock,TCP_ACTIVE,0, s_trcall,s_ttcall,s_tscall,up->tos,s); /* Wait for the connection to complete */ while((tcb = up->cb.tcb) != NULLTCB && tcb->state != TCP_ESTABLISHED){ if(up->noblock){ errno = EWOULDBLOCK; return -1; } else if((errno = pwait(up)) != 0){ return -1; } } if(tcb == NULLTCB){ /* Probably got refused */ free(up->peername); up->peername = NULLCHAR; errno = ECONNREFUSED; return -1; } return 0; } int so_tcp_recv(up,bpp,from,fromlen) struct usock *up; struct mbuf **bpp; char *from; int *fromlen; { int cnt; struct tcb *tcb; while((tcb = up->cb.tcb) != NULLTCB && tcb->r_upcall != trdiscard && (cnt = recv_tcp(tcb,bpp,(int16)0)) == -1){ if(up->noblock){ errno = EWOULDBLOCK; return -1; } else if((errno = pwait(up)) != 0){ return -1; } } if(tcb == NULLTCB){ /* Connection went away */ errno = ENOTCONN; return -1; } else if(tcb->r_upcall == trdiscard){ /* Receive shutdown has been done */ errno = ENOTCONN; /* CHANGE */ return -1; } return cnt; } int so_tcp_send(up,bp,to) struct usock *up; struct mbuf *bp; char *to; { struct tcb *tcb; int cnt; if((tcb = up->cb.tcb) == NULLTCB){ free_p(bp); errno = ENOTCONN; return -1; } cnt = send_tcp(tcb,bp); while((tcb = up->cb.tcb) != NULLTCB && tcb->sndcnt > tcb->window){ /* Send queue is full */ if(up->noblock){ errno = EWOULDBLOCK; return -1; } else if((errno = pwait(up)) != 0){ return -1; } } if(tcb == NULLTCB){ errno = ENOTCONN; return -1; } return cnt; } int so_tcp_qlen(up,rtx) struct usock *up; int rtx; { int len; switch(rtx){ case 0: len = up->cb.tcb->rcvcnt + len_p(up->ibuf); break; case 1: len = up->cb.tcb->sndcnt + len_p(up->obuf); break; } return len; } int so_tcp_kick(up) struct usock *up; { kick_tcp(up->cb.tcb); return 0; } int so_tcp_shut(up,how) struct usock *up; int how; { switch(how){ case 0: /* No more receives -- replace upcall */ up->cb.tcb->r_upcall = trdiscard; break; case 1: /* Send EOF */ close_tcp(up->cb.tcb); break; case 2: /* Blow away TCB */ reset_tcp(up->cb.tcb); up->cb.tcb = NULLTCB; break; } return 0; } int so_tcp_close(up) struct usock *up; { if(up->cb.tcb != NULLTCB){ /* In case it's been reset */ up->cb.tcb->r_upcall = trdiscard; /* Tell the TCP_CLOSED upcall there's no more socket */ up->cb.tcb->user = -1; close_tcp(up->cb.tcb); } return 0; } /* TCP receive upcall routine */ static void s_trcall(tcb,cnt) struct tcb *tcb; int cnt; { /* Wake up anybody waiting for data, and let them run */ psignal(itop(tcb->user),1); pwait(NULL); } /* TCP transmit upcall routine */ static void s_ttcall(tcb,cnt) struct tcb *tcb; int cnt; { /* Wake up anybody waiting to send data, and let them run */ psignal(itop(tcb->user),1); pwait(NULL); } /* TCP state change upcall routine */ static void s_tscall(tcb,old,new) struct tcb *tcb; int old,new; { int s,ns; struct usock *up,*nup,*oup; union sp sp; s = tcb->user; oup = up = itop(s); switch(new){ case TCP_CLOSED: /* Clean up. If the user has already closed the socket, * then up will be null (s was set to -1 by the close routine). * If not, then this is an abnormal close (e.g., a reset) * and clearing out the pointer in the socket structure will * prevent any further operations on what will be a freed * control block. Also wake up anybody waiting on events * related to this tcb so they will notice it disappearing. */ if(up != NULLUSOCK){ up->cb.tcb = NULLTCB; free_p(up->obuf); up->obuf = NULLBUF; free_p(up->ibuf); up->ibuf = NULLBUF; up->errcodes[0] = tcb->reason; up->errcodes[1] = tcb->type; up->errcodes[2] = tcb->code; } del_tcp(tcb); break; case TCP_SYN_RECEIVED: /* Handle an incoming connection. If this is a server TCB, * then we're being handed a "clone" TCB and we need to * create a new socket structure for it. In either case, * find out who we're talking to and wake up the guy waiting * for the connection. */ if(tcb->flags.clone){ /* Clone the socket */ ns = socket(AF_INET,SOCK_STREAM,0); nup = itop(ns); ASSIGN(*nup,*up); tcb->user = ns; nup->cb.tcb = tcb; /* Allocate new memory for the name areas */ nup->name = mallocw(SOCKSIZE); nup->peername = mallocw(SOCKSIZE); /* Store the new socket # in the old one */ up->rdysock = ns; up = nup; s = ns; } else { /* Allocate space for the peer's name */ up->peername = mallocw(SOCKSIZE); /* Store the old socket # in the old socket */ up->rdysock = s; } /* Load the addresses. Memory for the name has already * been allocated, either above or in the original bind. */ sp.p = up->name; sp.in->sin_family = AF_INET; sp.in->sin_addr.s_addr = up->cb.tcb->conn.local.address; sp.in->sin_port = up->cb.tcb->conn.local.port; up->namelen = SOCKSIZE; sp.p = up->peername; sp.in->sin_family = AF_INET; sp.in->sin_addr.s_addr = up->cb.tcb->conn.remote.address; sp.in->sin_port = up->cb.tcb->conn.remote.port; up->peernamelen = SOCKSIZE; /* Wake up the guy accepting it, and let him run */ psignal(oup,1); pwait(NULL); break; default: /* Ignore all other state transitions */ break; } psignal(up,0); /* In case anybody's waiting */ } /* Discard data received on a TCP connection. Used after a receive shutdown or * close_s until the TCB disappears. */ static void trdiscard(tcb,cnt) struct tcb *tcb; int cnt; { struct mbuf *bp; recv_tcp(tcb,&bp,(int16)cnt); free_p(bp); } /* Issue an automatic bind of a local address */ static void autobind(up) struct usock *up; { struct sockaddr_in local; int s; s = up - Usock + SOCKBASE; local.sin_family = AF_INET; local.sin_addr.s_addr = INADDR_ANY; local.sin_port = Lport++; bind(s,(char *)&local,sizeof(struct sockaddr_in)); } char * tcpstate(up) struct usock *up; { if(up->cb.tcb == NULLTCB) return NULLCHAR; return Tcpstates[up->cb.tcb->state]; } int so_tcp_stat(up) struct usock *up; { st_tcp(up->cb.tcb); return 0; }