2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ソースコード(simpletun)を読みながらトンネリングの仕組みを理解する

Posted at

概要

simpletun というトンネリングを実現するシンプルなツールを見つけたのでそれを動かしながらトンネリングの仕組みを確認していきます。
TUN/TAPデバイスの使い方が分からなかったので、その理解の一環で調べてみました。違うところがあればご指摘をもらえればと。😅

simpletun とは

IP over TCPを実現するツールと考えれば良いと思います。
サーバとクライアントにそれぞれ本ツールをインストールし、接続するとトンネルが張れるので、以後はトンネル用の仮想IPで通信できます。
以下は本ツールでトンネルを張ったあとにトンネルの仮想IFに対してPingした際のキャプチャです:

TCPのデータ部に、カプセル化前のIPヘッダとICMPヘッダが格納されています。

スクリーンショット_-_5月26日_午前0_06.png

検証(動作確認)

今回の例では、トンネルの先の応答用のサーバとして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)を想定しています。

図1.png

ソースコードリーディング

(要点だけ確認のため、コードは適宜抜粋しています。また、 で引用者によるコメントを付記しています)

まずTCPのソケットのファイルディスクリプタと、TUNデバイスをopenしたファイルディスクリプタを取得し、それらをselectで受信します。
TCPのソケットはポート番号を指定できますが、デフォルトでは55555となっています。
上記準備の通り、TUNデバイスはあらかじめ作成しておく必要があります。

simpletun.c(net_fd関連抜粋)
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))
simpletun.c#247(tap_fd関連抜粋)
fd = open(clonedev , O_RDWR))
...
tap_fd = tun_alloc(if_name, flags | IFF_NO_PI)
simpletun.c#(select関連抜粋)
    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経由でパケットを返します。

simpletun.c(抜粋)
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);
    }
}

上記処理をループすることにより、トンネリングを実現しています。

参考

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?