-/* tcp_usrreq.c 1.41 81/12/12 */
+/* tcp_usrreq.c 1.53 82/03/11 */
#include "../h/param.h"
#include "../h/systm.h"
#include "../net/tcp_timer.h"
#include "../net/tcp_var.h"
#include "../net/tcpip.h"
+#include "../net/tcp_debug.h"
#include "../errno.h"
-extern char *tcpstates[];
+/*
+ * 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
+ * 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.
*/
register struct tcpcb *tp;
int s = splnet();
int error = 0;
+ int ostate;
COUNT(TCP_USRREQ);
/*
- * Make sure attached. If not,
- * only PRU_ATTACH is valid.
+ * 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);
+ return (EINVAL); /* XXX */
}
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;
}
- error = in_pcbattach(so, &tcb, 2048, 2048, (struct sockaddr_in *)addr);
+ error = tcp_attach(so, (struct sockaddr *)addr);
if (error)
break;
- inp = (struct inpcb *)so->so_pcb;
- tp = tcp_newtcpcb(inp);
- if (so->so_options & SO_ACCEPTCONN) {
- if (tp == 0) {
- in_pcbdetach(inp);
- error = ENOBUFS;
- break;
- }
- tp->t_state = TCPS_LISTEN;
- } else
- tp->t_state = TCPS_CLOSED;
+ if ((so->so_options & SO_DONTLINGER) == 0)
+ so->so_linger = TCP_LINGERTIME;
+ tp = sototcpcb(so);
break;
+ /*
+ * 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:
- in_pcbdetach(inp);
+ if (tp->t_state > TCPS_LISTEN)
+ tcp_disconnect(tp);
+ else {
+ tcp_close(tp);
+ tp = 0;
+ }
break;
+ /*
+ * 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 = tcp_newtcpcb(inp);
- if (tp == 0)
- goto badcon;
tp->t_template = tcp_template(tp);
- if (tp->t_template == 0)
- goto badcon2;
- tp->t_inpcb = inp;
- inp->inp_ppcb = (caddr_t)tp;
+ if (tp->t_template == 0) {
+ in_pcbdisconnect(inp);
+ error = ENOBUFS;
+ break;
+ }
soisconnecting(so);
tp->t_state = TCPS_SYN_SENT;
tp->t_timer[TCPT_KEEP] = TCPTV_KEEP;
(void) tcp_output(tp);
break;
-badcon2:
- (void) m_free(dtom(tp));
-badcon:
- in_pcbdisconnect(inp);
- error = ENOBUFS;
+ /*
+ * 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;
- case PRU_ACCEPT:
- soisconnected(so);
- break;
+ /*
+ * 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;
- case PRU_DISCONNECT:
- if (tp->t_state < TCPS_ESTABLISHED)
- tcp_close(tp);
- else {
- soisdisconnecting(so);
- tcp_usrclosed(tp);
- (void) tcp_output(tp);
+ 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;
+ }
}
break;
+ /*
+ * Mark the connection as being incapable of further output.
+ */
case PRU_SHUTDOWN:
socantsendmore(so);
tcp_usrclosed(tp);
(void) tcp_output(tp);
break;
+ /*
+ * After a receive, possibly send window update to peer.
+ */
case PRU_RCVD:
(void) tcp_output(tp);
break;
+ /*
+ * 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);
/*
if (tp->t_flags & TF_PUSH)
tp->snd_end = tp->snd_una + so->so_snd.sb_cc;
*/
- if (tp->t_flags & TF_URG)
- tp->snd_up = tp->snd_una + so->so_snd.sb_cc + 1;
(void) tcp_output(tp);
break;
+ /*
+ * Abort the TCP.
+ */
case PRU_ABORT:
tcp_drop(tp, ECONNABORTED);
break;
+/* SOME AS YET UNIMPLEMENTED HOOKS */
case PRU_CONTROL:
error = EOPNOTSUPP;
break;
case PRU_SENSE:
error = EOPNOTSUPP;
break;
+/* END UNIMPLEMENTED HOOKS */
case PRU_RCVOOB:
- error = EOPNOTSUPP;
+ 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;
case PRU_SENDOOB:
- error = EOPNOTSUPP;
+#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;
+ (void) tcp_output(tp);
+ }
+#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);
+/*
+ if (tp->t_flags & TF_PUSH)
+ tp->snd_end = tp->snd_una + so->so_snd.sb_cc;
+ */
+ tp->t_force = 1;
+ (void) tcp_output(tp);
+ tp->t_force = 0;
break;
+ /*
+ * 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;
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);
}
+int tcp_sendspace = 1024*2;
+int tcp_recvspace = 1024*3;
+/*
+ * 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 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);
+}
+
+/*
+ * 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;
+{
+ 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);
+ }
+}
+
+/*
+ * 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;
{
tp->t_state = TCPS_LAST_ACK;
break;
}
+ if (tp->t_state >= TCPS_FIN_WAIT_2)
+ soisdisconnected(tp->t_inpcb->inp_socket);
}