【Ruby】IP over UDPトンネリングを実現する
はじめに
以下を目的にRubyで簡単なIP over UDPトンネリングを実装してみました。
- トンネリングの仕組みの理解を深めること
- Rubyのネットワークプログラミングの練習
トンネリングの仕組みは事前に参考(4)(5)等で調べましたので、本記事では仕組みの概要の記載は省略します。
実装の難しかったポイントとして、RubyではTUN/TAPデバイスの取り扱いがなかなか難しそうだったので(Cの既存の構造体をRubyでは文字列で扱う必要があるところや、それら関連のデバッグ等。ただし参考(1)の通りRubyだけで似たようなことを実現している方もいるようなので不可能ではない)、その部分は参考(3)のツールを利用させてもらいました。このツールでも低レイヤ周りの処理はNative Extensionとして実装しているようです。
色々試していたら以下のコードでとりあえず動いたので、一旦記事にまとめます。
IOに関する処理(read と read_nonblock の違いや、それらとTUNデバイスを使う時の影響とか)がイマイチ理解しきれていないので今後の課題としています。
コード
以下のコードを実行すると、TUNデバイスが作成&アドレス付与&アップされ、実NWインターフェースで、UDP/9876でリッスンされます。TUNデバイスにBINDするアプリケーションを実行することで、そのアプリケーションをトンネルの先に配置できます。
トンネリングの仕組みは参考(5)に記載しています。
下記コードは参考(2)にも記載しています。
require "socket"
require "rb_tuntap"
PEER = "[IP Address of PEER]"
PORT = 9876
tun = RbTunTap::TunDevice.new("tun0")
tun.open(false)
tun.addr = "192.168.0.1" # or 192.168.0.2, ノードでかぶらないようにする
tun.netmask = "255.255.255.0"
tun.up
tun_io = tun.to_io
sock = UDPSocket.open
sock.bind("0.0.0.0", 9876)
peer = Socket.pack_sockaddr_in(PORT, PEER)
while true
ret = IO::select([sock, tun_io])
ret[0].each do |d|
if d == tun_io
# TUNデバイスで受信した場合
data = tun_io.sysread(65535)
sock.send(data, 0, peer)
else
# 実NWで受信した場合
data = sock.recv(65535)
tun_io.syswrite(data)
end
end
end
実行手順
トンネルの先のアプリケーションとしてWebサーバ(Webrick)を用いる場合。
事前に rb_tuntap
をインストールしておきます。
サーバ側
% sudo ruby rb_simpletun.rb
[別のターミナル]
% sudo ruby -rwebrick -e 'WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 80, :BindAddress => "192.168.0.1").start'
クライアント側
% sudo ruby rb_simpletun.rb
% wget 192.168.0.1
パケットキャプチャ
WireSharkで実NWインターフェースとトンネルインターフェースでキャプチャしました。
実NWインターフェース
実NWインターフェースから見るとUDPパケットですが、データ部にHTTPのデータのようなものが乗っかっているのが分かります。
トンネルインターフェース
トンネルインターフェースでは素のHTTPのみが見えています。
参考
(1)
(2)
(3)
(4)
参考(自分の過去記事)
(5)
(6)
Discussion