Unix Network Programming とは 🔗
Unix オペレーティングシステム上で、socket/XTI を使ってネットワークプログラミングをするための技法を解説してくれている本。
かなり古い本だが、IPv4/v6 や TCP/UDP は現在も現役だし、頻繁に仕様変更が入るとも思えないので、学んでおく価値は十分にあるはず。
環境 🔗
Mac で VSCode Remote Container を使い、Ubuntu コンテナ内でソースコードを動かす。
- ホストマシン: Mac mini (M1, 2020)
- ホストOS: macOS Monterey 12.4
- コンテナイメージ: ubuntu 20.04.5
- Cコンパイラ: g++=4:9.3.0-1ubuntu2
セットアップ 🔗
ソースコードの入手する 🔗
https://github.com/unpbook/unpv13e
まずは上記を clone し、README に書かれている通りにライブラリ群のコンパイルを実行した。
configure に失敗したので、メッセージに書かれている通り、configure.guess を最新にした。
具体的には、http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD から取得できるスクリプトで、configure.guess を置き換えた。
その後再び configure を実行したら、ライブラリ群のコンパイルに成功した。
C 言語の開発環境を整備する 🔗
この本ではサンプルコードがC言語で提供されているため、C言語の開発環境を整えた。
- プラグインの導入
- VSCode プラグイン をインストールした。
- フォーマッターの設定
- llvm の clang format を使う。
- https://clang.llvm.org/docs/ClangFormatStyleOptions.html
setting.jsonのC_Cpp.clang_format_styleに{ BasedOnStyle: LLVM, BreakBeforeBraces: Attach, SpaceBeforeParens: Never, IndentWidth: 4, AlignConsecutiveAssignments: true, AlignConsecutiveDeclarations: true }"を記述した。
- デバッガー
- gdb を使う。
- インクルードパスの設定
c_cpp_properties.jsonのconfigurations[].includePath[]に、以下を追加する。- configure でビルドしたライブラリ:
${workspaceFolder}/unpv13e/lib - システムのライブラリ:
/usr/include- システムに依存するので、
cc -xc -E -v -などで確認する。
- システムに依存するので、
- configure でビルドしたライブラリ:
- definition の設定
- BSD の practice として
unsigned charをu_char、unsigned intをu_intなどと定義することがあるそう。 - この本のサンプルも、この慣習に習っているので、なにも設定していない状態だとエディターから指摘が入る。
c_cpp_properties.jsonのconfigurations[].definitions[]に、以下を追加する。- “u_char=unsigned char”
- “u_int=unsigned int”
1. 序説 🔗
-
片側のプロトコルスタックを下り、ネットワークを超えて、反対側のプロトコルスタックを上がっていく。
-
サーバー/クライアントはユーザープロセスだが、TCP/IPプロトコルはカーネル内のプロトコルスタック。
-
ルーターはWAN構築の基礎単位。
- ルーター内部が LAN。
- 今日最大のWANはInternet。
-
daytimetcpcli.cを写経してコンパイル。gcc -I../unpv13e/lib -g -O2 -D_REENTRANT -Wall -c -o daytimetcpcli.o daytimetcpcli.cgcc -I../unpv13e/lib -g -O2 -D_REENTRANT -Wall -o daytimetcpcli daytimetcpcli.o ../unpv13e/libunp.a -lpthread- connection refused.
- 先に
daytimetcpsrvを起動しておく必要がある。
- 先に
-
sockfd = socket(AF_INET, SOCK_STREAM, 0)- インターネット(AF_INET) の ストリームソケット(SOCK_STREAM) を生成。
- TCP ソケットに他ならない(らしい)。
AF_INET: IPv4 アドレスファミリを表す。SOCK_STREAM: 信頼できる接続思考のバイトストリーム通信。SOCK_DGRAM: 不当なコネクションレスデータグラム通信。SOCK_RAW: トランスポートプロトコルへの未加工のアクセス。- cf. https://docs.microsoft.com/ja-jp/windows-hardware/drivers/network/af-inet
- HINT:
man socket
-
ソケットという語は文脈によって2通りの意味で用いられる。
- ソケットを作成するときの API。
- TCP のエンドポイント。
-
port 13
- RFC867 で規定された、日付を返すサーバーの well known port.
-
htons- host to network short.
- 短整数をホスト・バイト・オーダーからネットワーク・バイト・オーダーに変換。
- (リトル|ビッグ)エンディアンは、プロセッサーに依存する。
- TCP/IP では、ビッグエンディアンが採用されている。
-
inet_pton- presentation to numeric.
- IPアドレスをテキストから2真数に変換する。
-
バイトストリームプロトコル
- レコード境界を持たない。
Fri Jan 12 14:27:52 1996\r\nという26バイト.- 1つの TCP セグメントに 26 バイトが乗って送られる。
- 26この TCP セグメントに 1 バイトずつ乗って送られる。
- の、両方起こり得る。
-
この例題では、レコードの終わりは、サーバーのコネクションクローズによって表現される。
- HTTP も同様にコネクションクローズがレコードの終わりを表す。
- 一方 FTP, SMTP では、
\r\nがレコードの終わりを表す。 - はたまた DNS では、レコードの先頭にレコードのサイズを表す部位がある。
- TCP ではレコード境界の機能を提供しておらず、アプリケーション側で区別する必要がある。
-
Unix 関数のエラー
- 大域変数
errnoにエラーの種類を表す正の値が設定され、関数の戻り値は-1になる。 - たとえばタイムアウトなら
errno=ETIMEDOUT。 err_sysなどのラッパー関数では、このerrnoの値によってメッセージを出し分ける。<sys/errno.h>に定義される。<>: インクルードパスからファイルを検索"": カレンとディレクトリからファイルを検索し、見つからなかったらインクルードパスを見にいく。
- 大域変数
-
サーバー側
INADDR_ANY- ホストが複数のインターフェイスを持っていた場合に、どのインターフェース宛のメッセージを受け付けるか。
INADDR_ANYはすべて受け付ける。
- リスニングソケット
socket()->bind()->listen()という流れを経て、カーネルがコネクションを受け入れられるようになる。LISTENQ- ソケットのキ
- 接続の確立
accept()により、入力待ちの状態になる。- 3 way handshake が完了すると、
accept()は接続済みのディスクリプターを返す。 - ディスクリプターは、接続ごとに異なる。
snprintfsprintfと異なり、バッファあふれを防げる。- buffer overflow による攻撃に対する防衛力を上げることができる。
- このサーバーは、同時に1つのクライアントしか捌けない
iterative server。- 対して同時に複数のクライアントを捌けるサーバーを
concurrent serverとよぶ。 - concurrent server を実装する最もシンプルな方法は
fork()を使うこと。- 1つのクライアントに対して、1つの子プロセスを持つ。
fork()以外にも、複数スレッドを用いる方法、あらかじめ子プロセスを pre-fork しておく方法などがある。
- 対して同時に複数のクライアントを捌けるサーバーを
- デーモン
- 端末の制御から切り離されて、バックグラウンドで実行されるプロセス。
-
OSI 参照モデル
- 1-4(トランスポート)までは kernel。
- 5-7(セッション/プレゼンテーション/アプリケーション)は user process。
- インターネットプロトコルスタックとしては、以下のような区分け。
- 1-2: デバイス・ハードウェア
- 3: IPv4/v6
- 4: TCP/UDP
- 5-7: アプリケーション
- この本では、1-2 層はまとめてネットワークハードウェアとみなして、詳細には関心を持たない。
- raw ソケット
- TCP/UDP をバイパスして、直接 IPv4/v6 を使うことも可能。
- トランスポート層とのインターフェースは大きく2つ。
- ソケットとXTI
-
ネットワークトポロジーの検出
apt install iproute2ss: socket statistics- 旧
netstat - list of open non-listening sockets that have established connection.
- 旧
ip route- 旧
netstat -r
- 旧
ip -s link- 旧
netstat -i
- 旧
ip madder- 旧
netstat -g
- 旧
- eth0: ethernet interface を表す。
- lo: ループバックインターフェースを表す。
- ブロードキャストアドレス
- ネットワーク内のすべてのアドレスに送信。
- ここに ping を打つと、大雑把にネットワーク内に存在するホストを洗い出せる。
-
POSIX
- Portable Operating System Interface
- 最初の POSIX 標準(1986) では、以下の領域に関するC言語のインターフェースを定めている。
- プロセスプリミティブ(fork, exec, signal, timer)
- プロセス実行環境(UserID, ProcessGroup)
- ファイル・ディレクトリ
- 端末I/O
- システムデータベース(パスワードファイルとグループファイル)
- tar/cpio のアーカイブフォーマット
- 次の POSIX 標準(1992: Posix.2) では、これに加えて、awk や vi、basename などのユーティリティに関する定義も追加。
- さらに次の POSIX 標準(1993: Posix.1b) では、最初の標準に以下を加えた。
- ファイル同期
- 非同期I/O
- セマフォ
- メモリ管理
- 実行スケジューリング
- クロックタイマー
- メッセージキュー
- さらに次の POSIX 標準(1996: Posix.1) では、Posix.1b にスレッドに関する定義を追加。
-
演習
- 1.5 が興味深い。
- サーバー側で TCP ソケットに複数回
writeしても、その回数がクライアント側のreadの回数と一致するとは限らない。 - 同じホスト上で動かす場合は、
readは 1回
- サーバー側で TCP ソケットに複数回
- 1.5 が興味深い。