下面的debug資訊來自與/tmp/telenet.debug檔案的一部分。
是客戶端按下字母e後發生的四個階段。這篇主要分析第一階段和第二階段。
也就是telrcv函式的主要的功能。
td: netread 1 chars
nd: 65 e
td: ptyflush 1 chars
pd: 65 e
td: ptyread 2 chars
pd: 0065 .e
td: netflush 1 chars
下面的是telnetd.c裡面最主要的一個函式,其中裡面的for迴圈也是理解telnetd的工作機制最主要的部分。
int
telnetd_run (void)
{
...
for (;;)
fd_set ibits, obits, xbits;
register int c;
if (net_input_level () < 0 && pty_input_level () < 0)
break;
FD_ZERO (&ibits);
FD_ZERO (&obits);
FD_ZERO (&xbits);
/* Never look for input if there"s still stuff in the corresponding
output buffer */
if (net_output_level () || pty_input_level () > 0)
FD_SET (net, &obits);
else
FD_SET (pty, &ibits);
if (pty_output_level () || net_input_level () > 0)
FD_SET (pty, &obits);
FD_SET (net, &ibits);
if (!SYNCHing)
FD_SET (net, &xbits);
if ((c = select (nfd, &ibits, &obits, &xbits, NULL)) <= 0)
if (c == -1 && errno == EINTR)
continue;
sleep (5);
}
if (FD_ISSET (net, &xbits))
SYNCHing = 1;
if (FD_ISSET (net, &ibits))
/* Something to read from the network... */
/*FIXME: handle !defined(SO_OOBINLINE) */
net_read (); 這裡是第一階段執行的函式
if (FD_ISSET (pty, &ibits))
/* Something to read from the pty... */
if (pty_read () <= 0)
/* The first byte is now TIOCPKT data. Peek at it. */
c = pty_get_char (1);
#if defined TIOCPKT_IOCTL
if (c & TIOCPKT_IOCTL)
pty_get_char (0);
copy_termbuf (); /* Pty buffer is now emptied. */
localstat ();
#endif
if (c & TIOCPKT_FLUSHWRITE)
static char flushdata[] = { IAC, DM };
netclear (); /* clear buffer back */
net_output_datalen (flushdata, sizeof (flushdata));
set_neturg ();
DEBUG (debug_options, 1, printoption ("td: send IAC", DM));
if (his_state_is_will (TELOPT_LFLOW)
&& (c & (TIOCPKT_NOSTOP | TIOCPKT_DOSTOP)))
int newflow = (c & TIOCPKT_DOSTOP) ? 1 : 0;
if (newflow != flowmode)
net_output_data ("%c%c%c%c%c%c",
IAC, SB, TELOPT_LFLOW,
flowmode ? LFLOW_ON : LFLOW_OFF, IAC, SE);
pty_get_char (0); /* Discard the TIOCPKT preamble. */
while (pty_input_level () > 0)
if (net_buffer_is_full ())
c = pty_get_char (0);
if (c == IAC)
net_output_byte (c);
if (c == "\r" && my_state_is_wont (TELOPT_BINARY))
if (pty_input_level () > 0 && pty_get_char (1) == "\n")
net_output_byte (pty_get_char (0));
net_output_byte (0);
if (FD_ISSET (net, &obits) && net_output_level () > 0)
netflush ();
if (net_input_level () > 0)
telrcv ();
if (FD_ISSET (pty, &obits) && pty_output_level () > 0)
ptyflush (); 這裡是第二階段執行的函式。
/* Attending to the child must come last in the loop,
* so as to let pending data be flushed, mainly to the
* benefit of the remote and expecting client.
*/
if (pending_sigchld) {
/* Check for pending output, independently of OBITS. */
if (net_output_level () > 0)
cleanup (SIGCHLD); /* Not returning from this. */
net_read函式分析。這個函式是接收來自net的一個字元。
ncc是個數,用到的netibuf,網路輸入緩衝區。可以這麼理解。
netip是網路輸入緩衝區的指標。
net_read (void)
ncc = read (net, netibuf, sizeof (netibuf));
if (ncc < 0 && errno == EWOULDBLOCK)
ncc = 0;
else if (ncc == 0)
syslog (LOG_INFO, "telnetd: peer died");
cleanup (0);
/* NOT REACHED */
else if (ncc > 0)
netip = netibuf;
DEBUG (debug_report, 1,
debug_output_data ("td: netread %d chars\r\n", ncc));
DEBUG (debug_net_data, 1, printdata ("nd", netip, ncc));
return ncc;
telrcv函式是一個關鍵的函式,在檔案state.c中定義。
和telnet協議狀態機有關。
比如如果第一個位元組是FF也就是IAC,那麼下面的位元組是命令位元組。命令選項位元組。
net_get_char函式和pty_output_byte函式是理解telrcv函式的主要的地方。
其他的語句都和狀態機有關。這兩個函式是取一個字元,函式放到pty緩衝區裡。
void
telrcv (void)
static int state = TS_DATA;
while ((net_input_level () > 0) & !pty_buffer_is_full ())
c = net_get_char (0);
#ifdef ENCRYPTION
if (decrypt_input)
c = (*decrypt_input) (c);
#endif /* ENCRYPTION */
switch (state)
case TS_CR:
state = TS_DATA;
/* Strip off \n or \0 after a \r */
if ((c == 0) || (c == "\n"))
/* FALL THROUGH */
case TS_DATA:
state = TS_IAC;
/*
* We now map \r\n ==> \r for pragmatic reasons.
* Many client implementations send \r\n when
* the user hits the CarriageReturn key.
*
* We USED to map \r\n ==> \n, since \r\n says
* that we want to be in column 1 of the next
* printable line, and \n is the standard
* unix way of saying that (\r is only good
* if CRMOD is set, which it normally is).
if ((c == "\r") && his_state_is_wont (TELOPT_BINARY))
int nc = net_get_char (1);
nc = (*decrypt_input) (nc & 0xff);
* If we are operating in linemode,
* convert to local end-of-line.
if (linemode
&& net_input_level () > 0
&& (("\n" == nc) || (!nc && tty_iscrnl ())))
net_get_char (0); /* Remove from the buffer */
c = "\n";
(*decrypt_input) (-1);
state = TS_CR;
pty_output_byte (c);
case TS_IAC:
gotiac:
switch (c)
* Send the process on the pty side an
* interrupt. Do this with a NULL or
* interrupt char; depending on the tty mode.
case IP:
DEBUG (debug_options, 1, printoption ("td: recv IAC", c));
send_intr ();
case BREAK:
send_brk ();
net_get_char (int peek)
if (peek)
return *netip;
ncc--;
return *netip++ & 0377;
return 0;
pty_output_byte (int c)
*pfrontp++ = c;
這裡是第二階段相關的函式。比較好理解。
主要的功能是把緩衝區的字元放到/dev/pty裡面
a b c d e f g
| |
pbackp pfrontp
上面的pfrontp指標指向的是字母g,如果再輸入一個字元h,那麼pfrontp指標就指向字元h。
上面的pbackp指標指向的是字母b,如果要拿出一個字元,那麼應該先拿出字元b,然後是c,再然後是d。
ptyflush (void)
int n;
if ((n = pfrontp - pbackp) > 0)
debug_output_data ("td: ptyflush %d chars\r\n", n));
DEBUG (debug_pty_data, 1, printdata ("pd", pbackp, n));
syslog (LOG_NOTICE, "ptyflush pbackp = %s", pbackp);
n = write (pty, pbackp, n);
if (n < 0)
if (errno == EWOULDBLOCK || errno == EINTR)
return;
pbackp += n;
if (pbackp == pfrontp)
pbackp = pfrontp = ptyobuf;
下面的debug資訊來自與/tmp/telenet.debug檔案的一部分。
是客戶端按下字母e後發生的四個階段。這篇主要分析第一階段和第二階段。
也就是telrcv函式的主要的功能。
td: netread 1 chars
nd: 65 e
td: ptyflush 1 chars
pd: 65 e
td: ptyread 2 chars
pd: 0065 .e
td: netflush 1 chars
下面的是telnetd.c裡面最主要的一個函式,其中裡面的for迴圈也是理解telnetd的工作機制最主要的部分。
int
telnetd_run (void)
{
...
for (;;)
{
fd_set ibits, obits, xbits;
register int c;
if (net_input_level () < 0 && pty_input_level () < 0)
break;
FD_ZERO (&ibits);
FD_ZERO (&obits);
FD_ZERO (&xbits);
/* Never look for input if there"s still stuff in the corresponding
output buffer */
if (net_output_level () || pty_input_level () > 0)
FD_SET (net, &obits);
else
FD_SET (pty, &ibits);
if (pty_output_level () || net_input_level () > 0)
FD_SET (pty, &obits);
else
FD_SET (net, &ibits);
if (!SYNCHing)
FD_SET (net, &xbits);
if ((c = select (nfd, &ibits, &obits, &xbits, NULL)) <= 0)
{
if (c == -1 && errno == EINTR)
continue;
sleep (5);
continue;
}
if (FD_ISSET (net, &xbits))
SYNCHing = 1;
if (FD_ISSET (net, &ibits))
{
/* Something to read from the network... */
/*FIXME: handle !defined(SO_OOBINLINE) */
net_read (); 這裡是第一階段執行的函式
}
if (FD_ISSET (pty, &ibits))
{
/* Something to read from the pty... */
if (pty_read () <= 0)
break;
/* The first byte is now TIOCPKT data. Peek at it. */
c = pty_get_char (1);
#if defined TIOCPKT_IOCTL
if (c & TIOCPKT_IOCTL)
{
pty_get_char (0);
copy_termbuf (); /* Pty buffer is now emptied. */
localstat ();
}
#endif
if (c & TIOCPKT_FLUSHWRITE)
{
static char flushdata[] = { IAC, DM };
pty_get_char (0);
netclear (); /* clear buffer back */
net_output_datalen (flushdata, sizeof (flushdata));
set_neturg ();
DEBUG (debug_options, 1, printoption ("td: send IAC", DM));
}
if (his_state_is_will (TELOPT_LFLOW)
&& (c & (TIOCPKT_NOSTOP | TIOCPKT_DOSTOP)))
{
int newflow = (c & TIOCPKT_DOSTOP) ? 1 : 0;
if (newflow != flowmode)
{
net_output_data ("%c%c%c%c%c%c",
IAC, SB, TELOPT_LFLOW,
flowmode ? LFLOW_ON : LFLOW_OFF, IAC, SE);
}
}
pty_get_char (0); /* Discard the TIOCPKT preamble. */
}
while (pty_input_level () > 0)
{
if (net_buffer_is_full ())
break;
c = pty_get_char (0);
if (c == IAC)
net_output_byte (c);
net_output_byte (c);
if (c == "\r" && my_state_is_wont (TELOPT_BINARY))
{
if (pty_input_level () > 0 && pty_get_char (1) == "\n")
net_output_byte (pty_get_char (0));
else
net_output_byte (0);
}
}
if (FD_ISSET (net, &obits) && net_output_level () > 0)
netflush ();
if (net_input_level () > 0)
telrcv ();
if (FD_ISSET (pty, &obits) && pty_output_level () > 0)
ptyflush (); 這裡是第二階段執行的函式。
/* Attending to the child must come last in the loop,
* so as to let pending data be flushed, mainly to the
* benefit of the remote and expecting client.
*/
if (pending_sigchld) {
/* Check for pending output, independently of OBITS. */
if (net_output_level () > 0)
netflush ();
cleanup (SIGCHLD); /* Not returning from this. */
}
}
net_read函式分析。這個函式是接收來自net的一個字元。
ncc是個數,用到的netibuf,網路輸入緩衝區。可以這麼理解。
netip是網路輸入緩衝區的指標。
int
net_read (void)
{
ncc = read (net, netibuf, sizeof (netibuf));
if (ncc < 0 && errno == EWOULDBLOCK)
ncc = 0;
else if (ncc == 0)
{
syslog (LOG_INFO, "telnetd: peer died");
cleanup (0);
/* NOT REACHED */
}
else if (ncc > 0)
{
netip = netibuf;
DEBUG (debug_report, 1,
debug_output_data ("td: netread %d chars\r\n", ncc));
DEBUG (debug_net_data, 1, printdata ("nd", netip, ncc));
}
return ncc;
}
telrcv函式是一個關鍵的函式,在檔案state.c中定義。
和telnet協議狀態機有關。
比如如果第一個位元組是FF也就是IAC,那麼下面的位元組是命令位元組。命令選項位元組。
net_get_char函式和pty_output_byte函式是理解telrcv函式的主要的地方。
其他的語句都和狀態機有關。這兩個函式是取一個字元,函式放到pty緩衝區裡。
void
telrcv (void)
{
register int c;
static int state = TS_DATA;
while ((net_input_level () > 0) & !pty_buffer_is_full ())
{
c = net_get_char (0);
#ifdef ENCRYPTION
if (decrypt_input)
c = (*decrypt_input) (c);
#endif /* ENCRYPTION */
switch (state)
{
case TS_CR:
state = TS_DATA;
/* Strip off \n or \0 after a \r */
if ((c == 0) || (c == "\n"))
break;
/* FALL THROUGH */
case TS_DATA:
if (c == IAC)
{
state = TS_IAC;
break;
}
/*
* We now map \r\n ==> \r for pragmatic reasons.
* Many client implementations send \r\n when
* the user hits the CarriageReturn key.
*
* We USED to map \r\n ==> \n, since \r\n says
* that we want to be in column 1 of the next
* printable line, and \n is the standard
* unix way of saying that (\r is only good
* if CRMOD is set, which it normally is).
*/
if ((c == "\r") && his_state_is_wont (TELOPT_BINARY))
{
int nc = net_get_char (1);
#ifdef ENCRYPTION
if (decrypt_input)
nc = (*decrypt_input) (nc & 0xff);
#endif /* ENCRYPTION */
/*
* If we are operating in linemode,
* convert to local end-of-line.
*/
if (linemode
&& net_input_level () > 0
&& (("\n" == nc) || (!nc && tty_iscrnl ())))
{
net_get_char (0); /* Remove from the buffer */
c = "\n";
}
else
{
#ifdef ENCRYPTION
if (decrypt_input)
(*decrypt_input) (-1);
#endif /* ENCRYPTION */
state = TS_CR;
}
}
pty_output_byte (c);
break;
case TS_IAC:
gotiac:
switch (c)
{
/*
* Send the process on the pty side an
* interrupt. Do this with a NULL or
* interrupt char; depending on the tty mode.
*/
case IP:
DEBUG (debug_options, 1, printoption ("td: recv IAC", c));
send_intr ();
break;
case BREAK:
DEBUG (debug_options, 1, printoption ("td: recv IAC", c));
send_brk ();
break;
int
net_get_char (int peek)
{
if (peek)
return *netip;
else if (ncc > 0)
{
ncc--;
return *netip++ & 0377;
}
return 0;
}
void
pty_output_byte (int c)
{
*pfrontp++ = c;
}
這裡是第二階段相關的函式。比較好理解。
主要的功能是把緩衝區的字元放到/dev/pty裡面
a b c d e f g
| |
pbackp pfrontp
上面的pfrontp指標指向的是字母g,如果再輸入一個字元h,那麼pfrontp指標就指向字元h。
上面的pbackp指標指向的是字母b,如果要拿出一個字元,那麼應該先拿出字元b,然後是c,再然後是d。
void
ptyflush (void)
{
int n;
if ((n = pfrontp - pbackp) > 0)
{
DEBUG (debug_report, 1,
debug_output_data ("td: ptyflush %d chars\r\n", n));
DEBUG (debug_pty_data, 1, printdata ("pd", pbackp, n));
syslog (LOG_NOTICE, "ptyflush pbackp = %s", pbackp);
n = write (pty, pbackp, n);
}
if (n < 0)
{
if (errno == EWOULDBLOCK || errno == EINTR)
return;
cleanup (0);
/* NOT REACHED */
}
pbackp += n;
if (pbackp == pfrontp)
pbackp = pfrontp = ptyobuf;
}