『Rustで始めるネットワークプログラミング』読んだ

ネットワークをもっと理解したかったし、500円と安かったので購入。

紹介ページ : https://cha-shu00.hatenablog.com/entry/2019/06/12/231526

環境はLinuxが想定されている。おれは1章と5章をWindows10で、2章から4章はWSL2のUbuntu 20.04で演習した。理由は後述する。

第1章 ようこそソケット通信の世界へ

単純なechoサーバを、TCPとUDP両方のプロトコルで作成する。
サーバプログラムの動作確認にtelnetまたはncコマンドを使用する。Windowsの場合はどちらもscoopからインストール可能。まあクライアントプログラムも作成するので不要っちゃ不要。
インストールする場合は通信プロトコルを指定できるnetcatのほうがいいかも。

$ scoop install telnet # or netcat

第2章 通信を監視する

指定したネットワークインターフェイスに流れるパケットをキャプチャし、標準出力に表示するプログラムを作成する。 いきなり難度の高そうなプログラムを作らされるが、ネットワーク関連のクレートであるpnetが強力なので案外コード量は少ない。

データリンク層のフレーム -> ネットワーク層のパケット -> アプリケーション層のバイナリデータ といった具合に、各レイヤーにおけるデータからヘッダー部をむきむきしてペイロード部を順々に取り出していく実装となる。

って、コンパイルエラーなるやないかーい。

error: linking with `link.exe` failed: exit code: 1181
  |
  = note: (長すぎるため省略)

  = note: Non-UTF-8 output: LINK : fatal error LNK1181: \x93\xfc\x97\xcd\x83t\x83@\x83C\x83\x8b \'Packet.lib\' \x82\xf0\x8aJ\x82\xaf\x82\xdc\x82\xb9\x82\xf1\x81B\r\n

error: could not compile `ch2-packet-capture` due to previous error

エラーがやたらと冗長だが、要はPacket.libなるものが必要らしい。
これはWindows用のパケットキャプチャライブラリのnpcapに同梱されている。以下のページよりNpcap SDK 1.11 (ZIP)をダウンロード(バージョンは2021/11/05時点)。

https://nmap.org/npcap/

ダウンロードしたzipを展開し、その中のLib/x64/Packet.libをどっか適当なフォルダに突っ込み、さらにそのフォルダのパスをLIB環境変数に登録する。するとビルドが成功する。(まあこのIssue見て解決方法知ったんすが)。

次にネットワークインターフェイスの名前を調べる。Linuxではip addrで取得できるが、WindowsのPowerShellではGet-NetAdapterを……使わない。以下のコードをmain.rsに追加し、ネットワークインターフェイス名を列挙させる。

    let interfaces = datalink::interfaces();
    // ここから追加分
    for iface in interfaces.iter() {
        println!("{}", iface);
    }
    // 追加分ここまで

実行結果は以下のようになる。

$ cargo run # Windowsではデータリンク層のパケット受信に管理者権限が必要ないのでこれでおk
\Device\NPF_{hoge}: flags=0
      index: d
      ether: xx:xx:xx:xx:xx:xx
       inet: x.x.x.x/x
\Device\NPF_{fuga}: flags=0
      index: e
      ether: yy:yy:yy:yy:yy:yy
       inet: y.y.y.y/y
~~~

\Device\NPF_{}の部分がWindowsにおけるネットワークインターフェイス名なので、インターネットに接続されてそうなものをcargo run "\Device\NPF_{}"とすることでパケットキャプチャ可能。

ちなみにGet-NetAdapterの実行結果は以下のようになる。Rustコードの実行結果とぜんぜん違うやないか。

$ Get-NetAdapter | % {$_.Name}
イーサネット
Wi-Fi
VirtualBox Host-Only Network
vEthernet (WSL)

ここまでやって面倒くさくなったので、以後おとなしくWSL2にて動作させることにした。

第3章 手づくりパケットでポートスキャン

待ち受け状態(リスニングソケットがバインドされている)ポートを特定するプログラムを作成する。
4種のポートスキャン手法を実装する。雑に言えばどの手法も相手になんらかのTCPパケットを送信するが、

  • SYNスキャンの場合 : 相手のポートが開いていたらSYN+ACKが返ってくる
  • それ以外のスキャンの場合 : 相手のポートが開いていたらパケットが返ってこない

上記の判断材料でもってポートの開閉をチェックすることになる。

第4章 ノンブロッキングなWEBサーバ

その名の通りのサーバを作成する。
ノンブロッキングI/Oは、I/O処理が完了して いない ことをカーネルがプロセスにエラー通知する方式。似たようなものとして非同期I/Oもあるが、こちらはI/O処理を担当するプロセスorスレッドが処理を完了したときに要請元プロセスorスレッドに通知する方式。
これがどう違ってくるねんて話だが、前者はイベントループによるポーリング方式と組み合わせることでI/O処理完了を待機するシングルスレッドのままにすることが可能。対して、後者はマルチプロセスorマルチスレッド前提の運用となる。
ちなみにプロセスには独自のメモリ空間が割り当てられるのに対し、同じプロセス内のスレッドはメモリ空間を共有する。

非同期I/Oでは、サーバのハードウェア性能に余裕があってもクライアントの同時接続数が1万台付近に達するとレスポンス性能が落ちるC10K問題が発生するらしい。原因は1リクエストにつき1プロセスorスレッドを割り当てるというぜいたくな方式なので、すぐにリソース上限が来るかららしい。
シングルプロセス・シングルスレッドたるノンブロッキングI/Oなら上記のようなことは起きないが、こっちはこっちでI/Oに時間がかかると全体のパフォーマンスに影響するらしい。まあ各リクエストは1つのイベントキューに突っ込まれて待機してるしな。

Webサーバを作成したらホストOS(Windows)のブラウザからアクセスしてみてもいいし、以下のようにターミナルから脆弱性をつついてみてもいい。

$ nc -C 127.0.0.1 8080 # -Cオプションは改行をCRLFに固定
GET /../Cargo.toml HTTP/1.1 # 入力部分(ここではパストラバーサルを仕込んでいる)
HTTP/1.0 200 OK
Server: mio webserver

[package]
name = "ch4-mio-webserver"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.45"
env_logger = "0.9.0"
log = "0.4.14"
mio = "0.6.11"
regex = "1.5.4"

第5章 RFCから作るDHCPサーバ

ネットワークに関連する仕様をまとめたドキュメントであるRFCを参照しながらDHCPサーバを作成する。DHCPサーバとはネットワーク内のホストにIPアドレスを付与するなど、ネットワーク設定を自動で行ってくれるサーバで、ルータがこれを兼任することが多い。
また、この章ではDHCPについて記述されているRFC2131を実際に見ながら演習していく。

DHCPパケットのフィールドの仕様を確認しながらビット列をいじいじする部分は、PLCにコマンドを送信するプログラムを業務で仕様書読みながら組んだことを思い出し懐かしい気持ちになった。

ただ、WSL2のネットワークはホストOS側にはveth扱いとなっている。ルータの設定を変更しDHCP機能を停止してもWSLの面倒をみるDHCPサーバは他にいるので意味はない。なので結局この章ではWindowsで戻ってきた。
2章のときと同じくコンパイル時にsqlite3.libがなくてどやされるので、やはりそれを同じようにパスに追加して実行した。

まとめ

新たに学べた事項は以下の通り。厳密にはこの本以外の部分でわかったことも含めている。

  • env_loggerクレートの使い方(いまさら?)。
  • pnetクレートの使い方。
  • ノンブロッキングと非同期の違い。
  • RFCを読む根性。
  • DHCPの大雑把な仕様。
  • RustでのDBライブラリの使い方。
  • ICMP : IPの補佐的プロトコル。Pingなどネットワークの疎通確認に使われる。

以前『Linuxで動かしながら学ぶTCP/IPネットワーク入門』を読んでネットワークについてそこそこ勉強したはずだが、案の定、大半の内容が忘却の彼方に追いやられていた。それを今回のプログラム写経によって少しは取り戻せたように思う。