.NC "The Design of the ARGO Transport Entity" .sh 1 "Protocol Hooks" .pp The design of the AOS kernel IPC support to some extent mandates the design of protocols. Each protocol must provide the following protocol hooks, which are procedures called through a protocol switch table (an array of type \fIprotosw\fR as described in Chapter Five. .ip "pr_input()" 5 Called when data are to be passed up from a lower layer. .ip "pr_output()" 5 Called when data are to be passed down from a higher layer. .ip "pr_init()" 5 Called when the system is brought up. .ip "pr_fasttimo()" 5 Called every 200 milliseconds by the clock functional unit. .ip "pr_slowtimo()" 5 Called every 500 milliseconds by the clock functional unit. .ip "pr_drain()" 5 This is meant to be called when buffer space is low. Each protocol is expected to provide this routine to free non-critical buffer space. This is not yet called anywhere. .ip "pr_ctlinput()" 5 Used for exchanging information between protocols, such as notifying a transport protocol of changes in routing or configuration information. .ip "pr_ctloutput()" 5 Supports the protocol-dependent \fIgetsockopt()\fR and \fIsetsockopt()\fR options. .ip "pr_usrreq()" 5 Called by the socket code to pass along a \*(lquser request\*(rq - in other words a service primitive. This call is also used for other protocol functions. The functions served by the \fIpr_usrreq()\fR routine are: .ip " PRU_ATTACH" 10 Creates a protocol control block and attaches it to a given socket. Called as a result of a \fIsocket()\fR system call. .ip " PRU_DISCONNECT" 10 Called as a result of a \fIclose()\fR system call. Initiates disconnection. .ip " PRU_DETACH" 10 Disassociates a protocol control block from a socket and recycles the buffer space used for the protocol control block. Called after PRU_DISCONNECT. .ip " PRU_SHUTDOWN" 10 Called as a result of a \fIshutdown()\fR system call. If the protocol supports the notion of half-open connections, this closes the connection in one direction or both directions, depending on the arguments passed to \fIshutdown\fR. .ip " PRU_BIND" 10 Gives an address to a socket. Called as a result of a \fIbind()\fR system call, also when socket without a bound address is used. In the latter case, an unused transport suffix is located and bound to the socket. .ip " PRU_LISTEN" 10 Called as a result of a \fIlisten()\fR system call. Marks the socket as willing to queue incoming connection requests. .ip " PRU_CONNECT" 10 Called as a result of a \fIconnect()\fR system call. Initiates a connection request. .ip " PRU_ACCEPT" 10 Called as a result of an \fIaccept()\fR system call. Dequeues a pending connection request, or blocks waiting for a connection request to arrive. In the latter case, it marks the socket as willing to accept connections. .ip " PRU_RCVD" 10 The protocol module is expected to have put incoming data into the socket's receive buffer, \fIso_rcv\fR. When a receive primitive is used (\fIrecv(), recvmsg(), recvfrom(), read(), readv(), \fRand \fIrecvv()\fR system calls) the socket code module copies data from the \fIso_rcv\fR to the user's address space. The protocol module may arrange to be informed each time the socket code does this, in which case the socket code calls \fIpr_usrreq\fR(PRU_RCVD) after the data were copied to the user. .ip " PRU_SEND" 10 This performs the protocol-dependent part of a send primitive (\fIsend(), sendmsg(), sendto(), write(), writev(), \fRand \fIsendv()\fR system calls). The socket code (procedures \fIsendit() and \fIsosend()\fR) moves outgoing data from the user's address space into a chain of \fImbufs\fR. The socket code takes as much data from the user as it determines will fit into the outgoing socket buffer, so_snd. It passes this much data in the form of an mbuf chain to the protocol via \fIpr_usrreq\fR(PRU_SEND). If there are more data than the so_snd can accommodate, the socket code, which is running on behalf of a user process, puts the user process to sleep. The protocol module is expected to wake up the user process when more room appears in so_snd. .ip " PRU_ABORT" 10 Called when a socket is closed and that socket is accepting connections and has queued pending connection requests or partially open connections. .ip " PRU_CONTROL" 10 Called as a result of an \fIioctl()\fR system call. .ip " PRU_SENSE" 10 Called as a result of an \fIfstat()\fR system call. .ip " PRU_RCVOOB" 10 Performs the work of receiving \*(lqout-of-band\*(rq data. The socket module has already allocated an mbuf into which the protocol module is expected to put the incoming \*(lqout-of-band\*(rq data. The socket code will then move the data from this mbuf to the user's address space. .ip " PRU_SENDOOB" 10 Performs the work of sending \*(lqout-of-band\*(rq data. The socket module has already moved the data from the user's address space into a chain of mbufs, which it now passes to the protocol module. .ip " PRU_SOCKADDR" 10 Supports the system call \fIgetsockname()\fR. Puts the socket's bound address into an mbuf. .ip " PRU_PEERADDR" 10 Supports the system call \fIgetpeername\fR(). Puts the peer's address into an mbuf. .ip " PRU_CONNECT2" 10 This is used in the Unix domain to support pipes. It is not generally supported by transport protocols. .ip " PRU_FASTTIMO, PRU_SLOWTIMO" 10 These are superfluous. None of the transport protocols uses them. .ip " PRU_PROTORCV, PRU_PROTOSEND" 10 None of the transport protocols uses these. .ip " PRU_SENDEOT" 10 This was added to support TP. This indicates that the end of the data sent in this send primitive should be marked by the protocol as the end of the TSDU. .sh 1 "The Interface Between the Transport Entity and Lower Layers" .pp The transport layer may run over a network layer such as IP or the ISO connectionless network layer, or it may run over a multi-purpose layer such as the service provided by X.25. X.25 is viewed as a network layer when TP runs over X.25, and as a subnetwork layer when IP is running over X.25. The software interface between data link and network layers differs considerably from the software interface between transport and network layers in AOS. For this reason some modification of the transport-to-lower-layer interface is necessary to support the suite of protocols included in ARGO. .pp In AOS it is assumed that the transport layer will run over one and only one network layer, and therefore it may call the network layer output procedure directly. In order to allow TP to run over a set of lower layers, all domain-specific functions have been put into a set of routines that are called indirectly through a domain-specific switch table. The primary reason for this is that the transport and network layers share information, mostly information pertaining to addresses. The protocol control blocks for different network layers differ, so the transport layer cannot just directly access the network layer's pcb. Similarly, a network layer may not directly access the transport pcb because a multitude of transport protocols can run over each of the network protocols. .pp To permit different network-layer protocol control blocks to coexist under one transport layer, all transport-dependent control information was put into a transport-specific protocol control block. A new field, \fIso_tpcb\fR, was added to the \fIsocket\fR structure to hold a pointer to the transport-layer protocol control block. The existing field \fCso_pcb\fR is used for the network layer pcb. .pp The following structure was added to allow domain-specific functions to be called indirectly. All these functions operate on a network-layer pcb. .pp .(b \fC .TS tab(+); l s s s. struct nl_protosw { .T& l l l l. +int+nlp_afamily;+/* address family */ +int+(*nlp_putnetaddr)();+/* puts addrs in pcb */ +int+(*nlp_getnetaddr)();+/* gets addrs from pcb */ +int+(*nlp_putsufx)();+/* transp suffix -> pcb */ +int+(*nlp_getsufx)();+/* gets t-suffix */ +int+(*nlp_recycle_suffix)();+/* zeroes suffix */ +int+(*nlp_mtu)();+/* get maximum +++transmission unit size */ +int+(*nlp_pcbbind)();+/* bind to pcb */ +int+(*nlp_pcbconn)();+/* connect */ +int+(*nlp_pcbdisc)();+/* disconnect */ +int+(*nlp_pcbdetach)();+/* detach pcb */ +int+(*nlp_pcballoc)();+/* allocate a pcb */ +int+(*nlp_output)();+/* emit packet */ +int+(*nlp_dgoutput)();+/* emit datagram */ +caddr_t+nlp_pcblist;+/* list of pcbs +++for management +++of connections */ }; .TE \fR .)b .lp The switch is based on the address family chosen when the \fIsocket()\fR system call is made prior to connection establishment. This unfortunately ties the address family to the domain, but the only alternative is to add an argument to the \fIsocket()\fR system call to let the user specify the desired network layer. In the case of a connection oriented environment with no multi-homing, it would be possible to determine which network layer is to be used from routing information, but to do this requires unrealistic assumptions about the environment. For these reasons, linking the address family to the network layer protocol is seen as the least of the evils. The transport suffixes are kept in the network layer's pcb as well as in the transport layer because full transport address pairs are used to identify a connection in the Internet domain. .sh 1 "The Architecture of the Transport Protocol Entity" .pp A set of protocol hooks is required by the AOS IPC architecture. These hooks are used by the protocol-independent parts of the kernel to gain entry to protocol-specific code. The protocol code can be entered in one of the following ways: .ip "1) " 5 at boot time, when autoconfiguration initializes each protocol through the \fIpr_init()\fR hook, .ip "2) " 5 from above, either a user program making a system call, through the \fIpr_usrreq()\fR or \fIpr_ctloutput()\fR hooks, or from a higher layer protocol using the \fIpr_output()\fR hook, .ip "3) " 5 from below, a device interrupt servicing an incoming packet through the \fIpr_input()\fR and \fIpr_ctlinput()\fR hooks, and .ip "4) " 5 from a clock interrupt through the \fIpr_slowtimo()\fR or the \fIpr_fasttimo()\fR hook. .\" FIGURE .so figs/trans_flow.nr .\".so figs/trans_flow.grn .pp The protocol code can be divided into the following modules, which are described in more detail below. .CF shows the flow of data and control among these modules. .in +5 .ip "Timers and References:" 5 The code executed on behalf of \fIpr_slowtimo()\fR. The fast timeout is not used by TP. .ip "Driver:" 5 This is the finite state machine for TP. .ip "Input: " 5 This is the module that decodes incoming packets, identifies or creates the pcb for which the packet is destined, and creates an "event" to pass to the driver. .ip "Output:" 5 This is the module that creates a packet header of a given type with fields containing values that are appropriate to the connection on which the packet is being sent, appends data if necessary, and hands a packet to the lower layer, according to the transport-to-lower-layer interface. .ip "Send: " 5 This module packetizes data from the outbound socket buffer, \fIso_snd\fR, handles retransmissions of packetized data, and drops packetized data from the retransmission queue. .ip "Receive:" 5 This module reorders packets if necessary, depacketizes data, passes it to the socket code module, and determines when acknowledgments should be sent. .in -5 .sh 1 "Timers and References" .pp TP identifies sockets by \fIreference numbers\fR, or \fIreferences\fR, which are \*(lqfrozen\*(rq (may not be reassigned) until some locally defined time after a connection is broken and its protocol control block is discarded. An array of \fIreference blocks\fR is maintained by TP. The reference number of a reference block is its offset in the array. When a reference block is in use it contains a pointer to the pcb for the socket to which the reference applies. .pp The system clock calls the \fIpr_slowtimo()\fR and \fIpr_fasttimo()\fR hooks for each protocol in the protocol switch table every 500 and 200 microseconds, respectively. Each protocol handles its own timers its own way. The timers in TP take two forms - those that typically are cancelled and those that usually expire. The latter form may have more than one instantiation at any given time. The former may not. The two are implemented slightly differently for the sake of performance. .pp The timers that normally expire are kept in a queue, their values all relative to the value of preceding timer. Thus all timer values are decremented by a single operation on the value of the first timer. The timer is represented by the Ecallout structure: .(b \fC .TS tab(+); l s s s. struct Ecallout { .T& l l l l. +int+c_time;+/* incremental time */ +int+c_func;+/* function to call */ +u_int+c_arg1;+/* argument to routine */ +u_int+c_arg2;+/* argument to routine */ +int+c_arg3;+/* argument to routine */ +struct Ecallout+*c_next; }; .TE \fR .)b .lp When an Ecallout structure migrates to the head of the E timer list, and its \fIc_time\fR field is decremented to zero, the function stored in \fIc_func\fR is called, with \fIc_arg1, c_arg2\fR, and \fIc_arg3\fR as arguments. Setting and cancelling these timers are accomplished by a linear search and one insertion or deletion from the timer queue. This queue is linked to the reference block associated with a communication endpoint. This form used for the reference timer and for the retransmission timers for data TPDUs. .pp The second form of timer, the type that typically is cancelled, is used for several timers - the inactivity timer, the sendack timer, and the retransmission timer for all types of TPDUs except data TPDUs. .(b \fC .TS tab(+); l s s s. struct Ccallout { .T& l l l l. +int+c_time;+/* incremental time */ +int+c_active;+/* this timer is active? */ }; .TE \fR .)b .lp All of these timers are stored directly in the reference block. These timers are decremented in one linear scan of the reference blocks. Cancelling, setting, and both cancelling and resetting one of these timers is accomplished by a single assignment to an array element. .sh 1 "Driver" .pp This is the finite state machine for TP. A connection is managed by the finite state machine (fsm). All events that pertain to a connection cause the finite state machine driver to be called. The driver takes two arguments - the pcb for the connection and an event structure. The event structure contains a field that discriminates the different types of events, and a union of structures that are specific to the event types. The driver evaluates a set of predicates based on the current state of the finite state machine (which is kept in the pcb) and the event type. The result of the predicate evaluation determines a set of actions to take and a state transition. The driver takes the actions and if they complete without errors, the driver makes the state transition. .pp The states, event types, predicates, actions, and state transitions are all specified as a \fIxebec transition file\fR. \fIXebec\fR is a utility that takes a human-readable description of a finite state machine and produces a set of tables and C source code for the driver. The driver procedure is called \fItp_driver()\fR. It is located in a file generated by xebec, \fCtp_driver.c\fR. For more details about xebec, see the manual page \fIxebec(1)\fR. .pp The transition file for TP is \fCtp.trans\fR, and it is a good place to begin a perusal of the TP source code. .sh 1 "Input" .pp This is the module that decodes an incoming packet, locates or creates the pcb for which the packet is destined, and creates an event to pass to the driver. The network layer passes a packet up to the appropriate transport layer by indirectly calling a transport input routine through the protocol switch table for the network domain. There is one protocol switch entry for TP for each domain in which TP will run (Internet, ISO). In the Internet domain, the protocol switch field \fIpr_input()\fR takes the value \fItpip_input()\fR. This procedure accepts a packet from IP, with the IP header still intact. It extracts the network addresses from the IP header, strips the IP header, and calls the domain-independent input procedure for TP, \fItp_input()\fR. \fITp_input()\fR decodes a TPDU. The multitude of options, the variable-length nature of the options, the semantics of the options, and the possible combinations of concatenated TPDUs make this a complex procedure. It is sensitive to changes, and from the point of view of a software maintenance, it is a potential hazard. Because it is in the critical path of TP however, some compromise was made between maintainability and efficiency. Multiple copies of sections of code were avoided as much as possible, not for the sake of saving space, but rather for the sake of maintainability. Ironically, this detracts somewhat from the readability of the code. .pp Once a TPDU has been decoded and a pcb has been identified for the TPDU, the appropriate fields of the TPDU are extracted and their values are placed in an event structure. Finally, \fItp_driver()\fR is called with the event structure and the pcb as parameters. .sh 1 "Output" .pp This module creates a TPDU header of a given type with field values that are appropriate to the connection on which the TPDU is being sent, appends data if necessary, and hands a TPDU to the lower layer according to the transport-to-lower-layer interface. Whenever a TPDU is to be sent to the peer or prospective peer, the function \fItp_emit()\fR is called, passing as arguments the pcb a TPDU type and several miscellaneous other type-specific arguments, possibly including some data. The data are in the form of an mbuf chain. \fITp_emit()\fR prepends to the data an mbuf containing a TP header, fills in the fields of the header according to the parameters given, performs the checksum if appropriate, and calls a domain-specific output routine. For the Internet domain, this output routine is \fItpip_output()\fR, which takes as arguments the mbuf chain representing the TPDU, and a network level pcb. Some protocol errors cannot be associated with a connection but require that TP issue an ER TPDU or a DR TPDU. When these errors occur the routine \fItp_error_emit()\fR is called. This procedure creates the appropriate type of TPDU and passes it to a domain-dependent routine for transmitting datagrams. In the Internet domain, \fItpip_output_dg()\fR is called. This takes as arguments an mbuf chain representing the TPDU, a source network address, and a destination network address. .sh 1 "Send" .\" FIGURE .so figs/mbufsnd.nr .\".so figs/mbufsnd.grn .pp This module packetizes data from the outbound socket buffer, \fIso_snd\fR, handles retransmissions of packetized data, and drops packetized data from the retransmission queue. The major routine in this module is \fItp_send()\fR, which takes a range of sequence numbers as arguments. For each sequence number in the range, it packetizes the an appropriate amount of outbound data, and places the resulting TPDU on a retransmission control queue subject to the constraints imposed by the rules of expedited data, maximum packet sizes, and end-of-TSDU markers. .pp The most complicating factor is that of managing expedited data. A normal datum may not be sent (for its first time) before the acknowledgment of any expedited datum that was received from the user after the normal datum was received. In order to enforce this rule, each TPDU must be marked in some way so that it will be known which expedited datum must be delivered and acknowledged by the peer before this TPDU may be transmitted for the first time. Markers are placed in \fIso_snd\fR when an outgoing expedited datum arrives from the user. A marker is an mbuf structure with an \fIm_len\fR of zero, but with the data area nevertheless containing the sequence number of an expedited data TPDU. The \fIm_type\fR of a marker is a new type, MT_XPD. .pp \fITp_send()\fR stops packetizing data when it encounters a marker for an unacknowledged expedited datum. If it encounters a marker for an expedited TPDU that has already been acknowledged, the marker is jettisoned. .CF illustrates the structure of the sending socket buffer used for normal data. .pp When \fItp_send()\fR moves data from mbufs on \fIso_snd\fR to the retransmission control queue, it needs to know how many octets of data can be placed in each TPDU. The appropriate amount depends on, among other things, the maximum transmission unit of the network layer on the route the packet will take. To determine the maximum transmission unit, TP queries the network layer through the domain-dependent switch table's field, \fInl_mtu\fR. In the Internet domain, this resolves to \fItp_inmtu()\fR. The header sizes for the network and transport layers also affect the amount of data that can go into a packet, and these sizes depend on the connection's characteristics. .pp Once the maximum amount of data per TPDU is determined, \fItp_send()\fR can pull this amount off the \fIso_snd\fR queue to form a TPDU, assign a TPDU sequence number, and place the new TPDU on the retransmission control queue. The retransmission control queue is a list of mbuf chains. Each mbuf chain represents one TPDU, preceded by an \fIrtc structure\fR: .(b \fC .TS tab(+); l s s s. struct tp_rtc { .T& l l l l. +struct tp_rtc+*tprt_next;+/* next rtc struct in list */ +SeqNum+tprt_seq;+/* seq # of this TPDU */ +int+tprt_eot;+/* end of TSDU? */ +int+tprt_octets;+/* # octets in this TPDU */ +struct mbuf+*tprt_data;+/* ptr to the octets of data */ .\"/* Performance measurment info: */ .\"int tprt_window; /* in which call to tp_send() was .\" * this TPDU formed? .\" */ .\"struct timeval tprt_sess_time; /* time session received the .\" * majority of the data for this packet on send; .\" * on recv, this is the time it's given to session .\" */ .\"struct timeval tprt_net_time; /* time first copy was given to net layer .\" * on send; on receive it's the time received from .\" * the network .\" */ }; .TE \fR .)b .lp Once TPDUs are on the retransmission control queue, they are retransmitted or dropped by the actions of timers. The procedure \fItp_sbdrop()\fR removes the TPDUs from the retransmission queue. It takes a sequence number as an argument and drops all TPDUs up to and including the TPDU with that sequence number. .pp When an AK TPDU arrives, the values from its credit and sequence number fields are passed to \fItp_goodack()\fR, which determines whether or not the AK brought any news with it, and therefore whether TP can send more data or expedited data. If this AK acknowledges something heretofore unacknowledged, \fItp_goodack()\fR drops the appropriate TPDU(s) from the retransmission control list, computes the smoothed average round trip time and standard deviation of the round trip time, and updates the retransmission timer based on these statistics. It sets a flag in the pcb if the TP entity is obliged to send the flow control confirmation parameter on its next AK TPDU. \fITp_goodack()\fR returns true if the AK brought some news with it, either with respect to a change in credit or with respect to new acknowledgments. .pp The function \fItp_goodXack()\fR is called when an XAK TPDU arrives. It takes the XAK sequence number as an argument and determines if the XAK acknowledges the last XPD TPDU sent. If so, it drops the expedited data from the outgoing expedited data buffer. By its definition in the TP specification, the expedited data stream has a window of size 1, that is, only one expedited datum (packet) can be buffered at a time. \fITp_goodXack()\fR returns true if the XAK acknowledged the last XPD TPDU sent and the data were dropped, and it returns false if the acknowledgment caused no action to be taken. .\" NEXT FIGURE .so figs/mbufrcv.nr .\".so figs/mbufrcv.grn .sh 1 "Receive" .pp This module reorders incoming TPDUs if necessary, depacketizes data, passes it to the socket code module, and determines when acknowledgments should be sent. The function \fItp_stash()\fR takes an DT TPDU as an argument, and if the TPDU is not in sequence, it saves the TPDU in a \fItp_rtc\fR structure in a list, with the TPDUs kept in order. When the next expected TPDU arrives, the list of out-of-order TPDUs is scanned for more TPDUs in sequence, updating a field in the pcb, \fItp_rcvnxt\fR which always contains the sequence number of the next expected TPDU. If an acknowledgment is to be generated at any time, the value of tp_rcvnxt goes into the \fIYR-TU-NR\fR\** field of the acknowledgment TPDU. .(f \** This is the name used in ISO 8073 for the field which indicates the sequence number of the next expected DT TPDU. .)f .pp \fITp_stash()\fR returns true if an acknowledgment needs to be generated immediately, false not. The acknowledgment strategy is therefore implemented in this routine. Acknowledgments may be generated for one or more of several reasons, listed below. \fITp_stash()\fR increments a counter for each of these reasons for which an acknowledgment is generated, and a counter for TPDUs that are not acknowledged immediately. .ip "ACK_STRAT_EACH" 5 The acknowledgment strategy in use calls for acknowledging each data packet with an AK TPDU. .ip "ACK_STRAT_FULLWIN" 5 The acknowledgment strategy in use calls for acknowledging upon receiving the DT TPDU that represents the upper window edge of the last advertised window. .ip "ACK_DUP" 5 A duplicate data TPDU was received. .ip "ACK_REORDER" 5 A DT TPDU arrived in the window but out of order. .ip "ACK_EOT" 5 A DT TPDU arrived, and it had the end-of-TSDU flag set. .pp Upon receipt of a DT TPDU that is in order, and upon reordering DT TPDUs, \fItp_stash()\fR places the TSDUs into the socket's receive socket buffer, \fIso->so_rcv\fR in mbuf chains, with TSDUs delimited by mbufs of the \fIm_type\fR MT_EOT, which is a new type with the ARGO kernel. .CF illustrates the structure of the receiving socket buffer used for normal data. .pp A separate socket buffer, \fItpcb->tp_Xrcv\fR, is used for buffering expedited data. Only one expedited data packet may reside in this buffer at a time because the TP standard limits the size of the window on expedited flow to be 1. This means the data structures are straightforward; there is no need to distinguish between separate TSDUs in this socket buffer. .pp Credit is determined by dividing the total amount of available space in the receive buffer by the negotiated maximum TPDU size. TP can often offer a larger credit than this if it uses an average of the measured actual TPDU sizes. This strategy was once an option in the ARGO kernel, but it was removed because unless the actual TPDU size is constant, it leads to reneging of credit, retransmissions, and decreased performance. It does not work well when there is any fluctuation in the sizes of TPDUs and it carries the penalty of lengthening the critical path of the TP entity. .sh 1 "Major Data Structures and Types" .pp In addition to the types commonly used in the kernel, such as .(b \fC .TS tab(+); l l l l. +typedef+unsigned char+u_char; +typedef+unsigned int+u_int; +typedef+unsigned short+u_short; .TE \fR .)b TP uses the following types: .(b \fC .TS tab(+); l l l l. +typedef+unsigned int+SeqNum +typedef+unsigned short+RefNum; +typedef+int+ProtoHook; .TE \fR .)b .pp Sequence numbers can be either 7 or 31 bits. An unsigned integer is used in all cases, and the proper type of arithmetic is performed with bit masks. Reference numbers are 16 bits. ProtoHook is the type of the procedures that are in switch tables, which, although they are not functions, are declared \fIint\fR rather than \fIvoid\fR to be consistent with the rest of the kernel. .pp The following structures are fundamental types used throughout TP, in addition to those already described in the section, "The Design of the Transport Entity". .(b \fC .TS tab(+); l s s s. struct tp_ref { .T& l l l l. +u_char+tpr_state;+/* REF_FROZEN...*/ +struct Ccallout+tpr_callout[N_CTIMERS];+/* C timers */ +struct Ecallout+tpr_calltodo;+/* E timers list */ +struct tp_pcb+*tpr_pcb;+/* --> PCB */ }; .TE \fR .)b .lp The reference structure is logically a part of the protocol control block and it is linked to a pcb, but it may outlive a pcb. When a connection is dissolved, the pcb may be recycled but the reference structure must remain until the reference timer goes off. The field \fItpr_state\fR takes the values REF_FROZEN (a reference timer is ticking), REF_OPEN (in use, has timers and an associated pcb), REF_OPENING (has a pcb but no timers), and REF_FREE (free to reallocate). .pp The TP protocol control block is too large to fit into one mbuf structure so it comprises two structures linked together, the \fItp_pcb\fR structure and the. \fItp_pcb_aux\fR structure. The \fItp_pcb_aux\fR structure contains items that are used less frequently than those in the former structure, since each access to these items requires a second pointer dereference. .(b \fC .TS tab(+); l s s s. struct tp_pcb_aux { .T& l l l s. +struct sockbuf+tpa_Xsnd;+/* for expedited data */ +struct sockbuf+tpa_Xrcv;+/* for expedited data */ +u_char +tpa_vers;+/* protocol version */ +u_char +tpa_peer_acktime;+/* to compute DT TPDU +++retrans timer value */ +SeqNum+tpa_Xsndnxt;+/* seq # of +++next XPD to send */ +SeqNum+tpa_Xuna;+/* seq # of +++unacked XPD */ +SeqNum+tpa_Xrcvnxt;+/* next XPD seq # +++expect to recv */ +/* addressing */ +u_short+tpa_domain;+/* domain AF_ISO,...*/ +u_short+tpa_fsuffixlen;+/* foreign suffix */ +u_char+tpa_fsuffix[MAX_TSAP_SEL_LEN];+ +u_short+tpa_lsuffixlen;+/* local suffix */ +u_char+tpa_lsuffix[MAX_TSAP_SEL_LEN];+ .T& l s s s. +/* AK subsequencing */ .T& l l l s. +u_short+tpa_s_subseq;+/* next subseq to send */ +u_short+tpa_r_subseq;+/* highest recv subseq */ }; .TE \fR .)b .pp The major portion of the protocol control block is in the \fItp_pcb\fR structure: .(b \fC .TS tab(%); l s s s. struct tp_pcb { .\" *************************************** .T& l l l l. .\" The next line sets the spacing for the table: 1+3 17+3 17+3 13+3 % % % .\"456789 123456789- 123456789 123456-789 123456789 1234567890 .\" %struct tp_ref%*tp_refp;% .T& l l l s. %%/* reference structure */% .\" *************************************** .T& l l l l. %struct tp_pcb_aux%*tp_aux;% .T& l l l s. %%/*rest of tpcb (auxiliary struct)*/% .\" *************************************** .T& l l l l. %caddr_t%tp_npcb;%/* to ll pcb */ %struct nl_protosw%*tp_nlproto;% .T& l l l s. % %/* domain-dependent routines */% .\" *************************************** .T& l l l l. %struct socket%*tp_sock;%/* back ptr */ .\" *************************************** .T& l s s s. /* local and foreign reference numbers: */ .T& l l l l. %RefNum%tp_lref;% %RefNum%tp_fref;% .\" *************************************** .T& l s s s. .\"456789 123456789 123456789 123456789 123456789 1234567890 /* Stuff for sequence space arithmetic: * Maintaining 2 sequence spaces is a pain so we set these * values once at connection establishment time. Sequence * number arithmetic is a set of macros which uses these. * Sequence numbers are stored as 32 bits. * tp_seqmask tells which of the 32 bits is used. * tp_seqibt is the lsb that is not used. When set, * it indicates wraparound has occurred. * tp_seqhalf is the value that is half the sequence space. * (or half plus one). */ .T& l l l l. %u_int%tp_seqmask;%/* mask */ %u_int%tp_seqbit;%/* wraparound */ %u_int%tp_seqhalf;%/* half space */ .\" *************************************** .T& l s s s. /* flags: values are defined in tp_user.h. * Here we keep such info as which options * are in use: checksum, extended format, * flow control in class 2, etc. * See tp(4p) man page. */ .\" *************************************** .T& l l l l. %u_short%tp_state;%/* fsm */ %short%tp_retrans;% .T& l l l s. % % /* # times to retransmit */% .\" *************************************** .T& l s s s. /* credit & sequencing info for SENDING: */ .T& l l l s. %u_short%tp_fcredit;% % %/* remote real window */% %u_short%tp_cong_win;% % %/* remote congestion window */% .\" *************************************** %SeqNum%tp_snduna;% .T& l l l s. % %/* seq # of lowest unacked DT */% .\" *************************************** .T& l l l l. %struct tp_rtc %*tp_snduna_rtc;% .T& l l l s. % %/* ptr to mbufs containing lowest% %% * unacked TPDUs sent so far% %% */% .\" *************************************** .T& l l l l. %SeqNum%tp_sndhiwat;% .T& l l l s. % %/* highest DT sent yet */% .\" *************************************** .T& l l l l. %struct tp_rtc%*tp_sndhiwat_rtc;% .T& l l l s. % %/* ptr to mbufs containing the last% %% * DT sent - this is the last item % %% * on the list that starts% %% * at tp_snduna_rtc% %% */% .\" *************************************** .T& l l l l. %int %tp_Nwindow;%/* for perf. measmt */ .\" *************************************** .T& l s s s. /* credit & sequencing info for RECEIVING: */ .\" *************************************** .T& l l l s. %SeqNum%tp_sent_lcdt;% %%/* cdt according to last AK sent */% %SeqNum%tp_sent_uwe;% % %/* upper window edge, according to% %% * the last AK sent % %% */* %SeqNum%tp_sent_rcvnxt;% % %/* rcvnxt, according to% %% * the last AK sent% %% */* .\" *************************************** .T& l l l l. %short%tp_lcredit;%/* local */ .\" *************************************** .T& l l l l. %SeqNum%tp_rcvnxt;% .T& l l l s. % %/* next DT seq# we expect to recv */% .\" *************************************** .T& l l l l. %struct tp_rtc%*tp_rcvnxt_rtc;% .T& l l l s. % %/* ptr to mbufs containing unacked % %% * DTs received out of order, and % %% * which we haven't acknowledged% %% */% .\" *************************************** .TE .TS tab(%); l s s s. /* Items kept in the aux structure: */ .\" *************************************** .T& l s s l. #define tp_vers%tp_aux->tpa_vers #define tp_peer_acktime%tp_aux->tpa_peer_acktime #define tp_Xsnd%tp_aux->tpa_Xsnd #define tp_Xrcv%tp_aux->tpa_Xrcv #define tp_Xrcvnxt%tp_aux->tpa_Xrcvnxt #define tp_Xsndnxt%tp_aux->tpa_Xsndnxt #define tp_Xuna%tp_aux->tpa_Xuna #define tp_domain%tp_aux->tpa_domain #define tp_fsuffixlen%tp_aux->tpa_fsuffixlen #define tp_fsuffix%tp_aux->tpa_fsuffix #define tp_lsuffixlen%tp_aux->tpa_lsuffixlen #define tp_lsuffix%tp_aux->tpa_lsuffix #define tp_s_subseq%tp_aux->tpa_s_subseq #define tp_r_subseq%tp_aux->tpa_r_subseq .\" *************************************** .T& l s s s. % % % /* parameters per-connection controllable by user: */ .\" *************************************** .T& l l l l. %struct%tp_conn_param%_tp_param; % % % .\" *************************************** .T& l s s l. #define tp_Nretrans%_tp_param.p_Nretrans #define tp_dr_ticks%_tp_param.p_dr_ticks #define tp_cc_ticks%_tp_param.p_cc_ticks #define tp_dt_ticks%_tp_param.p_dt_ticks #define tp_xpd_ticks%_tp_param.p_x_ticks #define tp_cr_ticks%_tp_param.p_cr_ticks #define tp_keepalive_ticks%_tp_param.p_keepalive_ticks #define tp_sendack_ticks%_tp_param.p_sendack_ticks #define tp_refer_ticks%_tp_param.p_ref_ticks #define tp_inact_ticks%_tp_param.p_inact_ticks #define tp_xtd_format%_tp_param.p_xtd_format #define tp_xpd_service%_tp_param.p_xpd_service #define tp_ack_strat%_tp_param.p_ack_strat #define tp_rx_strat%_tp_param.p_rx_strat #define tp_use_checksum%_tp_param.p_use_checksum #define tp_tpdusize%_tp_param.p_tpdusize #define tp_class%_tp_param.p_class #define tp_winsize%_tp_param.p_winsize #define tp_netservice%_tp_param.p_netservice #define tp_no_disc_indications%_tp_param.p_no_disc_indications #define tp_dont_change_params%_tp_param.p_dont_change_params .\" *************************************** .TE .\" *************************************** .\" *************************************** .\" *************************************** .TS tab(%); l l l l. .\" The next line sets the spacing for the table: 1+3 17+3 17+3 13+3 .\"456789 123456789- 123456789 123456-789 123456789 1234567890 .\" .T& l l l s. %%/* log2(the negotiated max size) */% .T& l l l l. %int%tp_l_tpdusize;%/* # bytes */ .\" *************************************** %struct timeval%tp_rtt;% .T& l l l s. % %/* smoothed avg round-trip time */% %struct timeval%tp_rtv;% % %/* std deviation of round-trip time */% %struct timeval%tp_rttemit[ TP_RTT_NUM + 1 ];% %%/* times that the last TP_RTT_NUM % %% * DT_TPDUs were transmitted % %% */% .\" *************************************** %unsigned % % % tp_sendfcc:1,%/* shall next ack % % %include flow control conf. param? */% .\" *************************************** .T& l l l s. % tp_trace:1,%/* is this pcb being traced?% %% * (not used yet) % %% */% .\" *************************************** % tp_perf_on:1,%/* statistics being kept? */% .\" *************************************** % tp_reneged:1,%/* have we reneged on credit% %% * since the last AK TPDU was sent? % %% */% % tp_decbit:4,%/* congestion experienced? */% % tp_flags:8,%/* see #defines below */% .\" *************************************** % tp_unused:16;%% .T& l s s l. #define TPF_XPD_PRESENT%TPFLAG_XPD_PRESENT #define TPF_NLQOS_PDN%TPFLAG_NLQOS_PDN #define TPF_PEER_ON_SAMENET%TPFLAG_PEER_ON_SAMENET %%% .\" *************************************** .T& l l l l. %struct tp_pmeas%*tp_p_meas;% .T& l l l s. % %/* ptr to mbuf to hold the perf.% %% * statistics structure % %% */% .\" *************************************** }; .TE \fR .\" .\" end of tpcb structure (thank you) .\" .)b .fi .sh 1 "Sequence Number Arithmetic" .pp Sequence numbers in TP can be either 7 bits (\*(lqnormal format\*(rq) or 31 bits (\*(lqextended format\*(rq). Sequence numbers are unsigned integers, regardless of their format. Three fields are kept in the pcb to manage the sequence number arithmetic: .(b \fC .TS tab(+); l l l l. +u_int+tp_seqmask;+/* mask for seq space */ +u_int+tp_seqbit;+/* bit for seq # wraparound */ +u_int+tp_seqhalf;+/* half the seq space */ .TE \fR .)b .lp \fITp_seqmask\fR is a bit mask indicating which bits are legitimate for a sequence number of either format. It takes the value 0x7f if 7-bit sequence numbers are in use, and 0x7fffffff if 31-bit sequence numbers are in use. \fITp_seqbit\fR is the bit that becomes set when a sequence number wraps around while being incremented. Its value is 0x80 for normal format, 0x80000000 for extended format. \fITp_seqhalf\fR takes the value which is in the middle of the sequence space, 0x40 for normal format, and 0x40000000 for extended format. .(b .nf The macro .fi \fC .TS tab(+); l l l l. SEQ(tpcb, x) .TE \fR .)b .lp extracts a sequence number from the location in which it is stored. .pp The macros .(b \fC .TS tab(+); l l s s l. +SEQ_GT(tpcb, seq, t)+is seq > t? +SEQ_GEQ(tpcb, seq, t)+is seq >= t? +SEQ_LT(tpcb, seq, t)+is seq < t? +SEQ_LEQ(tpcb, seq, t)+is seq <= t? +SEQ_INC(tpcb, seq)+seq\+\+ +SEQ_DEC(tpcb, seq)+seq-- +SEQ_SUB(tpcb, seq, amt)+seq -= amt +SEQ_ADD(tpcb, seq, amt)+seq \+= amt .TE \fR .)b .lp perform the indicated comparisons and arithmetic on their arguments. .pp An example of how these macros are used is as follows. To determine if a sequence number \fIseq\fR is in a receive window bounded by \fIlwe\fR and \fIuwe\fR, we define the macro .(b \fC .TS tab(+); l l. #define+IN_RWINDOW(tpcb, seq, lwe, uwe)\\ +( SEQ_GEQ(tpcb, seq, lwe) && SEQ_LT(tpcb, seq, uwe) ) .TE \fR .)b .sh 1 "TP Implementation Options" .pp The transport protocol specification leaves several things to the discretion of the implementor, some of which may affect the performance of individual connections and aggregate performance. Wherever different strategies are likely to favor the performance of individual connections to the detriment of aggregate performance or vice versa, the various strategies are under the control of options via the \fIgetsockopt()\fR and \fIsetsockopt()\fR system calls (see the manual pages \fIgetsockopt(2)\fR, \fIsetsockopt(2)\fR and \fItp(4p)\fR for details). In some cases the preferred strategies differ for the different subnetworks, so the strategies chosen will be determined by the subnetwork in use. .sh 2 "TPDU size" .pp The limitation of the maximum TPDU size to a power of two is unfortunate in the LAN environment. For example, if the maximum NSDU size is around 1500, as in the case of an Ethernet, using a maximum TPDU size of 1024 reduces the possible throughput by approximately 30%. TP negotiates a maximum TPDU size of 2048 and generates TPDUs of size around 1500. Obviously this works well only when the peer is known to be using the same scheme (so that the peer doesn't send TPDUs of size 2048 and cause its network layer to fragment the TPDUs). This is likely to be the case in a LAN where all protocol entities are under the same administrative control. The maximum TPDU size negotiated is under the control of the user, so it is possible to prevent this scheme from being used by default when the peer is not on the same LAN, by setting the \fItp.tpdusize\fR parameter in the ARGO directory service file to something less than the network's maximum transmission unit. .\"*********************************************************** .sh 2 "Congestion Window Strategy" .pp The congestion window strategy from the DoD Internet was adapted for use with TP. The strategy is intended to minimize the adverse effect of transport's retransmission on an already congested network. .pp A TP entity keeps two notions of the peer's window: the real window, which is that advertised by the peer in AK TPDUs, and the congestion window, which is a locally controlled window. TP uses the smaller of the two windows when transmitting. The congestion window starts small, which keeps a new connection from overloading the network with a sudden burst of packets immediately after connection establishement. This is called \fIslow start\fR. For each successful acknowledgment received, the congestion window grows by one, until eventually the real window is the one in use. If a retransmission timer expires, the congestion window is reset to size one. .pp The congestion window strategy is used for class 4 unless the transport user requests that it not be used. The slow start strategy is used for traffic over a PDN unless the transport user requests that it not be used. Slow start is not used for traffic over a LAN unless its use is requested by the transport user. .\"*********************************************************** .sh 2 "Retransmission strategies" .pp A retransmission timer is invoked for each set of DT TPDUs sent in one send operation (call to \fItp_send()\fR). This set of packets is called the \fIsend window\fR for the purpose of this discusssion. .pp The number of TPDUs in a send window depends on the remote credit and the amount of data in the local send buffers. When a retransmission timer goes off, the lower window edge is reevaluated but the upper window edge is not reevaluated. .pp There are several retransmission strategies implemented in ARGO TP. The choice of strategies is the user's, and is made with the \fIsetsockopt()\fR system call. The strategies are summarized here: .ip "Retransmit LWE TPDU only:" 5 Only the TPDU representing the new lower window edge is retransmitted. This is the default retransmission strategy. .ip "Retransmit whole send window:" 5 Retransmission begins with the new lower window edge and continues up to the old upper window edge. .pp The value of the data retransmission timer adapts to the average round trip time and the standard deviation of the round trip time. A round trip time is the time that passes between the moment of a packet's first transmission and the moment it is first acknowledged. The average round trip time is kept by the sending side of TP, using a formula for smoothing the average: .(b \fC .TS tab(+); l l l l. #define+TP_RTT_ALPHA+3 #define+TP_RTV_ALPHA+2 +++ #define+SMOOTH(alpha, old, new) \\ +(((new-old) >> alpha ) \+ (old) ) .TE \fR .)b .lp The times included in the average are chosen as follows. The time of each packet's initial transmission is kept (for the last \fIN\fR packets, where \fIN\fR is a defined constant). When an AK TPDU arrives, ARGO TP subtracts the initial transmission time for the lowest unacknowledged sequence number that was acknowledged by this AK TPDU from the current time, and apply the resulting time to the average. Hence, not all packets are included in this average, which is as it should be since the purpose of this measurement is to find a good value for the retransmission timer. .pp Each time part of a window is retransmitted, the retransmission timer for that window is increased. This does not affect the retransmission timers for other windows. .\"*********************************************************** .sh 2 "Acknowledgment strategies" .pp The transport protocol specification requires acknowledgments to be sent immediately upon receipt of CC TPDUs (in class 4), XPD TPDUs, and DT TPDUs containing an EOT marker, and at other times as required for flow control, otherwise acknowledgments may be delayed. In addition to the times when an acknowledgment is required, ARGO TP transmits an AK TPDU whenever the user receives some data, thereby increasing the size of the window. For those times when immediate acknowledgment is optional, ARGO TP offers two acknowledgment strategies: .ip " Acknowledge each TPDU" 10 Upon receipt of a DT TPDU and AK TPDU is sent. .ip " Acknowledge full window" 10 Acknowledgment is issued upon receipt of enough data to consume the last advertised credit. .pp The latter strategy requires a timer to trigger an acknowledgment in case the peer doesn't send the entire window quickly. This timer is called the \fIsendack timer\fR. The upper bound on the value of this timer is called the \fIlocal acknowledgment time\fR. The local acknowledgment time may be "advertised" to the peer during connection establishment, and the peer may choose to use this value to adjust its retransmission timers. The ARGO TP entity advertises its local acknowledgment time on a CR TPDU, but it is not constrained by the remote acknowledge time, should the peer advertise it. Instead, ARGO TP adapts its sendack timer to the behavior of the connection. .pp Under the assumption that the round trip time is often symmetric, and lacking a method to measure the round trip time in the other direction, ARGO TP uses the measured average round trip time to adjust the the sendack timer. .pp The choice of strategies is made with the \fIsetsockopt()\fR system call. The default strategy is to delay acknowledgments until the most recently advertised window is filled.