Unix Network Programming: Chap1

Sep 10, 2022 17:10 · 432 words · 3 minute read

Unix Network Programming とは 🔗

http://www.unpbook.com/

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言語の開発環境を整えた。

  1. プラグインの導入
  1. フォーマッターの設定
  • llvm の clang format を使う。
  • https://clang.llvm.org/docs/ClangFormatStyleOptions.html
  • setting.jsonC_Cpp.clang_format_style{ BasedOnStyle: LLVM, BreakBeforeBraces: Attach, SpaceBeforeParens: Never, IndentWidth: 4, AlignConsecutiveAssignments: true, AlignConsecutiveDeclarations: true }" を記述した。
  1. デバッガー
  1. インクルードパスの設定
  • c_cpp_properties.jsonconfigurations[].includePath[] に、以下を追加する。
    • configure でビルドしたライブラリ: ${workspaceFolder}/unpv13e/lib
    • システムのライブラリ: /usr/include
      • システムに依存するので、cc -xc -E -v -などで確認する。
  1. definition の設定
  • BSD の practice として unsigned charu_charunsigned intu_int などと定義することがあるそう。
  • この本のサンプルも、この慣習に習っているので、なにも設定していない状態だとエディターから指摘が入る。
  • c_cpp_properties.jsonconfigurations[].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.c
    • gcc -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() は接続済みのディスクリプターを返す。
      • ディスクリプターは、接続ごとに異なる。
    • snprintf
      • sprintf と異なり、バッファあふれを防げる。
      • 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 iproute2
    • ss: 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回