fix per Jeffrey Jongeward
[unix-history] / usr / src / sys / netinet / tcp_usrreq.c
index 4846f3f..b0ab7ea 100644 (file)
-/* tcp_usrreq.c 1.2 81/10/18 */
-#include "../h/param.h"
-#include "../bbnnet/net.h"
-#include "../bbnnet/tcp.h"
-#include "../bbnnet/ip.h"
-#include "../bbnnet/imp.h"
-#include "../bbnnet/ucb.h"
-#include "../bbnnet/fsm.h"
-#include "../bbnnet/tcp_pred.h"
-
-lis_cls(wp)             /* passive open (1) */
-struct work *wp;
-{
-       
-COUNT(LIS_CLS);
-       t_open(wp->w_tcb, PASSIVE);
-
-       return(LISTEN);
-}
-
-sys_cls(wp)             /* active open (6) */
-register struct work *wp;
-{
-
-COUNT(SYS_CLS);
-       t_open(wp->w_tcb, ACTIVE);
-       send_ctl(wp->w_tcb);            /* send SYN */
-
-       return(SYN_SENT);
-}
-
-cls_opn(wp)             /* close request before receiving foreign SYN (10) */
-struct work *wp;                           
-{
-
-COUNT(CLS_OPN);
-       t_close(wp->w_tcb, UCLOSED);
-
-       return(CLOSED);
-}
-
-cl2_clw(wp)             /* close request after receiving foreign FIN (13) */
-struct work *wp;
-{
-       register struct tcb *tp;
-
-COUNT(CL2_CLW);
-       tp = wp->w_tcb;
-
-       tp->snd_fin = TRUE;             /* send our own FIN */
-       send_ctl(tp);                   
-       tp->usr_closed = TRUE;
-
-       return(CLOSING2);
-}
-
-cls_rwt(wp)             /* rcv request after foreign close (20) */
-struct work *wp;
-{
-       register struct tcb *tp;
-
-COUNT(CLS_RWT);
-       tp = wp->w_tcb;
-
-       present_data(tp);       /* present any remaining data */
-
-       if (rcv_empty(tp)) {
-               t_close(tp, UCLOSED);
-               return(CLOSED);
-       } else
-               return(RCV_WAIT);
+/*     tcp_usrreq.c    1.57    82/04/30        */
 
 
-}
-
-fw1_syr(wp)             /* close request on synced connection (24,25) */
-struct work *wp;
-{
-       register struct tcb *tp;
-
-COUNT(FW1_SYR);
-       tp = wp->w_tcb;
-
-       tp->snd_fin = TRUE;                     /* send FIN */
-       send_ctl(tp);
-       tp->usr_closed = TRUE;
-
-       return(FIN_W1);
-}
-
-sss_syn(wp)             /* incoming seq on open connection (39) */
-struct work *wp;
-{
-       register struct tcb *tp;
-
-COUNT(SSS_SYN);
-       tp = wp->w_tcb;
-
-       rcv_data(tp, wp->w_dat);
-       present_data(tp);
-
-       return(SAME);
-}
-
-sss_snd(wp)             /* send request on open connection (40,41) */
-struct work *wp;
+#include "../h/param.h"
+#include "../h/systm.h"
+#include "../h/mbuf.h"
+#include "../h/socket.h"
+#include "../h/socketvar.h"
+#include "../h/protosw.h"
+#include "../net/in.h"
+#include "../net/route.h"
+#include "../net/in_pcb.h"
+#include "../net/in_systm.h"
+#include "../net/if.h"
+#include "../net/ip.h"
+#include "../net/ip_var.h"
+#include "../net/tcp.h"
+#include "../net/tcp_fsm.h"
+#include "../net/tcp_seq.h"
+#include "../net/tcp_timer.h"
+#include "../net/tcp_var.h"
+#include "../net/tcpip.h"
+#include "../net/tcp_debug.h"
+#include <errno.h>
+
+/*
+ * TCP protocol interface to socket abstraction.
+ */
+extern char *tcpstates[];
+struct tcpcb *tcp_newtcpcb();
+
+/*
+ * Process a TCP user request for TCP tb.  If this is a send request
+ * then m is the mbuf chain of send data.  If this is a timer expiration
+ * (called from the software clock routine), then timertype tells which timer.
+ */
+tcp_usrreq(so, req, m, addr)
+       struct socket *so;
+       int req;
+       struct mbuf *m;
+       caddr_t addr;
 {
 {
-       register struct tcb *tp;
-       register struct mbuf *m, *n;
-       register struct ucb *up;
-       register off;
-       sequence last;
-
-COUNT(SSS_SND);
-       tp = wp->w_tcb;
-       up = tp->t_ucb;
-
-       last = tp->snd_off;
-
-       /* count number of mbufs in send data */
-
-       for (m = n = (struct mbuf *)wp->w_dat; m != NULL; m = m->m_next) {
-               up->uc_ssize++;
-               last += m->m_len;
+       register struct inpcb *inp = sotoinpcb(so);
+       register struct tcpcb *tp;
+       int s = splnet();
+       int error = 0;
+       int ostate;
+COUNT(TCP_USRREQ);
+
+       /*
+        * When a TCP is attached to a socket, then there will be
+        * a (struct inpcb) pointed at by the socket, and this
+        * structure will point at a subsidary (struct tcpcb).
+        * The normal sequence of events is:
+        *      PRU_ATTACH              creating these structures
+        *      PRU_CONNECT             connecting to a remote peer
+        *      (PRU_SEND|PRU_RCVD)*    exchanging data
+        *      PRU_DISCONNECT          disconnecting from remote peer
+        *      PRU_DETACH              deleting the structures
+        * With the operations from PRU_CONNECT through PRU_DISCONNECT
+        * possible repeated several times.
+        *
+        * MULTIPLE CONNECTS ARE NOT YET IMPLEMENTED.
+        */
+       if (inp == 0 && req != PRU_ATTACH) {
+               splx(s);
+               return (EINVAL);                /* XXX */
        }
        }
-
-       /* find end of send buffer and append data */
-
-       if ((m = up->uc_sbuf) != NULL) {        /* something in send buffer */
-               while (m->m_next != NULL) {             /* find the end */
-                       m = m->m_next;
-                       last += m->m_len;
+       if (inp) {
+               tp = intotcpcb(inp);
+#ifdef KPROF
+               tcp_acounts[tp->t_state][req]++;
+#endif
+               ostate = tp->t_state;
+       }
+       switch (req) {
+
+       /*
+        * TCP attaches to socket via PRU_ATTACH, reserving space,
+        * and internet and TCP control blocks.
+        * If the socket is to receive connections,
+        * then the LISTEN state is entered.
+        */
+       case PRU_ATTACH:
+               if (inp) {
+                       error = EISCONN;
+                       break;
                }
                }
-               last += m->m_len;
-
-               /* if there's room in old buffer for new data, consolidate */
+               error = tcp_attach(so, (struct sockaddr *)addr);
+               if (error)
+                       break;
+               if ((so->so_options & SO_DONTLINGER) == 0)
+                       so->so_linger = TCP_LINGERTIME;
+               tp = sototcpcb(so);
+               break;
 
 
-               off = m->m_off + m->m_len;
-               while (n != NULL && (MSIZE - off) >= n->m_len) {
-                       bcopy((caddr_t)((int)n + n->m_off), 
-                             (caddr_t)((int)m + off), n->m_len);
-                       m->m_len += n->m_len;
-                       off += n->m_len;
-                       up->uc_ssize--;
-                       n = m_free(n);
+       /*
+        * PRU_DETACH detaches the TCP protocol from the socket.
+        * If the protocol state is non-embryonic, then can't
+        * do this directly: have to initiate a PRU_DISCONNECT,
+        * which may finish later; embryonic TCB's can just
+        * be discarded here.
+        */
+       case PRU_DETACH:
+               if (tp->t_state > TCPS_LISTEN)
+                       tcp_disconnect(tp);
+               else {
+                       tcp_close(tp);
+                       tp = 0;
                }
                }
-               m->m_next = n;
-
-       } else                                  /* nothing in send buffer */
-               up->uc_sbuf = n;
-
-       if (up->uc_flags & UEOL) {               /* set EOL */
-               tp->snd_end = last;
-       }
-
-       if (up->uc_flags & UURG) {              /* urgent data */
-               tp->snd_urp = last+1;
-               tp->snd_urg = TRUE;
-       } 
-
-       send(tp);
-
-       return(SAME);
-}
-
-sss_rcv(wp)             /* rcv request on open connection (42) */
-struct work *wp;
-{
-       register struct tcb *tp;
-
-COUNT(SSS_RCV);
-       tp = wp->w_tcb;
-
-       send_ctl(tp);                   /* send new window */
-       present_data(tp);
-
-       return(SAME);
-}
-
-cls_nsy(wp)                  /* abort request on unsynced connection (44) */
-struct work *wp;
-{
-
-COUNT(CLS_NSY);
-       t_close(wp->w_tcb, UABORT);
-
-       return(CLOSED);
-}
-
-cls_syn(wp)             /* abort request on synced connection (45) */
-struct work *wp;
-{
-       register struct tcb *tp;
-
-COUNT(CLS_SYN);
-       tp = wp->w_tcb;
-
-       tp->snd_rst = TRUE;            /* send reset */
-       send_null(tp);
-       t_close(tp, UABORT);
-
-       return(CLOSED);
-}
-
-cls_act(wp)             /* net closing open connection (47) */
-struct work *wp;
-{
-
-COUNT(CLS_ACT);
-       t_close(wp->w_tcb, UNETDWN);
-
-       return(CLOSED);
-}
-
-cls_err(wp)             /* invalid user request in closing states */
-struct work *wp;
-{
-COUNT(CLS_ERR);
-       to_user(wp->w_tcb->t_ucb, UCLSERR);
-
-       return(SAME);
-}
-
-lis_netr(wp)             /* incoming seg in LISTEN (3,4) */
-struct work *wp;
-{
-       register struct tcb *tp;
-       register struct th *n;
-
-COUNT(LIS_NETR);
-       tp = wp->w_tcb;
-       n = (struct th *)wp->w_dat;
-
-       if (!syn_ok(tp, n))             /* must have good SYN */
-               return(EFAILEC);
-
-       /* fill in unspecified foreign host address.  get/create entry
-          in foreign host table.  if none available, ignore.  probably
-          should send reset here. */
-
-       if ((tp->t_ucb->uc_host = h_make(&n->t_s)) == NULL)
-               return(EFAILEC);
-
-       tp->t_fport = n->t_src;
-
-       rcv_data(tp, n);
-
-       if (!tp->fin_rcvd) {            /* no FIN (4) */
-
-               /* start init timer now that we have foreign host */
-
-               tp->t_init = T_INIT/2;
-               return(L_SYN_RCVD);
-
-       } else {                        /* got a FIN, start timer (3) */
-               tp->t_finack = T_2ML;
-               tp->waited_2_ml = FALSE;
-               return(CLOSE_WAIT);
-       }
-}
-
-sys_netr(wp)            /* incoming segment after SYN sent (8,9,11,32) */
-struct work *wp;
-{
-       register struct tcb *tp;
-       register struct th *n;
-
-COUNT(SYS_NETR);
-       tp = wp->w_tcb;
-       n = (struct th *)wp->w_dat;
-
-       if (!syn_ok(tp, n))             /* must have good SYN */
-               return(EFAILEC);
-
-       rcv_data(tp, n);
-
-       if (tp->fin_rcvd) {             /* got a FIN */
-
-               /* if good ACK, present any data */
-
-               if (n->t_ack) {
-
-                       if (n->t_ackno > tp->iss)       /* 32 */
-                               present_data(tp);
+               break;
 
 
-               } else {                                /* 9 */
-                       tp->t_finack = T_2ML;
-                       tp->waited_2_ml = FALSE;
+       /*
+        * Initiate connection to peer.
+        * Create a template for use in transmissions on this connection.
+        * Enter SYN_SENT state, and mark socket as connecting.
+        * Start keep-alive timer, and seed output sequence space.
+        * Send initial segment on connection.
+        */
+       case PRU_CONNECT:
+               error = in_pcbconnect(inp, (struct sockaddr_in *)addr);
+               if (error)
+                       break;
+               tp->t_template = tcp_template(tp);
+               if (tp->t_template == 0) {
+                       in_pcbdisconnect(inp);
+                       error = ENOBUFS;
+                       break;
                }
                }
-               return (CLOSE_WAIT);
-
-       } else                          /* no FIN */
-
-               /* if good ACK, open connection, otherwise wait for one */
-
-               if (n->t_ack) {                         /* 11 */
-                       present_data(tp);
-                       return(ESTAB);
-               } else
-                       return(SYN_RCVD);               /* 8 */
-}
-
-cl1_netr(wp)            /* incoming seg after we closed (15,18,22,23,30,39) */
-struct work *wp;
-{
-       register struct tcb *tp;
-       register struct th *n;
-
-COUNT(CL1_NETR);
-       tp = wp->w_tcb;
-       n = (struct th *)wp->w_dat;
-
-       if (ack_fin(tp, n))                     /* got ACK of our FIN */
-
-               if (n->t_fin) {                 /* got for FIN (23) */
-        
-                       rcv_ctl(tp, n);
-                       tp->t_finack = T_2ML;
-                       tp->waited_2_ml = FALSE;
-                        return(TIME_WAIT);
-               } else {
+               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);
+               error = tcp_output(tp);
+               break;
 
 
-                       /* if wait done, see if any data left for user */
+       /*
+        * Initiate disconnect from peer.
+        * If connection never passed embryonic stage, just drop;
+        * else if don't need to let data drain, then can just drop anyways,
+        * else have to begin TCP shutdown process: mark socket disconnecting,
+        * drain unread data, state switch to reflect user close, and
+        * send segment (e.g. FIN) to peer.  Socket will be really disconnected
+        * when peer sends FIN and acks ours.
+        *
+        * SHOULD IMPLEMENT LATER PRU_CONNECT VIA REALLOC TCPCB.
+        */
+       case PRU_DISCONNECT:
+               tcp_disconnect(tp);
+               break;
 
 
-                       if (tp->waited_2_ml)
-        
-                               if (rcv_empty(tp)) {    /* 15 */
-        
-                                       t_close(tp, UCLOSED);
-                                       return(CLOSED);
-                               } else
-                                       return(RCV_WAIT);       /* 18 */
-        
-                       else
-                               return(TIME_WAIT);      /* 22 */
+       /*
+        * Accept a connection.  Essentially all the work is
+        * done at higher levels; just return the address
+        * of the peer, storing through addr.
+        */
+       case PRU_ACCEPT: {
+               struct sockaddr_in *sin = (struct sockaddr_in *)addr;
+
+               if (sin) {
+                       bzero((caddr_t)sin, sizeof (*sin));
+                       sin->sin_family = AF_INET;
+                       sin->sin_port = inp->inp_fport;
+                       sin->sin_addr = inp->inp_faddr;
                }
                }
-
-       else                            /* our FIN not ACKed yet */
-
-               if (n->t_fin) {                 /* rcvd for FIN (30) */
-
-                       rcv_ctl(tp, n);
-                       tp->t_finack = T_2ML;
-                       tp->waited_2_ml = FALSE;
-
-               } else {                        /* no FIN, just proc new data (39) */
-
-                       rcv_data(tp, n);
-                       present_data(tp);
                }
                }
+               break;
 
 
-       return(SAME);
-}
-
-cl2_netr(wp)            /* incoming seg after foreign close (16,19,31,39) */
-struct work *wp;
-{
-       register struct tcb *tp;
-       register struct th *n;
-
-COUNT(CL2_NETR);
-       tp = wp->w_tcb;
-       n = (struct th *)wp->w_dat;
-
-       if (ack_fin(tp, n)) {                   /* this is ACK of our fin */
+       /*
+        * Mark the connection as being incapable of further output.
+        */
+       case PRU_SHUTDOWN:
+               socantsendmore(so);
+               tcp_usrclosed(tp);
+               error = tcp_output(tp);
+               break;
 
 
-               /* if no data left for user, close; otherwise wait */
+       /*
+        * After a receive, possibly send window update to peer.
+        */
+       case PRU_RCVD:
+               (void) tcp_output(tp);
+               break;
 
 
-               if (rcv_empty(tp)) {                            /* 16 */
-                        
-                       t_close(tp, UCLOSED);
-                       return(CLOSED);
-               } else                                          /* 19 */
-                       return(RCV_WAIT);
+       /*
+        * Do a send by putting data in output queue and updating urgent
+        * marker if URG set.  Possibly send more data.
+        */
+       case PRU_SEND:
+               sbappend(&so->so_snd, m);
+#ifdef notdef
+               if (tp->t_flags & TF_PUSH)
+                       tp->snd_end = tp->snd_una + so->so_snd.sb_cc;
+#endif
+               error = tcp_output(tp);
+               break;
 
 
-       } else                                  /* no ACK of our FIN */
+       /*
+        * Abort the TCP.
+        */
+       case PRU_ABORT:
+               tcp_drop(tp, ECONNABORTED);
+               break;
 
 
-               /* duplicate FIN or data */
+/* SOME AS YET UNIMPLEMENTED HOOKS */
+       case PRU_CONTROL:
+               error = EOPNOTSUPP;
+               break;
 
 
-               if (n->t_fin)                                   /* 31 */
-                       send_ctl(tp);           /* ACK duplicate FIN */
+       case PRU_SENSE:
+               error = EOPNOTSUPP;
+               break;
+/* END UNIMPLEMENTED HOOKS */
 
 
-               else {                                          /* 39 */
-                       rcv_data(tp, n);                         
-                       present_data(tp);
+       case PRU_RCVOOB:
+               if (so->so_oobmark == 0 &&
+                   (so->so_state & SS_RCVATMARK) == 0) {
+                       error = EINVAL;
+                       break;
                }
                }
+               if ((tp->t_oobflags & TCPOOB_HAVEDATA) == 0) {
+                       error = EWOULDBLOCK;
+                       break;
+               }
+               *mtod(m, caddr_t) = tp->t_iobc;
+               break;
 
 
-       return(SAME);
-}
-
-fw1_netr(wp)            /* incoming seg after user close (26,27,28,39) */
-struct work *wp;
-{
-       register struct tcb *tp;
-       register struct th *n;
-
-COUNT(FW1_NETR);
-       tp = wp->w_tcb;
-       n = (struct th *)wp->w_dat;
-
-       /* process any incoming data, since we closed but they didn't */
-
-       rcv_data(tp, n);
-       present_data(tp);
-
-       if (ack_fin(tp, n))                     /* our FIN got ACKed */
-
-               if (tp->fin_rcvd) {                     /* got for FIN (28) */
-                       tp->t_finack = T_2ML;
-                       tp->waited_2_ml = FALSE;
-                       return(TIME_WAIT);
-               } else                                  /* no FIN, wait (27) */
-                       return(FIN_W2);
-
-       else                                    /* no ACK of FIN */
-
-               if (tp->fin_rcvd) {                     /* got for FIN (26) */
-                       tp->t_finack = T_2ML;
-                       tp->waited_2_ml = FALSE;
-                       return(CLOSING1);
-                } 
-
-       return(SAME);                                   /* 39 */
-}
-
-syr_netr(wp)             /* incoming seg after SYN rcvd (5,33) */
-struct work *wp;
-{
-       register struct tcb *tp;
-       register struct th *n;
-
-COUNT(SYR_NETR);
-       tp = wp->w_tcb;
-       n = (struct th *)wp->w_dat;
-
-       if (!n->t_ack || (n->t_ack && n->t_ackno <= tp->iss))  /* must have ACK of our SYN */
-               return(EFAILEC);
-
-       rcv_data(tp, n);
-       present_data(tp);
-
-       /* if no FIN, open connection, otherwise wait for user close */
-
-       if (tp->fin_rcvd)                               /* 33 */
-               return(CLOSE_WAIT);
-       else                                            /* 5 */
-               return(ESTAB);
-
-}
-
-est_netr(wp)            /* incoming seg on open connection (12,39) */
-struct work *wp;
-{
-       register struct tcb *tp;
-       register struct th *n;
-
-COUNT(EST_NETR);
-       tp = wp->w_tcb;
-       n = (struct th *)wp->w_dat;
+       case PRU_SENDOOB:
+#ifdef TCPTRUEOOB
+               if (tp->t_flags & TF_DOOOB) {
+                       tp->t_oobseq++;
+                       tp->t_oobc = *mtod(m, caddr_t);
+                       tp->t_oobmark = tp->snd_una + so->so_snd.sb_cc;
+printf("sendoob seq now %x oobc %x\n", tp->t_oobseq, tp->t_oobc);
+                       tp->t_oobflags |= TCPOOB_NEEDACK;
+                       /* what to do ...? */
+                       if (error = tcp_output(tp))
+                               break;
+               }
+#endif
+               if (sbspace(&so->so_snd) < -512) {
+                       error = ENOBUFS;
+                       break;
+               }
+               tp->snd_up = tp->snd_una + so->so_snd.sb_cc + 1;
+               sbappend(&so->so_snd, m);
+#ifdef notdef
+               if (tp->t_flags & TF_PUSH)
+                       tp->snd_end = tp->snd_una + so->so_snd.sb_cc;
+#endif
+               tp->t_force = 1;
+               error = tcp_output(tp);
+               tp->t_force = 0;
+               break;
 
 
-       rcv_data(tp, n);
-       present_data(tp);
+       case PRU_SOCKADDR:
+               in_setsockaddr((struct sockaddr_in *)addr, inp);
+               break;
 
 
-       /* if no FIN, remain open, otherwise wait for user close */
+       /*
+        * TCP slow timer went off; going through this
+        * routine for tracing's sake.
+        */
+       case PRU_SLOWTIMO:
+               tcp_timers(tp, (int)addr);
+               req |= (int)addr << 8;          /* for debug's sake */
+               break;
 
 
-       if (tp->fin_rcvd)                       /* 12 */
-               return(CLOSE_WAIT);
-       else                                    /* 39 */
-               return(SAME);
+       default:
+               panic("tcp_usrreq");
+       }
+       if (tp && (so->so_options & SO_DEBUG))
+               tcp_trace(TA_USER, ostate, tp, (struct tcpiphdr *)0, req);
+       splx(s);
+       return (error);
 }
 
 }
 
-fw2_netr(wp)            /* incoming seg while waiting for for FIN (12,39) */
-struct work *wp;
+int    tcp_sendspace = 1024*2;
+int    tcp_recvspace = 1024*2;
+/*
+ * Attach TCP protocol to socket, allocating
+ * internet protocol control block, tcp control block,
+ * bufer space, and entering LISTEN state if to accept connections.
+ */
+tcp_attach(so, sa)
+       struct socket *so;
+       struct sockaddr *sa;
 {
 {
-       register struct tcb *tp;
-       register struct th *n;
-
-COUNT(FW2_NETR);
-       tp = wp->w_tcb;
-       n = (struct th *)wp->w_dat;
-
-       /* process data since we closed, but they may not have */
-
-       rcv_data(tp, n);
-       present_data(tp);
-
-       /* if we get the FIN, start the finack timer, else keep waiting */
-
-       if (tp->fin_rcvd) {                     /* got for FIN (29) */
-               tp->t_finack = T_2ML;
-               tp->waited_2_ml = FALSE;
-               return(TIME_WAIT);
-       } else                                  /* 39 */
-               return(SAME);
+       register struct tcpcb *tp;
+       struct inpcb *inp;
+       int error;
+
+       error = in_pcbattach(so, &tcb,
+           tcp_sendspace, tcp_recvspace, (struct sockaddr_in *)sa);
+       if (error)
+               return (error);
+       inp = (struct inpcb *)so->so_pcb;
+       tp = tcp_newtcpcb(inp);
+       if (so->so_options & SO_ACCEPTCONN) {
+               if (tp == 0) {
+                       in_pcbdetach(inp);
+                       return (ENOBUFS);
+               }
+               tp->t_state = TCPS_LISTEN;
+       } else
+               tp->t_state = TCPS_CLOSED;
+       return (0);
 }
 
 }
 
-cwt_netr(wp)            /* incoming seg after exchange of FINs (30,31,39) */
-struct work *wp;
+/*
+ * Initiate (or continue) disconnect.
+ * If embryonic state, just send reset (once).
+ * If not in ``let data drain'' option, just drop.
+ * Otherwise (hard), mark socket disconnecting and drop
+ * current input data; switch states based on user close, and
+ * send segment to peer (with FIN).
+ */
+tcp_disconnect(tp)
+       struct tcpcb *tp;
 {
 {
-       register struct tcb *tp;
-       register struct th *n;
-
-COUNT(CWT_NETR);
-       tp = wp->w_tcb;
-       n = (struct th *)wp->w_dat;
-
-       /* either duplicate FIN or data */
-
-       if (n->t_fin) {
-
-               if (n->t_ack && n->t_ackno <= tp->seq_fin) {    /* dup ACK (30) */
-
-                       rcv_ctl(tp, n);
-                       tp->t_finack = T_2ML;
-                       tp->waited_2_ml = FALSE;
-               } else                                          /* 31 */
-                       send_ctl(tp);
-
-       } else {                /* duplicate data (39) */
-
-               rcv_data(tp, n);
-               present_data(tp);
+       struct socket *so = tp->t_inpcb->inp_socket;
+
+       if (tp->t_state < TCPS_ESTABLISHED)
+               tcp_close(tp);
+       else if (so->so_linger == 0)
+               tcp_drop(tp, 0);
+       else {
+               soisdisconnecting(so);
+               sbflush(&so->so_rcv);
+               tcp_usrclosed(tp);
+               (void) tcp_output(tp);
        }
        }
-
-       return(SAME);
 }
 
 }
 
-rwt_netr(wp)            /* incoming seg while waiting for user rcv (30,21) */
-struct work *wp;
+/*
+ * User issued close, and wish to trail through shutdown states:
+ * if never received SYN, just forget it.  If got a SYN from peer,
+ * but haven't sent FIN, then go to FIN_WAIT_1 state to send peer a FIN.
+ * If already got a FIN from peer, then almost done; go to LAST_ACK
+ * state.  In all other cases, have already sent FIN to peer (e.g.
+ * after PRU_SHUTDOWN), and just have to play tedious game waiting
+ * for peer to send FIN or not respond to keep-alives, etc.
+ * We can let the user exit from the close as soon as the FIN is acked.
+ */
+tcp_usrclosed(tp)
+       struct tcpcb *tp;
 {
 {
-       register struct tcb *tp;
-       register struct th *n;
 
 
-COUNT(RWT_NETR);
-       tp = wp->w_tcb;
-       n = (struct th *)wp->w_dat;
+       switch (tp->t_state) {
 
 
-       /* handle duplicate ACK of our FIN */
-
-       if (n->t_fin && n->t_ack && n->t_ackno <= tp->seq_fin) {    /* 30 */
-
-               rcv_ctl(tp, n);
-               tp->t_finack = T_2ML;
-               tp->waited_2_ml = FALSE;
-       } 
-
-       return(SAME);
-}
-
-timers(wp)              /* timer processor (14,17,34,35,36,37,38) */
-struct work *wp;
-{
-       register struct tcb *tp;
-       register type;
-
-COUNT(TIMERS);
-       tp = wp->w_tcb;
-       type = wp->w_stype;
-
-       switch (type) {
-
-       case TINIT:             /* initialization timer */
-
-               if (!tp->syn_acked) {           /* haven't got ACK of our SYN (35) */
-
-                       t_close(tp, UINTIMO);
-                       return(CLOSED);
-               }
-               break;
-
-       case TFINACK:           /* fin-ack timer */   
-
-               if (tp->t_state == TIME_WAIT) {
-
-                       /* can be sure our ACK of for FIN was rcvd,
-                          can close if no data left for user */
-
-                       if (rcv_empty(tp)) {            /* 14 */
-                               t_close(tp, UCLOSED);
-                               return(CLOSED);
-                       } else                          /* 17 */
-                               return(RCV_WAIT);
-
-               } else if (tp->t_state == CLOSING1)     /* 37 */
-
-                       /* safe to close */
-
-                       tp->waited_2_ml = TRUE;
-
-               break;
-
-       case TREXMT:            /* retransmission timer */
-
-               /* set up for a retransmission, increase rexmt time
-                  in case of multiple retransmissions. */
-
-               if (tp->t_rexmt_val > tp->snd_una) {   /* 34 */
-                       tp->snd_nxt = tp->snd_una;
-                       tp->rexmt = TRUE;
-                       tp->t_xmtime = tp->t_xmtime << 1;                  
-                       if (tp->t_xmtime > T_REMAX)
-                               tp->t_xmtime = T_REMAX;
-                       send(tp);
-               }
+       case TCPS_LISTEN:
+       case TCPS_SYN_SENT:
+               tp->t_state = TCPS_CLOSED;
+               tcp_close(tp);
                break;
 
                break;
 
-       case TREXMTTL:          /* retransmit too long */
-
-               /* tell user */
-
-               if (tp->t_rtl_val > tp->snd_una)        /* 36 */
-                       to_user(tp->t_ucb, URXTIMO);
-
-               /* if user has already closed, abort the connection */
-
-               if (tp->usr_closed) {
-                       t_close(tp, URXTIMO);
-                       return(CLOSED);
-               }
+       case TCPS_SYN_RECEIVED:
+       case TCPS_ESTABLISHED:
+               tp->t_state = TCPS_FIN_WAIT_1;
                break;
 
                break;
 
-       case TPERSIST:          /* persist timer */
-
-               /* force a byte send through closed window */
-
-               tp->force_one = TRUE;                   /* 38 */
-               send(tp);
+       case TCPS_CLOSE_WAIT:
+               tp->t_state = TCPS_LAST_ACK;
                break;
        }
                break;
        }
-
-       return(SAME);
+       if (tp->t_state >= TCPS_FIN_WAIT_2)
+               soisdisconnected(tp->t_inpcb->inp_socket);
 }
 }