; This file holds the modules for TCP. comment | STUFF TO DO Incoming ACKs should prevent retrans from aborting connection, since clearly it is still alive, just doesn't have room for our stuff (which is possibly overflowing its window). Note Clark suggs on windowing/ACKing. If input data seg doesnt have PUSH, don't send ACK, but set a timeout for sending ACK. Send ACK when: PUSH is seen outgoing seg forced out (new or retrans) timed out Output buffering stinks. If can't send buff due to too many segs, then should be able to keep adding to present segment. Provide way for output IOT to specify URGENT, and Handle URGENT when received on input. | SUBTTL TCP definitions %WYTCP==:7 ; Move to BITS later %MOD32==:740000 ; LH mask used for mod 32 arithmetic %TCPMI==:5 ; Max # segments in input queue per connection %TCPMO==:5 ; Max # segments in output queue per connection %TCPDS==:536. ; Default max # bytes per segment (when no ; knowledge of receiving host) %TCPMS==:2048.-40. ; Maximum possible segment size we can support ; This must be 7777 (octal) or less. %TCPMB==:%TCPMI*%TCPMS ; Max # bytes of data in queue (a bit fictional) %TCPMR==:20. ; Max # retransmit retries allowed %TCPMQ==:20 ; Max # pending RFCs allowed %TCPMP==:777 ; Max port # allowed for pending-RFC (SYN) conns ; Note pending-RFCs used ONLY for job startups, ; SYNs are not queued in general. ; Defintions of TCP Segment Header fields. %TCPHL==:5 ; # of 32-bit words in fixed part of TCP header TH%SRC==:777774,, ; 0 Source Port TH%DST==: 3,,777760 ; 0 Destination Port TH%SEQ==:777777,,777760 ; 1 Sequence Number TH%ACK==:777777,,777760 ; 2 Acknowledgement Number TH%THL==:740000,, ; 3 Data Offset (TCP Header Length in 32-bit wds) TH%RES==: 37400,, ; 3 Reserved (should be 0) TH%CTL==: 374,, ; 3 Control bits TH%WND==: 3,,777760 ; 3 Window TH%CKS==:777774,, ; 4 Checksum TH%UP==: 3,,777760 ; 4 Urgent Pointer ; 5 Start of Options/Data TH$SRC==:<.BP TH%SRC,0> TH$DST==:<.BP TH%DST,0 > TH$SEQ==:<.BP TH%SEQ,1> TH$ACK==:<.BP TH%ACK,2> TH$THL==:<.BP TH%THL,3> TH$RES==:<.BP TH%RES,3> TH$CTL==:<.BP TH%CTL,3> TH$WND==:<.BP TH%WND,3> TH$CKS==:<.BP TH%CKS,4> TH$UP==: <.BP TH%UP, 4> TH$OPT==:<441000,,5> ; An ILDB-type pointer to start of options. ; Control bit definitions (as located in full word) TC%URG==:<200,,> ; Urgent Pointer significant TC%ACK==:<100,,> ; Ack field significant TC%PSH==:< 40,,> ; Push Function TC%RST==:< 20,,> ; Reset connection TC%SYN==:< 10,,> ; Synchronize sequence numbers TC%FIN==:< 4,,> ; Finalize - no more data from sender ; TCP Connection tables, normally indexed by I ; These correspond to what the TCP document (RFC-793) calls ; the "Transmission Control Block" parameters. ; A TCB is "in use" if either XBUSER or XBSTAT is non-zero. ; XBUSER is set if a user job has channels associated with the TCB. ; XBSTAT is set if TCP is dealing with the TCB. ; PI level will never touch any TCBs which have a zero XBSTAT, so it is ; safe for the MP level to hack a zero-XBSTAT TCB without using NETOFF. IFNDEF XBL,XBL==10. ; Allow this many TCP connections for now. EBLK ; General variables XBUSER: BLOCK XBL ; RH User index XB%STY==:<770000,,> ; TTY # of STY connected to (0 if none) XB%ICH==:<007700,,> ; Input channel #+1 (77=IOPUSHed) XB%OCH==:<000077,,> ; Output channel #+1 (77=IOPUSHed) XB$STY==:<.BP XB%STY,XBUSER> XB$ICH==:<.BP XB%ICH,XBUSER> XB$OCH==:<.BP XB%OCH,XBUSER> XBSTAT: BLOCK XBL ; ,, ; Connection flags (internal to ITS) %XBMPL==:SETZ ; Current output segment locked at MP level (IOT) ; This must be sign bit for SGNSET/PCLSR to work. %XBCTL==:<374,,> ; Array of output request bits IFN %XBCTL-TH%CTL,.ERR %XBCTL flags must be the same as TH%CTL!! ; For all bits in %XBCTL the general meaning is ; "Set this bit in next outgoing segment". If no bits ; are set, output is sent every 2 sec, otherwise every ; 1/2 sec. If %XBNOW is set, output is sent as soon ; as something notices it. %XBNOW==:<1,,> ; Send output segment ASAP (else 1/2 sec clock) %XBACF==:<2,,> ; Our FIN has been ACKed %XBABT==:< 400,,> ; We're aborting. %XBFIN==:<1000,,> ; FIN received for input, input queue will not ; get any more additions. ; %XBWOK==:<100,,> ; State is OK for user to write (else get IOC err) ; %XBROK==:<200,,> ; State is OK for user to read (else get IOC err) ; Connection state, as in TCP document (RFC-793) ; Some test/dispatch code depends on the fact that the first ; 4 states have the values they do. ; ** NOTE: These .XSzzz symbols are not advertised to users. ; ** Maybe I'll rename them in here sometime. -- CSTACY 9/84 .XSCLS==:0 ; Closed (must be zero) .XSSYQ==:1 ; ADDITIONAL ITS STATE: Syn-Queued .XSLSN==:2 ; Listen .XSSYN==:3 ; Syn-Sent .XSSYR==:4 ; Syn-Rcvd .XSOPN==:5 ; Established (Open) .XSFN1==:6 ; Fin-Wait-1 .XSFN2==:7 ; Fin-Wait-2 .XSCLW==:10 ; Close-Wait .XSCLO==:11 ; Closing .XSCLA==:12 ; Last-Ack .XSTMW==:13 ; Time-Wait .XSTOT==:14 ; Total # of states XBSTAU: BLOCK XBL ; User Channel state ,, XBCLSU: BLOCK XBL ; Close reason ,, .XCNTO==:0 ; Never opened .XCUSR==:1 ; Closed by user .XCFRN==:2 ; Closed by foreign host .XCRST==:3 ; Fgn host reset things .XCDED==:4 ; Fgn host dead (apparently) .XCINC==:5 ; Incomplete transmission (retrans timeout) ; ==:6 ; Byte size mismatch - can't happen .XCNCP==:7 ; Local TCP went down .XCRFS==:10 ; Fgn host refused connection (valid RST ; received in SYN-SENT state) XBPORT: BLOCK XBL ; <4 zero bits> ; It is set up this way for fast lookup of ; incoming segments. XBHOST: BLOCK XBL ; Remote host (HOSTS3 format) XBLCL: BLOCK XBL ; Local host (HOSTS3 format) XBNADR: REPEAT XBL,-1 ; Net host address to give the device driver (-1 none) ; MP Input - see TCPI for detailed description XBITQH: BLOCK XBL ; Input Segment TCP queue header XBINBS: BLOCK XBL ; Total # bytes in input queue XBINPS: BLOCK XBL ; Total # segments in input queue XBIBP: BLOCK XBL ; Main prog BP to input XBIBC: BLOCK XBL ; # bytes available for this BP ; MP Output - see TCPW for detailed description XBOCOS: BLOCK XBL ; Current Output Segment pointer (0 if none) XBOBP: BLOCK XBL ; Main prog BP into output segment XBOBC: BLOCK XBL ; # bytes of room for this BP XBORTP: BLOCK XBL ; Retransmit parameters XBORTQ: BLOCK XBL ; Retransmit queue header XBORTL: BLOCK XBL ; Retransmit queue length (# of segments) XBORTC: BLOCK XBL ; Retransmit count (1st msg on queue) XBORTT: BLOCK XBL ; Retransmit timeout (1st msg on queue) ; TCP Send Sequence Variables XBSUNA: BLOCK XBL ; Send Unacknowledged XBSNXT: BLOCK XBL ; Send Next XBSWND: BLOCK XBL ; Send Window (offered window) XBSAVW: BLOCK XBL ; Available window (between SNXT and SUNA+WND) XBSUP: BLOCK XBL ; Send Urgent Pointer XBSWL1: BLOCK XBL ; Segment Seq number used for last window update XBSWL2: BLOCK XBL ; Segment Ack number used for last window update XBSMSS: BLOCK XBL ; Max seg size that receiver can handle ; TCP Receive Sequence Variables XBRNXT: BLOCK XBL ; Receive Next XBRWND: BLOCK XBL ; Receive Window XBRUP: BLOCK XBL ; Receive Urgent Pointer XBRMSS: BLOCK XBL ; Max seg size we are expecting/ have asked for BBLK NTSYNL: SIXBIT /TCP/ ; Start SYS;ATSIGN TCP for random SYNs. EBLK 0 ; Word for NUJBST etc to mung for above job starting TCPUP: -1 ; -1 to handle TCP stuff, 0 to turn off. TCPUSW: 0 ; -1 to disable net conns from anyone but ourself (like NETUSW) ; Perhaps eventually this should be the same as NETUSW. TCPRQN: 0 ; # of things in SYN queue, to keep it small TCPRQL: 0 ; Index of last SYN queued. TCPCRI: 0 ; Counter used for gensymming local port #s TISSLU: 0 ; Last ISS used TISSC: 0 ; Counter to further uniquize ISS TCPLCP: 0 ; Last TCB index allocated TCPBSW: -1 ? 0 ; Lock switch for allocating TCB indices TCPTMO: 4*30. ; Default timeout for retransmits (in 30'ths of sec) BBLK ; Macro to perform sequence-number range checking. ; Note all numbers are 32-bit positive integers, modulo 2**32. ; Use it like this: ; CMPSEQ ,,,,,, ; Left and Right are addrs of the range bounds. One of them ; must be an AC. ; Seqno must be an AC. ; LT and LE are the strings "<" and "=<". ; Lerr and Rerr are the places to JRST to if the left or ; right compares fail, respectively. Rerr can ; be omitted and will default to Lerr. ; e.g. ; CMPSEQ A,<,D,=<,XBSNXT(I),TSI30 ; NOTE CAREFULLY that only existence within a range is checked, ; and the bounds L,R of the range MUST be known to be L =< R! ; It does not work to use CMPSEQ for the degenerate case ; CMPSEQ A,<,B,<,B,ERR ; to see if A < B. DEFINE CMPSEQ (L),C1,S,C2,(R),(OUTV1),(OUTV2) %%%CML==0 %%%CMR==0 IFSE [C1][<] %%%CML==CAMG IFSE [C1][=<] %%%CML==CAMGE IFSE [C2][<] %%%CMR==CAML IFSE [C2][=<] %%%CMR==CAMLE IFE %%%CML&%%%CMR, .ERR Seq compare has bad relational arg %%%CMX==CAMLE IFSE [C1][=<] %%%CMX==CAML IFGE L-20,IFGE R-20, .ERR Seq compare needs ACs IFL L-20,CAMLE L,R ; Skip if normal order, L =< R .ELSE CAMGE R,L JRST [ ; Reverse order, R < L. Check S < R & L < S %%%CMR S,R ; Skipwin if S <(=) R %%%CMX S,L ; Unusual test here, win if S >(~=) L JRST .+5 ; If either wins, win completely! IFB OUTV2, JRST OUTV1 .ELSE CAML S,[020000,,] ? JRST OUTV1 ? JRST OUTV2 ] ; Normal order, L =< R %%%CML S,L ; Skipwin if S >(=) L JRST OUTV1 %%%CMR S,R ; Skipwin if S <(=) R IFB OUTV2, JRST OUTV1 .ELSE JRST OUTV2 TERMIN SUBTTL TCP Open system call ; .CALL TCPOPN ; arg 1 - receive channel number ; arg 2 - transmit channel number ; arg 3 - local port # (-1 to gensym unique port #) ; arg 4 - foreign port # (-1 for wild) ; arg 5 - foreign host address (HOSTS3 fmt) (-1 for wild) ; arg 6 - Retransmission timeout (optional) ;Control bits: ; - None needed for channels - they are opened as .UAI and .UAO ; automatically (no other modes possible). ; - 7 vs 8 bit ASCII transfers can be determined by user-space byte ; pointer used in SIOT. System buffers are always 8-bit bytes. %NOLSN==:100 ; Listen mode %NOBBI==:200 ; Use big buffer for input (not implemented yet) %NOBBO==:400 ; Use big buffer for output (not implemented yet) %NOWDA==:1000 ; Use word-align algorithm on transmit (not implemented yet) ; Note a value of -1 for either the foreign port or host will imply ; that the call is a "listen". For the time being, either also implies ; the other, i.e. wild port means wild host and vice versa. This is ; because I havent figured out what the right thing to do is for the ; various combinations that could result otherwise. ; Word-align means that for the transmit side, all segments sent will ; have the data aligned so that the first byte, and every fourth byte ; after that, will start on a 32-bit word boundary. This should ; produce a noticeable speedup for transfers that involve large blocks ; of words rather than small amounts of miscellaneous text. ; For the latter, it only makes things worse, so is not the default. ; Return is semi-immediate; the call may ; hang momentarily waiting for a free network buffer. (Have timeout? ; do a SKIPA SKIPA HANG to schedule, then fail if still none?) ; Use NETBLK ; to determine when the channels become open. For a non-listen call, ; there is an internal ITS timeout, but for listen the state can persist ; forever. TCPOPN: METER("TCP: syscal tcpopn") MOVEI A,(A) MOVEI B,(B) CAIGE A,NIOCHN CAIL B,NIOCHN JRST OPNL14 ; Bad channel # argument CAIN A,(B) JRST OPNL33 ; Illegal to use same channel # for both MOVEI J,(B) HRLI J,(A) ; Save chan #s in J/ ,, PUSH P,C PUSH P,D PUSH P,E PUSH P,J MOVEI R,(A) ; Close receive chan ADDI R,IOCHNM(U) PUSHJ P,CCLOSE ; Close whatever is already on channels. HRRZ R,(P) ; Close xmit chan ADDI R,IOCHNM(U) PUSHJ P,CCLOSE POP P,J POP P,E POP P,D POP P,C HLRZM J,UUAC(U) ; Remember input channel # for errs. SKIPN TCPUP ; If TCP disabled, JRST OPNL7 ; Fail, "device not ready". CALL SWTL ; Lock TCB assignment switch TCPBSW MOVE I,TCPLCP SOJL I,TCPO2 TCPO1: SKIPN XBUSER(I) ; Hunt for free TCB SKIPE XBSTAT(I) ; Must be both closed and unassigned. SOJGE I,TCPO1 JUMPGE I,TCPO3 ; Jump if got one! TCPO2: MOVEI I,XBL ; Hit beginning, wrap back to end CAMN I,TCPLCP JRST OPNL6 ; No free TCB's available MOVEM I,TCPLCP ; Might as well make faster next time SOJA I,TCPO1 TCPO3: MOVEM I,TCPLCP ; Save scan pointer for next time JRST TCPO4 ; (This is here for patching. -CSTACY) ; Got an index, now see if we're going to do a LISTEN ; or an active open. TCPO4: SETZ W, ; Assume active CAME C,[-1] ; Verify local port is OK CAIG C,177777 CAIA JRST OPNL11 ; Complain "illegal file name" CAMN D,[-1] AOJA W,.+3 CAILE D,177777 JRST OPNL11 CAMN E,[-1] ADDI W,2 ; W = 0 if no wildcards, =1 if port wild, =2 if host wild, =3 both. MOVE B,CTLBTS(U) ; Get control bits for call CAIE W, TRO B,%NOLSN ; Set "Listen" bit if implied by args. ; Crock - if either is wild, ensure both are. CAIE W, SETOB D,E SETZ R, ; Say we have no buffer TRNE B,%NOLSN ; Skip if not listening, doing active open. JRST TCPO20 ; Listening, don't need buffer. ; No wild-cards, this is going to be an active open. We will need ; a buffer to send the initial SYN, so let's get it now and get ; all possible PCLSR'ing over with, before turning off the NET PI. CALL PKTGFI ; Get a free packet, skip unless fail. CAIA ; Didn't get, skip to schedule. JRST TCPO15 ; Got it! SKIPA SKIPA ; Force a schedule CALL UFLS CALL PKTGFI ; Try again. If we fail again, net is full, JRST OPNL6 ; so better just return "device full" err. TCPO15: MOVEI R,(A) ; We have buffer! Fall through. TRCPKT R,"TCPO15 Alloc to send initial SYN" ; Okay, nothing can stop us now from running through to completion. ; We do all the following code with net interrupts OFF so that ; (a) We can scan all TCBs for port/host conflicts and be ; sure we checked everything right, ; (b) Incoming segments at int level won't be confused by ; an inconsistent state for this TCB. ; (c) We can check the pending-RFC queue safely. TCPO20: CONO PI,NETOFF ; Don't let PI level see dirty work. CAMN C,[-1] JRST [ CALL TCPPCR ; Assign unique TCP port # ROT D,-16. ; Put fgn port in high 16 bits DPB A,[.BP TH%DST,D] ; Deposit local port JRST TCPO30] ; Note that since port is unique, no ; possible conflict with existing, so skip chk. ; Also, low 4 bits indicate wildness if set. ; Note that low 4 bits of XBPORT are set to indicate wildness. ; This ensures that TCPIS won't find them, but TSISQ will. ; Have specific local port, check to make sure it doesn't already ; exist in TCB tables. ROT D,-16. ; Get fgn port in high 16 bits DPB C,[.BP TH%DST,D] ; Put together the ports word MOVSI T,-XBL TCPO22: CAMN D,XBPORT(T) ; Look for matching port set SKIPN XBSTAT(T) ; which is in use AOBJN T,TCPO22 JUMPL T,TCPO91 ; Ugh, found match! Must fail... ; OK, D has our unique port set, and we're ready to set things up. TCPO30: MOVEM D,XBPORT(I) ; Store port set SKIPL A,E CALL CVH3NA ; Make sure it's HOSTS3 format. MOVEM A,XBHOST(I) ; Store foreign host CALL IPBSLA ; Call IP for best local address MOVEM A,XBLCL(I) CALL TXBINI ; Initialize the TCB CALL TCPMSS ; Set default MSS values. Reexamined when ; foreign host known if this is a wild listen. CALL TCPRWS ; Open a default receive window HRRZM U,XBUSER(I) ; Make TCB/index in use HLRZ A,J ; Get back saved rcv channel # DPB A,[XB$ICH (I)] ; Deposit input channel DPB J,[XB$OCH (I)] ; and output channel MOVE B,[0101,,0] ; Increment both channel #'s by 1 ADDM B,XBUSER(I) ; So can distinguish chan 0 from no chan. HRLZ T,I ; Set up user's IOCHNM words HRRI T,TCPDUI ADDI A,IOCHNM(U) MOVEM T,(A) ; Set up input chan ,,TCPDUI HRRI T,TCPDUO ADDI J,IOCHNM(U) MOVEM T,(J) ; Set up output chan ,,TCPDUO ; Search pending-RFC queue to make sure we match up or reject ; with stuff in there. LDB B,[.BP TH%DST,XBPORT(I)] ; B gets local port # SETO D, ; D is -1 for any PE ptr. CALL TCPRQS ; Search queue, return index in A JUMPL A,TCPO41 ; Ignore further RFC checks if nothing. MOVEI C,(A) HRRZ A,XBITQH(C) CAIN A, BUG HALT HLRZ W,PK.IP(A) HLRZ H,PK.TCP(A) TRNE D,17 ; If we're "wild" accepting any request, JRST TCPO35 ; Take it! LDB B,[IP$SRC (W)] ; No, must try full match. CAMN B,XBHOST(I) ; If hosts match CAME D,TH$SRC(H) ; and ports match too JRST TCPO40 ; (don't) ; Matching request!! ; For now, we ignore the listen/active distinction here, and ; always try to establish connection with the pending RFC. ; So, can flush use of R for listen flag. Have to flush the ; extra buffer if it was "active" open, though, since we can ; just re-use the pending-RFC packet. TCPO35: METER("TCP: Open matched pending RFC") JUMPN R,TCPO36 MOVEI Q,XBITQH(C) ; If don't already have buffer, CALL PKQGF(PK.TCP) ; Get it from the queued SYN (C is idx to) SKIPN R,A ; It had better have a buffer! BUG HALT TRCPKT R,"TCPO36 Queued SYN used to answer pending RFC rqst" TCPO36: LDB B,[IP$SRC (W)] MOVEM B,XBHOST(I) ; Set host address LDB B,[IP$DST (W)] MOVEM B,XBLCL(I) ; Use local address the other end wants MOVE D,TH$SRC(H) MOVEM D,XBPORT(I) ; And ports CALL TCPMSS ; Find default segment sizes for connection CALL TCPRWS ; Set up receive window EXCH C,I ; C identifies slot of queued SYN. CALL TSISQF ; Flush the SYN from queue! MOVEI I,(C) CALL TSILSX ; Invoke interrupt level SYN+ACK, re-uses ; the packet and sets state and everything. JRST TCPO80 ; OK, take win return. ; Request doesn't match, restore it and fall thru. TCPO40: ; MOVEI Q,TCPRQH ; Thought we had something but didn't, ; CALL PKQPF(PK.TCP) ; so put back on queue. ; No matching request on pending-RFC queue. TCPO41: CAIN R, ; Skip if handling active open JRST [ MOVEI A,.XSLSN ; No, handling a listen. JRST TCPO70] ; Just change state and we're done. ; Active open, must fire off initial SYN. ; R has PE ptr to free packet to be used for the SYN. CALL TCPISS ; Get initial sequence # MOVEM A,XBSUNA(I) ; Set up sequence vars MOVEM A,XBSNXT(I) MOVSI T,(TC%SYN) ; Note no ACK in initial segment! TRCPKT R,"TCPO41 Send initial SYN" CALL TSOSSN ; Send SYN segment (clobber mucho ACs) MOVEI A,.XSSYN ; Set state to SYN-SENT and fall thru. TCPO70: HRRM A,XBSTAT(I) ; Set state LISTEN or SYN-SENT. CALL TCPUSI ; Change user state. TCPO80: CONO PI,NETON JRST LSWPJ1 ; Success return, unlock switch and skip. ; Port match failure, must back off and fail. TCPO91: CONO PI,NETON ; No need to hide our shame SKIPE A,R ; If we had a buffer, CALL PKTRT ; return it to freelist. JRST OPNL13 ; Say "file already exists". ; TXBINI - Initialize TCB connection table entries for specific index. ; The things it doesn't touch are commented out below. ; I/ TCB index TXBINI: ; SETZM XBUSER(I) ; Set after ; SETZM XBSTAT(I) ; Set after SETZM XBSTAU(I) SETZM XBCLSU(I) ; SETZM XBPORT(I) ; Set prior ; SETZM XBHOST(I) ; Set prior ; SETZM XBLCL(I) SETOM XBNADR(I) ; I/O vars SKIPE XBITQH(I) BUG CHECK,[TCP: Init TCB has input, I=],OCT,I,[list ],OCT,XBITQH(I) SETZM XBITQH(I) SETZM XBINBS(I) SETZM XBINPS(I) SETZM XBIBP(I) SETZM XBIBC(I) SKIPE XBOCOS(I) BUG CHECK,[TCP: Init TCB has output, I=],OCT,I,[list ],OCT,XBOCOS(I) SETZM XBOCOS(I) SETZM XBOBP(I) SETZM XBOBC(I) ; Retransmit stuff SETZM XBORTP(I) SKIPE XBORTQ(I) BUG CHECK,[TCP: Init TCB has retrans, I=],OCT,I,[list ],OCT,XBORTQ(I) SETZM XBORTQ(I) SETZM XBORTL(I) SETZM XBORTC(I) SETZM XBORTT(I) ; TCP Send Sequence Initialization SETZM XBSUNA(I) SETZM XBSNXT(I) SETZM XBSWND(I) SETZM XBSAVW(I) SETZM XBSUP(I) SETZM XBSWL1(I) SETZM XBSWL2(I) ; SETZM XBSMSS(I) ; Set after ; TCP Receive Sequence Initialization SETZM XBRNXT(I) SETZM XBRUP(I) ; SETZM XBRMSS(I) ; Set after ; SETZM XBRWND(I) ; Set after RET ; TCPPCR - Port Create. Creates a unique local port #. ; Returns # in A. Current algorithm is very simple/dumb. ; Must only be called at MP level with NETOFF. ; Clobbers T,Q TCPPCR: PUSH P,B MOVEI A,(U) ; Get user index IDIVI A,LUBLK ; Find job # AOS B,TCPCRI ; Bump and get new counter ROT B,-8. ; Put low bits into high LSHC A,8. ; Then shift them into port # CALL TCPPLU ; See if this port unique or not. JRST [ AOS TCPCRI ; If not, AOS stuff and keep going. AOJA A,.-1] POP P,B RET ; TCPPLU - Port Lookup. Skips if port # unique among local ports. ; A/ port # ; Clobbers T, Q. ; Returns .+1 if fail (number not unique) ; T/ idx of matching TCB TCPPLU: LSH A,4 ; Shift over for easier compare MOVSI T,-XBL TCPLU2: SKIPN Q,XBPORT(T) JRST TCPLU3 AND Q,[TH%DST] CAMN A,Q JRST TCPLU7 TCPLU3: AOBJN T,TCPLU2 AOS (P) TCPLU7: LSH A,-4 RET ; TCPMSS - Determine and set max bytes per segment for TCB in I ; I/ TCB index. XBHOST should be set already. ; Bashes A, T ; Base maximum TCP segment sizes on size of largest datagram IP wants ; to send to destination. This sets the default sizes. We will tell ; the foreign side what we want (XBRMSS) with a TCP MSS option in the ; outgoing SYN. We will adjust what we send (XBSMSS) down if foreign ; side requests it with MSS opton in an incoming SYN. TCPMSS: MOVE A,XBHOST(I) ; Foreign address CALL IPMTU ; IP datagram size to T SUBI T,40. MOVEM T,XBSMSS(I) ; Set default send and receive segment sizes MOVEM T,XBRMSS(I) RET SUBTTL Other TCP device system call routines ; Device name in DEVTAB, device code in DCHSTB, index in RSTB to some tables ; OPEN - from DEVADR TCPO: JRST OPNL12 ; Say "mode not avail" ; Save rest temporarily. HLRS C MOVSI A,(A) ; Save RH of FN1 in LH of IOCHNM JSP Q,OPSLC7 TCPDUI,,TCPDUO TCPDBI,,TCPDBO TCPDUI,,TCPDUO TCPDBI,,TCPDBO ; CLOSE - from CLSTB ; R/ addr of IOCHNM word TCPCLS: METER("TCP: syscal close") HLRZ I,(R) ; Get TCB index from LH of IOCHNM CAIL I,XBL ; Make sure it's reasonable BUG HALT,[TCP: CLS idx bad] HRRZ A,XBUSER(I) ; Verify user CAIE A,(U) BUG HALT,[TCP: CLS usr bad] SETO D, ; See if input or output HRRZ A,(R) CAIN A,TCPDUO ; Output? AOSA D CAIN A,TCPDUI ; Input? ADDI D,1 CAIGE D, ; D/ 0 for input, 1 for output. BUG ; IOCHNM value screwed up?? LDB A,[XB$ICH (I)] ; Get input chan # according to TCB LDB B,[XB$OCH (I)] ; Ditto output MOVEI C,(R) SUBI C,IOCHNM(U) ; Find channel # we're closing ADDI C,1 ; Increment since TCB # is really #+1 CAME C,A(D) ; Compare with channel # in TCB BUG HALT,[TCP: Close chan not same as TCB chan] JUMPN D,[MOVEI D,2 CAIE A, MOVEI D,3 JRST TCPC06] CAIE B, IORI D,1 TCPC06: ; D is now a 2-bit channel status index. ; Bit 1.2 is 0 for input, 1 for output. ; Bit 1.1 is 0 if other channel is closed, 1 if it is still open. SKIPN XBSTAT(I) ; Perhaps already gone? JRST TCPCL8 ; Yeah, flush channel etc. PUSH P,D CONO PI,NETOFF ; Ensure that state doesn't change on us. HRRZ J,XBSTAT(I) CAIL J,.XSTOT BUG HALT,[TCP: CLS state bad] XCT TCPCXT(J) ; Invoke closure stuff appropriate for state CONO PI,NETON ; TCB state hacking done, can re-enable ints. POP P,D ; Remove links between user channel and TCB. If both channels ; are gone, XBUSER is cleared completely. ; The TCB is not necessarily closed at this point (XBSTAT zero) ; but TCP will look after it independently to ensure it eventually ; goes away. TCPCL8: SETZ B, ; Get a zero MOVEI T,.XCUSR ; Use this for "Close reason" if needed TRNE D,2 ; Remember D bit 1.2 indicates output chan JRST [ DPB B,[XB$OCH (I)] ; Yup, clear output chan. CALL TCPUCO ; Set close reason if necessary HRRZ A,XBOCOS(I) ; Does a COS buffer exist? CAIN A, JRST TCPCL9 ; Nope, nothing to flush. CALL PKTRTA ; Aha, free it up. SETZM XBOCOS(I) SETZM XBOBP(I) SETZM XBOBC(I) JRST TCPCL9] DPB B,[XB$ICH (I)] ; Clear input chan. CALL TCPUCI ; Set close reason if need to. CALL TXBIFL ; Flush input queue TCPCL9: TRNN D,1 ; Skip if other channel still there. SETZM XBUSER(I) ; Else flush whole word incl user index! TRNE D,1 ; If a channel is left, CALL TCPUSI ; we may need to take interrupt on it. LDB A,[XB$STY (I)] ; Was a STY connected to channel? JUMPE A,CPOPJ ; Return if not. MOVEI I,(A) ; Ugh, must disconnect it! Set up TTY # CALL NSTYN0 ; Disconnect JFCL RET ; Return (CLOSE will clear IOCHNM/IOCHST) TCPCLE: BUG CHECK,[TCP: Illegal state in CLOSE, J=],OCT,J,[ D=],OCT,D CALL TXBFLS ; Flush all of TCB but XBUSER RET TCPCXT: OFFSET -. .XSCLS:: CALL TXBFLS ; Closed already, but flush again to make sure .XSSYQ:: CALL TCPCLE ; Syn-Queued - can't happen!! .XSLSN:: CALL TXBFLS ; Listen - flush TCB, enter closed state. .XSSYN:: CALL TXBFLS ; Syn-Sent - flush TCB, enter closed state. .XSSYR:: XCT TCPCXT+.XSOPN ; Syn-Rcvd - handled same as OPEN below .XSOPN:: XCT (D)[ ; Established (Open) CALL TCPCLE ; In (only) - Can't happen JFCL ; In (have Out) - Disconnect input CALL TCPC30 ; Out (only) - Send FIN, enter FIN-WAIT-1 CALL TCPC30] ; Out (have In) - " " " " .XSFN1:: XCT (D)[ ; Fin-Wait-1 JFCL ; In (only) - Disconnect input CALL TCPCLE ; In (have Out) - Can't happen CALL TCPCLE ; Out (only) - Can't happen CALL TCPCLE] ; Out (have In) - Can't happen .XSFN2:: XCT (D)[ ; Fin-Wait-2 CALL TXBFLS ; In (only) - Flush, give up waiting CALL TCPCLE ; In (have Out) - Can't happen CALL TCPCLE ; Out (only) - Can't happen CALL TCPCLE] ; Out (have In) - Can't happen .XSCLW:: XCT (D)[ ; Close-Wait CALL TCPCLE ; In (only) - Can't happen JFCL ; In (have Out) - Disconnect input CALL TCPC70 ; Out (only) - Send FIN, enter LAST-ACK CALL TCPC70] ; Out (have In) - " " " " .XSCLO:: XCT TCPCXT+.XSFN1 ; Closing - handled same as Fin-Wait-1 etc. .XSCLA:: XCT TCPCXT+.XSFN1 ; Last-Ack - handled same as Fin-Wait-1 etc. .XSTMW:: XCT TCPCXT+.XSFN1 ; Time-Wait - handled same as Fin-Wait-1 etc. .XSTOT:: OFFSET 0 ; Closing output channel while in SYN-RCVD state. ; Send a FIN and enter FIN-WAIT-1 state. TCPC30: CALL TCPCLF MOVEI J,.XSFN1 JRST TCPC75 ; Closing output channel while in CLOSE-WAIT state. ; Send a FIN and enter LAST-ACK state. TCPC70: CALL TCPCLF MOVEI J,.XSCLA TCPC75: HRRM J,XBSTAT(I) RET TCPCLF: MOVSI T,(TC%ACK+TC%FIN+%XBNOW) ; Tell TCP that output needs FIN and ACK. JRST TCPOFR ; Go force out current buffer if any ; TCPUC - Set "Reason-closed" states if not already set. ; T/ Reason to use, if none already exists. ; Clobbers Q TCPUC: CALL TCPUCI TCPUCO: HRRZ Q,XBCLSU(I) CAIN Q, HRRM T,XBCLSU(I) RET TCPUCI: HLRZ Q,XBCLSU(I) CAIN Q, HRLM T,XBCLSU(I) RET ; TXBFLS - Flush all info about a TCB from TCP viewpoint. ; Mostly consists of freeing up all buffers used, and then ; clearing out most other data cells of the TCB. ; Note that XBUSER and XBSTAU are not affected! ; TXBFLP - ditto but usable at PI level, it is careful not to smash ; things that MP level might be referencing. ; Clobbers A,T,Q ; TXBIFL - Flushes input queue ; TXBOFL - Flushes output queue (including retrans list!) TXBFLS: SETZM XBSTAT(I) CALL TXBIFL CALL TXBOFL SETZM XBPORT(I) SETZM XBHOST(I) RET ; TXBFLP - Things to be careful of: ; - swiping COS ; - flushing input queue (don't touch it) TXBFLP: CALL TXBOFL SETZM XBSTAT(I) ; Say off-limits to PI level now. SETZM XBPORT(I) SETZM XBHOST(I) LDB T,[XB$ICH (I)] ; See if input chan active CAIN T, CALL TXBIFL ; No input chan, so ensure input q flushed CALL TCPUSI ; Alert user to mung RET TXBIFL: SETZM XBINBS(I) SETZM XBINPS(I) SETZM XBIBP(I) SETZM XBIBC(I) MOVEI Q,XBITQH(I) CALL PKPFLS SKIPE XBITQH(I) BUG CHECK,[TCP: Incompl input fls I=],OCT,I,[list ],OCT,XBITQH(I) CALL TCPRWS ; Reset receive window. RET TXBOFL: HRRZ A,XBOCOS(I) ; If current output seg exists, CAIE A, SKIPGE XBSTAT(I) ; and isn't locked by MP level, CAIA JRST [CALL PKTRTA ; then free it SETZM XBOCOS(I) ; and clear the pointer. SETZM XBOBP(I) SETZM XBOBC(I) JRST .+1] SETZM XBORTT(I) SETZM XBORTC(I) MOVEI Q,XBORTQ(I) CALL PKPFL ; Flush retrans list carefully. SKIPE XBORTL(I) BUG CHECK,[TCP: Incompl output fls, I=],OCT,I,[list ],OCT,XBORTQ(I) MOVE A,XBSNXT(I) MOVEM A,XBSUNA(I) ; Claim everything ACK'd. SETZM XBSWND(I) ; Zero our send window. SETZM XBSAVW(I) ; and available window SETZM XBSUP(I) ; and urgent pointer. RET PKPFLS: PUSH P,Q PKPFL2: MOVE Q,(P) CALL PKQGF(PK.TCP) JUMPE A,POPQJ CALL PKTRTA ; Should always be freeable. JRST PKPFL2 ; Ditto, but for flushing retransmit queue, which has to be special ; since packets are linked on IP output list as well as TCP list. ; Since we can't take packets off the IP output list here, we just set ; a flag telling output PI level to ignore the packet. PKPFL: PUSH P,Q PKPFL3: MOVE Q,(P) CALL PKQGF(PK.TCP) JUMPE A,POPQJ CONO PI,PIOFF MOVE T,PK.FLG(A) ; Check packet flags TLNN T,(%PKODN) ; Output done? JRST [ TLO T,(%PKFLS) ; No, say to flush when hit it. MOVEM T,PK.FLG(A) CONO PI,PION TRCPKT A,"PKPFL3 Packet not flushed" JRST PKPFL4] CONO PI,PION CALL PKTRT PKPFL4: SOSGE XBORTL(I) BUG CHECK,[TCP: Retrans Q count err] JRST PKPFL3 SUBTTL TCP Main Program Input ; All TCP input segments for a connection are put on a queue that ; is headed at XBITQH. When this header is zero, there is no more ; input; if the %XBFIN flag is also set, the remote host has closed ; its transmit side and there will never be any more input. ; Segments are only added by PI level, at the end of the queue. ; Segments are only removed by MP level IOTs, at the start of the queue. ; (An incoming RST will of course flush the queue at PI level) ; If XBIBP is non-zero, it points into the first segment on the input queue, ; and XBIBC is also valid; things are ready for MP IOTing. ; However, neither XBIBP nor XBIBC is meaningful if XBITQH is zero. ; Input IOT - from IOTTB SKIPA T,[SIOKT] ; Come here for SIOT entry TCPI: MOVEI T,CHRKT METER("TCP: syscal in") HLRZ I,(R) ; Get TCB index ; Verify state, do misc setup for reading MOVSI B,(XB%STY) TDNE B,XBUSER(I) ; Can't IOT if direct-connected to STY. JRST IOCR10 ; "Chan in illegal mode" HLRZ B,XBSTAU(I) ; Just reading state, don't need NETOFF. SKIPG TCPTBI(B) ; Ensure meta-state allows reading. JRST [ HLRZ B,XBCLSU(I) ; Can't read, see if reason OK CAIN B,.XCFRN ; Only OK reason is clean fgn close. JRST UNIEOF ; Yeah, just return quietly. JRST IOCR10] MOVE E,[441000,,4] ; 8-bit bytes, 4 to a word MOVEI B,[ XBIBP(I) ; Byte pointer XBIBC(I) ; # bytes to read TCPIBG ; Routine to get next buffer TCPIBD ; Routine to discard buffer 0 ; not used TRNA ; Negative - TCPIBG and TCPIBD will do waiting. ] CALL (T) CAIA AOS (P) SKIPG XBIBC(I) ; If count for this buffer reached zero, CALL TCPIBD ; Flush it so XBITQH is valid indication of input avail RET ; TCPIBD - Discard input buffer, invoked by I/O. ; This is always called before TCPIBG is. TCPIBD: SKIPN XBIBP(I) ; Make sure something's there to discard. RET ; Nope, gone or was never set up. MOVEI Q,XBITQH(I) ; Point to TCP input queue header CALL PKQGF(PK.TCP) ; Get first thing off queue, into A CAIN A, ; Something better be there. BUG HALT,[TCP: IOTI queue lost] ; Check BP just out of sheer paranoia. HRRZ T,XBIBP(I) ; Find addr BP points to (maybe +1 actual) HLRZ Q,PK.TCP(A) ; Get addr of TCP header CAIL Q,(T) ; Header better be less than BP! JRST TCPIB2 TRZ Q,PKBSIZ-1 ; Get addr of start of buffer CAILE T,PKBSIZ(Q) ; BP should be within or just past end. TCPIB2: BUG HALT,[TCP: IOTI BP incons] ; Okay, end of paranoia, just flush the buffer. LDB T,[PK$TDL (A)] ; Find # chars we read MOVN T,T ADDM T,XBINBS(I) ; Update # chars avail for input. CALL PKTRT ; Return packet to freelist. SOSGE T,XBINPS(I) ; Decrement count of segs on input queue BUG CHECK,[TCP: Input Q count incons] CAIL T,%TCPMI/2 ; If we are now handling past 50% input, JRST [ MOVSI T,(TC%ACK) ; Make sure we send an ACK IORM T,XBSTAT(I) ; so new rcv window is reported. JRST .+1] CONO PI,NETOFF CALL TCPRWS ; Set new receive window CALL TXBIST ; Get new input chan state HRLM T,XBSTAU(I) ; Set it. Note interrupt is avoided here. CONO PI,NETON SETZM XBIBP(I) SETZM XBIBC(I) RET ; Always return with simple POPJ TCPRWS: MOVEI T,%TCPMI SUB T,XBINPS(I) ; Find # segs we can still queue up CAIGE T,1 ; If no full segs left, TDZA T,T ; Zero the window, no more segs allowed IMUL T,XBRMSS(I) ; Else will take N * MSS bytes TCPRW3: MOVEM T,XBRWND(I) RET IFN 0,[ ; This code turns out to lose because the code at TCPIS only ; checks XBRWND to see whether to compact input or not, and as ; long as XBRWND is non-zero, stuff will always be added to queue, ; using up all the packet buffers. ; Basically it's a question of whether or not to allow more input, ; up to limits of last queued buffer, if the queue has too many ; buffers on it. Metering will show whether most other implementations ; win or lose with our buffer-alloc type windowing. TCPRW2: HLRZ Q,XBITQH(I) ; Find # chars room in last seg LDB T,[PK$TDL (Q)] LDB Q,[PK$TDO (Q)] ADDI Q,(T) MOVEI T,576. SUBI T,(Q) CAIGE T, SETZ T, MOVEM T,XBRWND(I) RET ] ; TCPIBG - Get new input buffer (invoked by I/O, after TCPIBD) ; Return .+1 if can't get new buffer, must wait (Never, we do waiting) ; Return .+2 if OK, new BP and count set up. ; Return .+3 if "EOF", transfer complete TCPIBG: SKIPE XBIBP(I) ; Shouldn't be anything already there. BUG HALT,[TCP: IOTI buf incons] TCPIB3: SKIPN A,XBITQH(I) ; See if anything in input queue JRST TCPIB5 ; No, go handle EOF. LDB T,[PK$TDL (A)] ; Find # bytes input for this segment CAIN T, ; Something probably shd be there. BUG HALT,[TCP: IOTI null seg] MOVEM T,XBIBC(I) ; Store as new # bytes LDB T,[PK$TDO (A)] ; Get offset from start of header HLRZ Q,PK.TCP(A) ; Get addr of TCP header ROT T,-2 ; Divide offset by 4 ADDI Q,(T) ; Point to right word LSH T,-34. ; Right-justify the low 2 bits HRL Q,(T)[441000 ? 341000 ? 241000 ? 141000] ; Get right LH for BP MOVEM Q,XBIBP(I) ; Now store BP! JRST POPJ1 ; Say ready to go again... ; No input available. First check to see if there will ever ; be any more (FIN seen?), then whether to return right away or ; hang. TCPIB5: CONO PI,NETOFF ; Avoid timing inconsistencies SKIPE A,XBITQH(I) ; Check again JRST [ CONO PI,NETON ; Got some?? JRST TCPIB3] ; Try again. SKIPN XBINPS(I) ; No, should also have no segments SKIPE XBINBS(I) ; and no bytes BUG HALT,[TCP: IOTI count incons] MOVE A,XBRWND(I) ; Save value of rcv window CALL TCPRWS ; Then reset the window CAME A,XBRWND(I) ; Was previous value correct? METER("TCP: RCV.WND out of synch") MOVE T,XBSTAT(I) ; Get flags CONO PI,NETON TLNE T,(%XBFIN) ; FIN seen, and input queue empty? JRST TCPIB6 ; Yes, true EOF now. MOVE T,CTLBTS(U) ; See if call had "don't-hang" bit set TRNE T,10 JRST TCPIB7 ; No, return EOF. SKIPN XBITQH(I) ; Wait until input queue has something. CALL UFLS JRST TCPIBG ; Then call again. TCPIB6: TCPIB7: CALL TCPUSI ; Adjust user state. JRST POPJ2 ; and return "EOF" SUBTTL TCP Main Program Output ; Output IOT - from IOTTB ; Output segments are chained together from XBORTQ, which is ; the "retransmit queue". ; The queue only contains segments which occupy sequence space, since ; these are the only ones which require ACKs and possible retransmit. ; All others are sent directly to the IP output queue. ; While the transmit connection is open, ; Segments are only added by MP level IOTs, at the end of the queue. ; Segments are only removed by PI level ACKs, at the start of the queue. ; Main program I/O is done into the "Current Output Segment", which is NOT ; on the retransmit queue. There are three variables related to this COS. ; XBOCOS - ,, ; XBOBP - BP into the COS, for MP IOT writing. ; XBOBC - Count of # bytes left that MP IOT can deposit into. ; Note that the maximum possible size of the buffer is kept in PK$TDL ; (TCP segment Data Length). For windowing reasons it may be necessary ; to restrict the amount of space actually used, thus the initial value ; of XBOBC may be less than PK$TDL. This is why the initial value is also ; copied into the RH of XBOCOS, so that when XBOBC counts out we know ; exactly how much of the buffer was actually used. It is possible for ; XBOBC to be increased by interrupt level window processing, in order ; to increase utilization of the buffer. ; States: ; If XBOCOS is zero, XBOBP and XBOBC must also be zero; there is ; no COS. ; If XBOCOS is non-zero (a current output seg exists), then: ; if LH(XBOCOS) is zero, the segment hasn't yet been written ; into, and needs to be set up. ; XBOBP and XBOBC should be zero! ; else the segment is set up for writing. XBOBP should be set! ; If XBOBC is zero it means the segment now contains ; LH(XBOCOS) bytes of data. If this number is less ; than PK$TDL (max possible seg data) then the count ; may be reset to allow further output into this ; segment, or it may simply be sent as is. ; ; The current segment is put on the retransmit queue (and IP output queue) ; when: ; PI level (eg clock) decides it's time to send an ACK or do a FORCE. ; MP level IOT fills up the segment completely. ; MP level FORCE or CLOSE is invoked. ; The current segment is locked down during MP IOT, to keep PI level ; from ripping it away (which would leave entrails dangling). ; PCLSR'ing will clear this lock. If TCP flushes the TCB at PI level ; for some reason, XBOCOS will be freed unless locked. XBOBC and XBOBP ; will still be cleared even if locked, so as to cause a call to TCPOBW ; which will notice the condition and free the COS itself. SKIPA A,[SIOKT] ; Come here for SIOT entry TCPW: MOVEI A,CHRKT METER("TCP: syscal out") HLRZ I,(R) ; Get TCB index from IOCHNM wd ; Verify state, do misc setup for writing, lock segment. CONO PI,NETOFF HRRZ B,XBSTAU(I) ; Get output chan state SKIPG TCPTBO(B) ; See if meta-state allows writing JRST IOCR10 ; Can't, say "chan not open" (ugh) MOVSI B,(XB%STY) TDNE B,XBUSER(I) ; Also can't if direct-connected to STY. JRST IOCR10 MOVSI B,(%XBMPL) ; Set locked flag (must be sign bit!) IORM B,XBSTAT(I) CONO PI,NETON ; Okay, we've got it. CALL SGNSET ; Set PCLSR routine to unlock flag. XBSTAT(I) SKIPN XBOCOS(I) ; If no COS there, SETZM XBOBC(I) ; make SURE count is zapped so refill invoked. MOVE E,[441000,,4] ; 8-bit bytes, 4 to a word MOVEI B,[ SETZ XBOBP(I) ; Output BP found here (sign sez is output) XBOBC(I) ; # bytes of room remaining TCPOBG ; Routine to get another buffer (not used) TCPOBW ; Buffer full, routine to send it. 0 ; Not used TRNA] ; Negative - TCPOBG and TCPOBW will do waiting. CALL (A) CAIA AOS (P) ; Pass on a skip return. ; User IOT is done, now unlock the segment. ; We also check for wanting to do an immediate ACK and if needed ; ship out the current buffer right now, without waiting ; for the 1/2-sec clock to do it. SKIPN A,XBSTAT(I) ; See if XBSTAT is still set JRST IOCR10 ; No, take IOC error return! CAIL A, ; It better still be locked! BUG CHECK,[TCP: Output not locked] CALL LSWPOP ; Clear the lock flag TLNN A,(%XBNOW) ; Was "immediate-send" flag set? RET ; Nope, can just return. METER("TCP: TCPW exit force") CONO PI,NETOFF MOVSI T,(TC%PSH) ; Hmm, set up and shove out. CALL TCPOFR ; and force out current output segment. CONO PI,NETON RET TCPOBG: BUG CHECK,[TCP: IOT called wrong rtn (TCPOBG)] AOS (P) ; If proceeded, can still win. Make skip return ; and drop through to TCPOBW. ; TCPOBW - Write/Get output buffer, invoked by SIOKT/CHRKT when the ; buffer count (XBOBC) is zero. This routine can figure out ; whether it needs to ship out a full buffer, or get a new ; output buffer, or both. Always returns with XBOBP and ; XBOBC set up for additional output (otherwise it hangs and ; can be PCLSR'd) TCPOBW: SKIPE R,XBOCOS(I) ; Get PE ptr to COS JRST [ HLRZ A,R ; Got a COS, see if already set up JUMPN A,TCPOB5 ; Jump if so. JRST TCPOB2] ; Else must set it up. ; No current segment, must get a new one. HRRZ T,XBSTAU(I) ; First ensure output state is OK. SKIPG TCPTBO(T) ; Skip if still OK to output. JRST IOCR10 ; Blooie, say "Chan not open". CALL PKTGF ; Get one, hang until we succeed. MOVEI R,(A) ; Set up in std AC TRCPKT R,"TCPOBW Alloc for IOT output buffer" HRRZM R,XBOCOS(I) ; Store ptr ; Set up segment for IOT to deposit into. TCPOB2: MOVEI T,%TCPMO ; Get max # segments allowed on queue CAMG T,XBORTL(I) ; Hang until we have less than this. CALL UFLS ; Note that conn closure will unhang too, ; because it flushes output queue. CALL TSOINI ; Initialize the segment (set up W, H) LDB A,[PK$TDO (R)] ; Find offset data should start at. TRNE A,3 BUG HALT ; Should always start at wd boundary! LSH A,-2 ; Find # words ADDI A,(H) ; Add address of TCP header, HRLI A,441000 ; and now we have our initial BP. MOVEM A,XBOBP(I) ; Set it up. LDB A,[PK$TDL (R)] ; Get max length avail in this segment ; Now have a fresh buffer and nothing else to wait for. ; Freeze the world, make sure it's still OK to output, and find ; out how big an output segment we can allow. TCPOB4: CONO PI,NETOFF HRRZ T,XBSTAU(I) ; Still OK to output? Check again. SKIPG TCPTBO(T) JRST [ MOVEI A,(R) ; Bah, must return buffer. CALL PKTRTA SETZM XBOCOS(I) CONO PI,NETON JRST IOCR10] ; Barf "Chan not open". MOVEI T,(I) ; Get index in T for PCLSRing. CALL TCPOB9 ; Check available window JRST [ CONO PI,NETON ; Window too small, allow ints CALL TCPOB9 CALL UFLS JRST TCPOB4] ; Big enough, go back and re-try stuff. LDB Q,[PK$TDL (R)] ; Get max # bytes available CAMLE Q,XBSAVW(I) ; Greater than window? MOVE Q,XBSAVW(I) ; Yeah, truncate down to this size. HRLM Q,XBOCOS(I) ; Store original # bytes in LH of XBOCOS MOVEM Q,XBOBC(I) CONO PI,NETON RET ; Okay, all set up, return. TCPOB9: MOVE A,XBSWND(T) LSH A,-2 ; Get 25% offered window CAML A,XBSAVW(T) ; If 25% offered > avail window, RET ; punt and wait for better stuff. JRST POPJ1 ; Here when we were all set up, and output has used up all ; of the buffer space initially available. Check to make sure ; there isn't more we can fill out, and if not then fire off ; the segment. TCPOB5: HLRZ T,XBOCOS(I) ; Get # bytes we originally had CONO PI,NETOFF ; Avoid magic changes in send window CAML T,XBSAVW(I) JRST TCPOB6 ; Send window same or smaller (!), send seg. MOVE Q,XBSAVW(I) ; Send window is bigger! Get new size LDB A,[PK$TDL (R)] ; Get max size CAMLE A,Q MOVEI A,(Q) ; Use minimum of max size and send window. MOVEI Q,(A) ; Save result SUBI A,(T) ; Find # more bytes we can hack CAIG A, ; If there's no more, JRST TCPOB6 ; Just send it off anyway. HRLM Q,XBOCOS(I) ; Hurray, got more! Store new original # MOVEM A,XBOBC(I) ; And set up new count CONO PI,NETON RET ; And return happily. TCPOB6: TRCPKT R,"TCPOB6 IOT Send" CALL TCPOB7 JRST TCPOBW TCPOB7: DPB T,[PK$TDL (R)] ; Okay, say this many bytes of data are in seg PUSH P,B PUSH P,C PUSH P,E MOVSI T,(TC%PSH) ; Ensure seg is pushed out. IORM T,XBSTAT(I) CALL TSOSND ; Send data segment (# bytes in PK.TCI) ; This clobbers a lot of ACs! SETZM XBOCOS(I) ; No current output segment now. CONO PI,NETON SETZM XBOBP(I) SETZM XBOBC(I) POP P,E POP P,C POP P,B RET ; TCPOFR - Force out partially-filled current output segment ; Must have NETOFF. ; Called by FORCE and CLOSE at MP level ; by TCPCLK at PI clock level ; Note that we try to never have stuff in the COS which would ; over-run our send window, by hanging in MP IOT. This will ; be slightly screwed up if the receiver suddenly decreases the window ; size, since this routine always sends the whole thing anyway, ; but it's probably OK (helps avoid SWS) ; I/ TCB index ; T/ additional flags to use (PUSH, URG, FIN) ; Clobbers R and everything that TSOSND does (a lot!) TCPOFR: MOVE A,XBSTAT(I) ; Get flags for connection TLNE A,(%XBCTL) ; Wants anything added on? IOR T,A ; Yes, OR the bits in. JUMPL A,TCPOF6 ; If locked at MP level, don't send it! SKIPN R,XBOCOS(I) ; See if current output seg exists JRST TCPOF5 ; No, can't hack now. HLRZ TT,R ; Get # bytes of original buffer size JUMPE TT,TCPOF5 ; If none, nothing to hack. SUB TT,XBOBC(I) ; Subtract # left, to get # bytes data CAIG TT, JRST [ SETZ TT, ; No data, see if a flag wants to be sent. TLNN T,(TC%FIN+TC%ACK+TC%SYN) ; Any of these are impt. JRST TCPOF9 ; Nope, do nothing. JRST .+1] DPB TT,[PK$TDL (R)] ; Store back # bytes of real data AND T,[TH%CTL] ; Mask off the flags IORM T,XBSTAT(I) ; Stuff in as requests TRCPKT R,"TCPOFR Force send" CALL TSOSND ; Send out the stuff SETZM XBOCOS(I) SETZM XBOBP(I) SETZM XBOBC(I) TCPOF9: RET ; No current output segment, so no data to send. Check, though, ; to see if any flags need sending. TCPOF5: TLNN T,(TC%SYN+TC%ACK+TC%FIN) RET ; Nope, just return. MOVE E,T ; They do! Save em against smashage CALL PKTGFI ; Try to get a buffer (clobbers T,Q) JRST TCPOF6 ; Ugh, failed, see about setting flags. MOVEI R,(A) TRCPKT R,"TCPOF5 Alloc and send flags only in TCPOFR" MOVE T,E ; Restore flags CALL TSOSNR ; Set up the packet and send it! RET ; Can't get packet now, so set up the request flags for later hacking. ; Also comes here when current output seg is locked at MP level. TCPOF6: AND T,[%XBCTL] ; Clear out extraneous bits TLO T,(%XBNOW) ; Ask to send stuff immediately IORM T,XBSTAT(I) ; and set flags back. RET ; TCPOSB - Routine similar to TCPOBW, except that it doesn't hang, ; so that it is suitable for calling at PI level (by STYNTC esp) ; Returns .+1 if can't set up output buffer for writing. ; Returns .+2 if output buff is all set up, with non-zero XBOBC. TCPOSB: SKIPE R,XBOCOS(I) JRST [ HLRZ A,R ; Have COS, see if already set up JUMPN A,TCPOS5 ; Jump if so. JRST TCPOS2] ; Else just set it up. ; No current segment, get a new one. HRRZ T,XBSTAU(I) ; First ensure output state is OK. SKIPG TCPTBO(T) ; Skip if still OK to output. RET ; Blooie. CALL PKTGFI ; Get one, skip if successful RET ; Sigh... MOVEI R,(A) ; Set up in std AC TRCPKT R,"TCPOSB Alloc for STYNET output data" HRRZM R,XBOCOS(I) ; Store ptr ; Set up segment for IOT to deposit into. TCPOS2: MOVEI T,%TCPMO ; Get max # segments allowed on queue CAMG T,XBORTL(I) ; Fail if we have more than this. RET CALL TSOINI ; Initialize the segment (set up W, H) LDB A,[PK$TDO (R)] ; Find offset data should start at. TRNE A,3 BUG HALT ; Should always start at wd boundary! LSH A,-2 ; Find # words ADDI A,(H) ; Add address of TCP header, HRLI A,441000 ; and now we have our initial BP. MOVEM A,XBOBP(I) ; Set it up. LDB A,[PK$TDL (R)] ; Get max length avail in this segment ; Now have a fresh buffer and nothing else to wait for. ; Freeze the world, make sure it's still OK to output, and find ; out how big an output segment we can allow. TCPOS4: CONO PI,NETOFF HRRZ T,XBSTAU(I) ; Still OK to output? Check again. SKIPG TCPTBO(T) JRST [ MOVEI A,(R) ; Bah, must return buffer. CALL PKTRTA SETZM XBOCOS(I) CONO PI,NETON RET] ; Barf "Chan not open". MOVEI T,(I) ; Get index in T for testing (no PCLSR) CALL TCPOB9 ; Check available window JRST NETONJ ; Window too small, just return LDB Q,[PK$TDL (R)] ; Get max # bytes available CAMLE Q,XBSAVW(I) ; Greater than window? MOVE Q,XBSAVW(I) ; Yeah, truncate down to this size. HRLM Q,XBOCOS(I) ; Store original # bytes in LH of XBOCOS MOVEM Q,XBOBC(I) CONO PI,NETON AOS (P) RET ; Okay, all set up, return. ; Here when we were all set up, and output has used up all ; of the buffer space initially available. Check to make sure ; there isn't more we can fill out, and if not then fire off ; the segment. TCPOS5: HLRZ T,XBOCOS(I) ; Get # bytes we originally had CONO PI,NETOFF ; Avoid magic changes in send window CAML T,XBSAVW(I) JRST TCPOS6 ; Send window same or smaller (!), send seg. MOVE Q,XBSAVW(I) ; Send window is bigger! Get new size LDB A,[PK$TDL (R)] ; Get max size CAMLE A,Q MOVEI A,(Q) ; Use minimum of max size and send window. MOVEI Q,(A) ; Save result SUBI A,(T) ; Find # more bytes we can hack CAIG A, ; If there's no more, JRST TCPOS6 ; Just send it off anyway. HRLM Q,XBOCOS(I) ; Hurray, got more! Store new original # MOVEM A,XBOBC(I) ; And set up new count CONO PI,NETON AOS (P) RET ; And return happily. TCPOS6: TRCPKT R,"TCPOS6 STYNET Send" CALL TCPOB7 JRST TCPOSB TCPBI: TCPBO: RET ; No-ops, labels left in case want to use. ; STATUS - from LH(DTSTB) ; Must return status in LH(D). Must not smash C,R. ; R/ addr of IOCHNM word TCPSTA: HLRZ I,(R) ; Get TCB index SKIPN XBUSER(I) ; Probably an error if this is zero. BUG CHECK,[TCP: STATUS on unused conn ],OCT,I SETZ D, SKIPN XBSTAT(I) RET HRRZ A,(R) ; Find whether input or output CAIN A,TCPDUI SKIPA T,[TXBIST] MOVEI T,TXBOST CALL (T) DPB T,[140600,,D] RET TXBIST: HRRZ T,XBSTAT(I) CAIL T,.XSTOT BUG HALT SKIPGE T,XBCTBI(T) ; Get conversion JRST [ SKIPN XBITQH(I) ; Must test for input avail - any segs? SKIPA T,(T) ; None avail, use standard MOVE T,1(T) ; Have some waiting, use alternate state RET] RET XBCTBI: OFFSET -. .XSCLS:: SETZ [%NTCLS ? %NTCLI] ; 0 Closed .XSSYQ:: 0 ; Technically this is an impossible state... .XSLSN:: %NTLSN ; 1 Listen .XSSYN:: %NTSYN ; 4 Syn-Sent .XSSYR:: %NTSYR ; 2 Syn-Rcvd .XSOPN:: SETZ [%NTOPN ? %NTINP] ; 5/11 Established (open) .XSFN1:: SETZ [%NTOPN ? %NTINP] ; 7 Fin-Wait-1 .XSFN2:: SETZ [%NTOPN ? %NTINP] ; 7 Fin-Wait-2 .XSCLW:: SETZ [%NTCLU ? %NTCLI] ; 3/10 Close-Wait .XSCLO:: SETZ [%NTCLS ? %NTCLI] ; 7/10 Closing .XSCLA:: SETZ [%NTCLS ? %NTCLI] ; 7 Last-Ack .XSTMW:: SETZ [%NTCLS ? %NTCLI] ; 7 Time-Wait .XSTOT:: OFFSET 0 TXBOST: HRRZ T,XBSTAT(I) CAIL T,.XSTOT BUG HALT SKIPGE T,XBCTBO(T) ; Get conversion JRST [ SKIPN XBORTQ(I) ; Must test for output queued SKIPA T,(T) ; None, use standard MOVE T,1(T) ; Have some output waiting, use alternate state RET] RET XBCTBO: OFFSET -. .XSCLS:: %NTCLS ; 0 Closed .XSSYQ:: 0 ; Technically this is an impossible state... .XSLSN:: %NTLSN ; 1 Listen .XSSYN:: %NTSYN ; 4 Syn-Sent .XSSYR:: %NTSYR ; 2 Syn-Rcvd .XSOPN:: SETZ [%NTOPN ? %NTWRT] ; 5/6 Established (open) .XSFN1:: %NTCLX ; 7 Fin-Wait-1 .XSFN2:: %NTCLX ; 7 Fin-Wait-2 .XSCLW:: SETZ [%NTOPN ? %NTWRT] ; 5/6 Close-Wait .XSCLO:: %NTCLX ; 7 Closing .XSCLA:: %NTCLX ; 7 Last-Ack .XSTMW:: %NTCLX ; 7 Time-Wait .XSTOT:: OFFSET 0 ; WHYINT - from RH(DTSTB) ; Results are: ; A/ %WYTCP ; B/ ; C/ input - # bytes in input buff ; output - # bytes of room avail in output buff ; D/ Close reason (only valid if state %NTCLS) TCPWHY: HLRZ I,(R) ; Get TCB index METER("TCP: syscal whyint") CAIL I,XBL BUG HALT,[TCP: WHY idx bad] CALL TCPSTA LDB B,[140600,,D] ; Get state for channel HRRZ A,(R) ; Find whether input or output CAIN A,TCPDUI JRST [ HLRZ D,XBCLSU(I) ; Get input close reason MOVSI C,(XB%STY) TDNE C,XBUSER(I) ; No input avail if direct-conn to STY JRST [ SETZ C, ? JRST TCPWH5] SKIPLE C,XBINBS(I) JRST TCPWH5 SKIPN C,XBITQH(I) JRST TCPWH5 LDB C,[PK$TDL (C)] JRST TCPWH5] HRRZ D,XBCLSU(I) ; Get output close reason SKIPN C,XBOBC(I) ; Get # bytes of room left in current pkt JRST [ MOVEI C,%TCPMO ; If none, return total queue space instead SUB C,XBORTL(I) IMUL C,XBSMSS(I) CAIG C, SETZ C, JRST .+1] TCPWH5: MOVEI A,%WYTCP JRST POPJ1 ; RFNAME - from LH(DRFNTB) ; A/ LH of IOCHNM word for channel. TCPRCH: MOVEI I,(A) LDB B,[.BP TH%DST,XBPORT(I)] LDB C,[.BP TH%SRC,XBPORT(I)] MOVE D,XBHOST(I) MOVEI W,4 POPJ P, ; RFPNTR - from RH(DRFNTB) TCPRFP: JRST OPNL34 ; IOPUSH/POP - from LH(RSTBI) TCPIOP: HRRZ T,R SUBI T,IOCHNM(U) CAIN I, SKIPA T,[77] ; IOPUSH, use 77 ADDI T,1 ; IOPOP, use chan+1 HLRZ I,(R) ; Get TCB index HRRZ B,(R) ; Get direction CAIN B,TCPDUI ; as a BP to chan # SKIPA B,[XB$ICH (I)] MOVE B,[XB$OCH (I)] DPB T,B ; Store new saved channel # POPJ P, ; RESET - from RH(RSTBI) ; This doesn't have to do anything for a while yet. TCPRST: POPJ P, ; FORCE - from LH(DFRCTB) ; Should force out the TCP segment currently being written, ; and give it a good shove (ie PUSH). ; A/ LH of IOCHNM word, in RH. ; H/ IOCHNM word ; R/ ,, TCPFRC: METER("TCP: syscal force") HRRZ B,(R) ; This should be a TCP output channel. CAIE B,TCPDUO ; If not output, must be input, so JRST OPNL2 ; say "wrong direction". HLRZ I,(R) ; Get TCB index CAIL I,XBL ; Ensure validity BUG HALT,[TCP: FRC bad idx] ; Ensure that state allows sending anything. CONO PI,NETOFF ; So state doesn't change while we think. HRRZ J,XBSTAT(I) CAIE J,.XSOPN CAIN J,.XSCLW CAIA JRST OPNL7 ; Bad state, say "device not ready". PUSH P,R MOVSI T,(TC%PSH) ; Set PUSH flag (but not ACK, to avoid ; forcing send of empty buffer) CALL TCPOFR ; Force out! Clobber many ACs. CONO PI,NETON POP P,R JRST POPJ1 ; FINISH - from RH(DFRCTB) ; We already know that R is OK since FORCE looked at it first. ; In fact, I is still set up. ; R/ addr of IOCHNM word TCPFIN: METER("TCP: syscal finish") MOVSI T,(%XBNOW) TDNE T,XBSTAT(I) ; Wait until this bit is off (XBOCOS put on Q) CALL UFLS SKIPE XBORTQ(I) ; Hang until retransmit queue is empty. CALL UFLS JRST POPJ1 SUBTTL TCP STY connection routines ; STYTCP - invoked by STYNTC routine during 1/2 sec clock, for ; STYs connected to TCP channels. ; R/ TTY # STYTCP: MOVE I,STYNTI-NFSTTY(R) ; Get TCB index for connection LDB TT,[XB$STY (I)] ; Verify that TCB thinks we're hooked up CAIE TT,(R) BUG ; It doesn't?? ; First, check for and transfer any input for the STY. HLRZ T,XBSTAU(I) ; Get input state SKIPG TCPTBI(T) ; Make sure we can do input. JRST STYTC9 ; Nope, must disconnect. STYTC1: SOSGE XBIBC(I) JRST [ CALL TCPIBD ; Discard input buffer if any HRRZ A,XBITQH(I) ; Any more input avail? JUMPE A,STYTC5 ; No, done, check for output. CALL TCPIBG ; Have some! Set it up. Shd never hang. JFCL JRST STYTC1] ILDB A,XBIBP(I) ; Get the byte TRNE A,200 ; Special char? JRST [ AOS XBIBC(I) ; Ugh, must back up and get user's attention MOVSI B,8._14 ; Back up both count and 8-bit byte pointer ADDM B,XBIBP(I) ; by adding to P field of BP JRST STYTC9] ; Go disconnect. EXCH R,I ; I gets TTY #, R gets TCB index PUSH P,R PUSH P,I CONO PI,TTYOFF CALL NTYI5 ; Give the char to TTY input interrupt level CONO PI,TTYON POP P,R ; Note reverse order, so R gets TTY # POP P,I ; and I gets TCB index again. JRST STYTC1 ; Try for more input. ; Transfer chars from STY output to TCP connection STYTC5: SKIPGE TTYOAC(R) ; Do we have any output? JRST STYTC7 ; No, all's done, force out what we did. HRRZ A,XBSTAU(I) ; Check output state SKIPG TCPTBO(A) ; to verify that TCB is healthy. JRST STYTC9 ; Ugh, go disconnect STY. MOVSI A,(%XBMPL) IORM A,XBSTAT(I) ; Lock COS against PI level snarfing SKIPE XBOCOS(I) SKIPG E,XBOBC(I) ; Get # bytes room in output buff JRST [ ; Set up buffer, etc, possibly forcing out existing buff. PUSH P,R CALL TCPOSB ; Invoke special hang-less routine. JRST [POP P,R ; If can't get any more room, jump to STYTC6 JRST STYTC6] POP P,R SKIPG E,XBOBC(I) ; OK, should have bytes now. BUG JRST .+1] SKIPN D,XBOBP(I) ; Get BP into buffer BUG EXCH R,I CONO PI,TTYOFF MOVEM D,DBBBP ; Set up buffer for TTY output interrupt level MOVEM E,DBBCC MOVEM E,DBBCC1 PUSH P,R SETOM TYPNTF PUSHJ P,TYP ; Generate output SETZM TYPNTF POP P,R EXCH R,I ; Restore I/ TCB #, R/ TTY # MOVE D,DBBBP ; Advance pointers MOVEM D,XBOBP(I) MOVE E,DBBCC SUB E,DBBCC1 ; Minus # chars output generated CONO PI,TTYON ADDM E,XBOBC(I) JRST STYTC5 ; Check for more output ; No more output or we can't get more room, force out what ; we've currently got. STYTC6: CALL TCPUII ; Reactivate STY (expensive crock, but...) STYTC7: MOVSI A,(%XBMPL) ; Unlock the COS ANDCAM A,XBSTAT(I) MOVSI T,(TC%PSH) ; PUSH this stuff CALL TCPOFR ; Force out buffer JRST STYNT8 ; Then go check other STYs. ; Disconnect STY and get user's attention. Note this may be ; buggy in that STY output has not yet been transferred to the ; net by the time we get here, if we're here due to a 200 char. STYTC9: PUSH P,I MOVEI I,(R) ; Set up I/ TTY # CALL NSTYN0 ; Disconnect it BUG POP P,I CALL TCPUII ; Wake up the user program JRST STYNT8 ; Go handle other STYs. IFN 0,[ ;CALLED AT CLOCK LEVEL FROM STYNTC WHEN A CHAOS STY IS ENCOUNTERED ;TTY NUMBER IN I & R STYCHA: MOVE I,STYNTI-NFSTTY(R) ;GET CHAOS INDEX MOVE TT,CHSSTA(I) TLNN TT,%CFSTY JRST 4,. ;CHAOS CONNECTION CLAIMS NOT BE CONNECTED? JUMPL TT,STYCH9 .SEE %CFOFF ;OK TO USE? IF NOT, DISCONNECT SKIPGE TTYOAC(R) ;ANY OUTPUT? JRST STYCH1 ;NO, CHECK FOR INPUT SKIPN D,CHSOBP(I) ;IF BUFFER ALLOCATED, USE IT JRST [ SKIPG CHSNOS(I) ;OTHERWISE ALLOCATE ONE JRST STYCH1 ;WINDOW FULL, WAIT UNTIL REACTIVATED PUSHJ P,CHABGI JRST STYCH3 ;NO CORE, WAIT ONE CLOCK TICK MOVEI D,%CPKDT(A) HRLI D,440800 MOVEM D,CHSOBP(I) MOVEI E,%CPMXC MOVEM E,CHSOBC(I) JRST .+3 ] SKIPG E,CHSOBC(I) JRST STYCH4 ;BUFFER FULL, FORCE IT EXCH R,I ;I GETS TTY, R GETS CHAOS CONO PI,TTYOFF MOVEM D,DBBBP ;SET UP BUFFER FOR TTY OUTPUT INTERRUPT LEVEL MOVEM E,DBBCC MOVEM E,DBBCC1 PUSH P,R SETOM TYPNTF PUSHJ P,TYP ;GENERATE OUTPUT SETZM TYPNTF POP P,R EXCH R,I ;I GETS CHAOS, R GETS TTY MOVE D,DBBBP ;ADVANCE POINTERS MOVEM D,CHSOBP(I) MOVE E,DBBCC SUB E,DBBCC1 ;MINUS # CHARS OUTPUT GENERATED CONO PI,TTYON ADDM E,CHSOBC(I) STYCH4: PUSHJ P,CHAFC1 ;FORCE THE BUFFER JRST STYCHA ;CHECK FOR MORE OUTPUT STYCH3: PUSHJ P,CHINTI ;REACTIVATE SO WILL COME BACK ON NEXT CLOCK TICK STYCH1: SOSGE CHSIBC(I) ;GET INPUT, IF ANY JRST [ PUSHJ P,CHAIBD ;DISCARD EXHAUSTED INPUT BUFFER, IF ANY HLRZ A,CHSIBF(I) JUMPE A,STYNT8 ;NONE, RETURN TO STYNTC LDB TT,[$CPKOP(A)] CAIE TT,%CODAT JRST STYCH9 ;RANDOM PACKET, DISCONNECT PUSHJ P,CHPKIA ;ACKNOWLEDGE GOBBLING OF THIS PACKET SOS CHSNBF(I) ;REMOVE BUFFER FROM RECEIVE LIST MOVEI Q,CHSIBF(I) PUSHJ P,CHAQGF LDB E,[$CPKNB(A)] ;SET UP FOR BYTE STREAM INPUT MOVEM E,CHSIBC(I) MOVEI D,%CPKDT(A) HRLI D,440800 MOVEM D,CHSIBP(I) JRST STYCH1 ] ILDB A,CHSIBP(I) ;GET CHARACTER OF INPUT TRNE A,200 JRST [ AOS CHSIBC(I) ;WOOPS, SPECIAL CHARACTER, NEEDS USER ATTENTION MOVSI A,8_14 ;SO PUT IT BACK AND DISCONNECT ADDM A,CHSIBP(I) JRST STYCH9 ] EXCH R,I ;I GETS TTY, R GETS CHAOS PUSH P,R PUSH P,I CONO PI,TTYOFF PUSHJ P,NTYI5 ;GIVE CHARACTER TO TTY INPUT INTERRUPT LEVEL CONO PI,TTYON POP P,R POP P,I ;I GETS CHAOS, R GETS TTY ((POP IN REVERSE ORDER)) JRST STYCH1 ;TRY FOR MORE INPUT STYCH9: PUSH P,I MOVE I,R ;I GETS TTY PUSHJ P,NSTYN0 ;DISCONNECT THE STY JRST 4,. POP P,I ;I GETS CHAOS PUSHJ P,CHINTI ;WAKE UP THE TELNET SERVER JRST STYNT8 ;GO HANDLE OTHER STYS ] ;ifn 0 SUBTTL Other TCP system call functions ; TCPRQ - Handle .CALL NETRFC, return port # of next pending ; request for connection (SYN). ; Perhaps return a uniquizer in LH, so know when see ; the same request again? TCPRQ: TRNE C,%NQREF ; Skip if just getting, not flushing. JRST TCPRQ5 METER("TCP: syscal netrfc get") CONO PI,NETOFF ; In case a RST comes for it or something. ; MOVE I,TCPRQL ; Get last thing stored on queue SETOB B,D ; Look for any match CALL TCPRQS ; Search the queue... JUMPL A,OPNL4 ; None, say "file not found". MOVEI I,(A) LDB A,[.BP TH%DST,XBPORT(I)] ; Get local port # for the SYN HRLI A,(I) ; And put index in LH as uniquizer. CONO PI,NETON JRST POPJ1 TCPRQ2: BUG CHECK,[TCP: Pending SYN smashed!] RET ; Refuse indicated connection. TCPRQ5: METER("TCP: syscal netrfc ref") CAIGE W,2 ; Must have 2 args JRST OPNL30 ; "Too few args" HLRE D,A ; Get identifier HRRE B,A CONO PI,NETOFF CALL TCPRQS ; Search for the queued SYN JUMPL A,OPNL4 ; Now must refuse connection. MOVEI I,(A) MOVEI Q,XBITQH(I) CALL PKQGF(PK.TCP) ; Get queued SYN segment SKIPN XBITQH(I) ; Should have been only one SKIPG R,A ; and should have been one! BUG HALT CALL TXBFLS ; Flush the TCB. SOSGE TCPRQN ; Decrement count of queued SYNs BUG HALT HLRZ W,PK.IP(R) ; Move all this setup somewhere modular. HLRZ H,PK.TCP(R) LDB TT,[PK$TDL (R)] MOVE E,TH$CTL(H) TLNE E,(TC%SYN) ADDI TT,1 TLNE E,(TC%FIN) ADDI TT,1 CALL TSISLR ; Respond to this req with RST+ACK CONO PI,NETON JRST POPJ1 ; TCPRQS - Search pending-RFC queue. Must be called with NETOFF!! ; B/ local port # (-1 for any) ; D/ Index #, -1 for any (searches back from last one stored) ; Clobbers T,Q ; Returns ; A/ Index to matching SYN (-1 if no match) TCPRQS: JUMPGE D,TCPRQ7 MOVE A,TCPRQL MOVEI C,1 TCPRQ6: HRRZ T,XBSTAT(A) ; See if right state CAIN T,.XSSYQ JRST [ LDB T,[.BP TH%DST,XBPORT(A)] CAIL B, CAMN T,B RET JRST .+1] SOJGE A,TCPRQ6 MOVEI A,XBL-1 SOJGE C,TCPRQ6 TCPRQ9: SETO A, RET TCPRQ7: SKIPL A,D CAIL D,XBL JRST TCPRQ9 HRRZ T,XBSTAT(A) ; Verify state CAIE T,.XSSYQ JRST TCPRQ9 LDB T,[.BP TH%DST,XBPORT(A)] ; Got one! Get local port # CAIL B, CAIN T,(B) ; Must match given arg unless -1 RET ; Won! JRST TCPRQ9 ifn 0,[ TCPRQS: MOVEI A,TCPRQH-PK.TCP TCPRQ6: MOVEI Q,(A) ; Save ptr to prev node HRRZ A,PK.TCP(A) ; Get ptr to next PE JUMPE A,TCPRQ8 ; If not there, return 0 as error. JUMPL D,TCPRQ7 CAIE A,(D) ; See if identifier matches JRST TCPRQ6 ; Jump if not. TCPRQ7: HLRZ T,PK.TCP(A) ; Yes, verify port number CAIN T, ; Ensure ptr to TCP header exists. BUG HALT LDB T,[TH$DST (T)] CAIE T,(B) JRST TCPRQ6 ; Nope, get next thing. ; Found it! Take off list, a bit tricky. SOSGE TCPRQN ; Decrement count of entries BUG HALT MOVSI T,(%PQFL2) ; Clear the on-list flag for PK.TCP ANDCAM T,PK.FLG(A) IFN 2-PK.TCP,.ERR TCPRQS must fix %PQFL2 to match PK.TCP HRRZ T,PK.TCP(A) ; Get its next-ptr HRRM T,PK.TCP(Q) ; Store in node previous to this one. JUMPN T,TCPRQ8 ; If wasn't last thing, all's well. CAIN Q,TCPRQH-PK.TCP ; Last thing. If prev was actually hdr, SETZ Q, ; must store zero. HRLM Q,TCPRQH ; Set new "last" ptr in hdr. TCPRQ8: RET ] ;ifn 0 ; TSOINI - set up a raw PE for use as a TCP output segment. Means ; setting IP, TCP header pointers properly, so that all fields ; are contiguous. Note that PK.TCI is set to indicate XBSMSS(I) ; bytes of (available) data storage! ; Sets up PK.IP, PK.TCP, and PK.TCI. ; R/ PE ptr ; I/ TCB connection index (val put into PK.TCI) ; Returns with R, W, H pointing to PE, IP hdr, and TCP hdr. ; ; TSOINA - Ditto, but takes arg in A and only clobbers T (doesn't set W, H) TSOINI: HRRZ W,PK.BUF(R) ; Get addr of buffer HRLM W,PK.IP(R) ; Store as IP header addr MOVEI H,(I) ; Set up TCI with all fields. ANDI H,PK%TCB IOR H,[<<%TCPHL*4>_<.TZ PK%TDO,>>] MOVEM H,PK.TCI(R) ; MOVE H,XBSMSS(I) ; Allow XBSMSS(I) bytes with assumed offset. DPB H,[PK$TDL (R)] MOVEI H,%TCPHL(W) ; For now, this will do. HRLM H,PK.TCP(R) ; Store as TCP header addr RET TSOINA: HRRZ T,PK.BUF(A) ; Get addr of buffer HRLM T,PK.IP(A) ; Store as IP header addr ADDI T,%TCPHL ; For now, this will do to get TCP hdr. HRLM T,PK.TCP(A) ; Store as TCP header addr MOVEI T,(I) ; Set up TCI with all fields. ANDI T,PK%TCB IOR T,[<<%TCPHL*4>_<.TZ PK%TDO,>>] MOVEM T,PK.TCI(A) ; Set up index and header length fields MOVE H,XBSMSS(I) ; Allow XBSMSS(I) bytes with assumed offset. DPB H,[PK$TDL (R)] RET ; TCPUSI - TCP User State-change Interrupt. Called each time connection ; changes state (.XSnnn) or I/O queues start/end. Always tries ; to interrupt user, except for change %NTWRT->%NTOPN on output ; and %NTINP->%NTOPN on input. ; Moon: Interrupt when input rcvd and buff empty, or output full ; and becomes reasonably non-full. ; Clobbers T, Q TCPUSI: METER("TCP: tcpusi called") CALL TXBIST ; Check input state HLRZ Q,XBSTAU(I) CAIE T,(Q) ; New state? JRST TCPUS3 ; Yes, go handle. TCPUS2: CALL TXBOST HRRZ Q,XBSTAU(I) CAIN T,(Q) RET ; Output channel state change ; Q/ old state, T/ new state (%NT values, not .XS) HRRM T,XBSTAU(I) ; Store new state (old in Q) CAIN Q,%NTOPN ; If was open CAIE T,%NTWRT ; Changing to buff-full CAIA RET ; Then don't interrupt. MOVE Q,TCPTBO(Q) CAMN Q,TCPTBO(T) ; See if meta-state change RET ; Nope, ignore. LDB Q,[XB$OCH (I)] ; Yes, get channel # METER("TCP: User O ints") CALRET TCPUS5 ; Input channel state change TCPUS3: HRLM T,XBSTAU(I) ; Store new state (old in Q) CAIN Q,%NTINP ; If was input avail CAIE T,%NTOPN ; Changing to plain open CAIA JRST TCPUS2 ; Then don't interrupt. MOVE Q,TCPTBI(Q) CAMN Q,TCPTBI(T) ; See if meta-state change JRST TCPUS2 ; No ; Drop thru to interrupt ; Give input channel interrupt TCPUII: METER("TCP: User I ints") LDB Q,[XB$STY (I)] ; See if hooked to STY JUMPN Q,TCPUSS ; Jump to handle STY stuff if so. LDB Q,[XB$ICH (I)] ; No, just get input chan CALL TCPUS5 JRST TCPUS2 ; Give interrupt to STY that TCB is connected to. ; Q/ TTY # TCPUSS: CONO PI,PIOFF ; Protect list hacking SKIPL STYNTL-NFSTTY(Q) ; Don't put on list twice JRST PIONJ MOVE T,STYNTA ; Add to list MOVEM T,STYNTL-NFSTTY(Q) MOVEM Q,STYNTA JRST PIONJ ; Interrupt on channel in Q. TCPUS5: JUMPE Q,CPOPJ ; May be no channel there. PUSH P,U SKIPN U,XBUSER(I) BUG HALT ; Jumpe above should catch this. MOVSI T,(SETZ) IORM T,PIRQC(U) CAIN Q,77 ; If IOPUSH'ed, no interrupt. JRST POPUJ MOVE T,CHNBIT-1(Q) ; Q is -1 based. AND T,MSKST2(U) IORM T,IFPIR(U) POP P,U RET ; Input chan state type. Pos # means can read. ; 0 is pre-open, 1 is open, 2 is input avail, -1 is post-open. TCPTBI: OFFSET -. %NTCLS:: 0 ; 0 CLS %NTLSN:: 0 ; 1 LSN %NTSYR:: 0 ; 2 RFC %NTCLU:: -1 ; 3 RCL? %NTSYN:: 0 ; 4 RFS %NTOPN:: 1 ; 5 OPN %NTWRT:: 1 ; 6 RFN %NTCLX:: -1 ; 7 CLW %NTCLI:: 1 ; 10 CLI %NTINP:: 2 ; 11 INP OFFSET 0 ; Output chan state type. Pos # means can write. ; 0 is pre-open, 1 is open, 2 is buff full, -1 is post-open. TCPTBO: OFFSET -. %NTCLS:: 0 %NTLSN:: 0 %NTSYR:: 0 %NTCLU:: 1 %NTSYN:: 0 %NTOPN:: 1 %NTWRT:: 2 %NTCLX:: -1 %NTCLI:: 1 %NTINP:: 1 OFFSET 0 SUBTTL TCP Input Interrupt Level ; TCPIS - Process TCP Input Segment (PI level) ; R/ PE ptr to packet, not on any list. ; PK.BUF is set, ditto IP/TCP header pointers. ; W/ addr of IP header ; H/ addr of IP data (start of TCP header) ; J/ host-table index for address datagram received from. ; Can clobber all ACs except P, returns with POPJ. ; AC usage during incoming segment processing: ; R/ PE ptr to packet ; W/ addr of IP header ; H/ addr of TCP header ; I/ TCB index (if any) ; J/ TCB connection state ; TT/ # bytes of TCP data in segment ; E/ ,, ; D/ Segment Sequence no. ; Flags for RH of E %TSISL==1 ; Seq starts to left of rcv.nxt %TSISR==2 ; Seq starts to right of " ; if neither on, is = rcv.nxt %TSIFL==4 ; Bad seq, flush after handling RST/ACK/URG TCPIS: METER("TCP: Segs rcvd") SKIPN TCPUP ; Unless TCP claims to be up, JRST TSIFL ; Throw it away, no TCP yet, sigh. ; First verify that this is a valid TCP segment, by ; checksumming it (sigh!). TT gets total # bytes in TCP segment. CALL THCKSI ; Get checksum in A for segment LDB B,[TH$CKS (H)] ; Get segment's checksum CAME A,B ; Should match. JRST TSIF01 ; Failed, go bump err count and flush it. LDB T,[TH$THL (H)] ; Find TCP header length in words LSH T,2 ; Make it in octets SUBI TT,(T) ; TT now has # octets of segment data. ; Contents of segment have been validated (more or less), ; now set up convenient context values ; PK.TCI contents ; E/ Segment control flags (in LH) ; TT/ SEG.LEN HLLZ E,TH$CTL(H) ; Get word with segment control flags DPB T,[PK$TDO (R)] ; Store offset of data (from THCKSI) DPB TT,[PK$TDL (R)] ; Store length of data TLNE E,(TC%SYN) ; Note that SYN counts in seg.len ADDI TT,1 ; so allow for it TLNE E,(TC%FIN) ; And do same thing for FIN. ADDI TT,1 ; Either way, get SEG.LEN set up in TT. ; Then see if any TCB exists for this segment. SKIPE A,TH$SRC(H) ; Get source/dest port word SKIPN B,IP$SRC(W) ; Get source addr from IP header JRST TSIF02 ; Flush anything with zero field. LSH B,-4 ; Right-justify the addr MOVSI I,-XBL TSI02: CAMN A,XBPORT(I) ; Loop til we find it CAME B,XBHOST(I) TSI03: AOBJN I,TSI02 JUMPL I,TSI05 ; Jump if found existing connection JRST TSISQ ; Jump if no existing connection. TSI04: SKIPE XBSTAT(I) ; Found "closed" connection???? JRST TSI02 ; LH must have crud still set, ignore for now BUG CHECK,[TCP: Clsed TCB has active port/host] ; Shouldn't happen! SETZM XBHOST(I) ; If continued, fix up. JRST TSI02 ; Connection exists, TCB index now in I. ; Set up a little more context (PK.TCI and J) TSI05: DPB I,[PK$TCB (R)] ; Store TCB index in packet info MOVEM J,XBNADR(I) ; Save host-table idx of addr this seg is from. HRRZ J,XBSTAT(I) ; Get connection state CAIL J,.XSTOT ; Highest possible state. BUG HALT,[TCP: Bad conn state] METER("TCP: IS all states") XCT XSMTRS(J) ; Bump meter for each state CAIG J,.XSSYN ; If it's CLS, SYQ, LSN or SYN-SENT JRST @(J)[ ; then process specially. TSI04 ; Closed??? TSISQQ ; Syn-Queued? (Probably re-trans) TSILS ; Listen TSISS] ; Syn-sent ; Drop through to perform general sequence-number checking. ; Check Sequence Number!!! ; This code doesn't do two things: ; 1) it doesn't keep around stuff that arrives to the ; right of rcv.nxt. ; 2) for situation where seg.seq number is valid, ; (i.e. seq =< rcv.nxt) the code punts if ; end of seg is out of window. It should simply ; expand the window! LDB D,[TH$SEQ (H)] ; Get sequence number JUMPG TT,TSI10 ; Jump if data present. JUMPL TT,TSIF03 ; No data. Jump if error (neg data!) ; No data in this segment, it is probably a simple ACK. CAME D,XBRNXT(I) ; Seg.seq == snd.nxt (as expected?) JRST TSI01 METER("TCP: 0-len seg seq match") JRST TSI20 ; Yep, seg is acceptable instantly! TSI01: SKIPN C,XBRWND(I) ; Have some receive window? JRST TSI09 ADD C,XBRNXT(I) ; Get nxt+wnd TLZ C,%MOD32 ; all arith mod 32 CMPSEQ XBRNXT(I),=<,D,<,C,TSI07,TSI08 JRST TSI20 ; Within rcv window, buy it TSI07: METER("TCP: 0-len seg before rcv window") JRST TSISNE TSI08: METER("TCP: 0-len seg after rcv window") JRST TSISNE ; 0-data, 0-window, and SEG.SEQ != RCV.NXT TSI09: METER("TCP: Ifl 0-len 0-window seqerr") JRST TSISNE ; Sigh, flush it. ; Seq number check when data present. TSI10: CAME D,XBRNXT(I) ; Is seq # what we expect (seq = nxt)? JRST TSI11 SKIPE C,XBRWND(I) ; Yes! And is our window open? JRST TSI20 ; Yes! Fast dispatch! ; Data segment, with valid sequence number, but our window is ; zero. See if there's some way we can avoid throwing away the ; segment... if we can't take it then still must handle ; ACK/URG/RST flags. For now, we really handle this at TSI70. TSI12: METER("TCP: 0-wnd data seg") JRST TSI20 ; Sequence # isn't exactly what we hoped for, see if the ; segment overlaps a valid portion of sequence space. TSI11: SKIPN C,XBRWND(I) ;#3: Get window, is it zero? MOVEI C,512. ; If zero, substitute a dummy window. ; Both len>0 and wnd>0. ADD C,XBRNXT(I) ; Get nxt+wnd TLZ C,%MOD32 ; all arith mod 32 ;#4a: nxt =< seq < nxt+wnd CMPSEQ XBRNXT(I),=<,D,<,C,TSI13 ; Jump if fail this test, try 4b. ; Come here when sequence # is OK, but segment starts farther on ; than we want, i.e. there is a "hole" between rcv.nxt and seg.seq. ; Eventually we could keep this segment around, to speed up ; throughput for nets that get packets out of order, but for ; now we'll just flush it and force a retransmit. METER("TCP: Iseg hole") TRO E,%TSISR+%TSIFL ; Say starts to right, and flush later. JRST TSI20 ; Go process RST/ACK/URG etc. TSIF12: METER("TCP: Ifl seq dup") ; Segment falls in prev rcvd data. MOVE D,XBRNXT(I) ; Fake out, say seq # OK TRO E,%TSIFL ; and don't process data. JRST TSI20 ; Go handle RST/ACK/URG. TSIF13: METER("TCP: Ifl seq int err") ; Shouldn't ever happen, due to JRST TSISNE ; right-bound check code above. TSIF14: METER("TCP: Ifl seq old") JRST TSISNE TSIF15: METER("TCP: Ifl monster seg") ; Impossible error JRST TSISNE ; Segment does not overlap window to right, so see if it ; overlaps to left, i.e. sequence # falls within data we have ; already received. TSI13: MOVE A,XBRNXT(I) SUBI A,%TCPMB ; Make a fictional lower bound CAIGE A, ADD A,[1_32.] ; Keep bound mod 2^32 CMPSEQ A,=<,D,=<,XBRNXT(I),TSIF14,TSIF13 ; Yep, falls within received data. It's probably a duplicate ; retransmitted segment; see if there's any new data on right side. ; Note that we are not using XBRWND here, because as long as we ; have a non-zero window we will always accept everything in the ; segment. So we create another fictional bound to the right. ADD A,[%TCPMB*2] ; Get back to other side of rcv.nxt TLZ A,%MOD32 ; Keep mod 2^32 MOVE C,D ADDI C,-1(TT) ; Get seq+len-1 TLZ C,%MOD32 ;#4b: nxt =< seq+len-1 < nxt+wnd? CMPSEQ XBRNXT(I),=<,C,=<,A,TSIF12,TSIF15 ; If fail this too, error. ; Aha, have some new data in spite of being overlapped with some ; previously received data! Here, we ; twiddle things so that it appears to start properly at ; rcv.nxt. This is done without touching the segment contents ; at all, just modifying the packet entry info. METER("TCP: Iseg ovlap") MOVE A,XBRNXT(I) ; Get rcv.nxt CAMGE A,D ; Make sure it's greater than seg.seq TLO A,(1_32.) ; Mod 2^32 screw, make it greater (add 33d bit) SUB A,D ; Find # octets of sequence space diff CAMLE A,TT ; Shouldn't be greater than seg.len!! BUG CHECK,[TCP: Trim error] SUBI TT,(A) JUMPLE TT,TSIF12 ; If nothing left, drop this segment. TLZE E,(TC%SYN) ; Clear SYN since it's at front. SUBI A,1 ; If it was set, reduce cnt of actual data LDB T,[PK$TDL (R)] ; that we're going to flush. Get cnt SUBI T,(A) ; Decrement # valid data bytes in segment DPB T,[PK$TDL (R)] ; Put back LDB T,[PK$TDO (R)] ; Also adjust offset to valid data ADDI T,(A) ; Increment to point at new data DPB T,[PK$TDO (R)] ; Put back MOVE D,XBRNXT(I) ; Now say seg.seq = rcv.nxt! ; Segment sanitized, drop through. SKIPN XBRWND(I) ; Only proceed if our window not zero. JRST TSI12 ; It's zero! May have to flush it... ; Fall through to TSI20 for RST/ACK/URG processing. ; Now check RST TSI20: TLNE E,(TC%RST) ; RST bit set? JRST TSIRST ; Yeah, go process it. ; Now check security/precedence JFCL ; ho ho ho ; Now check SYN bit TSI40: TLNE E,(TC%SYN) ; SYN bit set? JRST TSISYN ; Yeah, go process it (basically error) ; Now check ACK bit TSI50: TLNN E,(TC%ACK) ; ACK bit set? JRST TSIF50 ; No, error. Drop segment. JRST @TSI51(J) ; Yes, dispatch depending on state. TSI51: OFFSET -. .XSCLS:: [JRST 4,TSI51] ; Closed .XSSYQ:: [JRST 4,TSI51] ; ITS: Syn-Queued .XSLSN:: [JRST 4,TSI51] ; Listen .XSSYN:: [JRST 4,TSI51] ; Syn-Sent .XSSYR:: TSI53 ; Syn-Rcvd .XSOPN:: TSI54 ; Established (open) .XSFN1:: TSI54 ; Fin-Wait-1 .XSFN2:: TSI54 ; Fin-Wait-2 .XSCLW:: TSI54 ; Close-Wait .XSCLO:: TSI54 ; Closing .XSCLA:: TSI54 ; Last-Ack .XSTMW:: TSIATW ; Time-Wait .XSTOT:: OFFSET 0 ; SYN-RCVD state, handling ACK. TSI53: LDB A,[TH$ACK (H)] ; Get ACK field MOVE B,XBSUNA(I) ; Need one CMPSEQ arg in AC ; Test: snd.una =< seg.ack =< snd.nxt CMPSEQ B,=<,A,=<,XBSNXT(I),TSISRA ; Jump if fail MOVEI J,.XSOPN ; ACK wins, we're now open! HRRM J,XBSTAT(I) ; Set new state, fall through to handle. CALL TCPUSI ; Adjust user state. ; Must initialize SND.WL1, SND.WL2, and SND.WND. ; Maybe later merge this with TSI55. MOVEM A,XBSWL2(I) ; Yes! Update send window, set WL2 to ACK MOVEM D,XBSWL1(I) ; and WL1 to SEQ LDB B,[TH$WND (H)] MOVEM B,XBSWND(I) ; and snd.WND to seg.WND. MOVEM B,XBSAVW(I) ; and make avail window be same as send wind. JRST TSI54X ; Skip repeating the ACK test. ; Handle ACK while in open state (also other receive-OK states) TSI54: LDB A,[TH$ACK (H)] ; Get ACK field MOVE B,XBSUNA(I) ; Need one CMPSEQ arg in AC ; Test: snd.una =< seg.ack =< snd.nxt ; If seg.ack < snd.una, go to TSI60 and ignore the ACK. ; If seg.ack > snd.nxt, go to TSISAK to drop segment (ACKing) CMPSEQ B,=<,A,=<,XBSNXT(I),TSI60,TSISAK ; Jump if fail ; ACK is fine. Update SND.UNA and clean up retransmit queue. TSI54X: MOVEM A,XBSUNA(I) ; Update snd.una ; Must check retransmit queue slowly to find right place to flush, ; if any. ; Procedure is: (1) pull off 1st thing on queue. ; (2) If the new 1st thing has a seq # =< snd.una, ; then can flush what we pulled off, and try again. ; (3) otherwise put it back on at front. TSI54A: MOVE C,A ; Save ACK # in C TSI54B: MOVEI Q,XBORTQ(I) ; Get pointer to retrans q CALL PKQGF(PK.TCP) ; Get 1st thing on queue JUMPE A,TSI54Z ; None left? Win! TRCPKT A,"TSI54B Mabye flush from rexmit Q" MOVE T,PK.FLG(A) ; Check packet flags, TLNN T,(%PKODN) ; to make sure output was completed. JRST TSI54Y ; Not done yet, so don't flush yet. HRRZ B,XBORTQ(I) ; Get pointer to next thing JUMPE B,[CAMN C,XBSNXT(I) ; No next thing, compare with snd.nxt JRST TSI54D ; Equal, can flush! JRST TSI54Y] ; If not equal, must have ack < snd.nxt ; so previous segment can't be flushed. HLRZ B,PK.TCP(B) ; Get addr of TCP hdr for 2nd queued segment LDB B,[TH$SEQ (B)] ; Get sequence # for it TSI54C: CMPSEQ B,=<,C,=<,XBSNXT(I),TSI54Y ; See if ACK comes after that # ; Hurray, matches or exceeds this seq #, ; So we can flush the seg we pulled off! TSI54D: TRCPKT A,"TSI54D Flushing from Q" TLO T,(%PKFLS) ; Tell IP to forget it if queued MOVEM T,PK.FLG(A) CALL PKTRT ; Flush if not otherwise occupied TSI54E: MOVE A,TIME ; Crock crock, set up new timeout. ADD A,TCPTMO MOVEM A,XBORTT(I) SETZM XBORTC(I) ; Reset retry counts SOSGE XBORTL(I) ; Decrement # segments on retrans q. BUG HALT,[TCP: Retrans Q count error] JRST TSI54B ; Keep going as long as we can. TSI54Y: MOVEI Q,XBORTQ(I) CALL PKQPF(PK.TCP) ; Put back on front of queue TSI54Z: MOVE A,C ; Restore ACK # to A. ; Now see if send window should be updated. CAMN D,XBSWL1(I) ; Fast check first, WL1 = SEQ? JRST TSI55C ; Yes, go check ACK then MOVE T,XBSWL1(I) ADDI T,-1 TLZ T,%MOD32 CMPSEQ XBSWL1(I),<,D,<,T,TSI56 ; Check if wl1 < seq < wl1+xxx JRST TSI55 ; Yes, must update window. TSI55C: MOVE T,XBSWL2(I) ADDI T,-1 TLZ T,%MOD32 CMPSEQ XBSWL2(I),=<,A,=<,T,TSI56 ; Fall-thru win if snd.wl2 =< seg.ack TSI55: MOVEM A,XBSWL2(I) ; Yes! Update send window, set WL2 to ACK MOVEM D,XBSWL1(I) ; and WL1 to SEQ LDB B,[TH$WND (H)] MOVEM B,XBSWND(I) ; and snd.WND to seg.WND. ; Drop thru ; Either SND.UNA or SND.WND was probably updated, so lets update ; SND.AVW also (available window). The following computes ; WND - (NXT - UNA) and assumes UNA =< NXT. TSI56: MOVE A,XBSNXT(I) CAMGE A,XBSUNA(I) ; If need mod 32 wrap, TLO A,(1_32.) ; wrap up the number that should be higher. SUB A,XBSUNA(I) ; Find NXT-UNA (# bytes not yet acked) CAIL A,0 CAILE A,177777 ; Make simple check BUG INFO,[TCP: Bad AVW calc, UNA=],OCT,XBSNXT(I),[NXT=],OCT,XBSUNA(I) MOVE B,XBSWND(I) SUBI B,(A) ; Find # bytes we can still send CAIGE B, ; Make sure it's not negative! SETZ B, MOVEM B,XBSAVW(I) ; Done with ACK processing for OPEN state, see if must handle ; idiosyncracies of other states. TSI57: CAIN J,.XSOPN ; Skip other checks if state is OPEN (normal) JRST TSI60 ; Go check for URG etc. CAIN J,.XSCLW JRST TSI80 CAIN J,.XSFN1 JRST [ SKIPE XBORTQ(I) ; If our FIN is ACK'd, enter FIN-WAIT-2 JRST TSI60 ; Not yet. MOVEI J,.XSFN2 ; Yes, FIN was ACKed, change state. HRRM J,XBSTAT(I) CALL TCPUSI ; Call this for any state change. LDB T,[XB$ICH (I)] ; Do we have an input chan? JUMPN T,TSI60 ; If so, CLOSE will handle the wrapup. MOVE T,TIME ; No, must set timeout. ADDI T,2*60.*30. ; Use 2*MSL MOVEM T,XBORTT ; set timeout. JRST TSI60] CAIN J,.XSFN2 JRST [ ; If retrans queue empty, transmit-chan CLOSE done. JRST TSI60] CAIN J,.XSCLO JRST [ SKIPE XBORTQ(I) ; If our FIN is ACK'd, JRST TSIF55 ; No-- flush the segment. CALL TSITMW ; then enter TIME-WAIT state, start timeout. JRST TSI80] ; Then go check for FIN, etc. CAIN J,.XSCLA ; LAST-ACK waiting for ACK of our FIN. JRST [ SKIPE XBORTQ(I) ; If our FIN has been ACK'd, JRST TSIF56 ; No-- flush the segment. METER("TCP: FIN acked in .XSCLA") CALL TXBFLP ; Flush the TCB immediately, PI level JRST TSIFL] ; then flush the segment. BUG CHECK,[TCP: Bad ACK state] ; Check the URG bit. The only states which get to this ; point are OPEN, FIN-WAIT-1, and FIN-WAIT-2. TSI60: TLNN E,(TC%URG) ; Segment has urgent pointer set? JRST TSI70 ; Nope, on to next step. LDB A,[TH$UP (H)] ; Get SEG.UP (urgent ptr from segment) ; This is where URGENT should be handled!!!! ; Drop through ; Finally process segment text! ; Only states OPEN, FIN-WAIT-1 and FIN-WAIT-2 can get here. TSI70: TRNE E,%TSIFL ; If segment being flushed after ACK/URG, JRST TSIF70 ; flush it now! LDB A,[PK$TDL (R)] ; Find # bytes of real data in segment JUMPLE A,TSI80 ; If none, no text processing. TLNE E,(TC%FIN) ; Check that # bytes data == seg.len JRST [ CAIE A,-1(TT) ; Must allow for funny non-data FIN. JRST TSI71 ; Nope JRST TSI72] ; Yep CAIE A,(TT) ; # bytes data should == seg.len TSI71: BUG CHECK,[TCP: seglen error] TSI72: SKIPE D,XBRWND(I) ; Note D used for flag, JRST TSI75 ; and is non-zero if no compaction done. ; Our window is zero, and technically we should throw away the ; data now that all RST/ACK/URG processing has been done. However, ; we try to see if we can possibly do a little compaction, since ; the overhead of doing this is a lot less than the overhead ; of re-processing the re-transmitted segment! MOVE A,XBINPS(I) ; Check length of input queue CAIL A,2 ; Must be at least 2 SKIPN XBITQH(I) BUG CHECK,[TCP: Wind & Queue both 0] ; See if it's worth trying to compact the input seg into the ; last one received (which hasn't yet been seen by MP level) HLRZ A,XBITQH(I) ; Get ptr to last input seg on queue LDB B,[PK$TDO (A)] ; Get offset to data in old seg LDB C,[PK$TDL (A)] ; See how much data is there LDB T,[PK$TDL (R)] ; Find # bytes in new segment ADDI B,(C) ; Get offset to end of data MOVEI D,(B) ADDI D,(T) ; Get projected total offset CAML D,XBRMSS(I) ; Crock method of ensuring enuf room. JRST TSI17 ; Not enough, we lose. Lose. Lose. ; Win! We're gonna compact! METER("TCP: Iseg cmpct") ADDI C,(T) ; Get new # bytes for prev seg DPB C,[PK$TDL (A)] ; Store it in advance. HLRZ D,PK.TCP(A) ; Find addr of TCP header in prev seg IDIVI B,4 ADDI D,(B) ; Get addr for BP to end of data HRL D,(C)[441000 ? 341000 ? 241000 ? 141000] ; Make LH LDB B,[PK$TDO (R)] ; Get data offset for new segment IDIVI B,4 ADDI B,(H) ; Get addr for BP to start of new data HRL B,(C)[441000 ? 341000 ? 241000 ? 141000] ; Make LH ; B/ BP to new data ; D/ BP to end of old data ; T/ # bytes of new data MOVEI A,(T) ; Save # added data in A TSI74: ILDB C,B IDPB C,D SOJG T,TSI74 SETZ D, ; Clear D to indicate compaction done. JRST TSI75 ; Can't accept segment data, period. TSI17: METER("TCP: Ifl 0-wnd") JRST TSIFL ; Flush the seg, sob. TSI75: MOVEI B,(TT) ADDB B,XBRNXT(I) ; Update rcv.nxt value by adding seg.len TLZE B,%MOD32 MOVEM B,XBRNXT(I) ; Updated! LDB B,[XB$ICH (I)] ; See if we have an input channel # JUMPE B,[METER("TCP: IS fl no chan") JRST TSI78] ; No input channel, so just throw away. MOVEI C,(A) ; Save # bytes data. ADDM A,XBINBS(I) ; Add new bytes to # bytes in input queue JUMPE D,TSI78 ; If compaction done, that's all... SKIPE B,XBINPS(I) ; If no segments previously on queue, MOVE B,XBIBC(I) ; or current input buff has zero cnt, ; then will definitely interrupt user later. AOS XBINPS(I) ; Bump # segments on queue ; Check to see how much to reduce window by. ; Amount is in C (defaults to amount we just received) CALL TCPRWS ; Set receive window ; Finally add segment to queue! MOVEI A,(R) ; Set up pointer to packet/segment MOVEI Q,XBITQH(I) ; Point to TCP input queue CALL PKQPL(PK.TCP) ; Add to end of queue, using TCP links. JUMPN B,TSI78 ; Check, jump unless had no input before CALL TXBIST ; If none, then must definitely change state! HRLM T,XBSTAU(I) ; CALL TCPUII ; And always give an input-avail int! ; Now must send an ACK, or rather arrange for one to be ; sent soon. FIN is also checked here, so as to bypass the ; code which assumes that XBRNXT hasn't been updated (if we are ; here, it certainly has!) TSI78: MOVSI A,(TC%ACK) ; Set bit asking for ACK to be sent. IORM A,XBSTAT(I) TLNN E,(TC%FIN) ; Perform FIN-bit check JRST TSI90 ; None, all done with segment! JRST TSI82 ; FIN exists, handle it (bypass bump of XBRNXT) ; Lastly check the FIN bit. ; Connection states which do not expect to receive data come here ; directly from ACK processing. TSI80: TRNE E,%TSIFL ; If duplicate segment being flushed after JRST TSIF70 ; ACK/URG, flush it now! TLNN E,(TC%FIN) JRST TSI90 CAIG J,.XSSYN JRST TSIF80 ; Flush if CLOSED, LISTEN, SYN-SENT ; Advance RCV.NXT over the FIN AOS A,XBRNXT(I) TLZE A,%MOD32 MOVEM A,XBRNXT(I) ; Rest of FIN processing, after processing new data in packet. ; Ack FIN, update connection state, inform user. TSI82: MOVSI A,(TC%ACK+%XBFIN) ; Set bit asking that ACK be sent, and FIN IORM A,XBSTAT(I) ; was seen. MOVEI T,.XCFRN ; Say foreign host closed input side. CALL TCPUCI ; Now effect some state changes CAIE J,.XSOPN ; If OPEN CAIN J,.XSSYR ; or SYN-RCVD JRST [MOVEI J,.XSCLW ; Change state to CLOSE-WAIT JRST TSI85] CAIN J,.XSFN1 JRST [ SKIPN XBORTQ(I) ; If our FIN was ACK'd, JRST TSI84 ; Go enter TIME-WAIT state MOVEI J,.XSCLO ; Otherwise enter CLOSING state. JRST TSI85] CAIE J,.XSFN2 CAIN J,.XSTMW JRST TSI84 ; Go to TIME-WAIT JRST TSI90 ; Any other states just do nothing. TSI84: CALL TSITMW ; Enter TIME-WAIT state, starting 2-MSL timeout JRST TSI90 TSI85: HRRM J,XBSTAT(I) ; Set new state and fall through. CALL TCPUSI ; Set user state. ; Done. Finally decide whether to keep segment around or not. TSI90: HLRZ A,XBITQH(I) ; Get ptr to last thing on input queue CAIN A,(R) ; Same as current seg (ie it was queued?) RET ; Yes, just return! JRST TSIF90 ; Else drop through to flush the segment. XSMTRS: OFFSET -. .XSCLS:: METER("TCP: state CLS") .XSSYQ:: METER("TCP: state SYQ") .XSLSN:: METER("TCP: state LSN") .XSSYN:: METER("TCP: state SYN") .XSSYR:: METER("TCP: state SYR") .XSOPN:: METER("TCP: state OPN") .XSFN1:: METER("TCP: state FN1") .XSFN2:: METER("TCP: state FN2") .XSCLW:: METER("TCP: state CLW") .XSCLO:: METER("TCP: state CLO") .XSCLA:: METER("TCP: state CLA") .XSTMW:: METER("TCP: state TMW") .XSTOT:: OFFSET 0 TSIF01: METER("TCP: ISeg cksm errs ") JRST TSIFL TSIF02: METER("TCP: IS zero port/addr") JRST TSIFL TSIF03: METER("TCP: IS fl neg data") JRST TSIFL ;TSIF10: ; Flush this later (retain til get new .METER LIST) METER("TCP: IS fls Seq # err") JRST TSIFL TSIF50: METER("TCP: IS fls Seq no ACK ") JRST TSIFL TSIF55: METER("TCP: IS fls CLO & FIN not ACKed") JRST TSIFL TSIF56: METER("TCP: IS fls CLA & FIN not ACKed") JRST TSIFL TSIF70: METER("TCP: IS fls seqerr processed A/U/R") JRST TSISNE ; Go respond with ACK TSIF80: METER("TCP: IS fls FINchk state") JRST TSIFL TSIF2A: METER("TCP: IS fls random RST") JRST TSIFL TSIF2B: METER("TCP: IS fls Fresh SYN already on SYNQ") JRST TSIFL TSIF90: METER("TCP: IS fls processed seg") JRST TSIFL ; Come here to flush the datagram/segment and return. TSIFL: METER("TCP: Isegs flushed") MOVEI A,(R) CALRET PKTRT ; TSITMW - Routine to enter TIME-WAIT state. ; TSITM2 is entry point when already in that state. ; Clobbers T, Q TSITMW: MOVEI J,.XSTMW HRRM J,XBSTAT(I) CALL TCPUSI ; Alert user if necessary. TSITM2: SKIPE XBORTQ(I) ; Unless retransmit still hogs timeout RET ; (if so, return) MOVE T,TIME ; then set up 2-MSL timeout. ADDI T,30.*2.*60. MOVEM T,XBORTT(I) RET ; TSISNE - Sequence number error, segment not acceptable, ; return an ACK unless RST was set. TSISNE: METER("TCP: IS NE seqerr") TLNE E,(TC%RST) JRST TSIFL ; Flush segment if RST was set ; Send an immediate ACK without data, re-using the ; packet/segment that R points to. TSOACK: MOVSI T,(TC%ACK) ; Send an ACK immediately TRCPKT R,"TSOACK return ACK in response to out-of-seq ACK" CALL TSOSNR RET ; TSISQ - Jumped to from TCPIS when TCP segment is received that matches ; no existing connection. Check to see if it's a valid connection ; request. If so, ; (1) see if it matches any wild listens; if so, process. ; (2) see if it's OK to start up a server for it; if so, process. TSISQ: TLNE E,(TC%RST) ; If it has RST set, JRST TSIF2A ; Go drop it quietly. TLNE E,(TC%ACK) ; If ACK, can't be a valid request either JRST TSISAR ; Go send a RST in response (with SEQ=SEG.ACK) TLNN E,(TC%SYN) ; Anything else had better have a SYN JRST TSISLR ; otherwise send RST with SEQ=0,ACK=SEQ+LEN ; Okay, we have a promising SYN. See if it matches any ; "wild" listens. METER("TCP: Fresh SYN") LDB B,[TH$DST (H)] ; Get desired port # LDB C,[TH$SRC (H)] ; Find port it's from LDB D,[IP$SRC (W)] ; and host it's from. MOVSI I,-XBL TSISQ2: HRRZ J,XBSTAT(I) ; Get state for TCB CAIE J,.XSLSN ; We're hunting for LISTEN TSISQ3: AOBJN I,TSISQ2 JUMPGE I,TSISQ5 ; Jump if no match. LDB A,[.BP TH%DST,XBPORT(I)] ; Get our local port (never wild) CAIE A,(B) ; It must match desired "dest port"! JRST TSISQ3 ; Nope, doesn't want this one. SKIPL XBHOST(I) ; Aha, very likely will match. Follow thru. CAMN D,XBHOST(I) CAIA JRST TSISQ3 ; Host didn't match. MOVE A,XBPORT(I) ; Check remote port field TRNE A,17 ; Low 4 bits are non-zero if remote wild. JRST TSISQ4 ; Won! LDB A,[.BP TH%SRC,A] ; Not wild, see if it matches request. CAIE A,(C) ; Compare our remote with its source. JRST TSISQ3 ; No, no match here. ; Matched a wild listen! Must fill in various stuff. TSISQ4: MOVEI A,17 ANDCAM A,XBPORT(I) ; Clear wild bits DPB C,[.BP TH%SRC,XBPORT(I)] ; Set remote port # MOVEM D,XBHOST(I) ; Set remote host addr LDB D,[IP$DST (W)] ; Set local address to whichever address other guy knows MOVEM D,XBLCL(I) DPB I,[PK$TCB (R)] ; Finish setting up context for dispatch CALL TCPMSS ; Correct MSS values for specified foreign host CALL TCPRWS ; Open up a receive window JRST TSILS ; Go handle SYN rcvd for LISTEN. ; No outstanding listens. Check the port number, to ; see if it's something we are likely to service. TSISQ5: LDB A,[TH$DST (H)] ; Get destination port # CAILE A,%TCPMP ; Fits max port # for RFC service? JRST TSISLR ; Naw, barf about it (send RST). ; See if we're actually willing to start up a job... LDB A,[IP$SRC (W)] ; See who it's from JSP T,IPLCLH ; Ask IP if this is one of us SKIPL TCPUSW ; It isn't, so make sure we're open for biz CAIA JRST TSISLR ; Sorry charlie (send RST) ; Okay, we'll take it as SYN-QUEUED! We know this is a new ; request, otherwise it would have been matched at TSI02 and ; dispatched to TSISQQ instead. ifn 0,[ ; first see if it's already on the queue! ; Note that we still have remote host # in D. SKIPN Q,TCPRQH ; Get pointer to 1st item on queue JRST TSISQ7 ; No queue, so not on. MOVE B,TH$SRC(H) ; Get req's source/dest ports MOVE D,IP$SRC(W) ; and its source addr TSISQ6: HLRZ T,PK.TCP(Q) ; Get addr of TCP header from queue HLRZ C,PK.IP(Q) ; and addr of IP header CAIE T, CAIN C, BUG CHECK,[TCP: SYNQ smashed] CAMN B,TH$SRC(T) ; Same ports? CAME D,IP$SRC(C) ; Same host? CAIA ; No JRST TSIF2B ; Yes, assume SYN is a dup, ignore it. HRRZ Q,PK.TCP(Q) ; Get next thing on pending queue JUMPN Q,TSISQ6 ; Not on queue, let's try to add it. TSISQ7: MOVE A,TCPRQN ; Find # of things on queue already CAIL A,%TCPMQ ; Keep its length reasonable JRST TSISQ8 ; Sigh, ran out. HRROI T,NTSYNL ; OK, now try loading job up! CALL NUJBST ; Queue request for job TCPRFC JRST TSISLR ; Bah, no job slots or something! MOVEI A,(R) ; It's on the way! Queue the SYN now. MOVEI Q,TCPRQH CALL PKQPL(PK.TCP) ; Add onto end of pending-RFC queue. ] ;ifn 0 MOVSI I,-XBL TSISQ6: SKIPN XBUSER(I) SKIPE XBSTAT(I) AOBJN I,TSISQ6 JUMPGE I,TSISQ8 ; Jump if no free slots. CALL TXBINI ; Got one, might as well verify it's cleared. MOVE A,TCPRQN ; Find # of things on queue already CAIL A,XBL/2 ; Keep number reasonable JRST TSISQ8 ; Sorry, too many. HRROI T,NTSYNL ; Now see if we can load up handler job. CALL NUJBST ; Do it JRST TSISLR ; Ugh, couldn't start new job... MOVEI J,.XSSYQ MOVEM J,XBSTAT(I) ; Set state SYN-QUEUED LDB A,[IP$SRC (W)] MOVEM A,XBHOST(I) ; Set up host # MOVE A,TH$SRC(H) ; and ports ; Don't need to set XBLCL, won't be looked at MOVEM A,XBPORT(I) ; That's all we need for now. CALL TCPMSS ; Might as well keep these right even though CALL TCPRWS ; this TCB will be flushed when conn opens. MOVE A,TIME ADDI A,10.*30. ; Let it stay queued for 10 seconds. MOVEM A,XBORTT(I) MOVEI Q,XBITQH(I) ; Put the segment on input queue for slot. MOVEI A,(R) CALL PKQPF(PK.TCP) HRRZM I,TCPRQL ; Save # of last SYN queued. AOS TCPRQN ; And increment count of entries. METER("TCP: Srvjob starts") RET ; All done! TSISQ8: BUG INFO,[TCP: SYN queue full] JRST TSISLR ; Sigh. ; TSISQQ - Come here when segment received that matches an ; existing port/host which is in SYN-QUEUED state. TSISQQ: TLNE E,(TC%RST) ; Is it an RST? JRST [ CALL TSISQF ; Yeah, flush the queued SYN. JRST TSIFL] ; and drop segment. TLNE E,(TC%ACK) ; An ACK? That's illegal etc... JRST [ CALL TSISQF ; Flush the queued SYN, JRST TSISAR] ; and send a RST in response. TLNN E,(TC%SYN) ; Anything else better be a SYN JRST [ CALL TSISQF ; else send RST. JRST TSISLR] JRST TSIF2B ; Most likely a duplicate SYN, so just ; flush it and return. ; Flush TCB for a queued SYN. TSISQF: SETZM XBSTAT(I) SETZM XBPORT(I) SETZM XBHOST(I) SETZM XBORTT(I) SKIPE XBITQH(I) CALL TXBIFL SOSGE TCPRQN BUG HALT RET ; TSISAR - Respond to current segment by sending a RST with ; SEQ=SEG.ACK. Re-uses the current segment's packet buffer. ; R, W, H set up for PE, IP, and TCP. ; E has seg flags. May not be anything in I, so re-use fields ; from given packet! ; TSISAQ - like TSISAR but just drops segment if it has RST in it. ; TSISLR - like TSISAR, but SEQ=0, ACK=SEG.SEQ+SEG.LEN ; This is used when responding to segments without an ACK, i.e. ; initial SYNs. TSISLR: METER("TCP: times at TSISLR") LDB A,[TH$SEQ (H)] ; Get SEQ. Assume TT still valid. ADDI A,(TT) ; ACK=SEG.SEQ+SEG.LEN LSH A,4 ; Left justify it. SETZ D, ; SEQ=0 MOVSI T,(TC%RST+TC%ACK) JRST TSISA2 TSISAQ: TLNE E,(TC%RST) ; Here, if incoming seg was RST, JRST TSIFL ; just ignore, don't respond. TSISAR: METER("TCP: times at TSISAR") MOVE D,TH$ACK(H) ; Use SEG.ACK for SEQ MOVSI T,(TC%RST) ; Here, A, D, and T must be set up. TSISA2: SETZ B, LDB C,[TH$SRC (H)] ; Get source port DPB C,[.BP TH%DST,B] ; Use as dest port LDB C,[TH$DST (H)] ; Get dest DPB C,[.BP TH%SRC,B] ; Use as... you guessed it. PUSH P,IP$DST(W) ; Which of my addresses to claim to be from MOVE C,IP$SRC(W) ; A/ ACK field (left justified) ; B/ (left justified) ; C/ remote host (left justified) ; D/ SEQ field (left justified) ; R/ PE ptr to packet responding to ; T/ flags to use SETZ I, CALL TSOINI ; Initialize W,H,PK.IP(R),PK.TCP(R),PK.TCI(R) ; Note everything in PK.TCI will be wrong. MOVEM C,IP$DST(W) ; Store remote host MOVEM B,TH$SRC(H) ; Store loc/rem ports MOVEM D,TH$SEQ(H) ; Deposit new SEQ field TLNN T,(TC%ACK) ; If sending an ACK SETZ A, MOVEM A,TH$ACK(H) ; Deposit ACK field. TLO T,240000 ; Set IHL MOVEM T,TH$CTL(H) ; Deposit segment flags MOVEI A,5*4 DPB A,[IP$TOL (W)] ; Say length just a std TCP header. POP P,IP$SRC(W) CALL THCKSM ; Figure TCP checksum DPB A,[TH$CKS (H)] ; Deposit in CALL IPKSND ; Put this buffer on IP output queue! RET ; TSILS - Segment received for this connection while in LISTEN state. ; TSILS: METER("TCP: Segs rcvd in LSN") TLNE E,(TC%RST) ; Ignore any RSTs. JRST TSIFL TLNE E,(TC%ACK) ; ACKs are bad too. JRST TSISAR ; Respond with a RST to them. TLNN E,(TC%SYN) ; It should be a SYN. JRST TSIFL ; If not, just flush. ; We've received a SYN that should be valid. Set up for ; SYN-RCVD state. Note that we ignore security/precedence ; except to remember it so our transmits look OK. ; NOTE!!! TSILSX is an entry point from MP level TCPOPN call, ; which is used to hook up a user OPEN to a matching SYN on ; the pending-RFC queue! METER("TCP: SYN in LSN") TSILSX: LDB D,[TH$SEQ (H)] ; Get sequence number LDB A,[TH$WND (H)] MOVEM A,XBSWND(I) ; Initialize send window MOVEM A,XBSAVW(I) ; and available window MOVEM D,XBSWL1(I) ; Save seg.seq used for last window update LDB A,[TH$ACK (H)] MOVEM A,XBSWL2(I) ; Save seg.ack used for last window update ADDI D,1 TLZ D,%MOD32 ; Get seg.seq+1 MOVEM D,XBRNXT(I) ; Store as initial RCV.NXT CALL TCPISS ; Select a new ISS in A (Initial Send Seq#) MOVEM A,XBSUNA(I) ; Set SND.UNA to ISS ; ADDI A,1 ; TLZ A,%MOD32 MOVEM A,XBSNXT(I) ; And SND.NXT also; assume that process of ; sending it will increment by 1. ; Check for TCP options at this point, and process if present LDB A,[TH$THL (H)] ; TCP header length CAILE A,%TCPHL ; If default, no options present CALL TCPPIO ; Else, process input options ; Nasty business - put together and send a segment with ; seq=ISS,ack=RCV.NXT,ctl=SYN+ACK. ; For now we can assume that initial SYNs will never ; contain text, and so we don't have to queue it up. ; Alternatively can hope that remote site is clever about ; retransmitting! ; This is because if we don't need to keep received segment ; around, can just re-use it. MOVSI T,(TC%SYN+TC%ACK) TRCPKT R,"TSISLX Reflecting incoming SYN with SYN" CALL TSOSSN ; Fire off SYN. Sends MSS option too. MOVEI J,.XSSYR ; Change state to SYN-RCVD. HRRM J,XBSTAT(I) CALL TCPUSI ; Set user state. RET ; TCPISS - Select new ISS, return in A TCPISS: MOVE A,TIME LSH A,13. TCPIS2: TLZ A,%MOD32 CAMN A,TISSLU ; Same as last used? JRST [ AOS A,TISSC ANDI A,17 LSH A,9. ADD A,TISSLU JRST TCPIS2] ; Jump to mask off and test again. MOVEM A,TISSLU RET ; TCPPIO - Process TCP options from incoming segment. ; This is only checked for SYN segments because the only interesting ; option (Max Segment Size) is only sent with SYN segments ; ; R/ Pkt buffer ; I/ TCB Index ; H/ TCP Header ; A/ TCP header size in 32-bit words TCPPIO: SUBI A,%TCPHL LSH A,2 ; Options length in bytes MOVE B,[TH$OPT (H)] ; BP to start of options TCPPIL: SKIPG A ; Anything left? RET ; Nope, done ILDB C,B ; Get option type CAIL C,TCPPIS ; In range? RET ; Have to give up if unknown option JRST @TCPPIT(C) TCPPIT: TCPPI0 TCPPI1 TCPPI2 TCPPIS==.-TCPPIT ;End of option list TCPPI0: RET ;NOP TCPPI1: SOJA A,TCPPIL ; Decrement length and loop ;Max Seg Size TYPE ? LENGTH ? MSB ? LSB TCPPI2: ILDB C,B ; Get length SUB A,C ; Count it ILDB C,B ; Get 16-bit quantity, updating B LSH C,8. ILDB D,B ADD C,D ; Now contains foreign MSS request CAMGE C,XBSMSS(I) ; Don't exceed our own limits! MOVEM C,XBSMSS(I) ; Set new value in TCB JRST TCPPIL ; TSISS - Segment received while in SYN-SENT state. ; Note that being in this state implies that there is one ; segment on the retransmit queue, which must be the initial SYN ; that we sent. TSISS: METER("TCP: Segs rcvd in SYN-SENT") LDB D,[TH$SEQ (H)] ; Get SEG.SEQ TLNN E,(TC%ACK) ; Has an ACK? JRST TSISS2 ; Nope, it better be RST or SYN. ; See if our SYN has been ACKed. Since we only send SYNs ; without data, this just means a test for SEG.ACK = SND.NXT. LDB B,[TH$ACK (H)] ; Have ACK. Get ack field CAME B,XBSNXT(I) ; It should ACK our initial SYN JRST TSISAQ ; If not, send a RST. ; MOVE A,XBSUNA(I) ; snd.una =< seg.ack =< snd.nxt ? ; CMPSEQ A,=<,B,=<,XBSNXT(I),TSISAQ ; If not good, send RST. TSISS2: TLNE E,(TC%RST) ; Check for RST JRST [ TLNN E,(TC%ACK) ; Ugh, have RST. Did we also get good ACK? JRST TSIFL ; No, can just flush this segment. MOVEI T,.XCRFS ; Yeah, our SYN is being refused, so CALL TCPUC ; say this is close-reason. JRST TSIRST] ; Then must go abort connection. ; Here we get to check security/precedence. Hurray. ; We should just copy the seg values, so as to fake sender out. ; Now finally check the SYN bit! TLNN E,(TC%SYN) ; Must be set JRST TSIFL ; Neither RST nor SYN? Flush it. ; It's a SYN. Update our send params from its values. ; We will either send an ACK or another SYN; in both cases the ; SYN segment currently on the retransmit queue should be flushed. MOVEI Q,XBORTQ(I) ; Point to retrans q CALL PKQGF(PK.TCP) ; Pluck off 1st thing SOSN XBORTL(I) ; Verify none left on queue CAIN A, ; and something was there! BUG CHECK,[TCP: SYN-SENT retrans Q bad] JUMPE A,TSISS3 ; Just for robustness TRCPKT A,"TSISS2 Flushing our SYN from rexmit Q" MOVE T,PK.FLG(A) TLO T,%PKFLS ; Tell IP to flush packet if seen MOVEM T,PK.FLG(A) CALL PKTRT ; Flush SYN packet if not otherwise busy SETZM XBORTT(I) ; and flush timeout. TSISS3: LDB A,[TH$WND (H)] MOVEM A,XBSWND(I) ; Initialize send window MOVEM A,XBSAVW(I) ; and available window MOVEM D,XBSWL1(I) ; Save seg.seq used for last window update LDB A,[TH$ACK (H)] MOVEM A,XBSWL2(I) ; Save seg.ack used for last window update ADDI D,1 TLZ D,%MOD32 MOVEM D,XBRNXT(I) ; Set RCV.NXT to SEQ+1 ; Process segment options in case sender specified MSS LDB A,[TH$THL (H)] ; TCP header length CAILE A,%TCPHL ; If default, no options present CALL TCPPIO ; Else, process input options TLNN E,(TC%ACK) JRST TSISS4 LDB A,[TH$ACK (H)] ; If ACK also present, (known acceptable) MOVEM A,XBSUNA(I) ; Set SND.UNA to SEG.ACK. ; Here must test if SND.UNA > ISS (our SYN has been ACKed). ; But this was already checked just before TSISS2. MOVSI T,(TC%ACK) ; Hurray, we're open! Must ACK the SYN TRCPKT R,"TSISS3 ACK SYN to open conn" CALL TSOSNR ; (Re-using its segment) MOVEI J,.XSOPN ; Hurray, we're open now! HRRM J,XBSTAT(I) CALL TCPUSI ; Update user state RET ; Our SYN not ACKed yet, so enter SYN-RCVD state. TSISS4: ; Must go send seq=ISS,ack=RCV.NXT,ctl=SYN+ACK LDB D,[TH$SEQ (H)] ; Get sequence number ADDI D,1 TLZ D,%MOD32 ; Get seg.seq+1 MOVEM D,XBRNXT(I) ; Store as initial RCV.NXT SOSGE A,XBSUNA(I) ; Set SND.UNA to ISS JRST [ MOVEI A,1 MOVEM A,XBSUNA(I) JRST .+1] MOVEM A,XBSNXT(I) ; And SND.NXT also; assume that process of ; sending it will increment by 1. MOVSI T,(TC%SYN+TC%ACK) TRCPKT R,"TSISS4 ACK and re-SYN SYN-SENT conn" CALL TSOSSN ; Fire off SYN/ACK with MSS option included. MOVEI J,.XSSYR ; Change state to SYN-RCVD. HRRM J,XBSTAT(I) CALL TCPUSI ; Set user state. RET ; TSIRST - valid RST segment received (not in LISTEN). ; Basically must flush the connection, signal user, etc. TSIRST: METER("TCP: Valid RSTs") CALL TXBFLP ; Flush the TCB immediately, PI level MOVEI T,.XCRST ; Say fgn host reset stuff CALL TCPUC ; as "close reason" CALRET TSIFL ; Flush segment. ; TSISYN - SYN segment received. ; If in window, error - send a RST and close things up. ; If not in window, return an ACK as for TSISNE. TSISYN: METER("TCP: Random SYN") CALRET TSIFL ; TSISRA - Bad ACK seen while in SYN-RCVD state, ; send a RST. TSISRA: METER("TCP: Bad ACK in SYR") CALRET TSIFL ; TSISAK - Received ACK for something not yet seen, send ACK and ; drop segment. TSISAK: METER("TCP: ACK for nxm") CALRET TSIFL ; TSIATW - Received ACK while in TIME-WAIT state. This should be ; a re-transmit of the remote FIN. ACK it, and restart ; 2-MSL timeout. TSIATW: METER("TCP: ACK in .XSTMW") MOVSI T,(TC%ACK) TRCPKT R,"TSIATW ACK send in TIME-WAIT" CALL TSOSNR ; Send simple ACK in response. JRST TSITM2 ; and restart 2-MSL timeout. SUBTTL TCP Send output segment ; Send TCP output segment. ; Send output (usually data) segment, for connection indexed by I. ; Note this differs from TSISAR etc. which don't have any active connection, ; thus no valid I. As much context as possible is taken from the ; TCB tables indexed by I. ; In particular, the %XBCTL flags are examined to see if anything should ; be added to the outgoing segment, other than what was requested in the ; call. ; Sequence space variables are updated. ; The following possibilities are independently possible: ; Re-using packet / using fresh packet ; Uses seq space (must retrans) / no seq space used ; ; TSOSND - send output segment while connection established ; R/ PE ptr to packet, ; PK.BUF, PK.IP and PK.TCP must be set. ; If these were not initialized by TSOINI so as to get ; the right offsets, you will probably lose. ; PK.TCI should have the # bytes of data and offset. ; I/ TCB index ; Clobbers A,B,C,D,E,W,H,Q,T,TT ; TSOSNR - Just sends a data-less "reply" type segment using ; TCB's sequence space vars. Seq=snd.nxt, ack=rcv.nxt, etc. ; R/ PE ptr to packet (packet will be smashed and re-used) ; I/ TCB index ; T/ flags to use (Neither ACK nor %XBCTL will be added automatically!) ; Clobbers A,B,C,D,E,W,H,Q,T,TT TSOSNR: CALL TSOINI ; Initialize (sets up W,H PK.IP,PK.TCP,PK.TCI) SETZ TT, ; Say zero bytes of real data DPB TT,[PK$TDL (R)] ; and make sure packet entry reflects this. JRST TSOSN ; Jump in to do it. ; TSOSSN - Send an initial SYN segment. No data, but add a TCP ; MSS option set from XBRMSS(I), and using TCB's sequence space ; vars. Seq=snd.nxt, ack=rcv.nxt, etc. ; R/ PE ptr to packet (packet will be smashed and re-used) ; I/ TCB index ; T/ flags to use (None, including SYN, will be added automatically) ; Clobbers A,B,C,D,E,W,H,Q,T,TT TSOSSN: CALL TSOINI ; Initialize (sets up W,H PK.IP,PK.TCP,PK.TCI) MOVE TT,XBRMSS(I) ; Max seg size we would like LSH TT,4 ; 32-bit option IOR TT,TSOMSO ; Add in type and length fields of option MOVEM TT,TH$OPT(H) ; Write it. Damn well better be first option. LDB TT,[PK$TDO (R)] ; Get current TCP header size ADDI TT,4 ; Adding 4-byte option DPB TT,[PK$TDO (R)] SETZ TT, ; Say zero bytes of real data DPB TT,[PK$TDL (R)] ; and make sure packet entry reflects this. JRST TSOSN ; Jump in to do it. TSOMSO: .BYTE 8 ? 2 ? 4 ? 0 ? 0 ? .BYTE ; Option 2, length 4, two data words TSOSND: MOVSI T,(TC%ACK) ; Simple data segment IOR T,XBSTAT(I) ; Plus whatever is being requested. HLRZ W,PK.IP(R) ; Get ptr to IP header HLRZ H,PK.TCP(R) ; and TCP header LDB TT,[PK$TDL (R)] ; Get # bytes of data ; LDB A,[PK$TDO (R)] ; Get offset of data ; ADDI TT,(A) ; Now have # bytes past std hdr length. ; TSOSN - Entry point if W, H, and TT already set up. ; I/ TCB index ; T/ flags for segment ; R/ PE ptr (PK.BUF, PK.IP, PK.TCP, PK.TCI must all be set) ; W/ IP header ptr ; H/ TCP header ptr ; TT/ # bytes of data (real data, not including header or SYN/FIN) ; Clobbers A,B,C,D,E,TT,T,Q and updates various TCB data. ; This code assumes TT is the bytes of DATA only. ; Must store the out-of-TCP info in the IP header field, so that ; the checksum and IPKSND routines will find it there. ; This info consists of: ; IP$SRC - Source address ; IP$DST - Dest address ; IP$TOL - TCP segment length including header ; IP$PTC - Protocol number (needn't set, assumes %PTCTC always) TSOSN: METER("TCP: Out segs") AND T,[TH%CTL] ; Ensure non-flag bits are flushed. MOVE A,T ANDCAB A,XBSTAT(I) ; Turn off these request bits TLNE A,(TH%CTL) ; Any request bits left? JRST TSOSN2 ; Yeah, can't turn off "now" bit. MOVSI A,(%XBNOW) ; Satisfied everything, so flush ANDCAM A,XBSTAT(I) ; the send-immediately bit. TSOSN2: LDB A,[PK$TDO (R)] ; Bytes of header ADDI A,(TT) ; Add bytes of data DPB A,[IP$TOL (W)] ; Store in IP length field MOVE A,XBLCL(I) LSH A,4 MOVEM A,IP$SRC(W) ; Set source host MOVE A,XBHOST(I) LSH A,4 MOVEM A,IP$DST(W) ; Set dest host ; Out-of-TCP info set up, now build the real TCP header. LDB A,[.BP TH%DST,XBPORT(I)] ; Get port sending from (local) DPB A,[TH$SRC (H)] LDB A,[.BP TH%SRC,XBPORT(I)] ; Get port to send to DPB A,[TH$DST (H)] MOVE A,XBSNXT(I) ; Get sequence number to use LSH A,4 MOVEM A,TH$SEQ(H) ; Set SEQ field TLNN T,(TC%ACK) ; Check flags, sending ACK? TDZA A,A ; If not, use zero field anyway. MOVE A,XBRNXT(I) ; Get ack number to use LSH A,4 MOVEM A,TH$ACK(H) ; Set ACK field SKIPE A,XBSUP(I) ; Urgent data being sent? JRST [ TLO T,(TC%URG) ; Yes! Say urgent pointer signif METER("TCP: Urgent dgms") MOVNI B,(TT) ADDB B,XBSUP(I) ; Adjust pointer as result of data sent CAIGE B, SETZM XBSUP(I) LSH A,4 JRST .+1] MOVEM A,TH$UP(H) ; Set urgent pointer if any MOVE A,XBRWND(I) ; Get our current receive window LSH A,4 IOR A,T ; Add in caller's flags LDB B,[PK$TDO (R)] ; Header length in bytes LSH B,-2 ; TCP wants length in 32-bit words DPB B,[<.BP TH%THL,A>] MOVEM A,TH$THL(H) ; Store header len, flags, window PUSH P,TT ; Goddam checksum clobberage CALL THCKSM ; Now figure out checksum POP P,TT DPB A,[TH$CKS (H)] ; TCP header set up. Now update our TCB connection vars to ; account for the stuff we're sending. TLNE T,(TC%SYN) ; Now find new seq # (SND.NXT) ADDI TT,1 ; SYN counts as 1 octet TLNE T,(TC%FIN) ; So does a FIN ADDI TT,1 JUMPLE TT,TSOSN8 ; If not actually using seq space, skip ; a bunch of update/retrans stuff. ; We're using up some sequence space! Must update avail window, ; and put the segment on retransmit queue. MOVE A,XBSAVW(I) ; Must update avail send window SUBI A,(TT) CAIGE A, ; If window becomes negative, SETZ A, ; keep it at zero. MOVEM A,XBSAVW(I) ADD TT,XBSNXT(I) ; Get new SND.NXT TLZ TT,%MOD32 MOVEM TT,XBSNXT(I) SKIPN XBORTT(I) ; Retrans timeout already set? JRST [ MOVE A,TIME ADD A,TCPTMO ; Make it 5 sec for now. MOVEM A,XBORTT(I) SETZM XBORTC(I) ; Clear count of retries. JRST .+1] TRCPKT R,"TSOSND Pkt w/seq space added to retransmit queue" MOVEI A,(R) ; Arg to PKQPL, A/ PE ptr MOVEI Q,XBORTQ(I) ; Arg to PKQPL, Q/ queue hdr ptr CALL PKQPL(PK.TCP) ; Put on TCP retrans queue AOS XBORTL(I) ; Bump count of segs on queue TSOSN8: CALL IPKSND ; Put on IP output queue RET SUBTTL TCP Retransmit and Timeout Comment | The following things in TCP need some sort of timeout: Retransmit output segment if not ACKed (removed) within RT sec Timeout to abort connection if retransmission fails for UT sec Timeout to ACK incoming data (ie avoid ACKing immediately, wait for more output or input). Timeout during TIME-WAIT to flush connection. | ; TCPCLK - This routine is called by 1/2-sec "slow" clock. What it has to do ; is scan all active TCB's for the following conditions: ; (1) Retransmit timeout has expired, must resend something. ; or TIME-WAIT timeout has expired. ; (2) An ACK must be sent, either by sending the current output ; buffer, or by generating an ACK without data. EBLK TCLKRC: 0 ; Count of segs compacted in pass over a retrans Q BBLK TCPCLK: SKIPN TCPUP ; Do nothing if turned off. RET MOVSI I,-XBL CONO PI,NETOFF SKIPA A,TIME TCLK05: SKIPA A,TIME TCLK10: SKIPN B,XBSTAT(I) JRST TCLK15 SKIPE C,XBORTT(I) CAMG A,C CAIA JRST TCLK20 ; Retrans timeout TCLK12: TLNE B,(TH%CTL+%XBNOW) ; Any flags set? JRST TCLK50 ; Wants ACK sent TCLK15: AOBJN I,TCLK10 CONO PI,NETON RET TCLK16: MOVE A,TIME AOBJN I,TCLK10 CONO PI,NETON RET ; Come here for timeout of some sort. TCLK20: SKIPE XBORTQ(I) ; If a retrans queue exists, JRST TCLK22 ; then assume it was a retrans timeout. MOVEI C,(B) ; No retrans Q, probably a TIME-WAIT one? CAIN C,.XSTMW ; State TIME-WAIT? JRST [ METER("TCP: Time-Wait timeout") CALL TXBFLP ; Flush the TCB completely, PI level JRST TCLK16] CAIN C,.XSSYQ ; State SYN-QUEUED? JRST [ METER("TCP: SYQ timeout") CALL TSISQF ; Flush the queued SYN. JRST TCLK16] CAIN C,.XSFN2 ; State FIN-WAIT-2? JRST TCLK21 METER("TCP: Random timeout") ; Sigh. SETZM XBORTT(I) ; Flush whatever it was. JRST TCLK16 TCLK21: METER("TCP: FN2 timeout") CALL TXBFLP ; Flush the TCB completely, PI level SKIPE XBUSER(I) ; Shouldn't still have anything open. BUG CHECK,[TCP: FN2 timo with active user] JRST TCLK16 TCLK22: METER("TCP: Retrans") AOS C,XBORTC(I) ; Retrans timeout. Send it again. SKIPE D,XBORTP(I) ; Has user set any retrans params? JRST [ JRST TCLK25] ; Yes! For now, non-Z means skip abort check. CAILE C,%TCPMR ; Tried too many times? JRST TCLK80 ; Ugh, abort the connection! SKIPN R,XBORTQ(I) JRST [ SETZM XBORTT(I) ; If nothing on queue, JRST TCLK12] ; just reset the timeout to nothing. SKIPGE A,PK.FLG(R) ; Ensure that packet isn't being output now JRST TCLK25 ; Still being output?? Reset timeout. ; Note that we don't check to see whether segment has already ; been transmitted, on the theory that compaction is going to ; pay off anyway. HLRZ W,PK.IP(R) HLRZ H,PK.TCP(R) SETZM TCLKRC ; Clear compaction count. ; Looks like we have to retransmit. Try to compact up as much ; stuff as possible into a single segment; this gets a bit ; hairy. Note that we compact as much as we can, ignoring the ; %PKPIL and %PKODN bits (except for setting the appropriate flush ; flags). TRCPKT R,"TCLK30 Segment being retransmitted" TCLK30: HRRZ J,PK.TCP(R) ; Get pointer to succeeding segment JUMPE J,TCLK39 ; If none following, can't compact (ignore ; possibility of adding XBOCOS for now) LDB B,[PK$TDO (R)] ; Get 1st offset LDB C,[PK$TDL (R)] ; Get 1st length LDB T,[PK$TDL (J)] ; Get 2nd length ADDI B,(C) ; Find offset to end of 1st data MOVEI D,(B) ADDI D,(T) ; Find total length after compaction CAILE D,576.-<5*4> ; Hack hack hack! Limit to 556. so std ; IP datagram is limited to 576. JRST TCLK39 ; If too big, don't compact. ; Compact two segments into one! ; R/ 1st seg D/ offset to end of data ; J/ 2nd seg T/ len of 2nd data METER("TCP: Retrans compact") TRCPKT J,"TCLK30 Segment being compacted into previous seg for rexmit" ADDI C,(T) ; Get new # bytes for 1st seg DPB C,[PK$TDL (R)] ; Store it in advance. ; HLRZ D,PK.TCP(R) ; Find addr of TCP header in 1st seg MOVEI D,(H) IDIVI B,4 ADDI D,(B) ; Get addr for BP to end of data HRL D,(C)[441000 ? 341000 ? 241000 ? 141000] ; Make LH LDB B,[PK$TDO (J)] ; Get data offset for 2nd seg IDIVI B,4 HLRZ A,PK.TCP(J) ; Get addr for BP to start of 2nd data ADDI B,(A) HRL B,(C)[441000 ? 341000 ? 241000 ? 141000] ; Make LH ; B/ BP to 2nd data ; D/ BP to end of 1st data ; T/ # bytes of 2nd data LDB A,[IP$TOL (W)] ; Get current length of whole datagram ADDI A,(T) ; Increment by length of added stuff DPB A,[IP$TOL (W)] ; Store back ADDI A,3 LSH A,-2 HRLM A,PK.BUF(R) ; Set up new count of # words in datagram. TCLK32: ILDB C,B IDPB C,D SOJG T,TCLK32 ; Data copied over, now update flags and stuff. HLRZ D,PK.TCP(J) MOVE A,TH$CTL(D) ; Get flags for 2nd seg AND A,[TH%CTL] ; Mask off just flags IORM A,TH$CTL(H) ; Add them to flags for 1st seg TLNE A,(TC%URG) ; If URGENT bit set, JRST [ LDB B,[TH$UP (D)] ; Get pointer from 2nd seg LDB C,[PK$TDL (R)] ; Sigh, get new len of 1st seg ADDI B,(C) ; Adjust for bytes in front LDB C,[PK$TDL (J)] ; But have to subtract length SUBI B,(C) ; of 2nd seg (already in 1st len) DPB B,[TH$UP (H)] ; Store ptr back in 1st seg JRST .+1] ; Compaction done! Now have to remove 2nd seg from queue. HRRZ B,PK.TCP(J) ; Get pointer to 3rd seg HRRM B,PK.TCP(R) ; Point 1st at it CAIN B, ; If 2nd was the last one, HRLM R,XBORTQ(I) ; must update "last" ptr in queue header. MOVE A,PK.FLG(J) ; Get flags IFN PK.TCP-2,.ERR %PQFL flag must match PK.TCP TLZ A,(%PQFL2) ; Say it's off the TCP list, to allow ; flushing from IP queue. TLO A,(%PKFLS) ; In fact, require it MOVEM A,PK.FLG(J) ; Store flags back JUMPGE A,[MOVEI A,(J) ; If not locked by PI output, TRCPKT A,"TCLK32 Seg flushed from rexmit by compaction" CALL PKTRT ; try to flush it now. JRST .+1] SOSGE XBORTL(I) ; Decrement count of retrans queue segs BUG HALT AOS TCLKRC ; Bump count of recompacts done JRST TCLK30 ; OK, try to recompact next seg! ; Note one possible problem with following code; although ; the segment being re-trans'd is given latest poop (ACK, WND), ; the ones following are not. This is usually OK as we assume ; that following segs have actually been sent out, but if it ; happens that they HAVEN'T (i.e. %PKODN not set) then their ; info is going to be a little out of date. This shouldn't ; screw things too much, however. TCLK39: MOVE D,XBRNXT(I) ; Get latest ACK value LSH D,4 MOVEM D,TH$ACK(H) ; Set it MOVE D,XBRWND(I) ; And latest window DPB D,[TH$WND (H)] CALL THCKSI ; Compute checksum for it (note not THCKSM) DPB A,[TH$CKS (H)] SKIPE TCLKRC ; Was any recompaction done? CALL IPKHD2 ; Yes, must recompute IP header (checksum etc) MOVE A,PK.FLG(R) TLNN A,(%PKODN) ; Has segment already been tried once? JRST [ ; No, don't put on output queue twice!! TRCPKT R,"TCLK39 Rexmit skipped because seg not yet output" METER("TCP: Pretrans compact") JRST TCLK25] TLO A,(%PKRTR) ; Set flag saying this is a retransmit MOVEM A,PK.FLG(R) MOVEI A,(R) CALL IPKSNQ ; Put back on IP output queue ; Note PK.BUF shd still be set up right. TCLK25: MOVE A,TIME HRRZ B,XBORTP(I) ; If RH set, use it for new timeout. CAIN B, MOVE B,TCPTMO ; Use timeout default. ADD B,A MOVEM B,XBORTT(I) JRST TCLK79 ; Here when need to send an ACK. First see if we can ; make use of existing output buffer. TCLK50: METER("TCP: slow ACKs") TLNE B,(TC%SYN+TC%RST) BUG CHECK,[TCP: SYN or RST set in XBSTAT clock req] SKIPE R,XBOCOS(I) ; Ensure there is one. TLNE B,(%XBMPL) ; and that it isn't locked. JRST TCLK60 ; Sigh, can't use it. ; There is an output buffer, and it's not locked, so use that ; to send stuff out! TRCPKT R,"TCLK50 COS used to send clock level ACK" MOVSI T,(TC%PSH) CALL TCPOFR ; Force it out. JRST TCLK16 ; Come here when we have to generate a new segment for ACK. TCLK60: TLNN B,(%XBNOW) ; Insisting that we ACK? JRST TCLK65 ; No, can semi-punt. CALL PKTGFI ; Get buffer JRST TCLK65 ; and forget about ACKing if we cant get one METER("TCP: Clk ACK") MOVEI R,(A) MOVE T,B ; Use request flags in segment. TRCPKT R,"TCLK60 Alloc and send ACK from clock level" CALL TSOSNR ; Send a simple ACK JRST TCLK16 TCLK65: MOVSI A,(%XBNOW) ; No, so just set insist flag IORM A,XBSTAT(I) ; and wait a bit longer. JRST TCLK16 TCLK79: JRST TCLK16 ; Abort the connection, timed out. TCLK80: METER("TCP: Timeout abort") CALL TXBFLP ; This is pretty drastic... flush, PI level. MOVEI T,.XCINC ; Say "incomplete transmission" CALL TCPUC ; as close reason. JRST TCLK16 TCLK90: CONO PI,NETON RET ; Checksum cruft. ; THCKSM - Figures TCP segment checksum, IP$TOL has TCP segment length. ; THCKSI - Figures TCP segment checksum, IP$TOL has IP header plus TCP seg. ; W/ addr of IP header ; H/ addr of TCP header ; Note that the following out-of-TCP values are looked up ; from the IP header in order to compute sum for the "pseudo header". ; IP$SRC - source host ; IP$DST - dest host ; IP$TOL - # octets in TCP segment (plus IP header) ; Finally, ; %PTCTC - Assumed value ; ; Clobbers B,C,D,E ; Returns ; A/ checksum ; TT/ Total # bytes in TCP segment THCKSM: TDZA C,C ; Compute as if IHL=0 THCKSI: MOVNI C,5*4 ; First compute pseudo header LDB A,[IP$SRC (W)] ; Source addr LDB B,[IP$DST (W)] ; Dest addr ADD A,B ADDI A,%PTCTC ; Add TCP protocol number LDB TT,[IP$TOL (W)] ; Get total length in octets JUMPE C,THCKS2 LDB B,[IP$IHL (W)] ; Find IP header length in 32-bit wds LSH B,2 ; mult by 4 to get # octets SUBI TT,(B) ; Find # octets of IP data (TCP segment) THCKS2: ADDI A,(TT) ; Add in. MOVEI C,-<5*4>(TT) ; Get # bytes in segment after 1st 5 wds ; Done with pseudo header (not folded yet, though). LDB B,[044000,,0(H)] ; Get wd 0 (src/dest) ADD A,B LDB B,[TH$SEQ (H)] ; Get wd 1 (seqno) ADD A,B LDB B,[TH$ACK (H)] ; wd 2 ADD A,B LDB B,[044000,,3(H)] ; wd 3 ADD A,B LDB B,[TH$UP (H)] ; wd 4 (part of) ADDI A,(B) LSHC A,-16. LSH B,-<16.+4> ADDI A,(B) ; Now have it folded up. JUMPLE C,THCKS7 ; If nothing more, can leave now. MOVEI E,5(H) HRLI E,442000 ; Set up 16-bit byte ptr to options/data LSHC C,-1 JUMPLE C,THCKS6 THCKS5: ILDB B,E ADDI A,(B) SOJG C,THCKS5 THCKS6: JUMPL D,[ ; Jump if odd byte left. ILDB B,E ; get it ANDCMI B,377 ; mask off low (unused) byte. ADDI A,(B) JRST .+1] %CKMSK==<-1#177777> ; Mask for stuff above 16 bits THCKS7: TDNE A,[%CKMSK] ; If any carries, add them in. JRST [ LDB B,[.BP %CKMSK,A] TDZ A,[%CKMSK] ADD A,B JRST THCKS7] ANDCAI A,177777 ; Complement sum and mask off. RET MTRCOD ; Last stuff -- expand meter tables. TRCCOD ; Expand trace tables