ESP8266-telnet-serverの話2(1クライアント対応版)
wroom-02にtelnetで話させようと思って色々調べております。
で前の記事を書いたんだけど、サーバとクライアントの方向を間違えて解析していたのに気づいたので
記事を書き直します。
前の記事は
→https://ameblo.jp/fc2miha/entry-12833772870.html
普通のTCP/IPで23番ポートで待ち受けてTeratermで接続を行った場合に、
telnetプロトコルが動くわけですが、こちらは標準では実装されていないみたいなので、
自分で作るべしと思い、telnetを解析することにした。
とりあえず、CentOS7にtelnetをインストールして
Teratermで接続したときの図。
Kernel 3.10.0-514.el7.centos.plus.i686 on an i686
localhost login:
これが画面に表示されるまでにCentOS側でtcpdumpを取得すると、
以下のようなやり取りが有る事が判る。
[root@localhost ~]# tcpdump -X port 23
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s10f0, link-type EN10MB (Ethernet), capture size 65535 bytes
23:07:41.482169 IP 192.168.33.33.56432 > localhost.localdomain.telnet: Flags [S], seq 434909477, win 8192, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
0x0000: 4500 0034 2a47 4000 8006 0d02 c0a8 2121 E..4*G@.......!!
0x0010: c0a8 2109 dc70 0017 19ec 3125 0000 0000 ..!..p....1%....
0x0020: 8002 2000 63fc 0000 0204 05b4 0103 0308 ....c...........
0x0030: 0101 0402 ....
23:07:41.482403 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [S.], seq 3664588868, ack 434909478, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 6], length 0
0x0000: 4500 0034 0000 4000 4006 7749 c0a8 2109 E..4..@.@.wI..!.
0x0010: c0a8 2121 0017 dc70 da6d 3044 19ec 3126 ..!!...p.m0D..1&
0x0020: 8012 7210 072b 0000 0204 05b4 0101 0402 ..r..+..........
0x0030: 0103 0306 ....
23:07:41.482533 IP 192.168.33.33.56432 > localhost.localdomain.telnet: Flags [.], ack 1, win 256, length 0
0x0000: 4500 0028 2a48 4000 8006 0d0d c0a8 2121 E..(*H@.......!!
0x0010: c0a8 2109 dc70 0017 19ec 3126 da6d 3045 ..!..p....1&.m0E
0x0020: 5010 0100 b90c 0000 0000 0000 0000 P.............
23:07:41.500196 IP 192.168.33.33.56432 > localhost.localdomain.telnet: Flags [P.], seq 1:16, ack 1, win 256, length 15
0x0000: 4500 0037 2a4a 4000 8006 0cfc c0a8 2121 E..7*J@.......!!
0x0010: c0a8 2109 dc70 0017 19ec 3126 da6d 3045 ..!..p....1&.m0E
0x0020: 5018 0100 82fd 0000 fffb 18ff fd03 fffb P...............
0x0030: 03ff fd01 fffb 1f .......
23:07:41.500674 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [.], ack 16, win 457, length 0
0x0000: 4500 0028 992f 4000 4006 de25 c0a8 2109 E..(./@.@..%..!.
0x0010: c0a8 2121 0017 dc70 da6d 3045 19ec 3135 ..!!...p.m0E..15
0x0020: 5010 01c9 b834 0000 P....4..
23:07:51.548648 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [P.], seq 1:13, ack 16, win 457, length 12
0x0000: 4510 0034 9930 4000 4006 de08 c0a8 2109 E..4.0@.@.....!.
0x0010: c0a8 2121 0017 dc70 da6d 3045 19ec 3135 ..!!...p.m0E..15
0x0020: 5018 01c9 80dd 0000 fffd 18ff fd20 fffd P...............
0x0030: 23ff fd27 #..'
23:07:51.549371 IP 192.168.33.33.56432 > localhost.localdomain.telnet: Flags [P.], seq 16:19, ack 13, win 256, length 3
0x0000: 4500 002b 2a53 4000 8006 0cff c0a8 2121 E..+*S@.......!!
0x0010: c0a8 2109 dc70 0017 19ec 3135 da6d 3051 ..!..p....15.m0Q
0x0020: 5018 0100 98e9 0000 fffc 2000 0000 P.............
23:07:51.549803 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [.], ack 19, win 457, length 0
0x0000: 4510 0028 9931 4000 4006 de13 c0a8 2109 E..(.1@.@.....!.
0x0010: c0a8 2121 0017 dc70 da6d 3051 19ec 3138 ..!!...p.m0Q..18
0x0020: 5010 01c9 b825 0000 P....%..
23:07:51.550269 IP 192.168.33.33.56432 > localhost.localdomain.telnet: Flags [P.], seq 19:25, ack 13, win 256, length 6
0x0000: 4500 002e 2a54 4000 8006 0cfb c0a8 2121 E...*T@.......!!
0x0010: c0a8 2109 dc70 0017 19ec 3138 da6d 3051 ..!..p....18.m0Q
0x0020: 5018 0100 98bc 0000 fffc 23ff fc27 P.........#..'
23:07:51.616565 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [.], ack 25, win 457, length 0
0x0000: 4510 0028 9932 4000 4006 de12 c0a8 2109 E..(.2@.@.....!.
0x0010: c0a8 2121 0017 dc70 da6d 3051 19ec 313e ..!!...p.m0Q..1>
0x0020: 5010 01c9 b81f 0000 P.......
23:07:51.622031 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [P.], seq 13:31, ack 25, win 457, length 18
0x0000: 4510 003a 9933 4000 4006 ddff c0a8 2109 E..:.3@.@.....!.
0x0010: c0a8 2121 0017 dc70 da6d 3051 19ec 313e ..!!...p.m0Q..1>
0x0020: 5018 01c9 9ffe 0000 fffb 03ff fd03 fffb P...............
0x0030: 01ff fd1f fffa 1801 fff0 ..........
23:07:51.622587 IP 192.168.33.33.56432 > localhost.localdomain.telnet: Flags [P.], seq 25:34, ack 31, win 256, length 9
0x0000: 4500 0031 2a55 4000 8006 0cf7 c0a8 2121 E..1*U@.......!!
0x0010: c0a8 2109 dc70 0017 19ec 313e da6d 3063 ..!..p....1>.m0c
0x0020: 5018 0100 3dca 0000 fffa 1f00 5000 1bff P...=.......P...
0x0030: f0 .
23:07:51.664072 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [.], ack 34, win 457, length 0
0x0000: 4510 0028 9934 4000 4006 de10 c0a8 2109 E..(.4@.@.....!.
0x0010: c0a8 2121 0017 dc70 da6d 3063 19ec 3147 ..!!...p.m0c..1G
0x0020: 5010 01c9 b804 0000 P.......
23:07:51.664232 IP 192.168.33.33.56432 > localhost.localdomain.telnet: Flags [P.], seq 34:45, ack 31, win 256, length 11
0x0000: 4500 0033 2a57 4000 8006 0cf3 c0a8 2121 E..3*W@.......!!
0x0010: c0a8 2109 dc70 0017 19ec 3147 da6d 3063 ..!..p....1G.m0c
0x0020: 5018 0100 64d8 0000 fffa 1800 7874 6572 P...d.......xter
0x0030: 6dff f0 m..
23:07:51.666453 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [.], ack 45, win 457, length 0
0x0000: 4510 0028 9935 4000 4006 de0f c0a8 2109 E..(.5@.@.....!.
0x0010: c0a8 2121 0017 dc70 da6d 3063 19ec 3152 ..!!...p.m0c..1R
0x0020: 5010 01c9 b7f9 0000 P.......
23:07:51.676340 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [P.], seq 31:40, ack 45, win 457, length 9
0x0000: 4510 0031 9936 4000 4006 de05 c0a8 2109 E..1.6@.@.....!.
0x0010: c0a8 2121 0017 dc70 da6d 3063 19ec 3152 ..!!...p.m0c..1R
0x0020: 5018 01c9 99e7 0000 fffd 01ff fb05 fffd P...............
0x0030: 21 !
23:07:51.677231 IP 192.168.33.33.56432 > localhost.localdomain.telnet: Flags [P.], seq 45:48, ack 40, win 256, length 3
0x0000: 4500 002b 2a58 4000 8006 0cfa c0a8 2121 E..+*X@.......!!
0x0010: c0a8 2109 dc70 0017 19ec 3152 da6d 306c ..!..p....1R.m0l
0x0020: 5018 0100 b7b1 0000 fffc 0100 0000 P.............
23:07:51.698058 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [P.], seq 40:93, ack 48, win 457, length 53
0x0000: 4510 005d 9937 4000 4006 ddd8 c0a8 2109 E..].7@.@.....!.
0x0010: c0a8 2121 0017 dc70 da6d 306c 19ec 3155 ..!!...p.m0l..1U
0x0020: 5018 01c9 5b32 0000 0d0a 4b65 726e 656c P...[2....Kernel
0x0030: 2033 2e31 302e 302d 3531 342e 656c 372e .3.10.0-514.el7.
0x0040: 6365 6e74 6f73 2e70 6c75 732e 6936 3836 centos.plus.i686
0x0050: 206f 6e20 616e 2069 3638 360d 0a .on.an.i686..
23:07:51.698258 IP 192.168.33.33.56432 > localhost.localdomain.telnet: Flags [P.], seq 48:54, ack 93, win 256, length 6
0x0000: 4500 002e 2a5a 4000 8006 0cf5 c0a8 2121 E...*Z@.......!!
0x0010: c0a8 2109 dc70 0017 19ec 3155 da6d 30a1 ..!..p....1U.m0.
0x0020: 5018 0100 b653 0000 fffe 05ff fc21 P....S.......!
23:07:51.737915 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [.], ack 54, win 457, length 0
0x0000: 4510 0028 9938 4000 4006 de0c c0a8 2109 E..(.8@.@.....!.
0x0010: c0a8 2121 0017 dc70 da6d 30a1 19ec 315b ..!!...p.m0...1[
0x0020: 5010 01c9 b7b2 0000 P.......
23:07:51.769232 IP localhost.localdomain.telnet > 192.168.33.33.56432: Flags [P.], seq 93:110, ack 54, win 457, length 17
0x0000: 4510 0039 9939 4000 4006 ddfa c0a8 2109 E..9.9@.@.....!.
0x0010: c0a8 2121 0017 dc70 da6d 30a1 19ec 315b ..!!...p.m0...1[
0x0020: 5018 01c9 35b9 0000 6c6f 6361 6c68 6f73 P...5...localhos
0x0030: 7420 6c6f 6769 6e3a 20 t.login:.
23:07:51.968827 IP 192.168.33.33.56432 > localhost.localdomain.telnet: Flags [.], ack 110, win 256, length 0
0x0000: 4500 0028 2a5e 4000 8006 0cf7 c0a8 2121 E..(*^@.......!!
0x0010: c0a8 2109 dc70 0017 19ec 315b da6d 30b2 ..!..p....1[.m0.
0x0020: 5010 0100 b86a 0000 0000 0000 0000 P....j........
これの中身を表にする。
コツはtelnetのやり取りは0xffで始まるって事でそこだけを抜き出して解析して行きます。
その結果
左は抜き出したもの ★ 右は解釈したもの
■■■■端末----->Server■■■■ ★ ■■■■端末----->Server■■■■
■■■■端末----->Server■■■■ ★ ■■■■端末----->Server■■■■
ff fb 18 ★ WILL : ターミナルタイプ
ff fd 03 ★ DO : GoAhead抑止
ff fb 03 ★ WILL : GoAhead抑止
ff fd 01 ★ DO : エコー
ff fb 1f ★ WILL : ウインドウサイズ
■■■■Server----->端末■■■■ ★ ■■■■Server----->端末■■■■
■■■■Server----->端末■■■■ ★ ■■■■Server----->端末■■■■
ff fd 18 ★ DO : ターミナルタイプ
ff fd 20 ★ DO : 調理モード
ff fd 23 ★ DO : 23
ff fd 27 ★ DO : 27
■■■■端末----->Server■■■■ ★ ■■■■端末----->Server■■■■
ff fc 20 00 00 00 ★ WON'T : 調理モード 00 00 00
■■■■Server----->端末■■■■ ★ ■■■■Server----->端末■■■■
■■■■端末----->Server■■■■ ★ ■■■■端末----->Server■■■■
ff fc 23 ★ WON'T : 23
ff fc 27 ★ WON'T : 27
■■■■Server----->端末■■■■ ★ ■■■■Server----->端末■■■■
■■■■Server----->端末■■■■ ★ ■■■■Server----->端末■■■■
ff fb 03 ★ WILL : GoAhead抑止
ff fd 03 ★ DO : GoAhead抑止
ff fb 01 ★ WILL : エコー
ff fd 1f ★ DO : ウインドウサイズ
ff fa 18 01 ★ SubN : ターミナルタイプ 01
ff f0 ★ SubnEND:
■■■■端末----->Server■■■■ ★ ■■■■端末----->Server■■■■
ff fa 1f 00 50 00 1b ★ SubN : ウインドウサイズ 00 50 00 1b
ff f0 ★ SubnEND:
■■■■Server----->端末■■■■ ★ ■■■■Server----->端末■■■■
■■■■端末----->Server■■■■ ★ ■■■■端末----->Server■■■■
ff fa 18 00 78 74 65 72 6d ★ SubN : ターミナルタイプ 00 78 74 65 72 6d
ff f0 ★ SubnEND:
■■■■Server----->端末■■■■ ★ ■■■■Server----->端末■■■■
■■■■Server----->端末■■■■ ★ ■■■■Server----->端末■■■■
ff fd 01 ★ DO : エコー
ff fb 05 ★ WILL : telnet状態オプション
ff fd 21 ★ DO : リモートフローコントロール
■■■■端末----->Server■■■■ ★ ■■■■端末----->Server■■■■
ff fc 01 00 00 00 ★ WON'T : エコー 00 00 00
■■■■Server----->端末■■■■ ★ ■■■■Server----->端末■■■■
■■■■端末----->Server■■■■ ★ ■■■■端末----->Server■■■■
ff fe 05 ★ DON'T : telnet状態オプション
ff fc 21 ★ WON'T : リモートフローコントロール
■■■■Server----->端末■■■■ ★ ■■■■Server----->端末■■■■
■■■■Server----->端末■■■■ ★ ■■■■Server----->端末■■■■
■■■■端末----->Server■■■■ ★ ■■■■端末----->Server■■■■
■TELNETでのオプション適用のルール
DO 相手にオプション使用を要求
→WILL 受信側で受け入れ
→WON'T 受信側で拒否
WILL 自分がオプション使用を宣言
→DO 受信側で受け入れ
→DON'T 受信側で拒否
WON'T 相手のオプション使用をやめさせる
→WON'T 受け入れる
DON'T 自分がオプション使用をやめる
→DON'T 受け入れる
■TMPDUMP結果をもっと簡単にすると、
端末側でのオプションの適用状況
ターミナルタイプ
GoAhead抑止
ウインドウサイズ
サーバ側でのオプションの適用状況
GoAhead抑止
エコー
それ以外は全部拒否する
サブネゴシェーション
相手からサブネゴシェーションが着たらとりあえず、エンドサブネゴシェーションまで全部読み込む
ウインドウサイズの受け入れを送信後にサブネゴシェーションでターミナルタイプ 01を送信
TELNET的に厳密には制御文字のやり取りとかいろいろ有るんですが、
そこは省略してもteratermとの通信を行う用途に限定すると以下の様なプログラムで行けそうです。
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
const char* ssid = "kurenai5";
const char* password = "0080868086";
WiFiServer server(23);
//読み込み用バッファ制御用
#define READ_BUFFER_SIZE 256
int read_buffer_save_idx=0;
int read_buffer_load_idx=0;
int read_buffer_size=0;
char read_buffer[READ_BUFFER_SIZE];
void setup(void)
{
//PCとの接続用
Serial.begin(115200);
//WIFI
WiFi.begin(ssid, password);
Serial.println("");
//接続を待つ
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
//接続情報
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
//サーバ開始
server.begin();
Serial.println("TCP server started");
//読み込みバッファクリア
memset(read_buffer, 0, sizeof(read_buffer));
}
//
//読み込みバッファに1バイト書込み
//
void push_buffer(char c)
{
if(read_buffer_size>=READ_BUFFER_SIZE){
Serial.printf("push_buffer overflow¥n");
return;
}
read_buffer[read_buffer_save_idx] = c;
read_buffer_save_idx++;
if(read_buffer_save_idx>=READ_BUFFER_SIZE){
read_buffer_save_idx = 0;
}
read_buffer_size++;
}
//
//読み込みバッファから1バイト読み込み
//
char pull_buffer()
{
if(read_buffer_size<=0){
Serial.printf("push_buffer underflow¥n");
return 0;
}
char c;
c = read_buffer[read_buffer_load_idx];
read_buffer_load_idx++;
if(read_buffer_load_idx>=READ_BUFFER_SIZE){
read_buffer_load_idx = 0;
}
read_buffer_size--;
return c;
}
//
//次の0xFFまでのバイト数を返す
// 0xFFが無い場合はget_buffer_sizeと同じ
//
int get_normal_char_bytes()
{
if(read_buffer_size<=0){
Serial.printf("push_buffer underflow¥n");
return 0;
}
int nCount = 0;
char c;
while(1){
int idx = read_buffer_load_idx;
c = read_buffer[idx];
if(c==0xFF){
break;
}
idx++;
if(read_buffer_load_idx>=READ_BUFFER_SIZE){
idx = 0;
}
nCount++;
if(nCount>=read_buffer_size){
break;
}
}
return nCount;
}
//
//全部のバッファーサイズ
//
int get_buffer_size()
{
return read_buffer_size;
}
//
//3バイトとコマンドを送信
//
void send_command(WiFiClient *pClient, unsigned char para1, unsigned char para2, unsigned char para3 )
{
unsigned char bytes[3];
bytes[0] = para1;
bytes[1] = para2;
bytes[2] = para3;
pClient->write((uint8_t*)bytes, sizeof(bytes));
}
//
//6バイトとコマンドを送信
//
void send_command_6bytes(WiFiClient *pClient, unsigned char para1, unsigned char para2, unsigned char para3, unsigned char para4, unsigned char para5, unsigned char para6 )
{
unsigned char bytes[6];
bytes[0] = para1;
bytes[1] = para2;
bytes[2] = para3;
bytes[3] = para4;
bytes[4] = para5;
bytes[5] = para6;
pClient->write((uint8_t*)bytes, sizeof(bytes));
}
//
//送信
//
void loop(void)
{
//接続なし
WiFiClient client = server.available();
if (!client) {
return;
}
//接続あり
Serial.println("");
Serial.println("New client");
//接続を待つ
while(!client.connected()){
delay(1);
yield();
}
//接続されたら接続が切れるまではこのループで回る
char r;
while(1){
//接続が無ければループ脱出
if(!client.connected()){
break;
}
//データが到着していたらバッファーにコピー
int len = client.available();
if(len > 0){
for(int i=0;i<len;i++){
if(get_buffer_size()<READ_BUFFER_SIZE){
char c = client.read();
push_buffer(c);
}
else{
break;
}
}
}
//
//バッファからデータを取り出して処理
//
if(get_buffer_size()>0){
//次の制御コードまでのバイト数を取得
int nNormalBytes = get_normal_char_bytes();
if(nNormalBytes==0){ //次は制御コードだ。
//
//ここから制御コードの処理
//
char c = pull_buffer();
if(c==0xff){
//コマンド
c = pull_buffer();
if(c==0xFD){ //DO
Serial.print("DO:");
char option = pull_buffer();
Serial.println(option, HEX);
if(option==0x03 || option==0x01){ // GoAhead抑止 エコー
//client.write(0xFF); client.write(0xFB); client.write((unsigned char)option); //WILL
send_command(&client, 0xFF, 0xFB, (unsigned char)option);
Serial.print("-->WILL ");
Serial.println(option, HEX);
}
else{ //その他
//client.write(0xFF); client.write(0xFC); client.write((unsigned char) option); //WON'T その他全部
send_command(&client, 0xFF, 0xFC, (unsigned char)option);
Serial.print("-->WON'T ");
Serial.println(option, HEX);
}
}else
if(c==0xFB){ //WILL
Serial.print("WILL:");
char option = pull_buffer();
Serial.println(option, HEX);
if(option==0x18 || option==0x03 || option==0x1f){ // ターミナルタイプ GoAhead抑止 ウインドウサイズ
//client.write(0xFF); client.write(0xFD); client.write((unsigned char) option); //DO
send_command(&client, 0xFF, 0xFD, (unsigned char)option);
Serial.print("-->DO ");
Serial.println(option, HEX);
//ウインドウサイズをDOを送った後にターミナルタイプ01を送る
if(option==0x1f){
//client.write(0xFF); client.write(0xFA); client.write(0x18); client.write(0x01); //SubNターミナルタイプ01
//client.write(0xFF); client.write(0xF0); //SubnEnd
send_command_6bytes(&client, 0xFF, 0xFA, 0x18, 0x01, 0xFF, 0xF0);
Serial.print("-->SubNTerminal01 ");
}
}
else{ //その他全部
//client.write(0xFF); client.write(0xFE); client.write((unsigned char) option); //DON'T その他全部
send_command(&client, 0xFF, 0xFE, (unsigned char)option);
Serial.print("-->DON'T ");
Serial.println(option, HEX);
}
}else
if(c==0xFC){ //WON'T
char option = pull_buffer();
Serial.print("WON'T:");
Serial.println(option, HEX);
//client.write(0xFF); client.write(0xFC); client.write((unsigned char)option); //WON'T
send_command(&client, 0xFF, 0xFC, (unsigned char)option);
}else
if(c==0xFE){ //DON'T
char option = pull_buffer();
Serial.println("DON'T:");
Serial.println(option, HEX);
//client.write(0xFF); client.write(0xFE); client.write((unsigned char)option); //DON'T
send_command(&client, 0xFF, 0xFE, (unsigned char)option);
}else
if(c==0xFA){ //サブネゴシェーション開始
Serial.println("SubN");
//サブネゴシェーションが終了するまで読み飛ばし
while(1){
if(get_buffer_size()>0){
char next = pull_buffer();
if(next==0xFF){
next = pull_buffer();
if(next==0xF0){ //サブネゴシェーションの終了
Serial.println("");
Serial.println("SubnEND");
}
else{
Serial.println("!!!! error");
}
break;
}
else{
Serial.print(":");
Serial.print(next, HEX);
}
}
}
}
else{ //その他
Serial.print("other:");
Serial.println(c, HEX);
}
continue;
}
}
else{
//
//client.writeは結構遅いので・・・
// 普通のキャラクタコードの場合はまとめて読み込んでechoする
//
int len =0 ;
unsigned char bytes[256];
for(int i=0;i<nNormalBytes;i++){
if(i>=256){
break;
}
bytes[i]=pull_buffer();
len++;
}
client.write((uint8_t*)&bytes, len);
}
}
delay(1);
yield();
}
client.stop();
Serial.println("Done with client");
}
その他判ったこと。
client.writeは遅いです。
がんがん入力が来た場合はechoは追いつかなくなるので
まとめて読み込んで
それを一気に1回のclient.writeで書かないととてもとても間に合いません。
そんな処理が入っているのでプログラムは読みにくいかもしれません。
参考図書:http://www.ncad.co.jp/~komata/netprg/