Telnetd flow control

Doug Graham dgraham at nortel.com
Sun Feb 1 18:48:57 UTC 2009


On Sun, Feb 01, 2009 at 05:14:46PM +0100, Denys Vlasenko wrote:
> On Thursday 22 January 2009 07:20, Doug Graham wrote:
> > Hello,
> > 
> > Busybox's telnetd does not disable local (client-side) flow control
> > properly.  It does not put the pty into packet mode and then notify the
> > client whenever flow control is disabled by an application running under
> > its control.
> 
> I'm confused. Are you talking about the case when application prints ^S?
> What should happen then? We should send corresponding telnet sequence
> to the network peer?

Yeah, I could have been clearer :-).  I mean the case where a user uses
a fully-functional telnet client to telnet in to a busybox telnetd, then
fires up emacs, then types ^S to initiate a search.  In this case, the
user's ^S is eaten by the terminal driver on the client machine and never
makes it to the server.  This is apparently not a problem if the busybox
telnet client is used, because it never enables remote flow control (ie:
the busybox telnet client always clears IXON in the termios c_iflag on
the client side, so it always passes ^S/^Q through to the server.

As I understand it, a fully functional client will by default pass ^S/^Q
through to the server, unless the telnet remote flow control option
has been negotiated.  When remote flow control *is* negotiated via the
TELOPT_LFLOW option (named TOGGLE-FLOW-CONTROL in RFCs 1080 and 1372),
the client enables flow control in the terminal driver on the client side
(by setting IXON).  Then when an application on the server disables flow
control by clearing IXON in the server side PTY, the telnet server is
supposed to notice this change and send the client a notification that
flow control should be disabled on the client side.

An alternate solution to the immediate problem is to simply never
enable the TELOPT_LFLOW option at all.  Busybox's telnetd sends
{IAC, DO, TELOPT_LFLOW} when the connection is established; if that is
simply removed, then emacs appears to work fine.  But, although the RFC
doesn't really explain the rationale for this option very well at all,
I think the reason that the remote flow control option exists is that
XON/XOFF flow control is more responsive when flow control is done at the
client side.  The alternative is that ^S/^Q get sent through as normal
characters to the server, which simply writes them to the master side of
the PTY and leaves it up to the terminal driver on the server to stop the
flow on the server side (which means any characters currently buffered
by the server or in-flight will be written to the user's terminal before
the flow stops).

Bottom line is that the server should not be enabling the TELNET_LFLOW
option unless it is prepared to actually implement the rest of RFC 1080.

> If my understanding above is correct, then it looks like
> you are not using correct bits:
> 
> Docs say:
> 
> TIOCPKT         Enable/disable packet mode. When applied to the master side of a pseudo terminal, each
> subsequent read(2) from the terminal will return data written on the slave part of the pseudo terminal preceded by a
> zero byte (symbolically defined as TIOCPKT_DATA), or a single byte reflecting control status information.
> In the latter case, the byte is an inclusive-or of zero or more of the bits:
> 
> TIOCPKT_FLUSHREAD     whenever the read queue for the terminal is flushed.
> TIOCPKT_FLUSHWRITE    whenever the write queue for the terminal is flushed.
> TIOCPKT_STOP    whenever output to the terminal is stopped a la ^S.
> TIOCPKT_START   whenever output to the terminal is restarted.
> TIOCPKT_DOSTOP  whenever t_stopc is ^S and t_startc is ^Q.
> TIOCPKT_NOSTOP  whenever the start and stop characters are not ^S/^Q.
> 
> The code is:
> 
> +                       control = TS_BUF2[ts->rdidx2];
> ...
> +                       if ((control & (TIOCPKT_DOSTOP|TIOCPKT_NOSTOP)) != 0
> +                        && ts->flowstate != (control & TIOCPKT_DOSTOP)) {
> 
> Shouldn't you check for TIOCPKT_STOP, not TIOCPKT_DOSTOP?

I'm not all that clear on what TIOCPKT_STOP and TIOCPKT_START would ever
be used for, but they're not the bits I want.  It's the TIOCPKT_DOSTOP
and TIOCPKT_NOSTOP bits that are set when a process on the slave PTY
sets or clears IXON in the termios c_iflag word,

> > The result is that ^S/^Q are not passed through to the 
> > application,
> 
> Huh? ^S/^Q are coming FROM application, right? Or are you talking
> about different case here?

Yep, different case.  Unless IXOFF is set in either of the two terminal
drivers (the client's tty or the server's pty), I don't think that ^S/^Q
coming *from* the application pose a problem.  IXOFF almost never set,
and anyway, I don't think emacs or any other well-behaved application
ever sends a literal ^S/^Q.

> What do I need to do to see the problem?

Emacs is the only application that I know of that actually uses
^S/^Q for things other than flow control, so I guess the first
step is to install emacs or an emacs clone.  I've never tried to
install the full-blown GNU emacs on a small system running busybox,
but testing could be done on a desktop machine if you don't want to
install emacs on an embedded system.  Or you could install this one:
ftp://ftp.kernel.org/pub/software/editors/uemacs/em-4.0.15-lt.tar.bz2
but beware that this version also has a problem with ^S/^Q because it
doesn't clear IXON.  You'll need the following patch before ^S/^Q will
work at all:

--- posix.c     2009/02/01 13:31:09     1.1
+++ posix.c     2009/02/01 13:32:12
@@ -51,6 +51,7 @@
        /* raw CR/NL etc input handling, but keep ISTRIP if we're on a 7-bit line */
        ntermios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK
                              | INPCK | INLCR | IGNCR | ICRNL);
+       ntermios.c_iflag &= ~IXON;
 
        /* raw CR/NR etc output handling */
        ntermios.c_oflag &= ~(OPOST | ONLCR | OLCUC | OCRNL | ONOCR | ONLRET);

You may also need another tweak or two just to get the thing built,
but nothing too onerous.  If you want my complete set of patches, let
me know.

Anyway, the idea is to use a non-busybox telnet client to telnet in to
a busybox telnetd, then launch emacs, then type ^S.  Emacs should start
a search.  If it does nothing, then flow control is screwed up somewhere.

If you want, I can write a simple test program that demonstrates the
problem with telnetd without requiring that you install emacs.

Thanks for looking at this,
Doug.


More information about the busybox mailing list