PACKET(7) | Linux Programmer's Manual | PACKET(7) |
packet - デバイスレベルのパケットインターフェース
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <net/ethernet.h> /* L2 プロトコル */ packet_socket = socket(AF_PACKET, int socket_type, int protocol);
packet ソケットは、デバイスドライバ (OSI レイヤ 2) レベルで 生のパケット (raw packet) を送受信するために用いられる。 packet ソケットを使うと、ユーザー空間で物理層の上に プロトコルモジュールを実装することができる。
socket_type には SOCK_RAW と SOCK_DGRAM のいずれかを指定する。 SOCK_RAW はリンクレベルヘッダーを含む raw パケットを、 SOCK_DGRAM はリンクレベルヘッダーが削除された加工済みパケットを示す。 リンクレベルヘッダー情報は sockaddr_ll で共通のフォーマットで入手できる。 protocol には IEEE 802.3 プロトコル番号を ネットワークバイトオーダーで指定する。 指定できるプロトコルのリストは、インクルードファイル <linux/if_ether.h> を参照。プロトコルを htons(ETH_P_ALL) にすると、全てのプロトコルが受信される。 外部から来たパケットのうち指定したプロトコルのものは、 カーネルに実装されているプロトコルに渡される前の段階で、 packet ソケットに渡される。
packet ソケットをオープンできるのは、 実効ユーザーID が 0 のプロセスか、 CAP_NET_RAW ケーパビリティを持つプロセスだけである。
SOCK_RAW パケットでは、パケットをデバイスドライバと受け渡しする際、 パケットデータに変更が行われることはない。 パケットの受信時には、アドレスの解析だけは行われ、 標準的な sockaddr_ll アドレス構造体に渡される。パケットの送信時には、ユーザーが指定する バッファーに物理層のヘッダーが含まれている必要がある。 パケットはそのまま修正を受けずに、行き先アドレスから決定される インターフェースのネットワークドライバにキューイングされる。 デバイスドライバによっては、他のヘッダーを常に追加するものもある。 SOCK_RAW は Linux 2.0 の obosolete な AF_INET/SOCK_PACKET と似ているが、互換性があるわけではない。
SOCK_DGRAM はやや高位のレベルで動作する。物理ヘッダーは、パケットがユーザーに 渡される前に削除される。 SOCK_DGRAM の packet ソケットを通して送られるパケットは、 sockaddr_ll の行き先アドレスの情報に基づき、適切な物理層のヘッダーが付加されてから、 キューに送られる。
デフォルトでは、指定したプロトコル型のパケットはすべて packet ソケットに送られる。特定のインターフェースからのパケットだけを 取得したい場合には、 struct sockaddr_ll にアドレスを指定して bind(2) を呼び、 packet ソケットをそのインターフェースに結び付ける (バインドする)。 バインドの際には、アドレスフィールドのうち sll_protocol と sll_ifindex だけが用いられる。
connect(2) 操作は packet ソケットではサポートされていない。
MSG_TRUNC フラグが recvmsg(2), recv(2), recvfrom(2) に渡されると、 (バッファーサイズより大きかったとしても) 常に実際に通信された パケットの長さが返される。
sockaddr_ll はデバイスに依存しない物理層のアドレスである。
struct sockaddr_ll {
unsigned short sll_family; /* 常に AF_PACKET */
unsigned short sll_protocol; /* 物理層のプロトコル */
int sll_ifindex; /* インターフェース番号 */
unsigned short sll_hatype; /* ARP ハードウェア種別 */
unsigned char sll_pkttype; /* パケット種別 */
unsigned char sll_halen; /* アドレスの長さ */
unsigned char sll_addr[8]; /* 物理層のアドレス */ };
sll_protocol は標準的なイーサネットプロトコルのタイプで、 ネットワーク バイトオーダーで記述する。 インクルードファイル <linux/if_ether.h> で定義されている。 これがこのソケットのプロト コルのデフォルトとなる。 sll_ifindex はそのインターフェースの interface index である (netdevice(7) を参照)。 0 は (バインドが許可されている) 任 意のインターフェースにマッチする。 sll_hatype は、インクルードファイル <linux/if_arp.h> で定義されている ARP 種別である。 sll_pkttype はパケット種別である。指定できる種別は以下のいずれかである: PACKET_HOST (ローカルホスト向けのパケット)、 PACKET_BORADCAST (物理層 のブロードキャストパケット)、 PACKET_MULTICAST (物理層のマルチキャストア ドレスに送るパケット)、 PACKET_OTHERHOST (他のホストに向けられたパケット のうち、 無差別モード (promiscuous mode: 後述) のデバイスドライバにより補足 されたもの)、 PACKET_OUTGOING (ローカルホストから発信され、 packet ソケッ トにループバックしてきたパケット)。 これらの種別が意味を持つのは受信時のみ である。 sll_addr と sll_halen は、物理層の (つまり IEEE 802.3 の) アドレスとその長さである。 厳密な解釈はデバイスに依存する。
パケットを送る場合は、 sll_family, sll_addr, sll_halen, sll_ifindex を指定すれば十分である。 その他のフィールドは 0 にしておくべきである。 sll_hatype と sll_pkttype には受信したパケットの情報が設定される。 バインドの際には、 sll_protocol と sll_ifindex だけが使用される。
パケットソケットのオプションは、レベル SOL_PACKET を指定して setsockopt(2) を呼び出すことで設定できる。
struct packet_mreq {
int mr_ifindex; /* インターフェース番号 */
unsigned short mr_type; /* 動作 */
unsigned short mr_alen; /* アドレスの長さ */
unsigned char mr_address[8]; /* 物理層のアドレス */ };
mr_ifindex は、ステータスを変更したいインターフェースの インターフェース番号である。 mr_type パラメーターは実行する動作を指定する: PACKET_MR_PROMISC は、共有している媒体からの全てのパケットを受信できるようにする (しばしば "無差別モード (promiscuous mode)" と呼ばれる)。 PACKET_MR_MULTICAST は、そのソケットを、 mr_address と mr_alen で指定される物理層のマルチキャストブループにバインドする。 PACKET_MR_ALLMULTI は socket を up にして、そのインターフェースに到達したすべての マルチキャストパケットを受信できるようにする。
昔からある ioctl だけでなく、 SIOCSIFFLAGS, SIOCADDMULTI, SIOCDELMULTI を同じ目的に用いることができる。
struct tpacket_auxdata {
__u32 tp_status;
__u32 tp_len; /* パケット長 */
__u32 tp_snaplen; /* キャプチャした長さ */
__u16 tp_mac;
__u16 tp_net;
__u16 tp_vlan_tci;
__u16 tp_padding; };
ファンアウトでは、 複数のソケットにトラフィックを分散させるアルゴリズムを複数サポートしている。 デフォルトのモードである PACKET_FANOUT_HASH では、同じフローのパケットは同じソケットに送信され、 フロー単位の順序が維持される。 パケットごとに、パケットフローのハッシュの、そのグループのソケット数に対する剰余が計算され、ソケットが選択される。 なお、フローハッシュはネットワーク層のアドレスとトランスポート層のポートフィールドに対するハッシュである (トランスポート層ポートは存在する場合のみ)。 負荷分散モード PACKET_FANOUT_LB はラウンドロビンアルゴリズムが採用されている。 PACKET_FANOUT_CPU では、 パケットが到着した CPU に基づいてソケットを選択する。 PACKET_FANOUT_ROLLOVER はすべてのデータを一つのソケットで処理し、 そのソケットで処理待ち (backlog) が発生した場合に次のソケットに移る。 PACKET_FANOUT_RND では擬似乱数発生器を使ってソケットが選択される。 PACKET_FANOUT_QM (Linux 3.14 以降で利用可能) では受信 skb に記録された queue_mapping を使ってソケットが選択される。
ファンアウトモードでは追加のオプションがある。 IP フラグメンテーションが起こると、 同じフローのパケットのフローハッシュが異なるハッシュを持つことになる。 フラグ PACKET_FANOUT_FLAG_DEFRAG をセットすると、 パケットはファンアウトを行う前にフラグメント再構築が行われるようになり、 フラグメントがあった場合でも順序が維持される。 ファンアウトモードとオプションは、 整数のオプション値の下位 16 ビットで指定される。 フラグ PACKET_FANOUT_FLAG_ROLLOVER を指定すると、 バックアップ戦略としてロールオーバー方式が有効になる。 元のファンアウトアルゴリズムが backlog ソケットを選択していれば、 パケットは次の利用可能なソケットにロールオーバーされる。
struct tpacket_stats {
unsigned int tp_packets; /* 総パケット数 */
unsigned int tp_drops; /* ドロップパケット数 */ };
統計情報を取得すると、内部カウンターはリセットされる。 TPACKET_V3 のリングを使う場合には、統計情報構造体は違うものになる。
SIOCGSTAMP を用いると、最後に受信したパケットのタイムスタンプを得ることができる。 引き数は struct timeval 型の変数である。
さらに、 netdevice(7) および socket(7) で定義されている標準の ioctl はいずれも packet ソケットに指定可能である。
packet ソケットは、パケットをデバイスドライバに渡すときに 起きたエラーしか処理しない。遅延エラー (pending error) に関する概念は持っていない。
上記以外のエラーが、低レベルのドライバで生成されることがある。
AF_PACKET は Linux 2.2 の新機能である。これより古いバージョンの Linux では SOCK_PACKET のみをサポートしていた。
移植性の必要なプログラムでは、 pcap(3) 経由で AF_PACKET を用いることをお薦めする。ただし、この方法では AF_PACKET の機能すべてを利用することはできない。
SOCK_DGRAM packet ソケットは、IEEE 802.3 フレームの IEEE 802.2 LLC ヘッダーの 生成や解析を行おうとしない。 ETH_P_802_3 が送信プロトコルに指定されると、カーネルは 802.3 フレームを 生成して length フィールドに書き込む。 完全に準拠したパケットを得るためにはユーザーが LLC ヘッダーを 与える必要がある。到着した 802.3 パケットでは、 DSAP/SSAP protocol の各フィールドは多重化 (multiplex) されていない。 代わりにこれらは LLC ヘッダーが前置された ETH_P_802_2 プロトコルとして与えられる。したがって、 ETH_P_802_3 にバインドすることはできない。かわりに ETH_P_802_2 にバインドし、自分自身でプロトコルの多重化を行うこと。 送信のデフォルトは、プロトコルフィールドを持つ 標準の Ethernet DIX encapsulation である。
packet ソケットは入出力の firewall chain に影響をうけない。
Linux 2.0 では、 packet ソケットを得る方法は socket(AF_INET, SOCK_PACKET, protocol) を呼ぶやり方しかなかった。この方法はまだサポートされているが、 用いないことを強く推奨する。現在の方法との主な違いは、 SOCK_PACKET ではインターフェースの指定に古い struct sockaddr_pkt を用いる点である。これには物理層からの独立性がない。
struct sockaddr_pkt {
unsigned short spkt_family;
unsigned char spkt_device[14];
unsigned short spkt_protocol; };
spkt_family はデバイスのタイプ、 spkt_protocol は <sys/if_ether.h> で定義されている IEEE 802.3 プロトコルタイプ、 spkt_device はデバイスの名前をヌル終端された文字列で与えたもの (例: eth0) である。
この構造体は obsolete であり、 新しくコードを書く時には用いるべきでない。
glibc 2.1 には SOL_PACKET
の定義がない。回避策としては、以下のようにするとよい。
#ifndef SOL_PACKET #define SOL_PACKET 263 #endif
この問題はそれ以降のバージョンの glibc では修正されている。
IEEE 802.2/803.3 の LLC の扱い方は、バグと考えても良いだろう。
ソケットフィルターについて記載されていない。
MSG_TRUNC recvmsg(2) 拡張は非常にまずい対処であり、制御メッセージで置き換えるべきである。 今のところ SOCK_DGRAM 経由でパケットについていた宛先アドレスを得る方法がない。
socket(2), pcap(3), capabilities(7), ip(7), raw(7), socket(7)
標準 IP Ethernet encapsulation に関しては RFC 894 を、 IEEE 802.3 IP encapsulation に関しては RFC 1700 を参照。
物理層のプロトコルに関する記述は <linux/if_ether.h> インクルードファイルにある。
Linux カーネルのソースツリー。 /Documentation/networking/filter.txt には Berkeley Packet Filters をパケットソケットにどのように適用するかの説明がある。 /tools/testing/selftests/net/psock_tpacket.c には、 PACKET_RX_RING と PACKET_TX_RING の利用可能なすべてのバージョンのサンプルソースコードがある。
この man ページは Linux man-pages プロジェクトのリリース 3.79 の一部 である。プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。
2014-08-19 | Linux |