tasman@CS.WISC.EDU (10/30/88)
A few years back, I along with Steve Dyer, Charles Lynn, and Dan Tappan set out to rationalize output flushing in the various BBN implementations of telnet over TCP/IP. The systems involved were the C/30 TAC (Terminal Access Controller), the C/70, DECSystem-20 (running a BBN version of TOPS-20), and a home-grown terminal server called a Fibrenet TC. Dave Plummer and Jon Postel were also valuable sources of advice. Because of the variety of implementations involved, we decided the only practical approach was to use (what recent TCP-IP messages have termed) an "open loop" scheme. The telnet client does not attempt to identify interrupt or flush characters; it simply passes them to the telnet server. It is the responsibility of the server to initiate an output flush, if appropriate. Many operating systems have some character (or string or characters) which, when typed from a hardwired terminal, will cause output to be flushed (i.e. discarded). Presumably, receipt of this character (or characters) causes a flush signal to be sent to the terminal driver, which discards the contents of it's output buffer. The particular character (e.g. ^C, ^O, HX, etc.) is irrelevant. The job of the telnet server is to, upon receipt of a flush signal, emulate the action of a hardwired terminal driver as closely as possible. Herein lies the first caveat: The telnet output stream contains embedded option negotations. Output cannot simply be discarded, or option negotiations may be lost. Thus, even while a flush is in progress, the telnet client still must scan the incoming stream for option negotiations; normal characters can be discarded. Upon receipt of a flush signal, the telnet server must as quickly as possible notify the client that a flush is in progress. This implies two things: a) The notification should be attached to the very next TCP segment sent to the client, even if there are several thousand bytes of TCP data buffered at the server's machine. b) As soon as a TCP segment with a flush indication arrives at the client's machine, the client should be signaled. This is true EVEN IF the TCP segment arrives out of sequence or is a retransmission. N.B.: The data contained in the segment should NOT be presented out-of-sequence; the client simply needs to know to begin flushing. Not surprisingly, this requires careful coding of the TCP implementations used by both the server and client; I'll address this issue again toward the end of the message. So, how is this accomplished? If the "URGENT" bit is set in a TCP segment, the segment contains the identification of an "interesting" byte in the data stream. The sequence number of the "interesting" byte is calculated as follows: sequence number = Segment Sequence Number + Urgent Pointer WARNING WARNING WARNING: Certain pages of the TCP Specification (both RFC 793 and MILSTD 1778), as well as the Stallings Handbook, state that this calculation yields the sequence number of the byte FOLLOWING the "interesting" byte. This is WRONG. Page 8 of the latest "Official Internet Protocols" (RFC1011) provides a clarification. Please check your implementation. Whenever the telnet server receives a flush signal, it inserts the two- character sequence IAC DM in the outgoing data stream, and instructs its TCP to mark the second character (DM) as "interesting". Note that multiple flushes are handled OK; outgoing TCP segments will always identify the latest (e.g. highest-sequence-numbered) "interesting" byte identification. Action by the telnet client is fairly straightforward. When an "interesting" byte identification arrives at the telnet client's machine, the client will be in one of two states: 1) Processing incoming data normally. The TCP implementation will immediately notify the client that there is an "interesting" byte somewhere ahead in the data stream, and the client will enter state 2. 2) Processing a flush (handling option negotiations, but discarding normal data) until the "interesting" byte has been read and an IAC DM sequence has been found. The TCP implementation will simply update its record of the "interesting" byte's sequence number. Thus two flushes in quick succession will be handled as a single longer flush: the client will find the first IAC DM, but discover that the "interesting" byte hasn't yet been read. Note that the preceding discussion assumes that: a) An "interesting byte" is read in-sequence. If byte 101 is "interesting", it will be read after bytes 99 and 100. For telnet, it's useless to be able to read the "interesting" byte out-of-sequence. Instead, the TCP implementation informs the telnet client asynchronously that an "interesting" byte lies somewhere ahead in the data stream. b) After a TCP read, the telnet client is able to inquire whether the "interesting" byte has been read. This is necessary so that the client can determine when to stop flushing. HINT: Because of the specification error regarding the calculation of the sequence number of the "interesting" byte, some TCP implementations may incorrectly mark the byte FOLLOWING the DM as "interesting". A strict interpretation of the telnet specification will result in the client flushing output forever, because the "interesting" byte will not yet have been read at the time the IAC DM is found, and the client will search forever for another IAC DM sequence. Thus, I recommend unconditionally terminating the flush after the "interesting" byte has been read, even if an IAC DM has not been located. Your users will be a lot happier! I previously mentioned that careful coding of the server and client TCP's was necessary for optimal performance. In particular, every TCP segment sent by the server's machine should contain the latest "interesting" sequence number. If possible, this should even apply to retransmissions. The client machine's TCP should immediately check all incoming segments for updated "interesting" byte information. The goal in both cases is to ensure that the telnet client is notified of the flush absolutely as soon as possible. These implementation guidelines become more critical as the TCP receive window size of the client is increased; the amount of TCP data buffered in the server and client machines can become very large indeed! Conclusions: A LOT of work was involved, but our telnet project at BBN was largely successful. The improvement was dramatic for 300 Baud terminals, but even at 9600 baud the reduction in output (after sending a character which resulted in a flush) was quite noticable when displaying short lines. I've been planning to write an RFC on the subject of TCP URGENT and its use with telnet; given the sudden flurry of interest, perhaps now would be a propitious time. An expanded (and more coherent!) version of this message may someday appear. Best of luck. Mitchell Tasman University of Wisconsin-Madison and BBN Communications Corporation
hedrick@geneva.rutgers.edu (Charles Hedrick) (11/02/88)
I appreciate your detailed description of output buffer flushing in telnet. I'd like to point out to other readers that your work apparently did not involve any extensions to the protocols. I feel it necessary to make this comment because the last few days have been filled with various proposals for inventing new mechanisms to do something for which there is an existing one. So we need to make it clear to people that your posting is not another such proposal: it describes correct implementation of the existing telnet sync mechanism.
LYNCH@A.ISI.EDU (Dan Lynch) (11/02/88)
Mitch, Thanks for the excellent treatise on Telnet flushing. While your "netnote" is great reading, you are right about putting in in the record with an RFC. We are only as good as our experiences and their "recording" so others can benefit from themn. Dan -------