telnetd源代码分析之输入一个字符的四个阶段

下面的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;
}


猜你喜欢

转载自blog.csdn.net/sitelist/article/details/78854613