C言語でイーサネットフレームを受信する
プログラムからイーサネットフレームを読み書きしたかったので、まずは受信する方法を確認しました。 ソケットAPIを扱うため、素直にC言語でいくことにします。
環境: Ubuntu 16.04 x86_64、GCC 5.4
実装
LinuxのソケットAPIはAF_PACKETを使うとリンク層レベルのやりとりが可能になります。
socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))
SOCK_RAWはイーサネットヘッダ含めて読み書きする指定です。 ETH_P_ALLで全部のパケット(自ホストからの送信含む)を取得する設定になります。
AF_PACKETやSOCK_RAWを指定したときはroot権限(もしくはCAP_NET_RAW capability)が必要で、 ない場合はソケットの作成に失敗します。
ソケットの作成が済めば、あとは普通にrecv()でフレームを受信できます。
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <ctype.h> #include <sys/socket.h> #include <net/ethernet.h> #include <arpa/inet.h> void die(const char* msg) { perror(msg); exit(1); } void dump(const uint8_t* p, int len) { for (int i = 0; i < len; i += 16) { printf("\t0x%04x: ", i); for (int j = 0; j < 16; j++) { if (j % 2 == 0) printf(" "); if (i + j < len) { printf("%02x", p[i + j]); } else { printf(" "); } } printf(" "); for (int j = 0; j < 16; j++) { if (i + j >= len) break; printf("%c", isprint(p[i + j]) ? p[i + j] : '.'); } printf("\n"); } } int main() { int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (fd < 0) die("socket"); int len; uint8_t buf[ETH_FRAME_LEN]; // ETH_FRAME_LEN : 1514 while (1) { if ((len = recv(fd, buf, ETH_FRAME_LEN, 0)) < 0) die("recv"); printf("recv: len %d\n", len); dump(buf, len); } return 0; }
動作確認
root権限なしだとsocket()で失敗してerrnoにEPERMがセットされます。
$ ./a.out socket: Operation not permitted
sudoで実行して、別の端末エミュレータからarpingを打つと次のような表示になります。 フレームの先頭から見て、宛先MACアドレスが6バイト、送信元MACアドレスで6バイト、EtherTypeは2バイト(x0806:ARP)と続いてます。 tcpdumpでも同じ結果なので問題なさそうです。
自ホストからの送信になる1つ目のフレームはペイロードが28バイト(仕様上は46〜1500バイト)のように見えてしまっています。 これはデータのキャプチャがゼロ埋めより先に行なわれているためです。
$ sudo ./a.out
recv: len 42
0x0000: ffff ffff ffff 94de 80e0 93e2 0806 0001 ................
0x0010: 0800 0604 0001 94de 80e0 93e2 c0a8 0007 ................
0x0020: ffff ffff ffff c0a8 0001 ..........
recv: len 60
0x0000: 94de 80e0 93e2 1066 82ff 4008 0806 0001 .......f..@.....
0x0010: 0800 0604 0002 1066 82ff 4008 c0a8 0001 .......f..@.....
0x0020: 94de 80e0 93e2 c0a8 0007 0000 0000 0000 ................
0x0030: 0000 0000 0000 0000 0000 0000 ............