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]
お名前:
  • HTML
  • PDF
RSS2.0 RSS1.0 Atom1.0