1
0
mirror of https://github.com/PDP-10/its.git synced 2026-02-11 10:44:41 +00:00
Files
PDP-10.its/src/system/tcp.275
2016-10-31 08:41:05 +01:00

3630 lines
119 KiB
Plaintext
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
; 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 ; <flags>,,<TCP state>
; 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 <input>,,<output>
XBCLSU: BLOCK XBL ; Close reason <input>,,<output>
.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 ; <remote port><local port><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>,<lt or le>,<seqno>,<lt or le>,<right>,<lerr>,<rerr>
; 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/ <rcv>,,<xmit>
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 <remote><local>
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 <TCB idx>,,TCPDUI
HRRI T,TCPDUO
ADDI J,IOCHNM(U)
MOVEM T,(J) ; Set up output chan <TCB idx>,,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 - <original # bytes XBOBC started with>,,<PE ptr to COS>
; 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/ <state>
; 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/ <LH of CLSTB entry>,,<addr of IOCHNM word>
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/ <seg control bits>,,<temp flags>
; 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/ <loc port><rem port> (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