[comp.protocols.tcp-ip] DRAFT RFC: Implementing TELNET Option Negotiation

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