brnstnd@stealth.acf.nyu.edu (Dan Bernstein) (04/18/89)
DRAFT RFC: Implementing TELNET Option Negotiation April 1989 Daniel J. Bernstein, brnstnd@stealth.acf.nyu.edu Status of This Memo This is a draft RFC supplementing RFC 854, TELNET PROTOCOL SPECIFICATION. It is intended as a specification for the Internet community. Distribution of this memo is unlimited. SECTION 1. Introduction This RFC amplifies, supplements, and extends the RFC 854 option negotiation rules and guidelines, which are insufficient to prevent all option negotiation loops. This RFC also presents an example of correct implementation. A TELNET implementation is not compliant with this RFC if it fails to satisfy all rules marked MUST. It is compliant if it satisfies all rules marked MUST. If it is compliant, it is unconditionally compliant if it also satisfies all rules marked SHOULD and conditionally compliant otherwise. Rules marked MAY are optional. Options are in almost all cases negotiated separately for each side of the connection. We will consider the option on one side to be separate from the option on the other side. Thus when we talk about ``the'' option referred to by a DONT/WONT or a DO/WILL, we are only combining the cases 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. SECTION 2. RFC 854 Option Negotiation Requirements We require the following, as per RFC 854: A TELNET implementation MUST obey a refusal to enable an option; i.e., if it receives a DONT/WONT in response to a WILL/DO, it MUST NOT enable the option. It MUST therefore remember that it is negotiating a WILL/DO, and this negotiation state MUST be separate from the enabled state and from the disabled state. During the negotiation state, any effects of having the option enabled MUST NOT be used. If it receives WONT/DONT and the option is enabled, it MUST respond DONT/WONT repectively and disable the option. It MUST NOT initiate a DO/WILL negotiation for an already enabled option or a DONT/WONT negotiation for a disabled option. It MUST NOT respond to receipt of such a negotiation. It MUST respond to receipt of a negotiation that does propose to change the status quo. The implementation MUST NOT automatically respond to the rejection of a request by submitting a new request. As a rule of thumb, new requests should be sent either at the beginning of a connection or in response to an external stimulus, i.e., input from the human user or from the process behind the server. Though this is not stated as strongly in RFC 854, a TELNET implementation MUST refuse (DONT/WONT) a request to enable an option for which it does not comply with the appropriate protocol specification. Any other action is counterproductive. SECTION 3. Rule: Remember DONT/WONT requests It is not clear from RFC 854 whether or not TELNET must remember beginning a DONT/WONT negotiation. The argument for remembering a DO/WILL negotiation does not apply, because WANTNO (negotiating for disabling) does not differ from NO the way that WANTYES differs from YES. (In state YES, the option is enabled; in state WANTYES, the option is disabled. However, in both states NO and WANTNO, the option is disabled.) There is no choice for the other side in responding to a DONT/WONT; the option is going to end up disabled. When we receive the WONT/DONT response, we will ignore it since the option is disabled. It appears then that we might as well not remember starting a DONT/WONT negotiation. Unfortunately, that conclusion is wrong. Consider the following TELNET conversation between two parties, ``me'' and ``him''. (The reader of this RFC may want to sort the steps into chronological order for a different view.) 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 have followed RFC 854; but we end in an option negotiation loop, as DONT DO DO and then DO DONT forever travel through the network one way, and WONT WONT followed by WILL WONT forever travel through the network the other way. The behavior in steps 1 and 9 is responsible for this loop. Therefore: A TELNET implementation MUST remember starting a DONT/WONT negotiation. We will consider later whether separate states are needed for WANTNO and WANTYES or a single negotiation state will suffice. SECTION 4. Rule: Prohibit new requests before completing old negotiation It is also unclear from RFC 854 whether or not a TELNET implementation may allow new requests about an option that is currently under negotiation; it certainly seems limiting to prohibit ``option typeahead.'' Unfortunately, it is necessary. Suppose an option is disabled, and I decide in quick succession to enable it, disable it, and reenable it. I send WILL WONT WILL and at the end remember that I am negotiating. The other side agrees with DO DONT DO. I receive the first DO, enable the option, and forget I have negotiated. Now DONT DO are coming through the network and both sides have forgotten they are negotiating; and consequently we loop. (All possible TELNET loops eventually degenerate into this same form, where WILL WONT [or WONT WILL, or WILL WONT WILL WONT, etc.] go through the network while both sides think negotiation is over; the response is DO DONT and this loops forever. TELNET implementors are encouraged to implement any option that can detect such a loop and cut it off; e.g., a method of explicitly differentiating requests from acknowledgments would be sufficient. No such option exists as of April 1989.) This particular case is of considerable practical importance, since most combinations of existing user-server TELNET implementations do enter an infinite loop when asked quickly a few times to enable and then disable an option. It is clear that a new rule is needed. One might try to prevent the several-alternating-requests problem by maintaining a more elaborate state than YES/NO/WANTwhatever, e.g., a state that records all outstanding requests; but it is difficult to specify such a scheme so that it won't blow up if both sides initiate several requests at once. Another possible answer would be to wait for a response before continuing at all; but this is very limiting and impractical over slow networks, and it is more restrictive than the solution we actually adopt, which is as follows: 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. SECTION 5. How to reallow the request queue The above rule prevents queueing of more than one request through the network. We discuss here how to queue new requests within the TELNET implementation, so that ``option typeahead'' is effectively restored. An obvious possibility is to maintain a list of requests that have been made but not yet sent, and when one negotiation completes, the next can be started immediately. So while negotiating for a WILL, TELNET could buffer the user's requests for WONT, then WILL again, then WONT, then WILL, then WONT, buffering as far as desired. This requires a dynamic and potentially unmanageable buffer. However, the restrictions upon possible requests guarantee that the list of requests must simply alternate between WONT and WILL. It is 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 to this rule do not outweigh the immense simplification afforded by remembering only 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 similarly during a WONT negotiation. In the interest of simplicity, we eliminate the WONT WILL possibility, though this is debatable. We are now left with a queue consisting of either nothing or the opposite of the current negotiation. When the TELNET implementation receives a reply to the negotiation, if the queue indicates that the option should be changed, it sends the opposite request immediately and empties the queue. Note that this does not conflict with the RFC 854 rule about automatic regeneration of requests, as these new requests are simply the delayed effects of user or process commands. Now the specific rules: An implementation SHOULD support the queue. If it does support the queue, it MUST handle a new request from the user or process before the option negotiation is complete as follows: If the queue is EMPTY, then set it to OPPOSITE if the request is for the opposite of the previous negotiation and otherwise indicate an error or ignore the request. If the queue is OPPOSITE, then set it to EMPTY if the request is for the same as the previous negotiation and otherwise indicate an error or ignore the request. If the queue is set to OPPOSITE and the negotiation completes in the affirmative (DO-WILL/WILL-DO, DONT-WONT/WONT-DONT), the TELNET implementation MUST immediately generate a new request for the opposite effect, and set the queue to EMPTY. The implementation MAY provide a method by which support for the queue may be turned off and back on. In this case, it MUST default to having the support turned on. SECTION 6. Rule: Separate WANTNO and WANTYES It is possible to maintain a working TELNET implementation if the NO/YES/WANTNO/WANTYES states are simplified to NO/YES/WANT. However, in a hostile environment this is a bad idea, as it means that handling a DO/WILL response to a WONT/DONT cannot be done correctly. It does not greatly simplify code; and the simplicity gained is lost in the extra complexity needed to maintain the queue. Thus, implementations MUST separate the state of negotiating WILL/DO from the state of negotiating WONT/DONT. 7. Example of Correct Implementation To ease the task of writing TELNET implementations, we present here a precise example of the response that a compliant TELNET implementation could give in each possible situation. All TELNET implementations SHOULD follow the procedures shown here, and implementors are very strongly encouraged to follow them unless they have an excellent reason otherwise. There are two sides, I (me) and he (him). I keep several variables. me: state of option on my side (NO/WANTNO/WANTYES/YES); also, if me is WANTNO or WANTYES, a ``queue bit'' meq (EMPTY/OPPOSITE). him: state of option on his side; also, if him is WANTNO or WANTYES, a ``queue bit'' himq. An option is enabled if and only if its state is YES. Note that me and meq could be combined into a NO/WANTNO/ WANTNOOPPOSITE/WANTYES/WANTYESOPPOSITE/YES state. ``Error'' below means that producing diagnostic information may be a good idea, though it isn't required. Upon receipt of WILL, I 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, I 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 of too little utility and too much potential waste; there is little chance that 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. End of DRAFT RFC