RFC 1143

The Q Method of Implementing TELNET Option Negotiation

๐Ÿ“… published February 1990, from ietf

RFC 1143 ๐ŸŽ€ The Q Method of Implementing TELNET Option Negotiation

Part 1 of 3 ๐Ÿ’œ more to come!

okay bestie, this is RFC 1143 ๐Ÿ’…, written by D. Bernstein at NYU for the Network Working Group โœจ, dated February 1990. Its title block doesn't list anything it obsoletes or updates, and there's no category tag up top, but the Status section tells us it's an experimental method of implementing a protocol ๐Ÿงš, not a protocol spec.

Status of This Memo ๐ŸŒธ

so this doc is about how you could handle option negotiation in the Telnet protocol (that's RFC 854) ๐Ÿ–ฅ๏ธ. It doesn't propose any changes to TELNET. It only covers the implementation of one feature, not the whole protocol. It's not a protocol specification, it's an experimental method, and it's not a recommendation from the Telnet Working Group of the Internet Engineering Task Force (IETF) ๐Ÿ™ˆ. It's copyright 1990, Daniel J. Bernstein, but you can distribute it in its original form, unlimited ๐Ÿ’œ.

1. Introduction โœจ

this RFC levels up the RFC 854 [7] option negotiation rules and guidelines ๐Ÿ”ฅ. Those rules aren't enough by themselves to stop every option negotiation loop ๐Ÿ’€. It also shows an example of correct implementation ๐Ÿฆ‹.

DISCUSSION:

the two things implementors care about most are 1. the loop examples below and 2. the TELNET state machine that prevents loops ๐Ÿง .

  1. Implementors of TELNET should read the loop examples and watch out, because preventing loops is a nontrivial task ๐Ÿ˜ญ.
  2. Section 7 of this RFC shows by example a working method of avoiding loops ๐Ÿ‘‘. It lays out the state information you must keep about each side of each option. It shows what to do in each state when you receive WILL/WONT/DO/DONT from the network, and when the user or process asks for an option to be enabled or disabled. An implementor who follows the procedures in that example doesn't need to worry about compliance with this RFC or with a large chunk of RFC 854 ๐Ÿ’–.

In short, all implementors should be familiar with TELNET loops, and some implementors may wish to use the pre-written example here when writing a new TELNET implementation ๐ŸŒˆ.

NOTE: Reading This Document ๐Ÿ“–

here's the compliance tea โ˜•. A TELNET implementation is not compliant with this RFC if it doesn't satisfy every rule marked MUST. It is compliant if it satisfies all the MUST rules. And if it's compliant, it's unconditionally compliant when it also satisfies all the rules marked SHOULD, and conditionally compliant otherwise. Rules marked MAY are optional ๐Ÿซถ.

Options are almost always negotiated separately for each side of the connection ๐Ÿฆ‹. The option on one side is its own thing, separate from the option on the other side. In this document, "the" option referred to by a DONT/WONT or DO/WILL is really two options, only combined for semantic convenience. Each sentence could be split into two, one with the words before the slash and one with the words after the slash.

An implementor should be able to tell whether an implementation complies with this RFC without reading any text marked DISCUSSION ๐Ÿ”. An implementor should also be able to build option negotiation machinery that complies with both this RFC and RFC 854, using just the information in Section 7 โœจ.

2. RFC 854 Option Negotiation Requirements ๐Ÿ”ฅ

straight from RFC 854: a TELNET implementation MUST obey a refusal to enable an option. Like, if it gets a DONT/WONT back after sending a WILL/DO, it MUST NOT enable the option ๐Ÿ™….

DISCUSSION:

when RFC 854 hints that the other side can say no to an enable request, it means you have to accept that no ๐Ÿ’….

So it MUST remember that it's negotiating a WILL/DO ๐Ÿง . That negotiation state MUST be kept separate from the enabled state and from the disabled state. While it's in the negotiation state, any effects of having the option enabled MUST NOT be used.

If it gets WONT/DONT while the option is enabled, it MUST respond DONT/WONT respectively and disable the option ๐Ÿซ . It MUST NOT start a DO/WILL negotiation for an option that's already enabled, or a DONT/WONT negotiation for one that's disabled. It MUST NOT respond to a negotiation like that. But it MUST respond to a negotiation that does propose changing the status quo ๐ŸŒธ.

DISCUSSION:

lots of existing implementations answer a rejection by confirming it. So if they send WILL and get a DONT, they send a WONT back ๐Ÿ™„. People have read this as acceptable under a certain (strained) reading of RFC 854. But allowing this possibility seriously complicates the later rules, and there doesn't seem to be any use for the wasted bandwidth and processing ๐Ÿ’€. Also, an implementation that complies with this RFC will simply ignore the extra WONT if the other side sends it ๐Ÿซถ.

The implementation MUST NOT automatically answer a rejection by sending a brand new request ๐Ÿ™…. As a rule of thumb, new requests should go out either at the start of a connection or in response to an external stimulus, i.e., input from the human user or from the process behind the server ๐Ÿ’–.

A TELNET implementation MUST refuse (DONT/WONT) any request to enable an option whose appropriate protocol specification it doesn't comply with ๐Ÿšซ.

DISCUSSION:

RFC 854 doesn't say this as strongly, but any other move would be counterproductive ๐Ÿ˜ค. This rule shows up in Requirements for Internet Hosts [6, Section 3.2.2], and it's repeated here for completeness.

3. Rule: Remember DONT/WONT requests ๐Ÿฆ‹

A TELNET implementation MUST remember starting a DONT/WONT negotiation ๐Ÿง .

DISCUSSION:

it isn't clear from RFC 854 whether TELNET must remember beginning a DONT/WONT negotiation ๐Ÿค”. There don't seem to be any reasons to remember starting one:

  1. The argument for remembering a DO/WILL negotiation (viz., the state of negotiating for enabling means different things for the data stream than the state of having the option enabled) doesn't apply.
  2. There's no choice for the other side in responding to a DONT/WONT. The option is going to end up disabled.
  3. If we just disable the option right away and forget about negotiating, we'll ignore the WONT/DONT response, since the option is already disabled.

Unfortunately, that conclusion is wrong ๐Ÿ’€. Here's a TELNET conversation between two parties, "us" and "him". (If you want a different view, you could sort the steps into chronological order yourself ๐Ÿงฉ.)

      LOOP EXAMPLE 1

         Both sides know that the option is on.

         On his side:
       1 He decides to disable.  He sends DONT and disables the option.
       2 He decides to reenable.  He sends DO and remembers he is
         negotiating.
       5 He receives WONT and gives up on negotiation.
       6 He decides to try once again to reenable.  He sends DO and
         remembers he is negotiating.
       7 He receives WONT and gives up on negotiation.
         For whatever reason, he decides to agree with future requests.
      10 He receives WILL and agrees. He responds DO and enables the
         option.
      11 He receives WONT and sighs. He responds DONT and disables the
         option.
         (repeat 10 and then 11, forever)

         On our side:
       3 We receive DONT and sigh.  We respond WONT and disable the
         option.
       4 We receive DO but disagree.  We respond WONT.
       8 We receive DO and decide to agree.  We respond WILL and enable
         the option.
       9 We decide to disable.  We send WONT and disable the option.
         For whatever reason, we decide to agree with future requests.
      12 We receive DO and agree.  We send WILL and enable the option.
      13 We receive DONT and sigh.  We send WONT and disable the option.
         (repeat 12 and then 13, forever)

Both sides followed RFC 854, and yet we still end up in an option negotiation loop ๐Ÿ˜ฑ. DONT DO DO, then DO DONT, go round and round forever in one direction, while WONT WONT, then WILL WONT, do the same in the other direction ๐Ÿ”. Steps 1 and 9 are what cause this loop, which is why this section has its rule ๐ŸŽ€. Section 6 below discusses whether "negotiate for disable" and "negotiate for enable" need separate states, or whether a single "negotiate" state is enough ๐Ÿ”ฎ.

4. Rule: Prohibit new requests before completing old negotiation โœจ

A TELNET implementation MUST NOT initiate a new WILL/WONT/DO/DONT request about an option that is under negotiation, i.e., for which it has already made such a request and not yet received a response. ๐Ÿ™…โ€โ™€๏ธ

DISCUSSION:

Okay so RFC 854 doesn't really say whether you can send new requests about an option that's already mid-negotiation ๐Ÿค” And honestly, banning "option typeahead" feels kinda harsh, but here's the thing: it goes wrong fast. Look at this:

LOOP EXAMPLE 2

   Suppose an option is disabled, and we decide in quick
   succession to enable it, disable it, and reenable it.  We send
   WILL WONT WILL and at the end remember that we are negotiating.
   The other side agrees with DO DONT DO. We receive the first DO,
   enable the option, and forget we have negotiated.  Now DONT DO
   are coming through the network and both sides have forgotten
   they are negotiating; consequently we loop.

Ngl that's a whole mess ๐Ÿ’€ Both sides forget they're negotiating, and then the DONT and DO just keep bouncing around forever. ๐Ÿ”

Basically, every possible TELNET loop ends up looking the same: WILL WONT (or WONT WILL, or WILL WONT WILL WONT, etc.) floats through the network while both sides think negotiation is over. The response is DO DONT, and we loop forever. TELNET implementors are encouraged to build any option that can spot a loop like this and cut it off. For example, a way to explicitly tell requests apart from acknowledgments would do the job. But no such option existed as of February 1990. ๐Ÿ˜ญ

This one is a big deal in real life too. Most combos of existing user-server TELNET implementations do get stuck in an infinite loop if you quickly ask them a few times to enable and then disable an option. ๐Ÿ™ˆ It matters even more now that LINEMODE [4] is out, since LINEMODE is the first option that tends to fire off rapidly changing requests during normal use. So yeah, a new rule is definitely needed. ๐Ÿ’…

Some people tried to stop the several-alternating-requests problem by keeping more detailed state than YES/NO/WANTwhatever, like a state that records every outstanding request. Dave Borman proposed a scheme [2] that seems to work, won't blow up even if both sides start several requests at once, and appears to stop option negotiation loops. The catch is that fully analyzing it is kind of tough, because TELNET would no longer be a finite-state automaton. He implemented it in the latest BSD telnet version [5], but as of May 1989 he didn't plan to publish it for others to use [3]. ๐Ÿซฃ

So the author decided to keep TELNET's finite-state property, for robustness, and because the result is easy to prove works. โœจ That's why we have the rule above.

A stricter option would be to buffer all data and do absolutely nothing until the response comes back. There's no obvious reason to do that, though some existing TELNET implementations do it anyway at the start of a connection, when most options get negotiated. ๐Ÿคทโ€โ™€๏ธ

5. How to reallow the request queue

DISCUSSION:

The rule above stops more than one request from being queued up through the network. BUT it's still possible to queue new requests inside the TELNET implementation, so "option typeahead" is basically back, just in a different way. ๐Ÿฆ‹

The obvious approach is to keep a list of requests that have been made but not yet sent, so that when one negotiation finishes, the next one can start right away. So while negotiating for a WILL, TELNET could buffer the user's requests for WONT, then WILL again, then WONT, then WILL, then WONT, as many as wanted.

Ugh, but that needs a dynamic and potentially unmanageable buffer. ๐Ÿ˜ฉ Here's the good part, though: the restrictions on possible requests mean the list can only ever alternate between WONT and WILL. It's wasteful to enable an option and then disable it, just to enable it again. We might as well just enable it in the first place. The few possible exceptions don't outweigh the huge simplification you get from only remembering the last few entries on the queue. ๐Ÿง โœจ

To be more precise: during a WILL negotiation, the only sensible queues are WONT and WONT WILL, and it's the same in reverse during a WONT negotiation. For simplicity, the Q method does not allow the WONT WILL possibility. ๐Ÿ™…โ€โ™€๏ธ

So now we're left with a queue that's either empty or holds the opposite of the current negotiation. When we get a reply to the negotiation, if the queue says the option should change, we send the opposite request immediately and empty the queue. This doesn't conflict with the RFC 854 rule about automatic regeneration of requests, because these new requests are just the delayed effects of user or process commands. ๐Ÿ’–

An implementation SHOULD support the queue, where support is defined by the rules following.

If it does support the queue, and an option is currently under negotiation, it MUST NOT handle a new request from the user or process to switch that option's state by sending a new request through the network. Instead, it MUST remember internally that the new request was made.

If the user or process makes a second new request to switch back again while the original negotiation is still incomplete, the implementation SHOULD handle it simply by forgetting the previous one. The third request SHOULD be treated like the first, and so on. In any case, these further requests MUST NOT generate immediate requests through the network. ๐Ÿ”„

When the option negotiation completes, if the implementation is remembering a request internally and that request is for the opposite state to the result of the completed negotiation, then the implementation MUST act as if that request had been made after the negotiation completed. It SHOULD thus immediately generate a new request through the network. ๐Ÿš€

The implementation MAY provide a method for turning support for the queue off and back on. In that case, it MUST default to having the support turned on. Furthermore, when support is turned off, if the implementation is remembering a new request for an outstanding negotiation, it SHOULD keep remembering it and then deal with it at the close of the outstanding negotiation, as if support were still turned on through that point. ๐ŸŽ€

DISCUSSION:

The author believes this queue system fully restores TELNET's functionality, and it's intended to. ๐Ÿ‘‘ Dave Borman also gave some informal analysis of this issue [1]. The biggest possible problem to note is that certain options which might need buffering could be slowed down by the queue. The author thinks buffering delays are independent of the implementation method, so the Q Method doesn't cause any problems. Examples back that up. ๐ŸŒธ

6. Rule: Separate WANTNO and WANTYES

Implementations SHOULD separate any states of negotiating WILL/DO from any states of negotiating WONT/DONT. ๐Ÿฆ‹

DISCUSSION:

You can keep a working TELNET implementation if you simplify the NO/YES/WANTNO/WANTYES states down to NO/YES/WANT. ๐Ÿ™ƒ

BUT in a hostile environment that's a bad idea. It means handling a DO/WILL response to a WONT/DONT can't be done correctly. ๐Ÿšจ Plus it doesn't really simplify the code, and whatever simplicity you gain gets eaten up by the extra complexity you need for the queue. Not it. ๐Ÿ’…

7. Example of Correct Implementation

To make writing TELNET implementations easier, the author gives a precise example of the response a compliant TELNET implementation could give in each possible situation. All TELNET implementations compliant with this RFC SHOULD follow the procedures shown here. ๐Ÿงš

EXAMPLE STATE MACHINE
FOR THE Q METHOD OF IMPLEMENTING TELNET OPTION NEGOTIATION

There are two sides, we (us) and he (him). We keep four variables:

us: state of option on our side (NO/WANTNO/WANTYES/YES)
usq: a queue bit (EMPTY/OPPOSITE) if us is WANTNO or WANTYES
him: state of option on his side
himq: a queue bit if him is WANTNO or WANTYES

An option is enabled if and only if its state is YES. Note that us/usq and him/himq could be combined into two six-choice states. ๐Ÿค“

"Error" below means that producing diagnostic information may be a good idea, though it isn't required.

Upon receipt of WILL, we choose based upon him and himq:

   NO            If we agree that he should enable, him=YES, send
                 DO; otherwise, send DONT.
   YES           Ignore.
   WANTNO  EMPTY Error: DONT answered by WILL. him=NO.
        OPPOSITE Error: DONT answered by WILL. him=YES*,
                 himq=EMPTY.
   WANTYES EMPTY him=YES.
        OPPOSITE him=WANTNO, himq=EMPTY, send DONT.

* This behavior is debatable. DONT will never be answered by WILL over a reliable connection between TELNETs compliant with this RFC, so this was chosen (1) not to generate further messages, because if we know we're dealing with a noncompliant TELNET we shouldn't trust it to be sensible; (2) to empty the queue sensibly. ๐Ÿคจ

Upon receipt of WONT, we choose based upon him and himq:

   NO            Ignore.
   YES           him=NO, send DONT.
   WANTNO  EMPTY him=NO.
        OPPOSITE him=WANTYES, himq=NONE, send DO.
   WANTYES EMPTY him=NO.*
        OPPOSITE him=NO, himq=NONE.**

* Here is the only spot a length-two queue could be useful. After a WILL negotiation was refused, a queue of WONT WILL would mean to request the option again. This seems like too little utility and too much potential waste, since there's little chance the other side will change its mind immediately. ๐Ÿ™„

** Here we don't have to generate another request, because we've been "refused into" the correct state anyway. ๐Ÿซถ

If we decide to ask him to enable:

   NO            him=WANTYES, send DO.
   YES           Error: Already enabled.
   WANTNO  EMPTY If we are queueing requests, himq=OPPOSITE;
                 otherwise, Error: Cannot initiate new request
                 in the middle of negotiation.
        OPPOSITE Error: Already queued an enable request.
   WANTYES EMPTY Error: Already negotiating for enable.
        OPPOSITE himq=EMPTY.

If we decide to ask him to disable:

   NO            Error: Already disabled.
   YES           him=WANTNO, send DONT.
   WANTNO  EMPTY Error: Already negotiating for disable.
        OPPOSITE himq=EMPTY.
   WANTYES EMPTY If we are queueing requests, himq=OPPOSITE;
                 otherwise, Error: Cannot initiate new request
                 in the middle of negotiation.
        OPPOSITE Error: Already queued a disable request.

We handle the option on our side by the same procedures, with DO-WILL, DONT-WONT, him-us, and himq-usq swapped. ๐Ÿ”๐Ÿ’œ

8. References โœจ

okay bestie, this is the bibliography ๐Ÿ“š every source the spec leans on, with all its details. ngl the private communications are giving mystery ๐Ÿ•ต๏ธโ€โ™€๏ธ

[1] Borman, D., private communication, April 1989.

[2] Borman, D., private communication, May 1989.

[3] Borman, D., private communication, May 1989.

[4] Borman, D., Editor, "Telnet Linemode Option", RFC 1116, Cray Research, August 1989.

[5] Borman, D., BSD Telnet Source, November 1989.

[6] Braden, R., Editor, "Requirements for Internet Hosts -- Application and Support", RFC 1123, USC/Information Sciences Institute, October 1989.

[7] Postel, J., and J. Reynolds, "Telnet Protocol Specification", RFC 854, USC/Information Sciences Institute, May 1983.

9. Acknowledgments ๐ŸŽ€

shoutout to Dave Borman ๐Ÿซถ who gave super helpful comments. the whole thing is better because of him, no cap โœจ

Author's Address ๐Ÿ’Œ

Daniel J. Bernstein
5 Brewster Lane
Bellport, NY 11713

Phone:  516-286-1339

Email:  brnstnd@acf10.nyu.edu
๐Ÿ“ฆ where this page comes from