pcap 库怎么获取抓到包的网卡信息

pcap 的基本使用

pcap 包处理函数的原型一般是这样的:

void packet_handler(u_char *args, const struct pcap_pkthdr *pkthdr, const u_char *p);

第三个参数指向一个 buffer,这个 buffer 里是网络包的数据。第二个参数 pcap_pkthdr 中只有下面这 3 个字段,记录跟这个网络包相关的一些元数据,然而并没有网卡相关的信息。

struct pcap_pkthdr {
    struct timeval ts;  // 时间戳
    bpf_u_int32 caplen; // 抓到的包的长度,可能截断了
    bpf_u_int32 len;    // 包的实际长度
}

tcpdump -i any -e (底层用的 pcap 库)看到的网卡信息等是怎么来的呢。

DLT_LINUX_SLL2 模式

翻 tcpdump 和 pcap 库的代码,发现在网卡是 any 的情况下,可以通过 pcap_set_datalink(handle, DLT_LINUX_SLL2) 这个调用让 pcap 库切换到 DLT_LINUX_SLL2 模式去,这个模式下,第二个参数返回不变,第三个参数网络包中的 以太网包头 会被一个 伪包头 替换掉,这个 伪包头 里包含了网卡号,包的类型流向等一些额外信息。

完整的使用代码如下:

void packet_handler(u_char *args, const struct pcap_pkthdr *pkthdr, const u_char *p) {
    // 网络包的一开始是 伪包头
    const struct sll2_header *sllp = (const struct sll2_header *)p;
    uint32_t if_index = ntohl(sllp->sll2_if_index);
    char ifname[IF_NAMESIZE];
    if (!if_indextoname(if_index, ifname))
        strncpy(ifname, "?", 2);

    // 后面开始是 IP 包头,以太网包头被前面的伪包头替换掉了
    p += SLL2_HDR_LEN;
    len = pkthdr->len - SLL2_HDR_LEN;

    printf("Nic: %s, Packet Length: %d %s\n", ifname, len);
    ...
}

int main() {
    ...
    // 需要是 any 网卡的时候,设置 DLT_LINUX_SLL2 才会生效
    handle = pcap_open_live("any", 65536, 1, 1000, errbuf);
    pcap_set_datalink(handle, DLT_LINUX_SLL2);

    pcap_loop(handle, 0, packet_handler, NULL);
    ...
}

如果编译的时候报 DLT_LINUX_SLL2 找不到,需要升级 pcap 库。

sll2_header 完整的字段如下:

struct sll2_header {
    // ether type 802.3/802.1Q/...
    uint16_t sll2_protocol;
    uint16_t sll2_reserved_mbz;
    // 网卡号
    uint32_t sll2_if_index;
    uint16_t sll2_hatype;
    // 包类型,可以是 HOST/BROADCAST/MULTICAST/OTHERHOST/OUTGOING
    uint8_t  sll2_pkttype;
    uint8_t  sll2_halen;
    // 网卡 MAC 地址
    uint8_t  sll2_addr[SLL_ADDRLEN];
};

还是用 tcpdump 来看一看这个里面可以打印出的一些信息, tcpdump -i any -e -XX 可以看到包的网卡、流向等信息,注意下面 16 进制打印出来的前 20 个字节就是这个 sll2_header 了,并不是实际的以太网包头。

# tcpdump -i any -e -XX
tcpdump: data link type LINUX_SLL2 👈 运行在 SLL2 模式下
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
07:37:36.139372 eth0  In  ifindex 2 48:57:02:f4:e2:92 (oui Unknown) ethertype IPv4 (0x0800), length 72: bogon.59104 > arch.10022: Flags [.], ack 474860, win 1156, options [nop,nop,TS val 1170175875 ecr 1805243877], length 0
    0x0000:  0800 0000 0000 0002 0001 0006 4857 02f4  ............HW..
    0x0010:  e292 0000 4540 0034 0000 4000 3e06 0dd9  ....E@.4..@.>...
    ...                👆 从这里开始是 IP 包头

更多细节可以参见 tcpdump 项目中 sll2 相关的打印代码: https://github.com/the-tcpdump-group/tcpdump/blob/master/print-sll.c