WebsockifyでのTCPProxy検証

websockifyは、websocket通信をTCPに変換してくれるツールです。

とあるソフトウェアがWebsocket通信に対応してなくて、
どうにかこうにかWebsocket経由で動かしたい…ということがあったので
Websockifyで遊んでみたのと、サンプルでソースが動作しなかったのでちょっと書き加えたり
telnet以外のTCP通信で動作させたらちょいとハマったのでその時のメモです。

サンプル検証

上記のリポジトリ内でwstelnet.htmlというサンプルコードがありましてブラウザからwebsocket経由でtelnet接続ができます。

環境構成図

全て同一セグメント内に配置してます。
サーバは全てCentOS7で作成しています。

環境構築

  • クライアント側
git clone https://github.com/novnc/websockify
cd websockify
open wstelnet.html
  • telnetが動作するサーバ
yum -y install telnet telnet-server
systemctl start telnet.socket
  • websockifyが動作するサーバ
yum install gcc gcc-devel make git
git clone https://github.com/novnc/websockify
cd websockify
make
./run 9999 192.168.0.20:23

サンプルソースの動作チェック

クライアントのブラウザからIP とPort入れてconnnectをクリックしても動きませんでした…^^;
Chromeの開発ツールを見てみると
arr.shift is not a function エラーを吐いてました。
websockify/include/wstelnet.jsの61行目でarrをconsole.logで出力してみたところ
Uint8Arrayと出力されました。

shiftってUint8Arrayには利用できないんですよね….

var x = [1,2,3,4,5]
console.log(x.shift()) => 1

var y = new Uint8Array(50)
console.log(y.shift()) => TypeError: y.shift is not a function

なので、ソースコードを若干書き換えました。

どこで弊害が出るか分かんないですが、動けばいいのでとりあえずはこれで。

で、画面をリロードしてもう一度IPとPort入れてconnect押すとちゃんとブラウザ経由でtelnet接続できてます。
これはすごい。

wsで繋いでみる

画面で動作するのは確認できたので、
次はws/websockets - Githubでnodeから繋いでみる。
telnet-serverは動くの分かったんで、今度は別のTCP Serverを用意します。

環境構成

  • クライアント

javascript のwsライブラリを利用しています。
送信するメッセージはテストなのでなんでも良いです。

npm で ws をインストールして下記のファイルをclient.js として保存します.

const WebSocket = require('ws');
const ws = new WebSocket('ws://192.168.0.10:9999', {
  perMessageDeflate: false
});

ws.on('open', function onOpen() {
  ws.send('{ "user_name" : "test" }');
});

ws.on('message', function(message) {
  console.log(message);
});

ws.on('close', function close() {
  console.log('disconnected');
});
  • websockfyが動作するサーバ

サンプルと同じなので割愛

  • TCP Server

2000番で待ち受けて、何かメッセージを受信したら '{"id" : 1}' を返却します。
下記はrubyで実装していて、server.rbとして保存しています。

require "socket"

port = 2000
server = TCPServer.open(port)

while true
  socket = server.accept

  while buffer = socket.gets
    socket.puts '{"id" : 1}'
  end

  socket.close
end

server.close

動作チェック

TCP Serverでruby server.rbを実行し、クライアントからはnode client.jsを実行します。
が、すぐDisconnectされる….

原因探し…

tcpdumpを利用してパケットのWebSocketのFINをのぞいてみると
Reason: Unsupported: Unknown opcode 0x01 というメッセージが残っていました。
ふむふむ…..

と、websockifyのコードを見てるとopcodeの項目があって
0x1はtext frameなんですね。

recvmsgを見てると、
opcodeが0x1の時の分岐がなくて全部elseで落とされてる。

telnet-serverの時は何で送られていたのか….
tcpdumpでWebSocketのパケットをのぞくと、
0x2のbinary frameを利用していました。

じゃあbinary frameで送ることができればTCP Serverともちゃんと通信できるかも…!
ということで、clientから送信するメッセージをバイナリに変換します。

  • クライアントのコードを変更

send時にUint8Arrayを利用してバイナリに変換しています。
これ、サンプルソースをちゃんと見てたら気づくの早かったかもしれない。
レスポンスを受け取る時はバイナリをstringに変換しています。

const WebSocket = require('ws');
const ws = new WebSocket('ws://192.168.0.10:9999', {
  perMessageDeflate: false
});

ws.on('open', function onOpen() {
  ws.send(Uint8Array.from(Buffer.from('{ "user_name": "test" }')));
});

ws.on('message', function(message) {
  console.log(Buffer.from(message).toString());
});

ws.on('close', function close() {
  console.log('disconnected');
});

再度、動作チェック

クライアントからはnode client.jsを再度実行。
またDisconnectされる….
なぜだ… とパケットを眺めてみるとフローがこんな感じになってました。

※フローは見やすくするために所々削ってる部分があります

clientがcloseした後に、TCP Serverにメッセージを投げている….
これ何が行けなかったかというと、メッセージ内に改行コード入っていないだけでした(笑)
ws.sendで送信するメッセージに改行コード入ってないと、websockifyでメッセージが滞留してしまい
closeのタイミングでTCP Serverにメッセージを投げてしまう、
だけどwebsocket通信は終了してしまっているのでクライアントにレスポンスを返せない。
という状況になってました。

なのでsendの時はちゃんと改行コードいれましょう….

  ws.send(Uint8Array.from(Buffer.from('{ "user_name": "test" }' + "\n")));

これで正常に動作しました。

ただwebsockifyを利用したかっただけなのだけど、
WebsocketのRFCを少し読んだり、
ssh先のサーバのtcpdumpをwiresharkに流し込むとか
いい勉強になったので面白かったですよ。