telnetで接続する (telnet ネゴシエーションの簡易実装)
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");
}
}
}
ソケット部分のコード
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を愛用