概要
simpletun というトンネリングを実現するシンプルなツールを見つけたのでそれを動かしながらトンネリングの仕組みを確認していきます。
TUN/TAPデバイスの使い方が分からなかったので、その理解の一環で調べてみました。違うところがあればご指摘をもらえればと。😅
simpletun とは
IP over TCPを実現するツールと考えれば良いと思います。
サーバとクライアントにそれぞれ本ツールをインストールし、接続するとトンネルが張れるので、以後はトンネル用の仮想IPで通信できます。
以下は本ツールでトンネルを張ったあとにトンネルの仮想IFに対してPingした際のキャプチャです:
TCPのデータ部に、カプセル化前のIPヘッダとICMPヘッダが格納されています。
検証(動作確認)
今回の例では、トンネルの先の応答用のサーバとしてWebサーバを用いました。
環境
- クライアント
- WSL(Ubuntu 20.04) on Windows 10(x86)
- HTTPクライアントとする(wget)
- サーバ
- Ubuntu 20.04(x86)
- Webサーバとする(Ruby/Webrick)
準備
simpletun のインストール
$ https://github.com/gregnietsky/simpletun.git
$ cd simpletun
$ make
tun デバイスの設定
実行する前にTUNデバイスの設定が必要。
名前は tun0
とし、仮想のIPアドレスは 192.168.0.1/24
とする。(サーバとクライアントのどちらかは.02にする)
以下のように設定すると 192.168.0.0/24 宛のルートがTUNデバイスに向くエントリがルーティングテーブルに自動的に追加されます。
$ sudo ip tuntap add dev tun0 mode tun
$ sudo ip link set tun0 up
$ sudo ip addr add 192.168.0.1/24 dev tun0
トンネリングの動作確認
# (サーバ側)
$ ./simpletun -i tun0 -s -d
# Webサーバ起動(今回はRuby/Webrickを使った)
$ ruby -rwebrick -e 'WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 80, :BindAddress => "192.168.0.1").start'
# (クライアント側)
$ ./simpletun -i tun0 -c [サーバの実IPアドレス] -d
仕組みの確認
概要図(多分こんな感じ)
以下のような動きと理解しましたので、その詳細を追っていきます。
図中の Tunnel App
はsimpletun を、 Web Server
はトンネル用の仮想IPにバインドしているWebサーバ(今回はWebrick)を想定しています。
ソースコードリーディング
(要点だけ確認のため、コードは適宜抜粋しています。また、 ★
で引用者によるコメントを付記しています)
まずTCPのソケットのファイルディスクリプタと、TUNデバイスをopenしたファイルディスクリプタを取得し、それらをselectで受信します。
TCPのソケットはポート番号を指定できますが、デフォルトでは55555となっています。
上記準備の通り、TUNデバイスはあらかじめ作成しておく必要があります。
sock_fd = socket(AF_INET, SOCK_STREAM, 0)
...
connect(sock_fd, (struct sockaddr*) &remote, sizeof(remote)
...
local.sin_port = htons(port); // ★デフォルトでは55555
...
bind(sock_fd, (struct sockaddr*) &local, sizeof(local)
...
net_fd = accept(sock_fd, (struct sockaddr*)&remote, &remotelen))
fd = open(clonedev , O_RDWR))
...
tap_fd = tun_alloc(if_name, flags | IFF_NO_PI)
fd_set rd_set;
FD_ZERO(&rd_set);
FD_SET(tap_fd, &rd_set); FD_SET(net_fd, &rd_set);
ret = select(maxfd + 1, &rd_set, NULL, NULL, NULL);
次に、実際にデータをトンネル経由で送信するときの動きを追っていきます。
クライアントが最初にトンネル経由でのリクエストを送信すると、クライアントのルーティングテーブルにしたがってTUNデバイスにデータが到達します。
TUNデバイス側で受信した場合は、そのデータを作成済みの実ネットワークのソケットを使って送信します。作成したソケットは、TCPのソケットですので、TCPのデータ部にこのデータが格納され送信されます。
次に送られたデータがサーバ側の実ネットワークIFに届きます。
カプセル化されたデータを抜き出し(この例ではIP+HTTPリクエスト)、TUNデバイスに書き込みます。そうすると、TUNデバイスのIPアドレスにバインドしている(この例では)Webサーバがそのデータを受信して処理します。
WebサーバはレスポンスをTUNデバイスに返信し、トンネリングのプログラムはループ処理によりTUNデバイスの入力を待ち受けているので、送信時と同じようにデータをTCPのデータ部にカプセル化して、実ネットワークIF経由でパケットを返します。
while(1) {
ret = select(maxfd + 1, &rd_set, NULL, NULL, NULL);
...
// ★TUNデバイスで受信した場合
if(FD_ISSET(tap_fd, &rd_set)) {
nread = cread(tap_fd, buffer, BUFSIZE);
/* write length + packet */
plength = htons(nread);
nwrite = cwrite(net_fd, (char *)&plength, sizeof(plength));
nwrite = cwrite(net_fd, buffer, nread);
}
// ★実ネットワークIFで受信した場合
if(FD_ISSET(net_fd, &rd_set)) {
/* Read length */
nread = read_n(net_fd, (char *)&plength, sizeof(plength)); // ★plength にはIPヘッダ+HTTPヘッダのサイズが格納されている
/* read packet */
nread = read_n(net_fd, buffer, ntohs(plength));
nwrite = cwrite(tap_fd, buffer, nread);
}
}
上記処理をループすることにより、トンネリングを実現しています。
参考