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            ............