如何使用 libpcap 实现 tcpdump -i interface arp

how to implement tcpdump -i interface arp with libpcap

我想执行命令 tcpdump -i eth0 arp 来观察我 ubuntu 上接口 eth0 上的 arp 数据包。我用的是libpcap,但是函数pcap_next_ex的return值一直是0,同时加上tcpdump -i eth0 arp,可以观察到arp包。

/*
 *  compile(root): gcc test.c -lpcap 
 *  run          : ./a.out
 *  output       : time out
 *                 time out
 *                 time out
 *                 ...
 */
#include <pcap.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define ARP_REQUEST  1
#define ARP_REPLY    2

typedef struct arp_hdr_s  arp_hdr_t;
struct arp_hdr_s {
    u_int16_t       htype;
    u_int16_t       ptype;
    u_char          hlen;
    u_char          plen;
    u_int16_t       oper;
    u_char          sha[6];
    u_char          spa[4];
    u_char          tha[6];
    u_char          tpa[4];
};

#define MAXBYTES2CAPTURE  2048

int 
main(int argc, char **argv)
{
    char                    err_buf[PCAP_ERRBUF_SIZE];
    const unsigned char    *packet; 
    int                     i;
    int                     ret;
    arp_hdr_t              *arp_header;
    bpf_u_int32             net_addr;
    bpf_u_int32             mask;
    pcap_t                 *desrc;
    struct pcap_pkthdr     *pkthdr; 
    struct bpf_program      filter;

    net_addr = 0;
    mask = 0;
    memset(err_buf, 0, PCAP_ERRBUF_SIZE);

    desrc = pcap_open_live("eth0", MAXBYTES2CAPTURE, 0, 512, err_buf);
    if (desrc == NULL) {
        fprintf(stderr, "error: %s\n", err_buf);
        exit(-1);
    }

    ret = pcap_lookupnet("eth0", &net_addr, &mask, err_buf);
    if (ret < 0) {
        fprintf(stderr, "error: %s\n", err_buf);
        exit(-1);
    }

    ret = pcap_compile(desrc, &filter, "arp", 1, mask);
    if (ret < 0) {
        fprintf(stderr, "error: %s\n", pcap_geterr(desrc));
        exit(-1);
    }

    ret = pcap_setfilter(desrc, &filter);
    if (ret < 0) {
        fprintf(stderr, "errnor: %s\n", pcap_geterr(desrc));
        exit(-1);
    }

    while (1) {
        ret = pcap_next_ex(desrc, &pkthdr, &packet);
        if (ret == -1) {
            printf("%s\n", pcap_geterr(desrc));
            exit(1);
        } else if (ret == -2) {
            printf("no more\n");
        } else if (ret == 0) {             // here
            printf("time out\n");
            continue;
        }

        arp_header = (arp_hdr_t *)(packet + 14);
        if (ntohs(arp_header->htype) == 1 && ntohs(arp_header->ptype == 0x0800)) {
                printf("src IP: ");
                for (i = 0; i < 4; i++) {
                    printf("%d.", arp_header->spa[i]);
                }
                printf("dst IP: ");
                for (i = 0; i < 4; i++) {
                    printf("%d.", arp_header->tpa[i]);
                }
                printf("\n");
        }

    }

    return 0;
}

无需深入研究您的代码,我就能看出一个主要问题:

在你使用pcap_open_live()时,你没有设置混杂模式:第三个参数应该是非零的。如果 ARP 请求不是针对您的接口 IP,如果没有混杂模式,pcap 将看不到它。 tcpdump 会,除非明确告知不要使用 --no-promiscuous-mode 这样做,否则请使用 promisc(因此将需要 CAP_NET_ADMIN 权限,您将通过 sudo 获得该权限你的程序也需要)。

旁注:

1/ 泄漏:您可能想在 pcap_setfilter().

之后使用 pcap_freecode() 释放过滤器

2/ 我假设你已经阅读了这里的官方教程:

http://www.tcpdump.org/pcap.html

...如果不是这种情况,您最好先这样做。我引用:

A note about promiscuous vs. non-promiscuous sniffing: The two techniques are very different in style. In standard, non-promiscuous sniffing, a host is sniffing only traffic that is directly related to it. Only traffic to, from, or routed through the host will be picked up by the sniffer. Promiscuous mode, on the other hand, sniffs all traffic on the wire. In a non-switched environment, this could be all network traffic. [... more stuff on promisc vs non-promisc]

编辑:

实际上,与我的代码 运行 相比,在生产级别(内部和客户)+1 年的代码中,我可以看到更多可能出错的地方:

  • 你从不打电话给pcap_create()
  • 你永远不要打电话给 pcap_set_promisc(),我们已经讨论过了
  • 你永远不会调用pcap_activate(),这可能是这里的核心问题

...pcap 对操作顺序非常敏感,首先获取 pcap_t 句柄,然后对其进行操作。

目前,我能给你的最好建议是:

1/阅读并play/tweak使用官方教程中的代码:

http://www.tcpdump.org/pcap.html

这是必填项。

2/ FWIW,我的 - 绝对有效 - 操作顺序是这样的:

  • pcap_lookupnet()
  • pcap_create()
  • pcap_set_promisc()
  • pcap_set_snaplen(),你可能需要也可能不需要这个
  • pcap_set_buffer_size(),你可能需要也可能不需要这个
  • pcap_activate() 附注:非常重要:首先激活,然后从 PCAP_SETNONBLOCK(3PCAP) 设置非阻塞:首次使用 pcap_activate() 激活或使用 pcap_open_live() ,捕获句柄不处于 non-blocking mode''; a call to pcap_set-nonblock() is required in order to put it into非阻塞模式。

...然后,因为我不使用带超时的臭blocking/blocking,忙循环:

  • pcap_setnonblock()
  • pcap_get_selectable_fd()

...然后并且只有那时: - pcap_compile() - 接着是 pcap_setfilter() - 然后正如我提到的 pcap_freecode() - 然后是我从 pcap_get_selectable_fd()pcap_dispatch() 得到的文件'des' 上的 select() 或系列,但这是另一个主题。

pcap 是一个从 80 年代开始的老 API,它真的 非常 非常 敏感.但不要气馁!太棒了 - 一旦你做对了。

如果你这样做可能会更好

if (ntohs(arp_header->htype) == 1 && ntohs(arp_header->ptype) == 0x0800) {

而不是

if (ntohs(arp_header->htype) == 1 && ntohs(arp_header->ptype == 0x0800)) {

后者计算 arp_header->type == 0x0800,当 运行 在小端机器(例如 PC)上时,它几乎总是计算为 "false",因为该值将在 ARP 数据包中看起来像 0x0008,而不是 0x0800 - ARP 类型是大端,所以它们在小端机器上看起来是字节交换的)。这意味着它将评估为 0,字节交换 0 为您提供零,因此 if 条件将评估为 "false",并且不会调用打印代码。

如果你修复它,你仍然会遇到很多超时,除非 ARP 数据包 泛滥 ,但至少你会偶尔打印出 ARP 数据包。 (我建议在超时时不打印任何东西;基于 pcap 的程序进行实时捕获应该期望超时应该发生,并且不应将它们报告为异常事件。)