DIVERT(4)

名称
divert - カーネルによるパケット迂回メカニズム

書式
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> int socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT)

解説

迂回ソケットは bind(2) システムコールを経由して特定の divert ポートにバインドすることができることを除き、 生の IP ソケットとほぼ同じです。 bind での IP アドレスは無視され、ポート番号のみが意味を持ちます。 迂回ポートにバインドされた迂回ソケットは、 何らか (ここでは特定しません) の カーネルメカニズムによってそのポートに迂回された全てのパケットを受信します。 パケットを迂回ポートに書き込むこともできます。 その場合はカーネルの IP パケット処理に再び入力されます。

通常、迂回ソケットは FreeBSD のパケットフィルタリングの実装や ipfw(8) プログラムと共に使われます。 迂回ソケットからの読み出しや書き込みによって、 合致するパケットをホストマシンを経由する時に任意の ``フィルタ'' を通したり、 特別なルーティングの細工を行うといったことができます。

パケットを読む
``入ってくるパケット'' であっても ``出ていくパケット'' であっても迂回 させることができます。 入力パケットは IP インタフェース上で受信された後に迂回されます。 出力パケットは次のホップに転送する前に迂回されます。

迂回パケットは read(2), recv(2), もしくは recvfrom(2) によってそのままの形で読み出すことができます。 後者の場合に得られるアドレスでは、 ポート番号にはパケット迂回者が提供するなんらかのタグ (通常は ipfw のルール番号) が設定され、 IPアドレス部には 入力パケットの場合はパケットが受信された (最初の) インタフェースアドレス、 出力パケットの場合は INADDR_ANY が設定されます。 入力パケットの場合はアドレスに続く 8 バイトの中に インタフェース名も (そこに収まるものとして) おかれます。

パケットを書く
迂回ソケットへの書き込みは生の IP ソケットへの書き込みと似ています。 パケットは ``そのままの'' 形で通常のカーネル IP パケット処理へ送られ、 最小のエラーチェックのみが行われます。 パケットは入力としても出力としても書くことができます。 すなわち write(2) もしくは send(2) がパケットの配送に使われるか、 sendto(2) が INADDR_ANY の宛先 IP アドレスと共に使われると、 パケットはそれが出力であるかのように扱われます。 つまりローカルでないアドレスへ差し向けられます。 その他の場合は、入力であると想定され、 全てのパケットルーティングが実行されます。

後者の場合では、 指定された IP アドレスは、 どれかのローカルインタフェースのアドレスと一致するか、 インタフェース名が IP アドレスの後に見つからなければなりません。 もしインタフェース名が見つかれば、 そのインタフェースが使われ、IP アドレスの値は無視されます (それが INADDR_ANY でないことは無視されません)。 これは、どのインタフェースからパケットが ``到着'' したかを示すためです。

通常、入力として読み出されるパケットは入力として書かれなければなりません。 出力パケットについても同様です。 パケットを読み出して書き戻す時には、 recvfrom(2) によって与えられたソケットアドレスと同じものを、そのままの形で sendto(2) に渡すことで物事が単純になります (下記参照)。

sendto(2) へ渡されるソケットアドレスのポート部分には、 迂回モジュールにとって意味のあるタグが含まれます。 ipfw(8) の場合には、タグは、 この次の ルール番号からルール処理を再開すべきと解釈されます。

ループの回避
迂回ソケットへ ( sendto(2) を使って) 書き込まれたパケットは、 ソケットアドレスのポート部分に与えられたタグに続くルール番号から、 パケットフィルタに再入されます。 通常、ソケットアドレスは迂回を発生させる (同じ番号においていくつかのルールがある場合は次のルールではない) ルール番号においてすでにセットされています。 もし 'タグ' が別の再入点を示すために置き換えられていれば、 同じパケットが同じルールで 1 度以上迂回されるようなループを 回避するための考慮がなされるべきです。

詳細
迂回ソケットを有効とするためには、 IPDIVERT オプションを指定してカーネルをコンパイルしなければなりません。

パケットは迂回されるがポートにバインドされるソケットがない場合、 もしくは IPDIVERT がカーネルにおいて有効にされていない場合は、パケットは捨てられます。

迂回される入力パケットフラグメントは配送前に全てリアセンブルされます。 フラグメントが 1 つでも迂回されると全てのパケットが迂回されることになります。 もし、フラグメントのいくつかが違うポートに迂回されると、 最後にどのポートが選択されるかは予期できません。

パケットは受信されて、変更されずに送信されますが、 出力として書かれたパケットの IP ヘッダのチェックサムは 正しい値に書き換えられます。 入力として書かれ、誤ったチェックサムを持つパケットは捨てられます。 それら以外は、全てのヘッダフィールドは変更されません (もちろんネットワークに届いた順番で)。

タイプ SOCK_RAW のソケットの生成と同様に、 1024 より小さいボート番号をバインドするためには スーパユーザアクセスが必要となります。

エラー
生のパケットの書き込み時に通常発生するエラーに加え、 迂回ソケットへの書き込みは以下のエラーを返すことがあります。

[EINVAL]
    パケットのヘッダが不正か、 パケットの IP オプションと設定されたソケットのオプションに不整合があります。

[EADDRNOTAVAIL]
    宛先アドレスに含まれる IP アドレスは、 INADDR_ANY と等しくなく、 どのインタフェースにも関連づけられていません。

関連項目
bind(2), recvfrom(2), sendto(2), socket(2), ipfw(8)

バグ
これはユーザモードプロセスがアドレス変換のような様々な IP の細工を実装するためのすっきりした方法を提供することを試みたものです。 しかし、もっとすっきりさせることができるでしょうし、 ipfw(8) に依存しすぎています。

入力のフラグメントが迂回の前に再構成されるべきかどうか については疑問の余地があります。 例えば、他のマシン宛のパケットのうち数個のフラグメントが ローカルマシン経由でルーティングされないだけで、 パケットが失われます。 これはおそらく何らかの方法で設定可能なオプションにするべきです。

作者
Archie Cobbs <archie@whistle.com>, Whistle Communications Corp.