Telnetプロトコルを実装しよう(サーバー側)†
うちの研究室で、「telnetでつないで倉庫番できたらおもしろいのに」みたいな意見が出たので実装してみました。
Telnetプロトコルなんてrawじゃねーの?†
実は違います。
概要は http://ja.wikipedia.org/wiki/Telnet に譲るとして、RFCを読んでみました。が、まったく理解できません。
とりあえずパケットキャプチャしたところ、FF xxとかFF xx xxのような形のメッセージが見つかりました。(相手はnethack.alt.orgを選びました。)自分からなにかメッセージを送り、相手がそれに対して返し、また相手からメッセージが来て…のようになってました。
というわけで、rawではないのでした。しかし自分で簡単なdumpサーバー(来た文字をstderrにhexdumpする)を立てて
$ telnet localhost 12345
のように接続しても何も来ない! これはおかしいですね…。
落とし穴†
23/tcpを使うのはまずいと思って別のポートでサーバーを動かした。しかしtelnetはtelnetプロトコルをしゃべってくれない!試しにdumpサーバーをrootで動かし、23/tcpで待ち受けてみました。
$ telnet localhost
すると、telnetプロトコルをしゃべり始めました。試しに
$ telnet localhost 23
のようにすると、今度は黙りました。はて、どんな違いがあるわけ?
この実験ではMac OS X 10.6.3を使っていました。とりあえずソースがあるはずなので読んでみます。( http://opensource.apple.com/source/remote_cmds/remote_cmds-22/telnet.tproj/commands.c )
そこには残念な事実がありました。
if (!portp) {
telnetport = 1;
portp = strdup("telnet");
} else if (*portp == '-') {
portp++;
telnetport = 1;
} else if (*portp == '+') {
portp++;
telnetport = -1;
} else
telnetport = 0;
ポート番号を指定すると、telnetportに0が入り、内部のモードが変わってしまうのでした。(ポート番号を指定しなければtelnetportに1が入り、ポート番号は"telnet"になる。これは/etc/services経由で23になる。)そこでよく読むと、ポート番号の前に+とか-をつけると挙動が変わるようです。
試しに、
$ telnet localhost -23
のようにすると、telnetがtelnetプロトコルをしゃべり始めました!試しに12345/tcpとか適当なところでlistenして、
$ telnet localhost -12345
のようにすると、今回もtelnetプロトコルをしゃべりました。
このソース読むまで、ポート番号の前に何かつけるなんて発想は全くなかったです。試しにlinuxでもやってみました。
$ telnet localhost 12345 (telnetプロトコルをしゃべらない) $ telnet localhost -12345 (12345というオプションは知らないと言い始める) $ telnet localhost -- -12345 (telnetプロトコルをしゃべる!)
linuxでも同様らしいです。オプションの指定に一癖ありますが。
なんでtelnetコマンドはこんな頭おかしい実装になってしまったのでしょうか。おとなしくどのポートでもtelnetプロトコルしゃべって、rawで話したい時はnetcatにその座を譲れば良かったのに…。
というわけで、telnetプロトコルでしゃべれるようになったわけだ。†
最初にパケットキャプチャしたものを眺めてからRFCを読むと、まあ何となく内容が分かってきました。
RFCによれば、
(RFC764) All TELNET commands consist of at least a two byte sequence: the "Interpret as Command" (IAC) escape character followed by the code for the command. The commands dealing with option negotiation are three byte sequences, the third byte being the code for the option referenced. (適当な訳) すべてのTELNETコマンドは少なくとも2バイトの並び: 「コマンドとして理解」(IAC) エスケープ文字の後に、コマンドのcodeが続く。 オプション付きコマンドは3バイトの並びで、3バイト目はコードのオプションです。
らしい。(今までTelnetプロトコルとか言ってたけどぶっちゃけTelnetコマンドが中心だったね…)
で、IACとかコマンドとかって、実際どんな値なのよ? と思って見ると…
(RFC764)
NAME CODE MEANING
SE 240 End of subnegotiation parameters
NOP 241 No operation
Data Mark 242 The data stream portion of a Synch
This should always be accompanied
by a TCP Urgent notification.
Break 243 NVT character BRK
Interrupt Process 244 The function IP
Abort output 245 The function AO
Are You There 246 The function AYT
Erase character 247 The function EC
Erase Line 248 The function EL
Go ahead 249 The GA signal
SB 250 Indicates that what follows is
subnegotiation of the indicated
option
WILL (option code) 251 Indicates the desire to begin
performing, or confirmation that
you are now performing, the
indicated option
WON't (option code) 252 Indicates the refusal to perform,
or continue performing, the
indicated option.
DO (option code) 253 Indicates the request that the
other party perform, or
confirmation that you are expecting
the other party to perform, the
indicated option.
DON'T (option code) 254 Indicates the demand that the
other party stop performing,
or confirmation that you are no
longer expecting the other party
to perform, the indicated option.
IAC 255 Data Byte 255.
らしいです。(この欄にIACの値を混ぜるなよ…これ見つけるまですごく時間かかったんだぞ…)
要は、"255 255"(0xFF 0xFF)とやれば"255"そのもの(Data Byte 255)だし、"255 251 xxx"(0xFF 0xFB xx)のようにすればそれはWILLコマンド(xxxは何がWILLなのか示すオプション)というわけ。
で、普通telnet使うと行指向なので、「hjklつかって移動~」とかできないわけで(Enter押さないと相手に送られないから)、それを無効化したいときはサーバー側がクライアントに向けて"255 254 34"(0xFF 0xFE 0x22)を送ると、これはDON'T LINE MODEを示すらしいので、ユーザーがキーを押した時に、すぐにサーバーにそのキーを送るようになります。(LINE MODEはRFC1116にあるらしいです。)
1. Command Names and Codes
LINEMODE 34
(snip)
2. Command Meanings
2.1 The LINEMODE function
(snip)
IAC DONT LINEMODE
The sender of this command DEMANDS that the remote side not begin
subnegotiation of the editing/signaling status.
(なんか言ってること違うように思えたけど、きっと僕の英語力が残念なだけだ…)
落とし穴その2†
これでキーを押した瞬間に押されたキーを送ってくれるようになって、めでたくhjklできるようになったと思いたいですが、僕の目的は倉庫番を作ることなので、押されたキーが表示されるとちょっと残念になってしまいそうです。(画面にhhjjjllkjとか出たら嫌だよね…)なので、押されたキーが表示されないようにしたいのです。(よくechoとか言われる奴です。)
そこはTelnetプロトコルで、echoを制御するコマンドがあります。それがこれ。
(RFC857)
1. Command Name and Code
ECHO 1
2. Command Meanings
IAC WILL ECHO
The sender of this command REQUESTS to begin, or confirms that it
will now begin, echoing data characters it receives over the
TELNET connection back to the sender of the data characters.
このコマンドはちょっと頭おかしくて、普通echoしたくなかったら"IAC WONTorDONT ECHO"とか送ると思うじゃないですか。(この場合は"255 252or254 1"になる。)それがTelnetでは逆で、echoコマンドの意味は「サーバー側がechoしてあげるか」ということらしいです。なので、"IAC WILLorDO ECHO"を送ることになります。(この場合"255 251or253 1"となる。)
ちなみにまだWILL(WONT)とDO(DONT)の違いが分かってないので、適当にやったらできたということになってしまうんですが、"IAC DO ECHO"だと駄目で、"IAC WILL ECHO"だと大丈夫らしいです。(その後、確かTelnetクライアントの方から"IAC WONT ECHO"が返ってくるのかな?)
で、結局どうなったのか†
めでたく倉庫番を実装できました。rubyで。telnetdいらず。
が、しかし、画面の描画が遅延するのでした。s.puts (画面)してからs.flushしてs.getcしてるけど、やっぱり遅延します。
結局解決できず。telnetd使った方が早かったんじゃねーの?
でも、知的好奇心満たせたのでよしとしよう。
おわり。
今回言いたかったこと†
- telnetプロトコルの情報が分散しすぎ。なんでTelnetプロトコルに関してRFC何個も出てるんだよ…
- IACの表現悪すぎ。文の中に「IACは255です」と一言入れとけ。表につっこむとマジで分からん。
- Echoコマンドは、とっつきにくい。
- おとなしくtelnetdを使った方が早い
追記欄†
追記1†
Telnetプロトコルについて、やさしく解説しているページを見つけました。参考にどうぞ。
http://www5e.biglobe.ne.jp/%257Eaji/3min/55.html
追記2†
画面が遅延する問題、いろいろ試した結果、rubyのTCPSocket#eofを呼び出すと起こるらしい。
かわりに、接続が切れたらTCPSocket#getsがnilを返すので、それで判断するべき? (未確認)
つっこめ。†
- xZhu5W <a href="http://suopzevvxxdu.com/">suopzevvxxdu</a>, [url=http://juevehoibtpn.com/]juevehoibtpn[/url], [link=http://jepqclvfbdrg.com/]jepqclvfbdrg[/link], http://vznhasehfzvs.com/ -- AhCWEyBUouvOkZFX [2011-03-14 00:49:39.675]
- k5ceSb <a href="http://creifekkywpn.com/">creifekkywpn</a>, [url=http://vmgjozikcjnp.com/]vmgjozikcjnp[/url], [link=http://lvvhjuyjvcxs.com/]lvvhjuyjvcxs[/link], http://bxhpjvurveqj.com/ -- XFhAtzPFqyNkBnggS [2010-11-15 13:19:19.673]





