telnetで接続する (telnet ネゴシエーションの簡易実装) - C#

telnetで接続する際に最初に ネゴシエーション オプションの通信が必要になります。この記事では簡易なネゴシエーションオプションの通信を実装しtelnetでログインするプログラムを作成します。

通信内容

クライアントがポート23に接続しクライアント側から送信を始めます。送信内容は
IAC WILL terminal_type
IAC DO suppress_go_ahead
IAC WILL suppress_go_ahead
IAC DO echo
IAC WILL window_size
を送信します。送信後サーバからの返事を待ちます。サーバからは
IAC WILL suppress_go_ahead
IAC DO echo
IAC DO window_size
IAC WILL status
IAC DO remote_flow_control
IAC DO terminal_type
IAC DO suppress_go_ahead
IAC WILL echo
が戻ります。これはTelnetサーバーにより違うため一例になります。サーバから受信後クライアントから
IAC WONT echo
IAC SB op_window_size 0x00 0x50 0x00 0x18
IAC SE DO echo
IAC DONT status
IAC WONT remote_flow_control
を送信します。
送信後、0x0A が返され、それに続き "login:" や "password:"などのプロンプトがサーバーから返され、ログイン待ちとなります。

コマンドの数値

ネゴシエーションコマンドの数値は
SE = 0xF0;
NOP = 0xF1;
DM = 0xF2;
BRK = 0xF3;
IP = 0xF4;
AO = 0xF5;
AYT = 0xF6;
EC = 0xF7;
EL = 0xF8;
GA = 0xF9;
SB = 0xFA;
WILL = 0xFB;
WONT = 0xFC;
DO = 0xFD;
DONT = 0xFE;
IAC = 0xFF;
オプションの数値は
suppress_go_ahead = 0x03;
status = 0x05;
echo = 0x01;
timing_mark = 0x06;
terminal_type = 0x18;
window_size = 0x1F;
terminal_speed = 0x20;
remote_flow_control = 0x21;
linemode = 0x22;
environment_variables = 0x24;
となっています。

ネゴシエーションオプションの通信内容を調べるには

ネゴシエーションオプションの通信内容を調べるには、TeraTerm Proのteraterm.iniファイルのTelLog=on を設定し、TeraTermからサーバーに接続するとTELNET.LOGにネゴシエーションオプションの通信内容が記録されます。詳細な手順は「TeraTerm Pro を利用してネゴシエーションオプションの通信内容を調べる」の記事を参照してください。

プログラム例

コンソールアプリケーションを作成し、下記のコードを記述します。
このプログラムでは、Yamahaのルーターに自動ログインし管理権限を取得する例です。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using iPentecSocket;

namespace RouterRestart
{
  class Program
  {
    static iPentecSyncClientSocket sock;

    //Telnet Commands
    const byte cmdSE   = 0xF0;
    const byte cmdNOP  = 0xF1;
    const byte cmdDM   = 0xF2;
    const byte cmdBRK  = 0xF3;
    const byte cmdIP   = 0xF4;
    const byte cmdAO   = 0xF5;
    const byte cmdAYT  = 0xF6;
    const byte cmdEC   = 0xF7;
    const byte cmdEL   = 0xF8;
    const byte cmdGA   = 0xF9;
    const byte cmdSB   = 0xFA;

    const byte cmdWILL = 0xFB;
    const byte cmdWONT = 0xFC;
    const byte cmdDO   = 0xFD;
    const byte cmdDONT = 0xFE;
    const byte cmdIAC  = 0xFF;

    //Telnet Options
    const byte op_suppress_go_ahead      = 0x03;
    const byte op_status                 = 0x05;
    const byte op_echo                   = 0x01;
    const byte op_timing_mark            = 0x06;
    const byte op_terminal_type          = 0x18;
    const byte op_window_size            = 0x1F;
    const byte op_terminal_speed         = 0x20;
    const byte op_remote_flow_control    = 0x21;
    const byte op_linemode               = 0x22;
    const byte op_environment_variables  = 0x24;

    static void Main(string[] args)
    {
      sock = new iPentecSyncClientSocket();
      sock.Connect += new iPentecSyncClientSocket.ConnectEventHandler(sock_Connect);
     
      sock.Port = 23;
      sock.Host = "yamaha-router.ipentec.com";
      sock.Timeout = 5000;
      sock.Open();

      byte[] data = new byte[15];
      data[0] = cmdIAC;
      data[1] = cmdWILL;
      data[2] = op_terminal_type;
      data[3] = cmdIAC;
      data[4] = cmdDO;
      data[5] = op_suppress_go_ahead;
      data[6] = cmdIAC;
      data[7] = cmdWILL;
      data[8] = op_suppress_go_ahead;
      data[9] = cmdIAC;
      data[10] = cmdDO;
      data[11] = op_echo;
      data[12] = cmdIAC;
      data[13] = cmdWILL;
      data[14] = op_window_size;
      sock.Write(data);

      int rbytes = sock.Read(out data);
      string recv = DataToString(data, rbytes);
      System.Console.WriteLine(recv);

      data = new byte[3];
      data[0] = cmdIAC;
      data[1] = cmdWONT;
      data[2] = op_echo;
      sock.Write(data);

      data = new byte[11];
      data[0] = cmdIAC;
      data[1] = cmdSB;
      data[2] = op_window_size;
      data[3] = 0x00;
      data[4] = 0x50; //80
      data[5] = 0x00;
      data[6] = 0x18; //24
      data[7] = cmdIAC;
      data[8] = cmdSE;
      data[9] = cmdDO;
      data[10] = op_echo;
      sock.Write(data);

      data = new byte[3];
      data[0] = cmdIAC;
      data[1] = cmdDONT;
      data[2] = op_status;
      sock.Write(data);

      data = new byte[3];
      data[0] = cmdIAC;
      data[1] = cmdWONT;
      data[2] = op_remote_flow_control;
      sock.Write(data);

      data = new byte[1];
      data[0] = 0x0D; //ここでCRを送っておくと1回目でログインできる。
      sock.Write(data);

      rbytes = sock.Read(out data);
      recv = DataToString(data, rbytes);
      System.Console.WriteLine(recv);


      //0D 0A Password
      recv = Encoding.ASCII.GetString(data);
      System.Console.WriteLine(recv);
      while (recv.IndexOf("Password") < 0) {
        sock.Read(out data);
        recv = Encoding.ASCII.GetString(data);
      }

      //(CRを送信していない場合は1度目は失敗する)
      string cmd = "ROUTER-PASSWORD\r";
      byte[] cmddata = Encoding.ASCII.GetBytes(cmd);
      sock.Write(cmddata);
      
      rbytes = sock.Read(out data);
      recv = Encoding.GetEncoding("SHIFT_JIS").GetString(data);
      System.Console.WriteLine(recv);

      //(CRを送信していない場合は2度目でログインできる。)
      /*
      sock.Write(cmddata);
      sock.Read(out data);
      recv = Encoding.GetEncoding("SHIFT_JIS").GetString(data);
      System.Console.WriteLine(recv);
      */

      //">"を待つ
      while (recv.IndexOf(">") < 0) {
        sock.Read(out data);
        recv = Encoding.GetEncoding("SHIFT_JIS").GetString(data);
      }
     
      cmd = "administrator\r\n";
      sock.Write(Encoding.ASCII.GetBytes(cmd));

      //"Password"を待つ
      rbytes = sock.Read(out data);

      recv = Encoding.GetEncoding("SHIFT_JIS").GetString(data);
      System.Console.WriteLine(recv);
      while (recv.IndexOf("Password") < 0) {
        rbytes = sock.Read(out data);

        recv = Encoding.GetEncoding("SHIFT_JIS").GetString(data);
        System.Console.WriteLine(recv);
      }

      cmd = "ROUTER-ADMINISTRATOR-PASSWORD\r";
      sock.Write(Encoding.ASCII.GetBytes(cmd));

      //"#"を待つ
      rbytes = sock.Read(out data);
      
      recv = Encoding.GetEncoding("SHIFT_JIS").GetString(data);
      System.Console.WriteLine(recv);
      while (recv.IndexOf("#") < 0) {
        rbytes = sock.Read(out data);
        
        recv = Encoding.ASCII.GetString(data);
        System.Console.WriteLine(recv);
      }

     //何らかの処理
    }

    static void sock_Send(byte[] SendData)
    {
      //throw new NotImplementedException();
    }

    static void sock_Receive(byte[] ReceiveData)
    {
      //throw new NotImplementedException();
    }

    static string DataToString(byte[] data, int dataBytes)
    {
      string result = "";
      for (int i = 0; i < Math.Min(data.Length, dataBytes); i++)
      {
        result += CmdToString(data[i]) + " ";
      }

      return result;
    }

    static string CmdToString(byte data)
    {
      switch (data)
      {
        case 0xF0:
          return "SE";
          break;
        case 0xF1:
          return "NOP";
          break;
        case 0xF2:
          return "DM";
          break;
        case 0xF3:
          return "BRK";
          break;
        case 0xF4:
          return "IP";
          break;
        case 0xF5:
          return "AO";
          break;
        case 0xF6:
          return "AYT";
          break;
        case 0xF7:
          return "EC";
          break;
        case 0xF8:
          return "EL";
          break;
        case 0xF9:
          return "GA";
          break;
        case 0xFA:
          return "SB";
          break;

        case 0xFB:
          return "WILL";
          break;
        case 0xFC:
          return "WONT";
          break;
        case 0xFD:
          return "DO";
          break;
        case 0xFE:
          return "DONT";
          break;
        case 0xFF:
          return "IAC";
          break;

        case 0x03:
          return "suppress_go_ahead";
          break;
        case 0x05:
          return "status";
          break;
        case 0x01:
          return "echo";
          break;
        case 0x06:
          return "timing_mark";
          break;
        case 0x18:
          return "terminal_type";
          break;
        case 0x1F:
          return "window_size";
          break;
        case 0x20:
          return "terminal_speed";
          break;
        case 0x21:
          return "remote_flow_control";
          break;
        case 0x22:
          return "linemode";
          break;
        case 0x24:
          return "environment_variables";
          break;

        default:
          return "unknown";
          break;
      }

    }

    static void sock_Connect(System.Net.Sockets.Socket client)
    {
      System.Console.WriteLine("Connect");
    }
  }
}

ソケット部分のコード

iPentecSocket.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;


namespace iPentecSocket
{
  public partial class iPentecSyncClientSocket : Component
  {
    public iPentecSyncClientSocket()
    {
      InitializeComponent();
    }

    public iPentecSyncClientSocket(IContainer container)
    {
      container.Add(this);

      InitializeComponent();
    }

    private int port = 0;
    private string host = "";
    private int timeout = 0;

    Socket clientSocket = null;

    private static String response = String.Empty;

    private int MAX_BUFFER_SIZE = 2048;

    public enum SocketAction { SA_READ, SA_WRITE, SA_CLOSE, SA_NONE }

    // イベント処理用のデリゲート
    public delegate void ConnectEventHandler(Socket client);
    public delegate void DisconnectEventHandler();
    public delegate void ReceiveEventHandler(byte[] ReceiveData);
    public delegate void SendEventHandler(byte[] SendData);
    //
    public event ConnectEventHandler Connect = null;
    public event DisconnectEventHandler Disconnect = null;
    public event ReceiveEventHandler Receive = null;
    public event SendEventHandler Send = null;

    public int Port
    {
      set { port = value; }
      get { return port; }
    }

    public string Host
    {
      set { host = value; }
      get { return host; }
    }
    
    public int Timeout
    {
      set { timeout = value; }
      get { return timeout; }
    }

    private void DisposedEvent(object sender, EventArgs e)
    {
      Close(clientSocket);
    }

    public void Open()
    {
      try {
        IPHostEntry ipHostInfo = Dns.GetHostEntry(host);

        IPAddress ipAddress = ipHostInfo.AddressList[0];
        //IPv6でないアドレスを取得
        for (int i = 0; i < ipHostInfo.AddressList.Length; i++) {
          if (ipHostInfo.AddressList[i].AddressFamily == AddressFamily.InterNetwork) {
            ipAddress = ipHostInfo.AddressList[i];
          }
        }
        IPEndPoint remoteEP = new IPEndPoint(ipAddress, port);

        // Create a TCP/IP  socket.
        clientSocket = new Socket(
          AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientSocket.ReceiveTimeout = timeout;

        try {
          clientSocket.Connect(remoteEP);
          if (Connect != null) {
            Connect(clientSocket);
          }
        }
        catch (ArgumentNullException ane) {
          Console.WriteLine("ArgumentNullException : {0}", ane.ToString());
        }
        catch (SocketException se) {
          Console.WriteLine("SocketException : {0}", se.ToString());
        }
        catch (Exception e) {
          Console.WriteLine("Unexpected exception : {0}", e.ToString());
        }
      }
      catch (Exception e) {
        Console.WriteLine(e.ToString());
      }

    }

    public void Close(Socket client)
    {
      clientSocket.Shutdown(SocketShutdown.Both);
      clientSocket.Close();

      if (Disconnect != null) {
        Close(clientSocket);
      }
    }

    public int Read(out byte[] bytes)
    {
      // Receive the response from the remote device.
      bytes = new byte[MAX_BUFFER_SIZE];

      int bytesReceive = clientSocket.Receive(bytes,SocketFlags.None);
      if (Receive != null) {
        Receive(bytes);
      }
      return bytesReceive;
    }

    public void Read(out byte[] bytes, out int bytesReceive)
    {
      bytesReceive = Read(out bytes);
    }

    public int Write(byte[] data, int bytesWrite)
    {
      int bytesRec = clientSocket.Send(data,bytesWrite,SocketFlags.None);

      if (Send != null) {
        Send(data);
      }
      return bytesRec;
    }

    public int Write(byte[] data)
    {
      int bytesRec = clientSocket.Send(data, SocketFlags.None);
      if (Send != null)
      {
        Send(data);
      }
      return bytesRec;
    }
  }
}
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2024-01-07
作成日: 2010-08-05
iPentec all rights reserverd.