# /* DCtcp.c */ /* EMACS_MODES: c !fill */ /* Translation of Dave Clark's Alto TCP into C. * * This file contains a bare-bones minimal TCP, suitable only for user Telnet * and other protocols which do not need to transmit much data. Its primary * weakness is that it does not pay any attention to the window advertised * by the receiver; it assumes that it will never be sending much data and * hence will never run out of receive window. Its other major weakness * is that it will not handle out-of-sequence packets; any out-of-sequence * data received is ignored. * * This version includes server-mode code, some of which is stolen from * Geof Cooper's Alto server. * * This TCP requires the UNIX tasking package to run. It runs as two tasks: * TCPsend, which handles all data transmission, and TCPrcv, which handles * all received data. */ typedef long time_t; /* ugly! */ #include #include #include #include #include "task.h" #include "timer.h" #include "DCtcp.h" #define EST 6 /* estimated retransmit time */ #define RXMAX 5 /* too many retransmissions */ #define TCPSTACK 512 /* tcp tasks stack sizes */ #define TCPWINDOW 1000 /* normal advertised window */ #define TCPLOWIND 200 /* low water mark on window */ int opening; /* conn. opening state */ int hisclosing; /* foreign conn. closing state */ int ourclosing; /* local conn. closing state */ int server; /* this is a server? */ caddr_t opbi; /* ptr to output packet buffer */ caddr_t ipbi; /* ptr to input packet buffer */ struct ip *oip; /* ptr to output pkt internet hdr */ struct tcp *otp; /* ptr to output pkt tcp hdr */ caddr_t odp; /* ptr to start of output pkt data */ struct tcpph *php; /* tcp psuedo hdr for cksum calc */ int odlen; /* bytes of data in output pkt */ int est; /* est. retransmission timeout */ int rxcount; /* retransmit counter */ timerid resend; /* restransmit timer identifier */ event sendef; /* send task's event flag */ event retef; /* retransmit event flag */ int tcpfd; /* file descriptor for tcp conn. */ in_name forhost; /* foreign host address */ in_name lochost; /* local host address */ unshort locsock; /* local socket */ unshort forsock; /* foreign socket */ int window; /* max window to advertise */ task *TCPsend; /* tcp sending task */ task *TCPrcv; /* tcp receiving task */ int TCPDEBUG; /* debugging (tracing) on flag */ int (*tc_ofcn) (); /* user function called on open */ int (*tc_dispose) (); /* user function to receive data */ int (*tc_cfcn) (); /* user function called on close */ int (*tc_fcfcn) (); /* user function called on fin rcvd */ int (*tc_tfcn) (); /* user function called on tmo */ int (*tc_sfcn) (); /* user function for send buf. open */ int rcvsig (), tcp_send (), tcp_rcv (), tc_rtns (); tcp_init (stksiz, extsiz, ofcn, infcn, cfcn, fcfcn, tmofcn, sfcn) /* This routine is called to initialize the TCP. It starts up the tasking * system, initiates the timer, TCPsend, and TCPrcv tasks, and sets up * the pointers to the up-callable user routines for open, received data, * foreign close, closed, timeout, and space available in send buffer. * It does not attempt to open the connection; that function * is performed by tcp_open (). * When it returns, the caller is running as the first task, on the primary * process stack. * * Arguments: */ int stksiz; /* user task stack size (bytes) */ int extsiz; /* user task static data size */ int (*ofcn) (); /* user fcn to call on open done */ int (*infcn) (); /* user fcn to process input data */ int (*cfcn) (); /* user fcn to call on close done */ int (*fcfcn) (); /* usr fcn called on foreign fin rcv */ int (*tmofcn) (); /* user fcn to call on rxmt timeout */ int (*sfcn) (); /* user fcn to call on snd buf avail */ { tc_ofcn = ofcn; /* save user fcn addresses */ tc_dispose = infcn; tc_cfcn = cfcn; tc_fcfcn = fcfcn; tc_tfcn = tmofcn; tc_sfcn = sfcn; tk_init (stksiz, extsiz); /* start tasking */ opening = 0; /* initially, conn. closed */ hisclosing = 2; ourclosing = 2; est = EST; TCPsend = tk_fork (tcp_send, TCPSTACK, 0); /* start send and */ TCPrcv = tk_fork (tcp_rcv, TCPSTACK, 0); /* receive tasks */ tm_init (); /* start the timer task too */ tk_yield (); } tcp_rcv () /* This routine forms the body of the TCP data receiver task. It blocks * on the arrival of network input (as indicated by the arrival of a * SIGAIO signal). It then attempts to read and process incoming packets. * The processing of each packet is divided into three phases: * 1) Processing acknowlegments. This involves "shifting" the data * in the output packet to account for acknowledged data. * 2) Processing state information: syn's, fin's, urgents, etc. * 3) Processing the received data. This is done by calling the * user's "input data" function (specified in his call to * tcp_init), passing it the address and length of the received * data. * Note that this routine does not presently handle out-of-sequence data. * Out-of-sequence packets are simply discarded. * Note also that this version of the TCP has code added to run as a server. * When running as a server, the TCP listens on a specified port for a * packet with syn set to come in. It then performs a process-level fork, * with the child opening up a new TCP connection to process the request * and the parent returning to listen on the server connection. */ { struct ip *iip; /* input pkt internet hdr */ register struct tcp *itp; /* input pkt tcp hdr */ register caddr_t idp; /* input pkt data ptr */ register int diff; /* temp for seq. number compares */ int idlen; /* len of input pkt in bytes */ unshort urg; /* urgent pointer */ signal (SIGAIO, rcvsig); /* set up my signal handler */ for (;;) { tk_block(); /* Woke up; process all available input packets */ while ((idlen = in_read (tcpfd, ipbi, INETLEN)) > 0) { iip = in_head (ipbi); itp = in_data (iip); tcp_swab (itp); idp = (caddr_t)itp + (itp->tc_thl << 2); if (TCPDEBUG) in_logpkt (ipbi, iip->ip_len - (iip->ip_ihl << 2), INPKT); idlen = iip->ip_len - (iip->ip_ihl << 2) - (itp->tc_thl << 2); /* compute incoming tcp checksum here... */ if (itp->tc_rst) { /* other guy's resetting me */ if (opening != 0) { cleanup ("reset"); (*tc_cfcn) (); } else printf ("Reset from %X with no open conn. ignored\n", iip->ip_src); continue; } if (itp->tc_syn) /* syn's take sequence no. space */ itp->tc_seq++; /* This code updates things based on incoming ack value */ if (opening > 1) { /* have we gotten his syn yet? */ if (itp->tc_fack) { /* yes, process his ack */ rxcount = 0; /* reset retransmit count */ diff = (int)otp->tc_seq + odlen - (int)itp->tc_ack; /* If we're the server, see if this is ack of our syn. If so, open */ if (server && otp->tc_syn) { otp->tc_seq++; otp->tc_syn = 0; opening = 3; diff++; if (diff != 0) diff = -2; else (*tc_ofcn)(); } /* See if this is ack of our fin. If so, account for it */ if (diff == -1 && otp->tc_fin) { otp->tc_fin = 0; /* don't send again */ otp->tc_seq++; diff=0; ourclosing++; } /* See if ack is for unsent data. This prompts us to reset. */ if (diff < 0) { otp->tc_rst = 1; otp->tc_seq = itp->tc_ack; tk_setef (TCPsend, &sendef); continue; } /* Account for ack of our urgent data by updating the urgent pointer */ if (otp->tc_furg) { otp->tc_urg = otp->tc_urg-odlen+diff; if (otp->tc_urg <= 0) { otp->tc_urg = 0; otp->tc_furg = 0; } } /* See if all our outgoing data is now acked. If so, turn off resend timer */ if (diff == 0) { tm_clear (&resend); (*tc_sfcn)(); /* let user know */ } /* Otherwise, shift the output data in the packet to account for ack */ if (diff < odlen) { shift (odp+odlen-diff, odp, diff); otp->tc_seq = otp->tc_seq+odlen-diff; odlen = diff; odp[diff] = 0; (*tc_sfcn)(); /* let user know */ } } /* Still trying to open the connection. Code here differs for server & user */ } else if (server) { /* Server. Packet must have syn. If so, fork off a child process to run */ /* transfer; parent returns to listening. */ if (!itp->tc_syn) { printf ("Rcvd pkt w/o syn from %X, skt %d\n", iip->ip_src,itp->tc_srcp); continue; } forhost = iip->ip_src; /* stash connection info */ forsock = itp->tc_srcp; /* for child in_open() */ if (dblfork()) /* parent */ continue; /* Child. Everything is now set up, so reply to the request. */ otp->tc_syn = 1; otp->tc_fack = 1; otp->tc_psh = 1; otp->tc_seq = 1; /* our init. seq. number */ otp->tc_ack = itp->tc_seq; otp->tc_dstp = forsock; php->tp_dst = forhost; /* don't forget psuedo-hdr */ opening = 2; /* waiting for his ack of our syn */ tk_setef (TCPsend, &sendef); /* wake up sender */ /* User mode. Check for syn and ack in incoming packet */ } else { if (!itp->tc_syn || (!server && !itp->tc_fack)) { printf ("Rcvd pkt w/o syn from %X, skt %d\n", iip->ip_src,itp->tc_srcp); continue; } /* Also, ack must be for our initial sequence number - namely, 1 */ if (itp->tc_ack != 1) { printf ("Error 2\n"); continue; } /* Connection successfully opened. Call the user and tell him. */ otp->tc_syn = 0; otp->tc_rst = 0; otp->tc_fack = 1; otp->tc_psh = 1; otp->tc_seq = 1; otp->tc_ack = itp->tc_seq; opening = 3; (*tc_ofcn) (); tk_setef (TCPsend, &sendef); } /* Now process the received data */ diff = otp->tc_ack - itp->tc_seq; if (diff < 0) /* packet out of seq */ continue; if (itp->tc_fin) { /* foreign close request */ if (hisclosing == 0) { /* remember he closed */ (*tc_fcfcn) (); /* tell user rcvd. fin */ hisclosing = 2; /* show his fin, our ack */ tk_setef (TCPsend, &sendef); /* wake sender */ } } /* Call the user to dispose of the incoming data */ if (idlen > 0) { if (itp->tc_furg) /* urgent data available? */ urg = itp->tc_urg - diff; else urg = -1; (*tc_dispose)(&idp[diff], idlen - diff, urg); tk_setef (TCPsend, &sendef); otp->tc_win = otp->tc_win - idlen + diff; } /* Set up to acknowledge his data */ if (diff <= idlen) { otp->tc_ack = itp->tc_seq; otp->tc_ack += idlen; otp->tc_ack += itp->tc_fin; } if (hisclosing >= 2 && ourclosing >= 2) { /* all done */ tk_yield (); /* let xmitter send finack */ (*tc_cfcn) (); /* connection closed */ } } } } shift (from, to, len) /* Just shift the data in a buffer, moving len bytes from from to to. * * Arguments: */ register caddr_t to; /* destination */ register caddr_t from; /* source */ register int len; /* length in bytes */ { while (len--) *to++ = *from++; } tcp_swab (pkt) /* Swap the bytes in a tcp packet. * * Arguments: */ struct tcp *pkt; /* ptr to tcp packet to be swapped */ { register int *ptr; register int *end = &((int *)pkt)[10]; for (ptr = (int *)pkt; ptr < end; ptr++) *ptr = swab (*ptr); } tcp_send () /* This routine forms the main body of the TCP data sending task. This task * is awakened for one of two reasons: someone has data to send, or a resend * timeout has occurred and a retransmission is called for. These conditions * are indicated by the setting of one of two event flags: the send event * flag sendef, and the retransmit event flag retef. * This routine in either case finishes filling in the header of the * output packet, and calls in_write () to send it to the net. */ { register int sndlen; /* bytes to send */ for (;;) { tk_block (); /* wait to be awakened */ if (opening == 0) /* if no connection open, punt */ continue; /* If this is not a timeout, clear the resend timer */ if (tst_and_clr (&sendef)) tm_clear (&resend); else if (tst_and_clr (&retef)) { /* retransmitting... */ if (++rxcount >= RXMAX) /* too many retransmits? */ if (ourclosing > 0) /* just waiting for finack, punt */ (*tc_cfcn) (); else (*tc_tfcn) (); } else continue; /* nothing to do */ if (otp->tc_win < TCPLOWIND) /* make sure to avoid silly windows */ otp->tc_win = window; tcp_swab (otp); /* swap the bytes */ sndlen = (opening == 3 ? odlen : 0); /* send no data in open pkt */ /* Finish filling in the TCP and internet headers */ php->tp_len = sndlen + sizeof (struct tcp); php->tp_len = swab (php->tp_len); otp->tc_cksum = cksum (php, sizeof (struct tcpph) >> 1, 0); otp->tc_cksum = ~cksum (otp, (sndlen + sizeof(struct tcp) + 1) >> 1,0); oip->ip_id = 0; oip->ip_dest = forhost; oip->ip_prot = TCPPROT; /* Send it */ in_write (tcpfd, opbi, sndlen + sizeof (struct tcp)); if (TCPDEBUG) in_logpkt (opbi, sndlen+sizeof (struct tcp), OUTPKT); tcp_swab (otp); /* swap it back so we can use it */ if (otp->tc_rst) { /* were we resetting? */ cleanup ("aborted\n"); (*tc_cfcn) (); /* call the user close routine */ } /* Start the retransmit timer, if not just an ack */ if (sndlen > 0 || otp->tc_syn || otp->tc_fin) tm_set (est, tc_rtns, 0, &resend); } } tc_rtns () /* Force a retransmission. Called on retransmit timeout from the * timer task. */ { tk_setef (TCPsend, &retef); } tc_put (c) /* Stuff a character into the send buffer for transmission, but do not * wake up the TCP sending task. This assumes that more data will * immediately follow. It returns TRUE if there is more room remaining * in the buffer for data, and FALSE otherwise. * * Arguments: */ char c; /* charater to send */ { if (odlen >= (MAXTCPBUF - 1)) { tk_setef (TCPsend, &sendef); /* make sure to send the data */ return (FALSE); } odp[odlen] = c; odlen++; odp[odlen] = 0; return (TRUE); } tc_fput (c) /* Stuff a character into the send buffer for transmission, and wake * up the TCP sender task to send it. Returns TRUE if there is more * space remaining in the buffer for data, and FALSE otherwise. * * Arguments: */ char c; /* charater to send */ { if (odlen >= (MAXTCPBUF - 1)) { tk_setef (TCPsend, &sendef); return (FALSE); } odp[odlen] = c; odlen++; odp[odlen] = 0; tk_setef (TCPsend, &sendef); return (TRUE); } tcpurgent () /* Indicate the presence of urgent data. Just sets the urgent pointer to * the current data length and wakes up the sender. */ { otp->tc_urg = odlen; otp->tc_furg = 1; tk_setef (TCPsend, &sendef); } tcp_close () /* Initiate the TCP closing sequence. This routine will return immediately; * when the close is complete the user close function will be called. */ { if (ourclosing != 0) return; otp->tc_fin = 1; ourclosing++; tk_setef (TCPsend, &sendef); } tcp_open (fh, fs, win) /* Actively open a tcp connection to foreign host fh on foreign socket * fs. Get a unique local socket to open the connection on. Returns * FALSE if unable to open an internet connection with the specified * hosts and sockets, or TRUE otherwise. * Note that this routine does not wait until the connection is * actually opened before returning. Instead, the user open function * specified as ofcn in the call to tcp_init () (which must precede * this call) will be called when the connection is successfully opened. * This routine also sets a timer to call the user timeout routine * on ICP timeout. * * Arguments: */ in_name fh; /* foreign host address */ unshort fs; /* foreign socket */ int win; /* window to advertise */ { forhost = fh; forsock = fs; window = win; locsock = getid (); if (locsock < 1000) locsock += 1000; opening = 1; /* syn-sent */ hisclosing = 0; ourclosing = 0; odlen = 0; /* no output data yet */ server = FALSE; /* we're a user */ if ((tcpfd = tin_open (TCPPROT, fh, fs, locsock)) < 0) { printf ("connection in use\n"); return (FALSE); } opbi = in_alloc (INETLEN, 0); /* alloc and set up output pkt */ oip = in_head (opbi); otp = in_data (oip); odp = (caddr_t)otp + sizeof (struct tcp); ipbi = in_alloc (INETLEN, 0); /* alloc a buffer for input pkts */ php = calloc (1, sizeof (struct tcpph)); /* alloc & set up psuedohdr */ php->tp_src = mymach; php->tp_dst = fh; php->tp_pro = TCPPROT; otp->tc_thl = sizeof (struct tcp) >> 2; /* fill in output tcp hdr */ otp->tc_srcp = locsock; otp->tc_dstp = forsock; otp->tc_seq = 0; otp->tc_syn = 1; otp->tc_urg = 0; otp->tc_win = window; tk_setef (TCPsend, &sendef); } tcp_lstn (ls, win) /* Listen on the specified local socket for a server connection. Will * listen with the foreign host and port unspecified. Returns * FALSE if unable to open an internet connection with the specified * hosts and sockets, or TRUE otherwise. * Note that this routine does not wait until the connection is * actually opened before returning. Instead, this routine will return * immediately. Then when a connection is successfully opened, a process * fork will occur in the tcp_rcv() routine, the child process will * proceed to open a (fully specified) internet connection with the * appropriate parameters, and the user open function will be called * in the child (the parent will return to listening on the connection). * * Arguments: */ unshort ls; /* local socket */ int win; /* window to advertise */ { forhost = 0L; forsock = 0; window = win; locsock = ls; opening = 0; /* no connection yet */ hisclosing = 0; ourclosing = 0; odlen = 0; /* no output data yet */ server = TRUE; /* indicate we're a server */ if ((tcpfd = tin_open (TCPPROT, forhost, forsock, locsock)) < 0) { printf ("connection in use\n"); return (FALSE); } opbi = in_alloc (INETLEN, 0); /* alloc and set up output pkt */ oip = in_head (opbi); otp = in_data (oip); odp = (caddr_t)otp + sizeof (struct tcp); ipbi = in_alloc (INETLEN, 0); /* alloc a buffer for input pkts */ php = calloc (1, sizeof (struct tcpph)); /* alloc & set up psuedohdr */ php->tp_src = mymach; php->tp_dst = forhost; php->tp_pro = TCPPROT; otp->tc_thl = sizeof (struct tcp) >> 2; /* fill in output tcp hdr */ otp->tc_srcp = locsock; otp->tc_dstp = forsock; otp->tc_seq = 0; otp->tc_syn = 1; otp->tc_urg = 0; otp->tc_win = window; /* Don't bother to send it now */ } dblfork () /* Do process-level forks to set up a child process with a connection open * to the foreign host/socket specified in forhost/forsock. This routine is * called when an initial connection request is received by a server TCP; * the child process will run the connection while the parent returns to * listening. The double fork is performed to make the child a child of * /etc/init, so it will be cleaned up after when it exits (otherwise the * parent would have to wait for it). * * Returns TRUE in the parent and FALSE in the child. */ { int pid; tc_ioff (); /* turn off net intrpts. for now */ if ((pid = fork ()) < 0) { /* can't fork, give up */ tc_ion (); return (TRUE); } else if (pid != 0) { /* parent */ wait (0); /* wait for child to exit */ tc_ion (); return (TRUE); } in_close (tcpfd); /* child; close the tcp conn */ if ((pid = fork ()) != 0) /* forking for grandchild */ exit (0); /* child; exit back to parent */ if ((tcpfd = tin_open (TCPPROT, forhost, forsock, locsock)) < 0) { printf ("connection in use to %X %x\n", forhost, forsock); exit (0); /* give up now */ } return (FALSE); /* show we're child */ } cleanup (why) char *why; { printf ("Closed: %s\n", why); hisclosing = 2; ourclosing = 2; } error (why) char *why; { printf ("dying: %s\n", why); exit (0); } tc_ioff () /* Turn off network interrupts. This is needed while writing data * to the terminal. */ { static int intsoff = 0; /* for ioctl */ ioctl (tcpfd, NIOCSETL, &intsoff); } tc_ion () /* Turn network interrupts back on. */ { static int intson = 1; /* for ioctl */ ioctl (tcpfd, NIOCSETL, &intson); } tcpdebug (onoff) /* Turn TCP debugging (packet-level tracing) on or off. * * Arguments: */ int onoff; /* boolean for debug state */ { TCPDEBUG = onoff; } rcvsig () { tk_wake (TCPrcv); }