%( **************************************************************** Copyright (c) 1992, Carnegie Mellon University All Rights Reserved Permission is hereby granted to use, copy, modify, and distribute this software provided that the above copyright notice appears in all copies and that any distribution be for noncommercial purposes. Carnegie Mellon University disclaims all warranties with regard to this software. In no event shall Carnegie Mellon University be liable for any special, indirect, or consequential damages or any damages whatsoever resulting from loss of use, data, or profits arising out of or in connection with the use or performance of this software. **************************************************************** )% %TITLE 'TCP_SEGIN - Process TCP network input segments' %SBTTL 'Network Segment Arrival Overview' %( Module: TCP_SEGIN - TCP input event processor. Facility: Process the newly arrived network segments. Abstract: Network segments are placed on the segment-input queue by IP after the inter-network protocols have been removed. TCP reads the queue & processes each segment. Arrival of segments drives the connection states along with user requests. Author: Original version by Stan C. Smith, Fall 1981 This version by Vince Fuller, CMU-CSD, Summer, 1987 Copyright (c) 1986, 1987, Vince Fuller and Carnegie-Mellon University Modification History: *** Begin USBR change log *** 6.7 05-Dec-1991 Henry W. Miller USBR Use TCB[SND_ACK_THRESHOLD] instead of ACK_THRESHOLD. Set TCB[SND_ACK_Threshold] from TCB[Max_SEG_Data_Size]. Sort of RFC1122 compliant. 6.6 14-Nov-1991 Henry W. Miller USBR In SEG$Process_Received_Segments(), log various types of currently unsupported ICMP segments. In Decode_Segment(), let TELNET module ACK it's own data, clean up logging. Change arithmetic and printout on timers from signed to unsigned. Call TCP$Enqueue_ACK() rather than just setting PENDING_ACK or calling TCP$Send_ACK() directly. 6.5f 30-Aug-1991 Henry W. Miller USBR In DECODE_SEGMENT(), queue user data if RCV_Q_COUNT is greater then zero, not just if it's big enough to fill user's buffer. This fixes the long outstanding dreaded TELNET pause syndrome. (At least here it does...) 6.5e 11-Mar-1991 Henry W. Miller USBR Fix port comparison logic. 6.5d 27-Feb-1991 Henry W. Miller USBR Fix port comparison logic. 6.5c 24-Jan-1991 Henry W. Miller USBR Fixed logging statements to print timestamp. 6.5b 15-Jan-1991 Henry W. Miller USBR Changed SND_WND, RCV_WND, and Old_RCV_WND to unsigned long values as recommended by RFC1122. 6.5a 13-Jan-1991 Henry W. Miller USBR In SEG$Log_Segment(), print checksum, data pointer and urgent pointer as unsigned word values. Updated IDENT to 6.5a. *** Begin CMU change log *** 6.4 16-MAR-1989 Dale Moore CMU-CS If we aren't doing Keep alives, don't timeout connections. 06-JUN-1988 Dale Moore CMU-CS/RI Check to see if we are truncated the data received before we process FIN bit. We might truncate the data if the window size is smal. 6.3 14-JAN-1988 Dale Moore CMU-CS/RI In Process_Received_Segments: On Duplicate SYN, drop packet rather than RST connection. 6.2 23-Nov-87, Edit by VAF In FORK_SERVER: re-initialize length of PROCNAME descriptor each time through loop. Failing to do so restricts the process name string length to be whatever the first attempt sets it to (thanks to Mike Iglesias at UCI). 6.1 18-Nov-87, Edit by VAF Flush connections if Send_SYN$ACK fails for incoming connection. This shouldn't happen unless the systems is misconfigured. 6.0 4-Aug-87, Edit by VAF Add hooks for internal TELNET Virtual Terminal support. Rewrite options processing to be more general. 5.9 21-Jul-87, Edit by VAF Clean up the way circular queues are handled a bit. Rearrange some code in PROCESS_RECEIVED_SEGMENT to facilitate adding of special processing for incoming TELNET. Reorder incoming segment handling code for easier reading. 5.8 23-Mar-87, Edit by VAF Know about dynamic buffer sizes. Log setting of maximum segment size. 5.7 3-Mar-87, Edit by VAF Flush I/O request tags. Change calling sequence of User$Post_IO_Status. 5.6 27-Feb-87, Edit by VAF If in FIN-WAIT-2 and there is data pending to be read, don't close the connection yet - set FIN_RCVD flag for receive routine to check later. 5.5 18-Feb-87, Edit by VAF From Ed Cetron - update send window when SYN-RECV connection becomes established. This fixes a problem with hosts that open connections with an initial zero receive window. 5.4 9-Feb-87, Edit by VAF Make server priviliges be set from the config file. 5.3 4-Feb-87, Edit by VAF Give forked servers PRV$V_PHY_IO so they can open priviliged ports. 5.2 10-Dec-86, Edit by VAF Change order of arguments to Gen_Checksum. Code to wraparound RX pointer was wrong - fix it. 5.1 12-Nov-86, Edit by VAF Move WKS data structure here. Make it configured at runtime. Flush SYN wait list queue - incorporate it into the WKS structure. 5.0 11-Nov-86, Edit by VAF Remove RF_QMAX literal, replace with FQ_MAX variable set by configuration routine. Allow up to 30 of each type of forked server. Also, only log server creation when it succeedes. Rewrite portions of the SYN-wait-list handling routines to get rid of silly PASCAL syntax. 4.9 3-Nov-86, Edit by VAF Use reasonable coding (i.e. define some literals) in the SRTT calculation. Make minor change to calculation to conform with the suggested calculation in RFC793. 4.8 1-Oct-86, Edit by VAF Set TCB[NMLOOK_Flag] When we start an address to name lookup. 4.7 30-Sep-86, Edit by VAF Up RF_QMAX from 5 to 8. Fix SEG$Purge_RT_Queue - make it clear the retransmission count, flags, and update the pointer. 4.6 22-Sep-86, Edit by VAF When FIN arrives and we go into CLOSE-WAIT state, force a close if the TCB has been aborted (i.e. owning process has exited). In this case, the user's close was done and posted long ago, so it won't happen again. 4.5 12-Sep-86, Edit by VAF When we fill-in an unspecified foreign host, also start an name lookup to fill-in the foreign host name. 4.4 13-Aug-86, Edit by VAF Set TCB[IS_SYNCHED] when TCB becomes synchronized. BUILD_HEADER now uses this flag to determine if it should set the ACK bit. 4.3 6-Aug-86, Edit by VAF Redo retransmission handling to use circular queue. 4.2 5-Aug-86, Edit by VAF Redo segment input to use circular queue. 4.1 25-Jul-86, Edit by VAF Restrict number of segments on TCB NR queue. 4.0 22-Jul-86, Edit by VAF Clean up RST processing a little. Really drop SYN data - old code caused segments not to be deallocated. Change duplicate SYN processing to cause resets for all states. Don't set return value based on UOFFSET - actually set RETCODE to false when we deliver some data to the user. Separate ICMP logging out with LOG$ICMP flag. 3.9 18-Jul-86, Edit by VAF In Queue_Network_Data - drop segments if TCB is aborted. In Decode_Segment - set Ucount to 0 if text is not usable (i.e. not in Established, Fin-wait-1 or Fin-wait-2) so seg will be dropped. 3.8 17-Jul-86, Edit by VAF Debugging code for tracking incoming segments. 3.7 15-Jul-86, Edit by VAF Send out an ACK when we put something on the future queue. Idea is to let the bozo know that he is ahead of where he should be. 3.6 15-Jul-86, Edit by VAF Correctly handle case where QUEUE_NETWORK_DATA cannot place the segment in its sequence ordering. Old code would leave the segment in limbo. We fixed it to at least drop the segment. Also, count and log these bad (strange?) segments - this really shouldn't be happening. Change some LOG$TCPs to use new flag - LOG$TCPERR. Rearrange change log. 3.5 11-Jul-86, Edit by VAF Limit number of segments on future queue to 5. 3.4 9-Jul-86, Edit by VAF Revise sequence number checks. Should handle all cases where sequence numbers wrap around from positive to negative. 3.3 07-JUL-86, Edit by Dale Moore Change to include CS$Last_Ack in some of the same SELECT states as CS$Closing. 3.2 30-Jun-86, Edit by VAF Implement future segments queue. This queue contains all segments received that are in the window but are beyond the left edge of the window (and thus are not immediately usable). 3.1 25-Jun-86, Edit by VAF Don't use index 0 of VALID_TCB table. 3.0 11-Jun-86, Edit by VAF Make ACK_RT_QUEUE call SEND_DATA if it actually removes anything from the RT queue. Idea is to make SEND processing event driven. Fix receive sequence number check to agree with RFC. (We were sometimes getting undetected duplicates) 2.9 10-Jun-86, Edit by VAF When taking input segments, don't subtract SYN/FIN sequence space from starting data offset (since they don't really exist). Count up incoming segments and data octets in TCB. If incoming data exceeds size of 1st element on user receive queue, deliver data immediately in Decode_Segment. 2.8 22-May-86, Edit by VAF Use VMS error message facility. 2.7 8-May-86, Edit by VAF Post user CLOSE only when connection goes into Time_Wait state. In CS$Closing state, when ACK of FIN received, go to Time_Wait state (old code deleted the TCB, which violates the spec). Add LAST_ACK state. 2.6 2-May-86, Edit by VAF Fix SEG$Log_Segment data/option printing for byteswapped packets. 2.5 29-Apr-86, Edit by VAF Debugging code in TCP ICMP receive. 2.4 21-Apr-86, Edit by VAF Phase II of flushing XPORT - use $FAO for formatting output. 2.3 18-Apr-86, Edit by VAF Teach SEG$Log_Segment about byteswapping for some packets. 2.2 10-Apr-86, Edit by VAF Give sequence numbers, port numbers, etc. in both hex and decimal when writing log entries. Fix bug where SEQcount not initialized in DECODE_SEGMENT. 2.1 8-Apr-86, Edit by VAF Add LOG$TCBSTATE logging flag. Minor changes to logging. SET_TCB_STATE and INACTIVATE_TCB are now routines, not macros. 2.0 7-Apr-86, Edit by VAF First pass at more general logging code. 1.9 2-Apr-86, Edit by VAF Move some code to the USER module that belongs there. 1.8 25-Mar-86, Edit by VAF Support for ICMP at the TCP level. Reorganize segment input handler to make this possible. 1.7 24-Mar-86, Edit by VAF Move TCP input AST routine to this module for consistancy. Various changes between 1.6 and this, including fixing of problems with sequence number checks. 1.6 10-Mar-86, Edit by VAF Valid sequence number check was wrong. Accept segments with sequence numbers before RCV_NXT if they contain sequence numbers beyond RCV_NXT. **N.B. TCB[DASM_*] crap should be flushed. 1.5 7-Mar-86, Edit by VAF Phase I of flushing XPORT - get rid of 'LOGGER'. 1.4 21-Feb-86, Edit by VAF Flush "known_hosts" junk. Some work should be done to check local host # for incoming segments. *** End CMU change log *** 1.1 [10-1-83] stan smith Original version. 1.2 [7-15-83] stan force byte-size on some external literals. 1.3 [5-30-85] noelan olson Set index into known host table before sending ACK SYN so that the proper internet address will be used to calculate checksum. )% %SBTTL 'Module Definition' MODULE SEGIN(IDENT='6.7',LANGUAGE(BLISS32), ADDRESSING_MODE(EXTERNAL=LONG_RELATIVE, NONEXTERNAL=LONG_RELATIVE), LIST(NOREQUIRE,ASSEMBLY,OBJECT,BINARY), OPTIMIZE,OPTLEVEL=3,ZIP)= BEGIN LIBRARY 'SYS$LIBRARY:STARLET'; ! VMS system definitions LIBRARY 'CMUIP_SRC:[CENTRAL]NETXPORT'; ! BLISS transportablity package LIBRARY 'CMUIP_SRC:[CENTRAL]NETERROR'; ! Network error codes LIBRARY 'CMUIP_SRC:[CENTRAL]NETVMS'; ! VMS specifics LIBRARY 'CMUIP_SRC:[CENTRAL]NETCOMMON';! Network common defs LIBRARY 'CMUIP_SRC:[CENTRAL]NETTCPIP'; ! TCP/IP protocols LIBRARY 'STRUCTURE'; ! TCB & Segment Structure definitions LIBRARY 'TCPMACROS'; ! Local macros LIBRARY 'TCP'; ! TCP related definitions LIBRARY 'SNMP'; ! Simple Network Management Protocol !XQDEFINE; EXTERNAL TCP_MIB : TCP_MIB_struct; ! TCP management Information Block EXTERNAL ROUTINE LIB$GET_VM : ADDRESSING_MODE(GENERAL), ! USER.BLI User$Post_IO_Status : NOVALUE, ! TCP_USER.BLI TCP$Post_Active_Open_IO_Status : NOVALUE, TCP$Post_User_Close_IO_Status : NOVALUE, TCP$KILL_PENDING_REQUESTS : NOVALUE, TCP$Deliver_User_Data : NOVALUE, Gen_CheckSum, TCP$Adlook_Done : NOVALUE, ! TCP_TELNET.BLI TELNET_CREATE, TELNET_OPEN, TELNET_INPUT : NOVALUE, TELNET_OUTPUT : NOVALUE, ! MEMGR.BLI TCB$Create, TCB$Delete : NOVALUE, MM$Seg_Free : NOVALUE, MM$QBLK_Free : NOVALUE, MM$QBLK_Get, MM$UArg_Free : NOVALUE, ! TCP.BLI TCP$Send_Data : NOVALUE, TCP$Send_Ctl, TCP$Enqueue_Ack : NOVALUE, TCP$Send_Ack : NOVALUE, TCP$Dump_TCB : NOVALUE, TCP$Inactivate_TCB : NOVALUE, TCP$Set_TCB_State : NOVALUE, CQ_Enqueue : NOVALUE, TCP$TCB_CLOSE, TCP$Compute_RTT, ! NMLOOK.BLI NML$GETNAME : NOVALUE, ! MACLIB.MAR Time_Stamp, SwapBytes : NOVALUE, ! IOUTIL.BLI ASCII_Hex_Bytes : NOVALUE, ASCII_Dec_Bytes : NOVALUE, Log_Time_Stamp : NOVALUE, OPR_FAO : NOVALUE, LOG_FAO : NOVALUE, LOG_OUTPUT : NOVALUE, ACT_FAO : NOVALUE, ACT_OUTPUT : NOVALUE; EXTERNAL INTDF, AST_IN_PROGRESS, CONN_TIMEVAL : UNSIGNED, MYUIC, ! TCP's UIC. Log_State, SegIN : Queue_Header_Structure(SI_Fields), ! ConectPtr : REF Connection_table_Structure VOLATILE, FQ_MAX, ! Max segments allowed on future queue. MAX_RECV_DATASIZE, ! Configured Maximum Segment Size TELNET_SERVICE, ! Flag if internal TELNET server is enabled Keep_Alive, ! Flag if Keep alives used ! tcp stats TS$SEG_BAD_CKSUM, ! bad segment checksum TS$SERVERS_FORKED, ! # of well-known servers forked. TS$ACO, ! Active opens that were established. TS$DUPLICATE_SEGS, ! total duplicate segments received. TS$OORW_SEGS, ! Total Out Of Recv Window segs received. TS$FUTURE_RCVD, ! # received in window but after RCV.NXT TS$FUTURE_DUPS, ! # of future duplicates dropped TS$FUTURE_USED, ! # of such used TS$FUTURE_DROPPED, ! # of such dropped TS$BADSEQ, ! Packets dropped in Queue_Network_data TS$ABORT_DROPS, ! packets dropped because TCB aborted TS$QFULL_DROPS, ! packets dropped because TCB NR queue full TS$SR; ! segments recevied. ! Forward declarations Forward Routine SEG$Log_Segment : NOVALUE; LITERAL GLOBAL_MINSRV = 1, GLOBAL_MAXSRV = 30; %SBTTL 'Declaration of WKS server process table & SYN-wait list' GLOBAL WKS_COUNT : INITIAL(0), WKS_LIST : WKS_Structure, SYN_WAIT_COUNT : INITIAL(0); %SBTTL 'ACK-RT-Queue: Check if "ACK" segment acks any segments.' %( Function: Check if "ACK" segment acks any segments currently on the retransmission queue. If so then delete the segment from the queue. Inputs: TCB = TCB pointer. ACKNUM = All sequence #'s < acknum are ack'ed. Implicit Inputs: TCB[RT_Qhead] & TCB[RT_Qtail] form the re-transmission queue header. Outputs: None. Side Effects: Re-transmission queue elements may be deleted if ACK'ed. )% LITERAL ALPHA = 90, ! Smoothing constant, per RFC973, pg 41 BETA = 150, ! Delay variance factor ALPHACOMP = (100 - ALPHA), ! "Compliment" of ALPHA (i.e. 1 - ALPHA) MINRTT = 1, ! Minimum reasonable RTT MINSRTT = 10; ! Minimum smoothed RTT ROUTINE ACK_RT_Queue(TCB: REF TCB_Structure,AckNum) : NOVALUE = BEGIN LOCAL Delta: UNSIGNED, crtt : UNSIGNED, Tmp, deqc, oldcount; oldcount = .TCB[SRX_Q_Count]; deqc = .AckNum - .TCB[RX_SEQ]; XLOG$FAO(LOG$TCP,%STRING( '!%T ACK-RXQ: TCB=!XL, SEQ=!XL, PTR=!XL, CNT=!SL!/', '!%T ACK-RXQ: CTL=!SL, ACK=!XL, DEQC=!SL!/'), 0,.TCB,.TCB[RX_SEQ],.TCB[SRX_Q_DEQP],.TCB[SRX_Q_Count], 0,.TCB[RX_CTL],.AckNum,.DEQC); ! If the ACK number is greater than the RX sequence, then we are acknowleging ! at least part of the RX space. IF .deqc GTR 0 THEN BEGIN LOCAL oldrtt : UNSIGNED, newc, oldcount; ! Clear SYN flag if we acked anything, since it is the first sequence number ! Also decrement number of octets we acked - SYN takes up one sequence number. TCB[RX_SEQ] = .TCB[RX_SEQ] + .deqc; IF .TCB[RX_CTL] NEQ 0 THEN IF (.TCB[RX_CTL] EQL M$SYN) OR (.TCB[RX_CTL] EQL M$SYN_ACK) THEN BEGIN TCB[RX_CTL] = 0; deqc = .deqc - 1; END; ! Clear retransmission count TCB[RX_Count] = 0; ! Calculate number of bytes removed from RX data queue. ! newc LSS 0 SHOULD mean that a FIN has been acked. newc = .TCB[SRX_Q_Count] - .deqc; IF .newc LSS 0 THEN BEGIN IF .TCB[RX_CTL] EQL M$FIN THEN TCB[RX_CTL] = 0 ELSE XLOG$FAO(LOG$TCPERR,'!%T ?RX-ACK - newc = !SL!/',0,.newc); newc = 0; END; ! Update RX queue count and pointer. TCB[SRX_Q_Count] = .newc; IF .newc EQL 0 THEN TCB[SRX_Q_DEQP] = .TCB[SND_Q_DEQP] ELSE BEGIN LOCAL newptr; newptr = .TCB[SRX_Q_DEQP] + .deqc; IF .newptr GTR .TCB[SRX_Q_END] THEN newptr = .TCB[SRX_Q_BASE] + (.newptr - .TCB[SRX_Q_END]); TCB[SRX_Q_DEQP] = .newptr; END; ! Compute round trip time for adaptive retransmission. TCP$Compute_RTT(.TCB) ; ! oldrtt = .TCB[round_trip_time]; ! crtt = MAXU(Time_Stamp()-.TCB[Xmit_Start_Time],MINRTT); ! Compute smoothed round trip time, see RFC793 (TCP) page 41 for details. ! delta = ((.TCB[round_trip_time]*ALPHA)/100) + ! ((.crtt*ALPHACOMP)/100); ! TCB[round_trip_time] = .delta; ! delta = (BETA*.delta)/100; ! TCB[calculated_rto] = MINU(MAX_RT_TIMEOUT,MAXU(.delta,MIN_RT_TIMEOUT)); ! XLOG$FAO(LOG$TCP, ! '!%T ACK-RXQ: Prev SRTT=!UL, Cur RTT=!UL, New SRTT=!UL, RTO=!UL!/', ! 0,.oldrtt,.crtt,.TCB[round_trip_time],.TCB[calculated_rto]); END; ! If TVT and send queue just became non-full, try to get some more TVT data IF .TCB[IS_TVT] THEN IF (.oldcount + .TCB[SND_Q_Count]) GEQ .TCB[SND_Q_Size] AND (.oldcount GTR .TCB[SRX_Q_Count]) THEN BEGIN TELNET_OUTPUT(.TCB); END; ! If the RT queue is now empty, see if data needs to be sent. ! Since we implement the "Nagle" algorithm, the data send routine is ! effectively blocked by anything on the RT queue. IF .TCB[SRX_Q_Count] EQL 0 THEN TCP$Send_Data(.TCB); END; GLOBAL ROUTINE SEG$Purge_RT_Queue(TCB: REF TCB_Structure): NOvalue= ! ! Routine to clean out the retransmission queue. All this really involves ! is clearing the RX count and flags and advancing the RX pointer to the ! send dequeue pointer. ! BEGIN TCB[SRX_Q_Count] = 0; ! Clear retransmit byte count TCB[SRX_Q_DEQP] = .TCB[SND_Q_DEQP]; ! Empty retransmit queue TCB[RX_CTL] = 0; ! And clear retransmit flags END; %SBTTL 'Send-Reset: RESET a Known connection' %( Function: Send a "RESET" segment in response to receiving strange or unexpected segments. Inputs: TCB = TCB pointer. SeqNum = Number to use as the segments sequence number. Outputs: None. Side Effects: "RESET" segment is constructed & delivered to IP for network transmission. )% ROUTINE Send_Reset(TCB: Ref TCB_Structure,SEQNum): NOvalue= BEGIN LOCAL Sav$Snd_Nxt; ! save send next sequence # & use SEQnum instead. Sav$Snd_Nxt = .TCB[Snd_Nxt]; TCB[Snd_Nxt] = .SEQNum; ! Build the "RESET" segment & give it to IP for transmission. Send_RST(.TCB); ! Restore the world to its proper state. TCB[Snd_Nxt] = .Sav$Snd_Nxt; END; %SBTTL 'Reset-Unknown-Connection' %( Function: Send a "RST" reset segment in reply to receiving a segment for a unknown connection that was NOT a well-known local port. Inputs: SEG - points at offending segment. QB - points at it's queue block, Network receive queue (QB_NR_Fields). Outputs: None. Side Effects: "RST" segment is built & sent. A FAKE TCB must be created & partially initialized for the control-segment send rtns. After the RESET has been sent, delete the fake TCB. )% ROUTINE RESET_UNKNOWN_CONNECTION(SEG,QB): NOVALUE = BEGIN MAP Seg: REF Segment_Structure, QB : REF Queue_Blk_Structure(QB_NR_Fields); REGISTER TCB: REF TCB_Structure; ! Create & fill in the temporary TCB TCB = TCB$Create(); TCB[LP_Next] = TCB[LP_Back] = TCB[LP_Next]; ! Init Local Port queue. TCB[Local_Port] = .Seg[SH$Dest_Port] AND %X'FFFF' ; TCB[Local_Host] = .QB[NR$Dest_Adrs]; TCB[Foreign_Port] = .Seg[SH$Source_Port] AND %X'FFFF' ; TCB[Foreign_Host] = .QB[NR$Src_Adrs]; TCB[State] = CS$Established; TCB[Rcv_Wnd] = TCB[Snd_Wnd] = TCB[Rcv_Nxt] = 100; ! If the ACK bit (received seg) is TRUE, then ! ELSE IF .Seg[SH$C_ACK] THEN TCB[Snd_Nxt] = .Seg[SH$ACK] ELSE BEGIN TCB[Snd_Nxt] = 0; TCB[Rcv_Nxt] = .Seg[SH$SEQ] + (.QB[NR$Size] - .Seg[SH$Data_Offset]*4) + .seg[sh$c_syn] + .seg[sh$c_fin]; END; ! Send the reset. XLOG$FAO(LOG$TCPERR, '!%T RESET for unknown conn, ACK=!XL (!UL), SEQ=!XL (!UL)!/', 0,.Seg[SH$SEQ],.Seg[SH$SEQ],.Seg[SH$ACK],.Seg[SH$ACK]); Send_RST(.TCB); ! Delete the TCB TCB$Delete(.TCB); END; %SBTTL 'Queue-Network-Data' %( Function: Queue sequenced data to the connection TCB (NR_Qhead). Insert the queue block in the queue while maintaining sequence space ordering for the data. Inputs: TCB = TCB pointer. QB = Queue block for current data bearing segment (QB_NR_Fields). Outputs: Boolean indicating whether segment should be deallocated. Side Effects: Segment is place in sequence space order. Connection Receive window is decremented by the data size of this segment. )% ROUTINE QUEUE_NETWORK_DATA(TCB,QB) = BEGIN MAP TCB: REF TCB_Structure, QB: REF Queue_BLK_Structure(QB_NR_Fields); REGISTER Ucount, Uptr; LITERAL NR_Qmax = 8; ! Max segs on receive queue per connection ! Is the TCB aborted? If so, just toss the data. IF .TCB[IS_Aborted] THEN BEGIN XLOG$FAO(LOG$TCPERR, '!%T QND: TCB aborted, drop QB !XL, seg !XL, SEQ !XL/!XL!/', 0,.QB,.QB[NR$Buf],.QB[NR$SEQ_Start],.QB[NR$SEQ_End]); ts$abort_drops = .ts$abort_drops+1; RETURN ERROR; ! Deallocate this segment END; ! Set push flag and pointer if the segment has it on IF ((.QB[NR$EOL]) OR (.TCB[RCV_WND] LEQU .TCB[SND_ACK_THRESHOLD])) THEN BEGIN TCB[RCV_Push_Flag] = TRUE; TCB[RCV_PPtr] = .QB[NR$SEQ_End]; XLOG$FAO(LOG$TCP, '!%T QND: EOL set, Q cnt !UW !/', 0, .TCB[RCV_Q_Count]); END; ! Enqueue the data onto the receive circular queue Ucount = .QB[NR$Ucount]; ! Count of user data in segment Uptr = .QB[NR$Uptr]; ! Address of data Ucount = MIN(.Ucount,.TCB[RCV_Q_Size]-.TCB[RCV_Q_Count]); IF .Ucount LEQ 0 THEN BEGIN XLOG$FAO(LOG$TCPERR, '!%T QND: TCB rcv q full, drop QB !XL, seg !XL, SEQ !XL/!XL!/', 0,.QB,.QB[NR$Buf],.QB[NR$SEQ_Start],.QB[NR$SEQ_End]); ts$qfull_drops = .ts$qfull_drops+1; RETURN ERROR; ! Deallocate this segment END; ! Append the segment data to the queue and deallocate it XLOG$FAO(LOG$TCP, '!%T QND: ENQ !SL from !XL,EQ=!XL,DQ=!XL,RCQ=!XL/!XL!/', 0,.Ucount,.Uptr,.TCB[RCV_Q_ENQP],.TCB[RCV_Q_DEQP], .TCB[RCV_Q_BASE],.TCB[RCV_Q_END]); CQ_Enqueue(TCB[RCV_Q_Queue],.Uptr,.Ucount); XLOG$FAO(LOG$TCP, '!%T QND: Q cnt !UW !/', 0, .TCB[RCV_Q_Count]); ! Adjust the receive window to reflect the resources used by the data bearing ! portion of this segment. IF (.TCB[Rcv_Wnd] LSSU .QB[NR$SEQ_count]) THEN BEGIN XLOG$FAO(LOG$TCPERR,'!%T Segin(queue_net_data) RCV_WND (!UL) < SEQ_CNT (!UL)!/', 0, .TCB[RCV_WND], .QB[NR$SEQ_count]); TCB[Rcv_Wnd] = 0 ; END ELSE BEGIN TCB[Rcv_Wnd] = .TCB[Rcv_Wnd] - .QB[NR$SEQ_count]; END ; RETURN TRUE; END; %SBTTL 'Handle TCP segment input' %(***************************************************************************** Function: Queue a network segment to TCP. Place the segment portion of a datagram on TCP's segment input queue. Called at AST level from IP input handling. Inputs: Src$Adrs = Source internet address Dest$Adrs = Destinatin internet address BufSize = size of buffer containing segment (for seg$free) Buf = address of buffer containing segment (for seg$free) SegSize = size in bytes of the segment. Seg = address of TCP segment Outputs: None. ******************************************************************************* )% GLOBAL ROUTINE SEG$Input(Src$Adrs,Dest$Adrs,BufSize,Buf,SegSize,Seg): NOVALUE = BEGIN LOCAL QB: REF Queue_Blk_Structure(QB_NR_Fields); QB = MM$QBLK_Get(); QB[NR$Buf_Size] = .BufSize; ! Total size of network buffer. QB[NR$Buf] = .Buf; ! Start adrs of network buffer. QB[NR$Size] = .SegSize; ! byte size of segment within network buffer. QB[NR$Seg] = .Seg; ! Start adrs of segment within net buf. QB[NR$SRC_Adrs] = .SRC$Adrs;! Source internet adrs. QB[NR$Dest_Adrs] = .Dest$Adrs; ! destination internet adrs. QB[NR$Flags] = 0; ! Clear input flags INSQUE(.QB,.Segin[SI_Qtail]); ! queue to tail of segment in queue. !~~~XINSQUE(.QB,.Segin[SI_Qtail],TCP_Input,Q$SEGIN,Segin[SI_Qhead]); TCP_MIB[MIB$tcpInSegs] = .TCP_MIB[MIB$tcpInSegs] + 1; END; %SBTTL 'ICMP handler for TCP' %( Handle ICMP conditions that were generated by TCP segments that we sent out. Called at AST level from ICMP protocol handler whenever one of the following types of ICMP message is received: ICM_DUNREACH - Destination Unreachable. Should cause connection to be aborted. ICM_SQUENCH - Source Quench. Should cause us to back off on transmission rate. ICM_REDIRECT - Redirect. Maybe cause us to switch to a new first-hop destination for packets (currently, IP makes all of these descisions, but we could reduce routing costs by remembering redirects here). ICM_TEXCEED - Time Limit Exceeded. Probably should cause any Open in progress to be aborted. ICM_PPROBLEM - Parameter Problem. Check to see if it was a TCP option that caused the problem. If so, there is a bug somewhere and TCP should probably crash. Makes a dummy "segment" containing relavent ICMP info for processing by normal segment input handler. )% GLOBAL ROUTINE SEG$ICMP(ICMtype,ICMex,IPsrc,IPdst,Seg,Segsize, buf,bufsize) : NOVALUE = !ICMtype - ICMP packet type !ICMex - Extra data from ICMP packet (pointer for ICM_PPROBLEM) !IPsrc - Source address of offending packet !IPdst - Destination address of offending packet !Seg - First 64-bits of data from offending packet (will have ports) !Segsize - Calculated octet count of Seg !Buf - address of network buffer, released by TCP later !Bufsize - size of network buffer BEGIN LOCAL QB: REF Queue_Blk_Structure(QB_NR_Fields); QB = MM$QBLK_Get(); QB[NR$Buf_Size] = .BufSize; ! Total size of network buffer. QB[NR$Buf] = .Buf; ! Start adrs of network buffer. QB[NR$Size] = 8; ! byte size of segment within network buffer. QB[NR$Seg] = .Seg; ! Start adrs of segment within net buf. QB[NR$SRC_Adrs] = .ipsrc; ! Source internet adrs (us) QB[NR$Dest_Adrs] = .ipdst; ! destination internet adrs. QB[NR$FLAGS] = 0; QB[NR$ICMP] = TRUE; ! Indicate that this is an ICMP packet QB[NR$ICM_TYPE] = .ICMtype; ! Remember ICMP type QB[NR$ICM_EX] = .ICMex; ! and extra information INSQUE(.QB,.Segin[SI_Qtail]); ! queue to tail of segment in queue. RETURN; END; %SBTTL 'Check-SYN-Wait-List' %( Function: Check the SYN wait list for a "SYN" segment which caused a server to be forked. Idea is that when a server (well-known local-port) is passivly OPEN'ed there maybe a "SYN" segment waiting for the passive open to establish a connection. Inputs: TCB - TCB pointer. Outputs: None. Side Effects: IF a match is made for Foreign_Host & port then the SYN was destined for this connection. "SYN" segmenet is processed as if it had just arrived. Connection handshake is started. )% FORWARD ROUTINE Decode_Segment; GLOBAL ROUTINE SEG$Check_SYN_Wait_List(TCB: REF TCB_Structure) : NOVALUE = BEGIN LOCAL QB: REF Queue_Blk_Structure(QB_NR_Fields), Seg: REF Segment_Structure, LP, WIX; LABEL X,Y; ! Find the WKS entry for the port LP = .TCB[Local_Port] AND %X'FFFF' ; X: BEGIN INCR I FROM 0 TO (.WKS_COUNT-1) DO IF .LP EQL .WKS_LIST[.I,WKS$Port] THEN BEGIN WIX = .I; LEAVE X; END; RETURN; ! Port not found - no SYN pending END; ! Point at the head of the SYN wait list for this port QB = .WKS_LIST[.WIX,WKS$SYN_Qhead]; ! Search the list looking for a match on Foreign host & port. ! Handle wild-card foreign host & port cases. Y: BEGIN WHILE (.QB NEQA WKS_LIST[.WIX,WKS$SYN_Qhead]) DO BEGIN IF (.TCB[Foreign_Host] EQL Wild) OR (.TCB[Foreign_Host] EQL .QB[NR$Src_Adrs]) THEN IF (.TCB[Foreign_Port] EQL Wild) OR (.TCB[Foreign_Port] EQLU .QB[NR$Src_Port]) THEN LEAVE Y; QB = .QB[NR$NEXT]; END; RETURN; END; ! Here on match. Remove the SYN segment from the list & process it. XLOG$FAO(LOG$TCP,'!%T SYN-wait-list match,TCB=!XL,QB=!XL,Seg=!XL!/', 0,.TCB,.QB,.QB[NR$Seg]); REMQUE(.QB,QB); ! Remove entry from syn-wait-list. SYN_WAIT_COUNT = .SYN_WAIT_COUNT + 1; WKS_LIST[.WIX,WKS$SYN_QCOUNT] = .WKS_LIST[.WIX,WKS$SYN_QCOUNT] + 1; Seg = .QB[NR$Seg]; ! point at segment. IF Decode_Segment(.TCB,.Seg,.QB) THEN BEGIN ! Delete segment (match = [-1,0,1]). MM$Seg_Free(.QB[NR$Buf_Size],.QB[NR$Buf]); MM$QBlk_Free(.QB); END; END; %SBTTL 'Fork a Server Process' %( Function: Create a Detached processes to handle the Well-Known local port function. Process runs detached & does a wild-card Foreign-Host & Foreign-Port network connection open. Waiting for the server is the SYN which started this mess in the first place. Method in creating a process is to give it a unique name so we don't get a SS$_DUPLNAM error return from $CREPRC. What happens is that we will try to crate the process in a Loop "n" times. The loop index value plus a "." is appended to the end of the server process name in an attempt to make it unique. The reason this works is that most servers will change their UIC to the requesting user's thus allowing additional servers to be forked under TCP's UCI. Inputs: IDX = Index into well-known server table. Entry contains information about how to create the server process. Outputs: True = process forked succsssfully. False = Error in process creation. Side Effects: None. )% ROUTINE Fork_Server(IDX, IP_Address, Remote_Port) = BEGIN LITERAL PNAMLEN = 20; LOCAL RC, RP, DESC$STR_ALLOC(ProcName,PNAMLEN), NewPID; EXTERNAL ROUTINE PokeAddr : ADDRESSING_MODE(GENERAL); RP = (.Remote_Port AND %X'FFFF') ; ! Try a bunch of times to find one with a non-duplicate name. INCR J FROM GLOBAL_MINSRV TO .WKS_LIST[.Idx,WKS$MaxSrv] DO BEGIN ProcName[DSC$W_LENGTH] = PNAMLEN; $FAO(%ASCID'!AS.!SL',ProcName[DSC$W_LENGTH],ProcName, WKS_LIST[.IDX,WKS$Process],.J); RC = $CREPRC(PIDADR=NewPID, IMAGE=WKS_LIST[.Idx,WKS$Image], INPUT=WKS_LIST[.Idx,WKS$Input], OUTPUT=WKS_LIST[.Idx,WKS$Output], ERROR=WKS_LIST[.Idx,WKS$Error], BASPRI=.WKS_LIST[.Idx,WKS$Prior], PRCNAM=ProcName, PRVADR=WKS_LIST[.Idx,WKS$Priv], UIC=.MYUIC, STSFLG=.WKS_LIST[.Idx,WKS$Stat], QUOTA = .WKS_LIST[.Idx,WKS$Quotas] ); ! See if it worked. SELECTONE .RC OF SET [SS$_Normal]: ! Success - done BEGIN XLOG$FAO(LOG$TCP,'!%T Forked Server: !AS!/',0,ProcName); ACT$FAO('!%D Forked Server: !AS(PID:!XW) !/',0, ProcName, .NewPID<0,16,0>, .IP_Address<0,8>,.IP_Address<8,8>, .IP_Address<16,8>,.IP_Address<24,8> ); ts$servers_forked = .ts$servers_forked + 1; ! count the servers. PokeAddr(.NewPID, .IP_Address, .RP); RETURN TRUE; END; [SS$_DuplNam]: ! Duplicate name - try next name 0; [OTHERWISE]: ! Hard failure BEGIN XLOG$FAO(LOG$TCPERR,'%T Server CREPRC Failed for !AS, RC = !XL!/', 0,ProcName,.RC); RETURN FALSE; END; TES; END; ! Failed after max number of tries. Log error and return failure. XLOG$FAO(LOG$TCPERR,'!%T Failed to fork server !AS!/',0,ProcName); RETURN FALSE; END; %SBTTL 'Check-Well-Known-Port: Is this a server port?' %( Function: Check if specified port is in well known range. If so then fork the associated server process. Inputs: Newly received SYN segment addresses: Dhost = Destination Host address. DPort = Destination port #. Shost = Senders Host address Sport = Senders port #. Outputs: TRUE - Segment matches WKS entry, server started, segment queued FALSE - Segment not for a WKS that we support, or queue full, or failed to fork process (need to send RST) ERROR - Segment matches but is duplicate Side Effects: Segment queue block may be INSQUE'd onto the SYN wait list for the well-known-port, server process may be started. )% ROUTINE Check_WKS(DHost,DPort,SHost,SPort,QBNEW,Segnew) = BEGIN MAP QBNEW : REF Queue_BLK_Structure(QB_NR_Fields), SegNew : REF Segment_Structure; LOCAL QB: REF Queue_Blk_Structure(QB_NR_Fields), Seg: REF Segment_Structure, DP, SP, WIX; LABEL X; DP = .DPort AND %X'FFFF' ; SP = .SPort AND %X'FFFF' ; ! See if we know about the port X: BEGIN INCR I FROM 0 TO (.WKS_COUNT-1) DO IF .DP EQL .WKS_LIST[.I,WKS$PORT] THEN BEGIN WIX = .I; LEAVE X; END; RETURN FALSE; END; ! See if main queue count exceeded IF .SYN_WAIT_COUNT LEQ 0 THEN BEGIN XLOG$FAO(LOG$TCP,'!%T SYN wait list full for SYN on WKS !SL!/', 0,.Dport); RETURN FALSE; END; ! See if queue count for this WKS exceeded IF .WKS_LIST[.WIX,WKS$SYN_QCOUNT] LEQ 0 THEN BEGIN XLOG$FAO(LOG$TCP,'!%T SYN wait list for port !SL full!/',0,.Dport); RETURN FALSE; END; ! Next, check for duplicate SYN segments on the SYN wait list - drop them. XLOG$FAO(LOG$TCP,'!%T SYN for WKS !SL received!/',0,.Dport); QB = .WKS_LIST[.WIX,WKS$SYN_QHEAD]; WHILE (.QB NEQA WKS_LIST[.WIX,WKS$SYN_QHEAD]) DO BEGIN ! Check IP addresses. IF (.DHost EQLU .QB[NR$Dest_Adrs]) AND (.Shost EQLU .QB[NR$Src_Adrs]) THEN BEGIN Seg = .QB[NR$Seg]; ! point at TCP segment ! Check TCP ports. If duplicate, return "error" so it will be deallocated. IF (.DP EQLU .Seg[SH$Dest_Port]) AND (.SP EQLU .Seg[SH$Source_Port]) THEN BEGIN XLOG$FAO(LOG$TCP,'!%T Dup SYN on Syn-wait list dropped!/'); RETURN ERROR; END; END; QB = .QB[NR$Next]; ! Look at next element. END; ! Fire up a server to handle it and queue up the segment on success. IF Fork_Server(.WIX, .Shost, .SP) THEN BEGIN ! Fill in Queue block fields for quick checking during processing of SYN wait ! list. This prevents having to set up a segment pointer, after all we have ! extra space in the queue block so why not use it. QBNEW[NR$Src_Port] = .SegNew[SH$Source_Port]; QBNEW[NR$Dest_Port] = .SegNew[SH$Dest_Port]; QBNEW[NR$TimeOut] = Time_Stamp() + Max_Seg_Lifetime; ! Save this for later processing. INSQUE(.QBNEW,.WKS_LIST[.WIX,WKS$SYN_QTAIL]); WKS_LIST[.WIX,WKS$SYN_QCOUNT] = .WKS_LIST[.WIX,WKS$SYN_QCOUNT]-1; SYN_WAIT_COUNT = .SYN_WAIT_COUNT - 1; RETURN TRUE; END ELSE RETURN FALSE; END; %SBTTL 'Timeout_Syn_Wait_List - Check SYN wait list for expired entries' GLOBAL ROUTINE SEG$Timeout_Syn_Wait_List(Now: UNSIGNED) : NOVALUE = BEGIN LOCAL WIX, QB : REF Queue_BLK_Structure(QB_NR_Fields), Tmp; INCR WIX FROM 0 TO (.WKS_COUNT-1) DO BEGIN QB = .WKS_LIST[.WIX,WKS$SYN_Qhead]; WHILE .QB NEQA WKS_LIST[.WIX,WKS$SYN_Qhead] DO BEGIN IF .QB[NR$TimeOut] LSSU .now THEN BEGIN ! Timed-out REMQUE(.QB,QB); ! Remove queue entry. WKS_LIST[.WIX,WKS$SYN_Qcount]=.WKS_LIST[.WIX,WKS$SYN_Qcount]+1; SYN_WAIT_COUNT = .SYN_WAIT_COUNT + 1; MM$Seg_Free(.qb[NR$Buf_Size],.qb[NR$Buf]); ! Release the seg. TMP = .QB; ! tmp Qblk pointer. QB = .QB[NR$Next]; ! next entry. MM$QBlk_Free(.TMP);! & the queue block. END ELSE QB = .QB[NR$Next]; ! point at next entry. END; END; END; %SBTTL 'ADD_WKS - Add an entry to the WKS table' %( Called by the CONFIG module when a WKS command is seen, this routine adds the new WKS entry to the table. )% GLOBAL ROUTINE SEG$WKS_CONFIG(PORT,PRNAME_A,IMNAME_A,STAT,PRIV, PRIOR,QLIMIT,MAXSRV,Quotas, Input_A, Output_A, Error_A) : NOVALUE = BEGIN MAP QUOTAS : REF $BBLOCK; BIND PRNAME = .PRNAME_A : $BBLOCK, IMNAME = .IMNAME_A : $BBLOCK, INPUT = .INPUT_A : $BBLOCK, OUTPUT = .OUTPUT_A : $BBLOCK, Error = .ERROR_A : $BBLOCK; EXTERNAL ROUTINE STR$COPY_DX : BLISS ADDRESSING_MODE (GENERAL); LOCAL WIX, QUOPTR, Status; ! Make sure there is room for this entry IF (.WKS_COUNT GEQ WKS_TABLE_SIZE) THEN BEGIN OPR$FAO('Too many WKS entries in INET$CONFIG - entry for !AS ignored', PRNAME); RETURN; END; WIX = .WKS_COUNT; WKS_COUNT = .WKS_COUNT + 1; ! Fill in the WKS entry fields WKS_LIST[.WIX,WKS$Port] = .PORT; ! Process Name $BBLOCK [WKS_List [.WIX, WKS$Process], DSC$W_LENGTH] = 0; $BBLOCK [WKS_List [.WIX, WKS$Process], DSC$B_DTYPE] = DSC$K_DTYPE_T; $BBLOCK [WKS_List [.WIX, WKS$Process], DSC$B_CLASS] = DSC$K_CLASS_D; $BBLOCK [WKS_List [.WIX, WKS$Process], DSC$A_POINTER] = 0; Status = STR$COPY_DX (WKS_LIST[.WIX,WKS$Process], PRNAME); IF NOT .Status THEN Signal (.Status); ! Process image file $BBLOCK [WKS_List [.WIX, WKS$Image], DSC$W_LENGTH] = 0; $BBLOCK [WKS_List [.WIX, WKS$Image], DSC$B_DTYPE] = DSC$K_DTYPE_T; $BBLOCK [WKS_List [.WIX, WKS$Image], DSC$B_CLASS] = DSC$K_CLASS_D; $BBLOCK [WKS_List [.WIX, WKS$Image], DSC$A_POINTER] = 0; Status = STR$COPY_DX (WKS_List [.WIX, WKS$Image], IMNAME); IF NOT .Status THEN Signal (.Status); ! Process input stream $BBLOCK [WKS_List [.WIX, WKS$Input], DSC$W_LENGTH] = 0; $BBLOCK [WKS_List [.WIX, WKS$Input], DSC$B_DTYPE] = DSC$K_DTYPE_T; $BBLOCK [WKS_List [.WIX, WKS$Input], DSC$B_CLASS] = DSC$K_CLASS_D; $BBLOCK [WKS_List [.WIX, WKS$Input], DSC$A_POINTER] = 0; Status = STR$COPY_DX (WKS_LIST[.WIX,WKS$Input], Input); IF NOT .Status THEN Signal (.Status); ! Process output stream $BBLOCK [WKS_List [.WIX, WKS$Output], DSC$W_LENGTH] = 0; $BBLOCK [WKS_List [.WIX, WKS$Output], DSC$B_DTYPE] = DSC$K_DTYPE_T; $BBLOCK [WKS_List [.WIX, WKS$Output], DSC$B_CLASS] = DSC$K_CLASS_D; $BBLOCK [WKS_List [.WIX, WKS$Output], DSC$A_POINTER] = 0; Status = STR$COPY_DX (WKS_LIST[.WIX,WKS$Output], Output); IF NOT .Status THEN Signal (.Status); ! Process error stream $BBLOCK [WKS_List [.WIX, WKS$Error], DSC$W_LENGTH] = 0; $BBLOCK [WKS_List [.WIX, WKS$Error], DSC$B_DTYPE] = DSC$K_DTYPE_T; $BBLOCK [WKS_List [.WIX, WKS$Error], DSC$B_CLASS] = DSC$K_CLASS_D; $BBLOCK [WKS_List [.WIX, WKS$Error], DSC$A_POINTER] = 0; Status = STR$COPY_DX (WKS_LIST[.WIX,WKS$Error], Error); IF NOT .Status THEN Signal (.Status); ! Allocate the block for the process quota list WKS_LIST[.WIX,WKS$QUOTAS] = (IF .Quotas[DSC$W_LENGTH] GTR 0 THEN BEGIN Status = LIB$GET_VM(%REF(.Quotas[DSC$W_LENGTH]),QUOPTR); IF NOT .Status THEN FATAL$FAO('Seg$WKS_Config - failed to allocate quolst, RC = !XL',.Status); CH$MOVE(.QUOTAS [DSC$W_LENGTH],.QUOTAS [DSC$A_POINTER],.QUOPTR); .QUOPTR END ELSE 0); WKS_LIST[.WIX,WKS$Stat] = .STAT; CH$MOVE(8,.PRIV,WKS_LIST[.WIX,WKS$PRIV]); WKS_LIST[.WIX,WKS$Prior] = .PRIOR; WKS_LIST[.WIX,WKS$MaxSrv] = (IF .MAXSRV EQL 0 THEN GLOBAL_MAXSRV ELSE .MAXSRV); WKS_LIST[.WIX,WKS$SYN_Qcount] = .QLIMIT; WKS_LIST[.WIX,WKS$SYN_Qhead] = WKS_LIST[.WIX,WKS$SYN_Qhead]; WKS_LIST[.WIX,WKS$SYN_Qtail] = WKS_LIST[.WIX,WKS$SYN_Qhead]; END; %SBTTL 'Log-Segment: dump debug data about segment to log file.' %( Inputs: Seg = segment pointer Size = segment size in bytes TR_Flag: True = called on segment reception. False = called from seg transmission. Outputs: None. Side effects: Output to log file. )% GLOBAL ROUTINE SEG$Log_Segment(Seg: REF Segment_Structure,Size, TR_Flag,BS_Flag): NOVALUE= BEGIN LOCAL dataoff, segcopy : Segment_Structure, seghdr : REF Segment_Structure, sptr; seghdr = .seg; IF .BS_Flag THEN ! Need to byteswap the header... BEGIN CH$MOVE(TCP_Header_Size,CH$PTR(.seg),CH$PTR(segcopy)); seghdr = segcopy; SwapBytes(TCP_Header_size/2,.seghdr); ! Swap header bytes back Seghdr[SH$SEQ] = ROT(.Seghdr[SH$SEQ],16); Seghdr[SH$ACK] = ROT(.Seghdr[SH$ACK],16); END; ! Point at segment data (past header and possible options) dataoff = .seghdr[sh$data_offset]*4; IF .TR_Flag THEN sptr = %ASCID'Received' ELSE sptr = %ASCID'Sent'; ! Write title line... LOG$FAO('!%T !AS Network Segment, SEG=!XL, OPT=!XL, DATA=!XL:!/', 0,.sptr,.seg,.seg+TCP_Header_size,.seg+.dataoff); ! Log most of the data - N.B. We don't write the final CRLF yet... LOG$FAO(%STRING('!_SrcPrt:!_!XL (!UL)!_DstPrt:!_!XL (!UL)!/', '!_SEQnum:!_!XL (!UL)!_ACKnum:!_!XL (!UL)!/', '!_Window:!_!UW!_CKsum:!_!UW!_DatPtr:!_!UW!_UrgPtr:!_!UW!/', '!_Control Flags:!_!XL'), .seghdr[SH$Source_port],.seghdr[SH$Source_port], .seghdr[SH$Dest_port],.seghdr[SH$Dest_port], .seghdr[SH$SEQ],.seghdr[SH$SEQ],.seghdr[SH$ACK],.seghdr[SH$ACK], .seghdr[SH$Window],.seghdr[SH$Checksum],.seghdr[SH$data_offset], .seghdr[SH$Urgent],.seghdr[SH$C_All_Flags]); IF .Seghdr[SH$C_All_Flags] NEQ 0 THEN BEGIN LOG$OUT(' = '); SELECT TRUE of SET [.Seghdr[SH$C_URG]]: LOG$OUT('URG '); [.Seghdr[SH$C_ACK]]: LOG$OUT('ACK '); [.Seghdr[SH$C_EOL]]: LOG$OUT('EOL '); [.Seghdr[SH$C_RST]]: LOG$OUT('RST '); [.Seghdr[SH$C_SYN]]: LOG$OUT('SYN '); [.Seghdr[SH$C_FIN]]: LOG$OUT('FIN '); TES; END; LOG$OUT(%CHAR(13,10)); IF .dataoff GTR TCP_header_size THEN BEGIN LITERAL maxopt = 20; LOCAL DESC$STR_ALLOC(optstr,maxopt*3), optcnt, outcnt; optcnt = .dataoff-TCP_header_size; IF .optcnt GTR maxopt THEN outcnt = maxopt ELSE outcnt = .optcnt; ASCII_Hex_Bytes(optstr,.outcnt,.seg+TCP_header_size, optstr[DSC$W_LENGTH]); LOG$FAO('!_Options:!_!SL = !AS!/',.optcnt,optstr); END; IF .Size GTR .dataoff THEN BEGIN LITERAL maxhex = 20, maxasc = 50; LOCAL datcnt, asccnt, hexcnt, DESC$STR_ALLOC(dathex,maxhex*3); datcnt = .size-.dataoff; IF .datcnt GTR maxasc THEN asccnt = maxasc ELSE asccnt = .datcnt; IF .datcnt GTR maxhex THEN hexcnt = maxhex ELSE hexcnt = .datcnt; ASCII_Hex_Bytes(dathex,.hexcnt,.seg+.dataoff,dathex[DSC$W_LENGTH]); LOG$FAO('!_Data Count: !SL!/!_HEX:!_!AS!/!_ASCII:!_!AF!/', .datcnt,dathex,.asccnt,.seg+.dataoff); END; END; %SBTTL 'Append a segment to the end of the "future" queue' %( )% ROUTINE Append_Future_Q(TCB, QB, Seg, SEQsize) = BEGIN MAP TCB : REF TCB_Structure, QB : REF Queue_Blk_Structure(QB_NR_Fields), Seg : REF Segment_Structure; LOCAL FQB : REF Queue_Blk_Structure(QB_NR_Fields); ts$future_rcvd = .ts$future_rcvd+1; IF .TCB[RF_Qcount] GEQ .FQ_MAX THEN BEGIN XLOG$FAO(LOG$TCPERR,'!%T FQ full for seg !XL, QB !XL!/',0,.Seg,.QB); ts$future_dropped = .ts$future_dropped+1; RETURN TRUE; ! Caller should delete END; QB[NR$SEQ_Start] = .Seg[SH$SEQ]; QB[NR$SEQ_End] = .Seg[SH$SEQ] + .SEQsize; QB[NR$SEQ_Count] = .SEQsize; FQB = .TCB[RF_Qhead]; ! Find where to put this segment on the future queue WHILE TRUE DO IF .FQB EQL TCB[RF_Qhead] THEN EXITLOOP (FQB = .TCB[RF_Qtail]) ELSE IF .QB[NR$SEQ_Start] LSSU .FQB[NR$SEQ_Start] THEN EXITLOOP (FQB = .FQB[NR$Last]) ELSE IF .QB[NR$SEQ_Start] EQLU .FQB[NR$SEQ_Start] THEN BEGIN XLOG$FAO(LOG$TCP, '!%T Drop duplicate FQ seg !XL, QB !XL!/', 0,.Seg,.QB); ts$future_dups = .ts$future_dups+1; RETURN TRUE;! Tell caller to deallocate END ELSE FQB = .FQB[NR$Next]; ! Insert this segment in proper sequence XLOG$FAO(LOG$TCP,'!%T Seg !XL, QB !XL inserted on FQ at FQB !XL!/', 0,.Seg,.QB,.FQB); TCB[RF_Qcount] = .TCB[RF_Qcount]+1; INSQUE(.QB,.FQB); !~~~XINSQUE(.QB,.FQB,Append_Future_Q,Q$TCBFQ,TCB[RF_Qhead]); RETURN FALSE; ! Don't deallocate - it's on our queue END; %SBTTL 'Check and attempt to process segments on "future" queue' %( )% ROUTINE Check_Future_Q(TCB : REF TCB_Structure) : NOVALUE = BEGIN LOCAL NQB : REF Queue_Blk_Structure(QB_NR_Fields); ! Handle segments on the future queue that are no longer in the future. NQB = .TCB[RF_Qhead]; WHILE .NQB NEQA TCB[RF_Qhead] DO BEGIN LOCAL SEQoffset, SEQsize, delete, QB : REF Queue_Blk_Structure(QB_NR_Fields); QB = .NQB; NQB = .QB[NR$Next]; delete = false; SEQsize = .QB[NR$SEQ_Count]; SEQoffset = .TCB[RCV_NXT]-.QB[NR$SEQ_Start]; SELECTONE TRUE OF SET ! If first in-window octet is beyond end of segment, then this segment ! has become obsolete - drop it. [.SEQoffset GEQU .SEQsize]: BEGIN delete = true; !~~~ XREMQUE(.QB,QB,Check_Future_Q,Q$TCBFQ,TCB[RF_Qhead]); REMQUE(.QB,QB); TCB[RF_Qcount] = .TCB[RF_Qcount]-1; XLOG$FAO(LOG$TCP,'!%T Flushing FQ seg !XL, QB !XL, SEQ=!XL,!XL!/', 0,.QB[NR$Seg],.QB,.QB[NR$SEQ_Start],.QB[NR$SEQ_End]); ts$future_dropped = .ts$future_dropped+1; END; ! If first in-window octet is within the segment, then the segment is ! now usable - process it in Decode_Segment routine. [.SEQoffset GEQ 0]: BEGIN !~~~ XREMQUE(.QB,QB,Check_Future_Q,Q$TCBFQ,TCB[RF_Qhead]); REMQUE(.QB,QB); TCB[RF_Qcount] = .TCB[RF_Qcount]-1; XLOG$FAO(LOG$TCP,'!%T Using FQ seg !XL, QB !XL, SEQ=!XL,!XL!/', 0,.QB[NR$Seg],.QB,.QB[NR$SEQ_Start],.QB[NR$SEQ_End]); delete = Decode_Segment(.TCB,.QB[NR$Seg],.QB); ts$future_used = .ts$future_used+1; END; ! Other cases should mean the segment is still in the future, so we ! just leave the segment on the queue. TES; IF .delete THEN BEGIN MM$Seg_Free(.QB[NR$Buf_Size],.QB[NR$Buf]); MM$QBLK_Free(.QB); END; END; END; %SBTTL 'READ_TCP_Options: process TCP segment options in SYN segment' ROUTINE READ_TCP_OPTIONS(TCB,SEG) : NOVALUE = BEGIN MAP TCB: REF TCB_Structure, SEG: REF Segment_Structure; LOCAL OPTR : REF TCP$OPT_BLOCK, OPTLEN, DATAPTR; ! Start at beginning of options area (start of segment data) OPTR = SEG[SH$DATA]; DATAPTR = .SEG+(.SEG[SH$DATA_OFFSET]*4); ! Scan the the entire options area until we hit the start of TCP data WHILE .OPTR LSS .DATAPTR DO BEGIN ! Deal with options we can handle ! N.B. We may modify the options area by byteswapping. SELECTONE .OPTR[TCP$OPT_KIND] OF SET [TCP$OPT_KIND_EOL]: ! End of options list EXITLOOP; [TCP$OPT_KIND_NOP]: ! No-op option OPTR = .OPTR + 1; ! Advance pointer by one byte [TCP$OPT_KIND_MSS]: ! Maximum segment size option BEGIN LOCAL SZ; OPTLEN = .OPTR[TCP$OPT_LENGTH]; IF .OPTLEN EQL TCP$OPT_LENGTH_MSS THEN BEGIN ! Length is correct SZ = .OPTR[TCP$OPT_DWORD]; SWAPWORD(SZ); ! Change to VAX byte order TCB[MAX_SEG_DATA_SIZE] = MIN(.SZ,.MAX_RECV_DATASIZE); TCB[SND_ACK_Threshold] = .TCB[Max_SEG_Data_Size] ; XLOG$FAO(LOG$TCP, '!%T TCP MSS option = !SL set to !SL, TCB=!XL!/', 0,.SZ,.TCB[MAX_SEG_DATA_SIZE],.TCB); END ELSE XLOG$FAO(LOG$TCP+LOG$TCPERR, '!%T ?TCP MSS option wrong size=!SL, TCB=!XL!/', 0,.OPTR[TCP$OPT_LENGTH],.TCB); OPTR = .OPTR + .OPTR[TCP$OPT_LENGTH]; END; [OTHERWISE]: ! Some unknown option type BEGIN XLOG$FAO(LOG$TCP+LOG$TCPERR, '!%T ?Bad TCP option type !SL, size !SL for TCB !XL!/', 0,.OPTR[TCP$OPT_KIND],.OPTR[TCP$OPT_LENGTH],.TCB); EXITLOOP; ! Can't procede, since length may not be valid END; TES; END; END; %SBTTL 'Decode-Segment: Figure out what to do with a network segment.' %( Function: We have received this segment from the network, figure out what to do with it. Examine the Connection state & the TCP header. Process control information & optional data. Inputs: TCB = TCB of connection which gets this segment. Seg = Segment pointer. QB = Network receive queue block. Implicit Inputs: Segment has passed checksum test & is bound for this connection (TCB). Outputs: True(1): Have caller delete Segment & Queue Block data structures. False(0): Segment & queue-Blk have been put on another queue, don't delete. Error(-1): TCB has been (deleted or inactivated), caller deletes Segment & queue-blk. Side Effects: Many......This is where connection states change due to network segment arrival. Connection timeout reset if valid segment for this connection. )% ROUTINE Decode_Segment(TCB,Seg,QB) = BEGIN EXTERNAL ROUTINE TCB_Promote; MAP TCB: REF TCB_Structure, Seg: REF Segment_Structure, QB: REF Queue_Blk_Structure(QB_NR_Fields); LOCAL RC, AckTst_OK, SeqTst_OK, Ack, DataSize, SEQsize, SEQcount, SEQstart, SEQoffset, QBR: REF Queue_Blk_Structure(QB_UR_Fields), EOL, QBN: REF Queue_Blk_Structure(QB_NR_Fields), Ucount, Uoffset, RetCode, Seg_Trimmed : INITIAL (0); %( IF $$LOGF(LOG$TCBDUMP) THEN BEGIN LOG$FAO('!/!%T Decode Seg - TCB dump of !XL!/',0,.TCB); Dump_TCB(.TCB)); END; )% RetCode = True; ! Let caller handle memory mgmt. ! Compute amount of data available in this segment. ! Also set the value in the associated Queue block. DataSize = QB[NR$Data_Size] = .QB[NR$Size] - .Seg[SH$Data_Offset]*4 ; ! Point at first user request QBR = .TCB[UR_Qhead]; !******************************************************** ! Process unsynchronized Connection States. ! !******************************************************** SELECTONE .TCB[State] OF SET !******************************************************** ! LISTEN State ! !******************************************************** [CS$LISTEN]: BEGIN ! XLOG$FAO(LOG$TCP,'!%T Received Seg in LISTEN State.!/', 0); SELECTONE TRUE OF SET [.Seg[SH$C_ACK]]: ! "ACK" ? BEGIN IF NOT .Seg[SH$C_RST] THEN Send_Reset(.TCB,.Seg[SH$Ack]); RETURN(.RetCode); END; [.Seg[SH$C_RST]]: XLOG$FAO(LOG$TCP, '!%T Received RST in listen state, TCB=!XL!/',0,.TCB); [.Seg[SH$C_SYN]]: BEGIN ! Set TCB's sequence space counters. ! snd_nxt (send next) & snd_Una(send oldest unackowledged seq #) are ! both sent during TCB initialization (Init-TCB). ! XLOG$FAO(LOG$TCBSTATE, ! '!%T Decode-Seg: Received SYN for Passive connection.!/',0); TCB[RCV_NXT] = .Seg[SH$Seq] + 1; ! Next expected rcv sequence #. TCB[IRS] = TCB[Snd_WL] = .Seg[SH$Seq];! set Initial rcv seq # & window update. TCB[Snd_Wnd] = .Seg[SH$Window]; ! Send window size in bytes. TCB[Snd_Max_Wnd] = MAXU(.TCB[Snd_Max_Wnd], .TCB[Snd_Wnd]) ; TCP$Set_TCB_State(.TCB,CS$SYN_RECV); ! Connection state change. ! Process segment options, if any. IF .seg[sh$data_offset] GTR tcp_data_offset THEN Read_TCP_Options(.TCB,.Seg); ! Fill in any unspecified Foreign Host or Port ! Also, if wild foreign host, start an address to name lookup for it. IF (.TCB[Foreign_Host] EQL Wild) OR (.TCB[Foreign_Port] eql Wild) THEN BEGIN IF .TCB[Foreign_Host] EQL WILD THEN BEGIN TCB[Foreign_Host] = .QB[NR$Src_Adrs]; TCB[NMLOOK_Flag] = TRUE; NML$GETNAME(.QB[NR$Src_Adrs],TCP$Adlook_Done,.TCB); END; IF .TCB[Foreign_Port] EQL Wild THEN TCB[Foreign_Port] = (.Seg[SH$Source_Port] AND %X'FFFF') ; ! Move TCB to head of local port list as now it's fully specified. TCB_Promote ( .TCB ); END; ! Fill in unspecified local host IF (.TCB[Local_Host] eql Wild) THEN TCB[Local_Host] = .QB[NR$Dest_Adrs]; ! Now that the connection has been fully specified we can "ACK" the "SYN". ! If SYN-ACK send fails (no route - config error), flush the connection. IF NOT Send_Syn$Ack(.TCB) THEN BEGIN XLOG$FAO(LOG$TCPERR, '!%T Decode-seg(LISTEN): Send_SYN$ACK failed, TCB=!XL!/', 0,.TCB); Send_Reset(.TCB,.Seg[SH$Ack]); TCP$KILL_PENDING_REQUESTS(.TCB,NET$_NRT); TCB$Delete(.TCB); Return(Error); END; ! XLOG$FAO(LOG$TCBSTATE,'!%T Decode-seg: SYN-ACK sent.!/', 0); ! If data present then queue it for later. IF .DataSize GTR 0 THEN BEGIN XLOG$FAO(LOG$TCPERR,'!%T Decode Seg(Listen): SYN data dropped!/', 0); !~~~ RetCode = False; END; ! make sure the connection stays alive. TCB[Connection_TimeOut] = Active_Open_Timeout + Time_Stamp(); END; [OTHERWISE]: XLOG$FAO(LOG$TCPERR, '!%T Decode Seg(Listen): NON SYN control!/',0); TES; RETURN (.RetCode); END; ! "LISTEN" State. !******************************************************** ! "SYN-SENT" State. ! !******************************************************** [CS$SYN_SENT]: BEGIN ! XLOG$FAO(LOG$TCP, '!%T Received Seg in SYN-SENT State.!/', 0); ACK = -1; IF .Seg[SH$C_ACK] THEN BEGIN IF (.Seg[SH$ACK] LEQU .TCB[ISS]) OR (.Seg[SH$ACK] GTRU .TCB[Snd_Nxt]) THEN BEGIN Send_Reset(.TCB,.Seg[SH$Ack]); ! Unacceptable "ACK". ! XLOG$FAO(LOG$TCPERR, '!%T (Syn-sent)Unacceptable ACK.!/', 0); RETURN(True); END ELSE ! acceptable "ACK" BEGIN IF (.TCB[Snd_UNA] LEQU .Seg[SH$Ack]) AND (.Seg[SH$Ack] LEQU .TCB[Snd_Nxt]) THEN BEGIN ! XLOG$FAO(LOG$TCP, '!%T (Syn-sent)Valid ACK.!/', 0); Ack = True; TCB[Snd_UNA] = .Seg[SH$ACK]; ACK_RT_Queue(.TCB,.Seg[SH$ACK]); END ELSE RETURN(True); ! Invalid ACK. END; END; ! at this juncture we have a valid ACK. ! Check the "RST" control bit in TCP Header. IF .Seg[SH$C_RST] THEN ! Connection refused - reset BEGIN TCP$KILL_PENDING_REQUESTS(.TCB,NET$_CRef); TCP$Inactivate_TCB(.TCB,NET$_CR); XLOG$FAO(LOG$TCBSTATE, '!%T Decode seg:(SYN-Sent) RESET TCB !XL!/', 0,.TCB); RETURN(Error); ! Caller deletes seg, Queue-blk. END ELSE ! Not a reset seg, check for "SYN" bit. BEGIN IF .Seg[SH$C_SYN] THEN BEGIN ! set TCB sequence space counters. TCB[RCV_NXT] = .Seg[SH$SEQ] + 1; TCB[Snd_Wnd] = .Seg[SH$Window]; TCB[Snd_Max_Wnd] = MAXU(.TCB[Snd_Max_Wnd], .TCB[Snd_Wnd]) ; TCB[IRS] = TCB[Snd_WL] = .Seg[SH$SEQ]; ! Was our "SYN" ack'ed? IF .TCB[Snd_UNA] GTRU .TCB[ISS] THEN BEGIN TCB[IS_Synched] = TRUE; TCP$Set_TCB_State(.TCB,CS$ESTABLISHED); ! Tell the user the Connection is OPEN as the IO status has yet to be posted. ! Valid only for connections not openned in immediate return mode. IF NOT .TCB[Open_NoWait] THEN TCP$Post_Active_Open_IO_Status(.TCB,SS$_Normal,0); XLOG$FAO(LOG$TCP, '!%T Our SYN ACKed, Connection established.!/',0); END ELSE TCP$Set_TCB_State(.TCB,CS$SYN_RECV); ! TCP$Send_Ack(.TCB); TCP$Enqueue_Ack(.TCB); ! If data present then queue for later IF .DataSize GTR 0 THEN BEGIN XLOG$FAO(LOG$TCPERR, '!%T Decode Seg: SYN-ACK data dropped, TCB=!XL!/', 0,.TCB); !~~~ RetCode = False; END; ! Process segment options, if any. IF .seg[sh$data_offset] GTR tcp_data_offset THEN Read_TCP_Options(.TCB,.Seg); END; END; ! Reset connection time out as something useful has happened. IF .Keep_Alive THEN TCB[Connection_Timeout] = .CONN_TIMEVAL + Time_Stamp() ELSE TCB[Connection_TimeOut] = 0; RETURN (.RetCode); END; ! End: "SYN-SENT" State. TES; !******************************************************* ! Process "Synchronized" Connection States ! !******************************************************* ! Advance connection timeout since the other end is sending something IF .Keep_Alive THEN TCB[Connection_TimeOut] = .CONN_TIMEVAL + Time_Stamp() ELSE TCB[Connection_TimeOut] = 0; ! Determine the sequence space occupied by this segment. ! Set field "NR$SEQ_END" in the Queue-blk associated with this segment. SeqTst_OK = False; ! assume the worst, seg is unacceptable. ! compute sequence space occupied. ! SEQsize is the count of seqence numbers in the packet ! SEQcount is the count of them that are in the window SEQsize = .DataSize; ! Start with packet data count SEQcount = 0; ! Assume no usable seq #s in packet yet IF .Seg[SH$C_SYN] THEN SEQSize = .SEQsize + 1; ! SYN occupies seqence space. IF .Seg[SH$C_FIN] THEN SEQsize = .SEQsize + 1; ! So does "FIN". ! Set last sequence # in this segment. IF .SEQsize LEQ 0 THEN qb[nr$seq_end] = .Seg[SH$SEQ] ELSE qb[nr$seq_end] = (.Seg[SH$SEQ] + .SEQsize) - 1; ! Assume no user data in the segment yet Ucount = 0; Uoffset = 0; ! Test segment acceptablity by checking that the segment sequence numbers ! fall inside the receive window (TCB[RCV_Nxt] + TCB[RCV_WND]). SELECTONE TRUE OF SET [.TCB[RCV_WND] EQLU 0]: ! Zero window cases BEGIN SELECTONE TRUE OF SET [.SEQsize EQL 0]: ! Empty packet case IF .Seg[SH$Seq] EQLU .TCB[Rcv_Nxt] THEN SeqTst_OK = True; [.SEQsize NEQ 0]: ! Nonempty packet case IF .Seg[SH$C_ACK] OR .Seg[SH$C_RST] OR .Seg[SH$C_URG] THEN BEGIN ! Data is unacceptable but take [ack,rst, urg] SeqTst_OK = True; SEQsize = .SEQsize - .DataSize; ! remove data from SEQ DataSize = 0; END; TES; END; ! Sequence number test doesn't match the TCP spec since we will take a segment ! that exceeds the window and use only the data the lies in the window. [.TCB[RCV_WND] NEQU 0]: ! Window is open case BEGIN SELECTONE TRUE OF SET [.SEQsize EQL 0]: ! Empty packet case IF (.Seg[SH$SEQ] EQLU .TCB[RCV_NXT]) THEN SeqTst_OK = True; [.SEQsize GTR 0]: ! Nonempty packet case (the useful one) BEGIN ! SEQoffset is the offset of the first in-window octet in the ! segment. SEQoffset = .TCB[RCV_NXT] - .Seg[SH$SEQ]; SELECTONE TRUE OF SET ! .SEQoffset >= 0 means it is at or before the left edge of the ! window. .SEQoffset <= .SEQsize means the first in-window octet ! is inside the segment and thus the segment is usable. [(.SEQoffset GEQ 0) AND (.SEQoffset LSS .SEQsize)]: BEGIN SEQstart = .TCB[RCV_NXT]; SEQcount = .SEQsize - .SEQoffset; ! Trim to fit in window IF .SEQcount GTRU .TCB[RCV_WND] THEN BEGIN Seg_Trimmed = 1; SEQcount = .TCB[RCV_WND]; QB[NR$SEQ_end] = .SEQstart+.SEQcount; END; SeqTst_OK = True; END; ! .SEQoffset < 0 means that the segment is beyond the left edge ! of the window. .TCB[RCV_WND] > (-.SEQoffset) means that the ! start of the segment is before the right edge and the segment ! may be held for future use. ! [(.SEQoffset LSS 0) AND (.TCB[RCV_WND] GTR (-.SEQoffset))]: [(.SEQoffset LSS 0) AND (.TCB[RCV_WND] LSSU (.SEQoffset))]: BEGIN RetCode = Append_Future_Q(.TCB,.QB,.Seg,.SEQSize); ! TCB[Pending_ACK] = TRUE; ! Force an ACK for this TCP$Enqueue_Ack(.TCB); RETURN .RetCode; END; TES; END; TES; END; TES; ! If segment was not acceptable, drop it. ! Test for duplicate segment, if True, re-ack it as the original "ACK" ! might have been lost. We get a free window update anyway. IF NOT .SeqTST_ok THEN BEGIN ts$oorw_segs = .ts$oorw_segs + 1; ! Test for duplicate IF (.Seg[SH$SEQ] LEQU .TCB[Rcv_Nxt]) AND (.Seg[SH$Seq] GEQU .TCB[IRS]) THEN BEGIN ! Probable duplicate. IF (.Seg[SH$ACK] GEQU .TCB[ISS]) AND (.Seg[SH$ACK] LEQU .TCB[Snd_Nxt]) THEN BEGIN ! Duplicate segment, RE-ACK. If time-wait state then reset the TW timer. ts$duplicate_segs = .ts$duplicate_segs + 1; ts$oorw_segs = .ts$oorw_segs - 1; ! don't count seg twice. IF .TCB[State] EQL CS$Time_Wait THEN TCB[Time_Wait_Timer] = Max_Seg_Lifetime + Time_Stamp(); ! Use window from duplicate segment as it is more current than our present one. ! Make sure we take into account transmitted but unacknowledged sequence ! numbers. Check that the update is new than what we have (snd_wl). IF .TCB[Snd_Wl] LEQU .Seg[SH$Seq] THEN BEGIN TCB[snd_wnd]= .seg[sh$window] - (.TCB[snd_nxt] - .seg[sh$ack]); TCB[Snd_Max_Wnd] = MAXU(.TCB[Snd_Max_Wnd], .TCB[Snd_Wnd]) ; TCB[snd_wl] = .seg[sh$seq]; END; XLOG$FAO(LOG$TCPERR, '!%T Decode Seg: dup seg - Seg-ack (!UL) <=Snd-nxt (!UL)!/', 0, .seg[sh$ack], .TCB[SND_NXT]); ! TCP$Send_Ack(.TCB); TCP$Enqueue_Ack(.TCB); END ELSE XLOG$FAO(LOG$TCPERR, '!%T Duplicate Segment: Seg-Seq (!UL) <= Recv-Nxt (!UL)!/', 0, .seg[sh$seq], .TCB[RCV_NXT]); END; IF $$LOGF(LOG$TCPERR) THEN LOG$FAO('!%T SEGIN: !XL (!UL) Failed seq tests, RCV_WND=!UL, RCV_NXT=!XL (!UL), SND_NXT=!XL (!UL)!/', 0, .seg[sh$seq],.seg[sh$seq],.TCB[rcv_wnd],.TCB[rcv_nxt], .TCB[rcv_nxt],.TCB[snd_nxt],.TCB[snd_nxt]); RETURN(True); ! Let caller delete the segment. END; ! Check if ACK actually acknowledges something valid IF (.TCB[Snd_Una] LSSU .Seg[SH$Ack]) AND (.Seg[SH$Ack] LEQU .TCB[Snd_Nxt]) THEN ACKTst_OK = True ELSE AckTST_OK = False; ! Segment is acceptable (in the receive window[rcv_wnd]). !******************************************************** ! Check the "ACK" bit. ! !******************************************************** IF .seg[SH$c_ack] THEN BEGIN SELECTONE .TCB[State] OF SET [CS$SYN_RECV]: BEGIN ! If unacceptable ACK then send a RESET reply & drop the segment. ! Otherwise: If NOT a reset segment then our SYN-ACK is acked & the ! connection becomes established. If the seg contained a RESET flag then ! fall thru the ACK processing & into RESET processing. IF NOT .AckTST_OK THEN BEGIN Send_Reset(.TCB,.Seg[SH$ACK]); RETURN(True); ! caller deletes segment & Qblk. END; IF NOT .Seg[SH$C_RST] THEN BEGIN TCB[Snd_Una] = .Seg[SH$Ack]; Ack_RT_Queue(.TCB,.Seg[SH$ACK]); TCB[IS_Synched] = TRUE; TCP$Set_TCB_State(.TCB,CS$Established); XLOG$FAO(LOG$TCP,'!%T SYN_RECV Connection established.!/',0); ! Make sure we update the send window here, since some systems have a habit of ! opening connections with zero windows. IF .TCB[SND_WL] LEQU .Seg[SH$Seq] THEN BEGIN TCB[snd_wnd]=.seg[sh$window]-(.TCB[snd_nxt]-.TCB[snd_una]); TCB[Snd_Max_Wnd] = MAXU(.TCB[Snd_Max_Wnd], .TCB[Snd_Wnd]) ; TCB[snd_wl]=.seg[sh$seq]; XLOG$FAO(LOG$TCP, '!%T Updated SND_WND=!UL, SND_NXT=!XL (!UL), SND_UNA=!XL (!UL)!/', 0,.TCB[snd_wnd],.TCB[snd_nxt],.TCB[snd_nxt], .TCB[snd_una],.TCB[snd_una]); END; ! Finish up pending I/O on this TCB. IF .TCB[IS_TVT] THEN BEGIN ! If it's a TVT, then we need to try to open it. On failure, the TCB has been ! flushed, so no further processing is possible for this segment. IF NOT TELNET_OPEN(.TCB) THEN BEGIN Reset_Unknown_Connection(.Seg,.QB); RETURN Error; END; END ELSE IF NOT .TCB[Open_NoWait] THEN TCP$Post_Active_Open_IO_Status(.TCB,SS$_Normal,0); END; END; [CS$Established,CS$Fin_Wait_1,CS$Fin_Wait_2,CS$Close_Wait]: BEGIN IF .AckTst_ok THEN BEGIN TCB[Snd_Una] = .Seg[SH$Ack]; ACK_RT_Queue(.TCB,.Seg[SH$Ack]); ! If state is CS$FIN_WAIT_1 & the RT-Queue is empty (ie, SND_NXT = Seg_ACK) ! then change state to FIN_WAIT_2. IF .TCB[State] EQL CS$Fin_Wait_1 THEN IF .TCB[Snd_Nxt] EQLU .Seg[SH$ACK] THEN TCP$Set_TCB_State(.TCB,CS$Fin_Wait_2); END; ! Update send window, taking into account transmitted by unacknowleged sequence ! numbers (SND_UNA). ! Snd-WL is updated to the current segment sequence #. ! Record seg seq # at last window update. IF .TCB[Snd_Wl] LEQU .Seg[SH$Seq] THEN BEGIN TCB[snd_wnd] = .seg[sh$window]-(.TCB[snd_nxt] - .TCB[snd_una]); TCB[Snd_Max_Wnd] = MAXU(.TCB[Snd_Max_Wnd], .TCB[Snd_Wnd]) ; TCB[snd_wl] = .seg[sh$seq]; XLOG$FAO(LOG$TCP, '!%T Updated SND_WND=!UL, SND_NXT=!XL (!UL), SND_UNA=!XL (!UL)!/', 0,.TCB[snd_wnd],.TCB[snd_nxt],.TCB[snd_nxt], .TCB[snd_una],.TCB[snd_una]); END; END; [CS$Time_Wait]: TCB[Time_Wait_Timer] = Time_Stamp() + Max_Seg_LifeTime; [CS$Closing]: BEGIN ! If the ACK acknowledges outstanding FIN, then enter Time_Wait state. IF .TCB[snd_nxt] EQLU .Seg[sh$ack] THEN BEGIN ! "FIN" has been ack'ed. XLOG$FAO(LOG$TCBSTATE, '!%T DS(Closing): FIN ACKed for conn !XL!/', .TCB); TCP$Set_TCB_State(.TCB,CS$Time_Wait); IF NOT .TCB[Close_NoWait] THEN BEGIN TCP$Post_User_Close_IO_Status(.TCB,SS$_Normal,0); TCB[Time_Wait_Timer] = Time_Stamp() + Max_Seg_LifeTime; END ELSE BEGIN TCP$KILL_PENDING_REQUESTS(.TCB,NET$_KILL); TCB$Delete(.TCB); XLOG$FAO(LOG$TCBSTATE, '!%T Connection purged and deleted !XL!/', .TCB); RETURN(Error); END; RETURN(True); ! Let caller delete segment END ELSE RETURN(True); ! Ignore segment, caller deletes. END; ! likewise, in LAST-ACK state, but delete connection [CS$Last_Ack]: BEGIN IF .TCB[snd_nxt] EQLU .Seg[sh$ack] THEN BEGIN ! "FIN" has been acked. XLOG$FAO(LOG$TCBSTATE, '!%T DS(Last-ACK): FIN ACKed, deleting conn !XL!/', 0,.TCB); TCP$KILL_PENDING_REQUESTS(.TCB,NET$_CC); TCB$Delete(.TCB); RETURN(Error); ! TCB gone - let caller delete the segment END ELSE RETURN(True); ! Ignore segment END; TES; END; ! End: Check "ACK" Bit. !******************************************************** ! Check "RST" Bit. ! !******************************************************** IF .Seg[sh$c_rst] THEN BEGIN SELECTONE TRUE OF SET ! RESET in SYN-RECV is special in that passive connections return to the ! LISTEN state, and are not actual RESET. [(.TCB[State] EQL CS$SYN_RECV) AND (NOT .TCB[Active_Open])]: BEGIN TCP$Set_TCB_State(.TCB,CS$Listen); SEG$Purge_RT_Queue(.TCB); RETURN(Error); ! caller deletes seg, TCB is dead. END; ! For all other cases, just reset the connection. [OTHERWISE]: BEGIN IF .TCB[State] EQL CS$Time_Wait THEN rc = NET$_CC ELSE rc = NET$_CR; TCP$KILL_PENDING_REQUESTS(.TCB,.rc); XLOG$FAO(LOG$TCBSTATE, '!%T DS(!SL): RESET Connection !XL!/', 0,.TCB[State],.TCB); TCP$Inactivate_TCB(.TCB,.rc); ! Let user know connection was reset. RETURN(Error); ! Caller deletes segment. END; TES; END; ! End: Check "RST" Bit. !******************************************************** ! Check "SYN" Bit. ! !******************************************************** IF .Seg[sh$c_syn] THEN BEGIN ! Is segment in the receive window? IF .Seg[SH$Seq] GTRU (.TCB[Rcv_Nxt] + .TCB[Rcv_Wnd]) THEN RETURN(True) ! not in window just drop ELSE BEGIN ! If sequence number is same as IRS, just ignore - it is an old duplicate. ! Any other in-window SYN is bad news, however - we RESET the connection. IF .Seg[SH$Seq] EQLU .TCB[IRS] THEN RETURN(True) ! Old, duplicate SYN - just drop it ELSE BEGIN ! In-window SYN - Bad news Send_Reset(.TCB,.Seg[SH$Ack]); TCP$KILL_PENDING_REQUESTS(.TCB,NET$_CR); XLOG$FAO(LOG$TCBSTATE, '!%T Dup SYN, deleting connection !XL!/',0,.TCB); TCB$Delete(.TCB); RETURN(Error); ! TCB is gone, caller deletes. END END; END; ! End: Check "SYN" Bit. !******************************************************** ! Check "URG" Bit. ! !******************************************************** IF .Seg[SH$C_URG] THEN BEGIN IF (.TCB[State] EQL CS$Established) OR (.TCB[State] EQL CS$Fin_Wait_1) OR (.TCB[state] EQL CS$Fin_Wait_2) THEN BEGIN TCB[Rcv_UP] = MAX(.TCB[Rcv_UP],.Seg[SH$Urgent]); QB[NR$Urg] = True; END; END; !******************************************************** ! Process Segment Text (Check for Data & EOL). ! !******************************************************** IF .SEQcount GTR 0 THEN BEGIN ! Have something in window ! EOL asserted? IF .Seg[SH$C_EOL] THEN ! Check EOL (Push) flag. QB[NR$EOL] = True; SELECTONE .TCB[State] OF SET [CS$Established,CS$Fin_Wait_1,CS$Fin_Wait_2]: BEGIN ! Log updated RCV.NXT IF $$LOGF(LOG$TCP) THEN BEGIN LOCAL new; new = .TCB[RCV_NXT] + .SEQcount; LOG$FAO('!%T Update RCV_NXT !XL (!UL) to !XL (!UL)!/', 0,.TCB[RCV_NXT],.TCB[RCV_NXT],.new,.new); END; ! Calculate actual number of octets for the user Ucount = .SEQcount-(.SEQsize-.datasize); Uoffset = .SEQoffset; ! Account for sequence space & queue any user data to the TCB for later ! delivery. TCB[Rcv_Nxt] = .TCB[RCV_NXT]+.SEQcount; !account for accepted bytes IF .Ucount GTR 0 THEN BEGIN ! acceptable data for user LOCAL dataptr; IF .TCB[User_timeout] NEQ 0 THEN TCB[User_timeout] = Time_stamp() + .TCB[User_timeval]; dataptr = .Seg+.Seg[SH$Data_Offset]*4; QB[NR$Ucount] = .Ucount; ! number of bytes for user QB[NR$Uptr] = .dataptr+.Uoffset; ! point to first data byte QB[NR$SEQ_start] = .SEQstart; ! first usable seq # QB[NR$SEQ_count] = .SEQcount; ! count of usable seq #s RetCode = Queue_Network_Data(.TCB,.QB); ! maintain FIFO ! IF .Retcode EQL TRUE THEN ! Don't ACK on error... ! TCB[pending_ack] = TRUE; ! TCP$Send_Ack(.TCB); ! If this is a TVT, tell TVT processing that there's new data. IF .TCB[IS_TVT] THEN BEGIN TELNET_INPUT(.TCB) ; END ELSE ! If we now have enough data to fill the first user receive request, then ! hurry things along by delivering to the user now. BEGIN IF (.Retcode EQL TRUE) THEN ! Don't ACK on error... ! TCB[pending_ack] = TRUE; BEGIN TCP$Enqueue_Ack(.TCB); END ; IF (.QBR NEQ TCB[UR_Qhead]) AND ! ((.TCB[RCV_Q_Count] GEQ .QBR[UR$Size]) OR ((.TCB[RCV_Q_Count] GTR 0) OR (QB[NR$EOL])) THEN BEGIN TCP$Deliver_User_Data(.TCB); END ; END ; END; END; TES; END; !******************************************************** ! Check "FIN" Bit. ! !******************************************************** IF .Seg[SH$C_FIN] AND NOT .Seg_Trimmed THEN BEGIN ! TCB[Pending_ACK] = True;! Flag we need to ACK. TCP$Enqueue_ACK(.TCB) ; ! Set PUSH pointer to end of this segment to force any current data to be ! pushed. Also, attempt to give the user any data that is left. IF .TCB[RCV_Q_Count] GTR 0 THEN BEGIN TCB[RCV_Push_Flag] = TRUE; TCB[RCV_PPtr] = .Seg[SH$SEQ] + .SEQsize; IF .TCB[IS_TVT] THEN TELNET_INPUT(.TCB) ! Attempt to give TVT input ELSE TCP$Deliver_User_Data(.TCB); ! Attempt to give user data. END; ! If all user data delivered, then flush the user receive queue now. IF .TCB[RCV_Q_Count] EQL 0 THEN BEGIN WHILE (REMQUE(.TCB[UR_Qhead],QBR)) NEQ Empty_Queue DO BEGIN User$Post_IO_Status(.QBR[UR$Uargs], SS$_NORMAL,0,NSB$PUSHBIT,0); MM$UArg_Free(.QBR[UR$Uargs]); ! Free user arg blk. MM$QBlk_Free(.QBR);! Free Queue Block. END; END; ! Change the Connection State. SELECTONE .TCB[State] OF SET [CS$Established]: BEGIN TCP$Set_TCB_State(.TCB,CS$Close_Wait); IF (.TCB[IS_Aborted] OR .TCB[IS_TVT]) THEN TCP$TCB_Close(TCB); END; [CS$Fin_Wait_1]: TCP$Set_TCB_State(.TCB,CS$Closing); [CS$Fin_Wait_2]: BEGIN ! If no data left or TCB is aborted, then finish closing it now. IF (.TCB[RCV_Q_Count] EQL 0) OR .TCB[IS_Aborted] THEN BEGIN TCP$Set_TCB_State(.TCB,CS$Time_Wait); IF NOT .TCB[Close_NoWait] THEN TCP$Post_User_Close_IO_Status(.TCB,SS$_Normal,0); TCB[Time_Wait_Timer] = Time_Stamp() + Max_Seg_LifeTime; END ELSE TCB[FIN_RCVD] = TRUE; END; TES; END; ! *Done with segment* ! Return value indicating whether or not someone is retaining the segment. ! FALSE means that the segment shouldn't be deallocated (it is on someone ! else queue). TRUE or ERROR means OK to deallocate segment. RETURN .RetCode; END; ROUTINE Check_Cum_Ack ( TCB : REF TCB_Structure , Idx , P1 , P2 ) = BEGIN IF .TCB[Pending_ACK] THEN BEGIN IF NOT .TCB[IS_TVT] THEN TCP$Deliver_User_Data(.TCB); ! Try to give the user data. XLOG$FAO(LOG$TCP,'!%T SEGIN sending cum ACK, TCB=!XL!/', 0,.TCB); ! TCP$Send_Ack(.TCB); ! send the cumulative ACK segment. TCP$Enqueue_Ack(.TCB); ! send the cumulative ACK segment. 1 END ELSE 0 END; %SBTTL 'Network Segment Arrival Main Processing Loop' %( Function: Process the network segment queue (SEGIN[SI_Qhead]). This queue is built by IP. When IP receives a datagram from the network it handles IP protocols & removes them. IP then places the TCP segment on the SEGIN queue. Each element in the queue is a standard queue block with fields defined by "QB_NR_Fields". These queue blocks point at the actual TCP segments & contain information about the segment. Inputs: None. Implicit Inputs: SEGIN queue header ("SEGIN") is valid. Outputs: None. Side Effects: TCP segments are processed according to TCP header fields. Actual segment maybe deleted or queued depending on the segments contents. )% LITERAL WKS$TELNET = 23; ! Well-known-port for TELNET GLOBAL ROUTINE SEG$Process_Received_Segments : NOVALUE = BEGIN EXTERNAL ROUTINE TCB_FIND, VTCB_SCAN; REGISTER TCB: REF TCB_Structure, SEG: REF Segment_Structure; LOCAL RQV, QB: REF Queue_Blk_Structure(QB_NR_Fields), sum, count, Need_2_ACK: INITIAL(False), ! assume: NO cumulative ACK's needed. IP_Address, Delete; LABEL X,Y; ! Process segments until queue is empty !~~~WHILE XREMQUE(.Segin[SI_Qhead],QB,Process_Received_Segments,Q$SEGIN,0) !~~~ NEQ Empty_Queue DO WHILE (RQV = REMQUE(.Segin[SI_Qhead],QB)) NEQ Empty_Queue DO BEGIN ts$sr = .ts$sr + 1; ! count segments received from IP. Seg = .QB[NR$Seg]; ! point at segment proper. Delete = True; ! assume we will delete this segment. SELECTONE .QB[NR$ICMP] OF SET [TRUE]: ! ICMP message for TCP BEGIN SwapBytes(.QB[NR$Size]/2,.Seg); ! Swap header bytes back ! Find out what connection this is for. The "segment" is the first part of ! the TCP segment we sent out to generate the ICMP reply. TCB = TCB_Find(.Seg[SH$Source_Port],.QB[NR$Dest_Adrs], .Seg[SH$Dest_Port]); IF .TCB EQL 0 THEN BEGIN IF $$LOGF(LOG$TCPERR OR LOG$ICMP) THEN BEGIN LOCAL DESC$STR_ALLOC(fhstr,20); !! ASCII_DEC_BYTES(fhstr,4,.QB[NR$Dest_Adrs], ASCII_DEC_BYTES(fhstr,4,QB[NR$Dest_Adrs], fhstr[DSC$W_LENGTH]); LOG$FAO('!%T ICMP for unknown TCB,FH=!AS,FP=!XL,LP=!XL!/', 0,fhstr,.seg[SH$Dest_Port],.seg[SH$Source_Port]); END; END ELSE BEGIN XLOG$FAO(LOG$ICMP, '!%T ICMP type !SL for TCB !XL!/', 0,.QB[NR$ICM_TYPE],.TCB); SELECTONE .QB[NR$ICM_TYPE] OF SET [ICM_DUNREACH]: ! Destination unreachable - treat as RESET BEGIN TCP$KILL_PENDING_REQUESTS(.TCB,NET$_URC); XLOG$FAO(LOG$TCBSTATE OR LOG$ICMP, '!%T TCB !XL killed by ICMP Dest Unreachable!/', 0,.TCB); TCP$Inactivate_TCB(.TCB,NET$_URC); END; [ICM_TEXCEED]: ! Time exceeded - treat as RESET BEGIN TCP$KILL_PENDING_REQUESTS(.TCB,NET$_CTO); XLOG$FAO(LOG$TCBSTATE OR LOG$ICMP, '!%T TCB !XL killed by ICMP Time Exceeded!/', 0,.TCB); TCP$Inactivate_TCB(.TCB,NET$_CTO); END; [ICM_SQUENCH]: ! Source quench - not yet supported BEGIN XLOG$FAO(LOG$TCBSTATE OR LOG$ICMP, '!%T TCB !XL received ICMP Source Quench!/', 0,.TCB); TCB[SQUENCH] = TRUE ; TCB[SQUENCH_Timer] = Time_Stamp() + SQUENCH_Interval ; END; [ICM_REDIRECT]: ! Redirect - not yet supported BEGIN XLOG$FAO(LOG$TCBSTATE OR LOG$ICMP, '!%T TCB !XL received ICMP Redirect!/', 0,.TCB); END; [ICM_PPROBLEM]: ! Parameter problem - not yet supported BEGIN XLOG$FAO(LOG$TCBSTATE OR LOG$ICMP, '!%T TCB !XL received ICMP Parameter Problem!/', 0,.TCB); END; TES; END; END; ! ICMP case [FALSE]: ! A real TCP segment X: BEGIN ! Good segment. Verify checksum. sum = Gen_Checksum(.QB[NR$Size],.Seg,.QB[NR$Src_Adrs], .QB[NR$Dest_Adrs],TCP_Protocol); IF .sum NEQU %X'FFFF' THEN BEGIN ! Checksum error - punt it IF $$LOGF(LOG$TCPERR) THEN BEGIN LOG$FAO('!%T TCP Checksum error (sum=!XL) for segment:!/', 0,.sum); SEG$Log_Segment(.Seg,.QB[NR$Size],True,True); END; LEAVE X; END; SwapBytes(TCP_Header_size/2,.Seg); ! Swap header bytes back Seg[SH$SEQ] = ROT(.Seg[SH$SEQ],16); Seg[SH$ACK] = ROT(.Seg[SH$ACK],16); IF $$LOGF(LOG$TCP) THEN SEG$LOG_Segment(.Seg,.QB[NR$Size],True,False); ! Now, find the connection that this segment is destined for TCB = TCB_Find (.Seg[SH$Dest_Port],.QB[NR$Src_Adrs], .Seg[SH$Source_Port]); ! If no connection found, then check for special controls we can handle. IF .TCB EQL 0 THEN Y: BEGIN SELECTONE TRUE OF SET [.Seg[SH$C_RST]]: ! RESET segment? BEGIN XLOG$FAO(LOG$TCPERR, '!%T RST received for unknown TCB, SP=!SL,DP=!SL!/', 0,.Seg[SH$Source_Port],.Seg[SH$Dest_Port]); LEAVE X; END; [.Seg[SH$C_SYN]]: ! "SYN" Segment? BEGIN LOCAL TMP; ! Check for SYN on well-known-socket (port). If OK, then fork server and queue ! the SYN for later processing on the SYN-wait-list. TMP = Check_WKS(.QB[NR$Dest_Adrs],.Seg[SH$Dest_Port], .QB[NR$Src_Adrs],.Seg[SH$Source_Port], .QB,.Seg); IF (.TMP EQL ERROR) THEN LEAVE X; IF (.TMP EQL TRUE) THEN BEGIN Delete = FALSE; LEAVE X; END; ! No WKS defined. If TELNET port, then try to create TVT connection. IF (.Seg[SH$Dest_Port] EQL WKS$TELNET) AND (.TELNET_SERVICE NEQ 0) THEN BEGIN TCB = TELNET_CREATE(.QB[NR$Dest_Adrs], .Seg[SH$Dest_Port], .QB[NR$Src_Adrs], .Seg[SH$Source_Port]); IF .TCB NEQ 0 THEN LEAVE Y; END; ! Connection not found. Give a RESET back. IP_Address = .QB[NR$Src_Adrs]; ACT$FAO('!%D SYN received for unknown port !UW from !/',0, .Seg[SH$Dest_Port], .IP_Address<0,8>,.IP_Address<8,8>, .IP_Address<16,8>,.IP_Address<24,8>); Reset_Unknown_Connection(.Seg,.QB); LEAVE X; END; ! Connection not found and not SYN or RST - return an RST. [OTHERWISE]: BEGIN Reset_Unknown_Connection(.Seg,.QB); LEAVE X; END; TES; END; ! Here when we have a TCP connection. Process the segment. ! ** Warning** TCB/connection maybe deleted during segment processing. ! Condition is flaged by Decode-segment return code of Error(-1). delete = Decode_Segment(.TCB,.Seg,.QB); IF .delete NEQ Error THEN BEGIN ! Do we need to check for cumulative ACK transmission after the Segment ! input queue is exhausted? IF .TCB[pending_ack] THEN Need_2_ACK = True; ! See if we can remove some stuff from the future queue IF Queue_Not_Empty(TCB[RF_Qhead]) THEN Check_Future_Q(.TCB); END; END; ! TCP segment case (block X) TES; ! Clean up, possibly delete segment & queue-block. ! *** Warning *** "Delete" can be one of 3 values(-1,0,1). Following test ! checks low-bit only( case covers BOTH 1 & -1). Be aware! ! check if we can really delete the segment or does somebody else lay claim. IF (.Delete NEQ 0) THEN BEGIN MM$Seg_Free(.QB[NR$Buf_Size],.QB[NR$Buf]); MM$QBLK_Free(.QB); END; END; ! "While" ! Check if any connections need to have a cumulative ACK transmitted. ! If True then check all valid connections & if the TCB[Pending_ACK] ! bit is set then Attempt to deliver user data & send an ACK (cumulative). ! Otherwise continue checking connections. IF .Need_2_ACK THEN VTCB_Scan ( Check_Cum_Ack , 0 , 0 ); END; END ELUDOM