BSDではbpfを使用して生のパケットをやりとりすることができます。 ところがlinuxのカーネルにはbpfなんてものはありません。 でも、bootpやdhcp, tcpdumpなんかでプロトコル無しなパケットを やりとりしているはずです。いったいどうやっているんでしょう。
linuxのアプリケーションでは、ネットワークインターフェースへの入出力は、 すべてsocketインターフェースを使用することになります。 socketを使用するということは、カーネルのサポートするなんらかの プロトコルを使用するということです。生のパケットをやりとりするためには packet interface(PF_PACKET)というプロトコルドライバ?を利用します。
以下にpacketインターフェースを使用して生のデータを送受信するまでの、 おおまかな流れを示します。
socket()の引数のプロトコルとしてPF_PACKET、ソケットの種類にSOCK_RAWを 指定して、packet interfaceを使用するソケットを取得します。
pd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
ちなみに以下のbindをしなくても、この段階ですべてのインターフェースからの パケットが受信できるし送信も可能です。
bindやsendtoするときに使用するリンクレベルアドレスsockaddr_llには インターフェースのインデックス番号が必要なので、インターフェース名から インデックス番号をしらべます。
struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, "eth0", IFNAMSIZ); ioctl(pd, SIOCGIFINDEX, &ifr); interface_index = ifr.ifr_ifindex;
bindすることによって特定のインターフェースからのパケットのみを 受信するようになります。
struct sockaddr_ll sll; memset(&sll, 0xff, sizeof(sll)); sll.sll_family = AF_PACKET; /* allways AF_PACKET */ sll.sll_protocol = htons(ETH_P_ALL); sll.sll_ifindex = interface_index; bind(pd, (struct sockaddr *)&sll, sizeof sll);
packet インターフェースは、特定のインターフェースにbindされるまで、 すべてのインターフェースからのパケットを受信し続けています。 そのため、実際の受信ループに入る前に、それら受信済の不必要なパケット を捨てておいたほうがよいと思います。
do { fd_set fds; struct timeval t; FD_ZERO(&fds); FD_SET(pd, &fds); memset(&t, 0, sizeof(t)); i = select(FD_SETSIZE, &fds, NULL, NULL, &t); if (i > 0) recv(pd, buf, i, 0); } while (i);
recv(pd, buf, sizeof(buf), 0);
ひとたびbindさえできてしまえば、sendtoとrecvで簡単に送受信できます。
受信したデータの先頭には物理層のヘッダも含まれています。 めでたしめでたし。
struct sockaddr_ll sll; memset(&sll, 0, sizeof(sll)); sll.sll_ifindex = ifindex; c = sendto(pd, tmp, c, 0, (struct sockaddr *)&sll, sizeof(sll));
packetインターフェースでは、connectedな状態になることはないので、 send()を使用することはできません。 そのため、常にsendto()を使用することになります。
ネットワークインターフェースに流れるデータを扱うんだから、ソケット経由で 扱えた方が、bpfのようにデバイスとして見せるよりも美しいように思えます。
しかし、パケットインターフェースには、フィルタ機能がありません。 bpfはBerkery Packet Filterの名を示す通り、filterが売りの一つです。 bpfのフィルタはインストラクションセットをもつ仮想マシンとして実装されていて、 忘れかけていたマシン語プログラミング(ハンドアセンブル)の楽しさを思い出させてくれる、とっても楽しい機能です。linuxでも使いたいにきまってます。
linuxではフィルタ機能はユーザランドのライブラリで実現します。
pcapなるライブラリがそれで、tcpdumpに含まれています。
フィルタプログラミングはlinuxでも楽しめるみたいです。
というわけで次回はlibpcapで遊んでみようかな。
FENIX/ MEMBERS/ thomas's HOME/ MEMO/ linux_raw_packet
佐藤益弘 thomas@fenix.ne.jp