如何用libpcap实现tcpdump -i interface arp

时间:2016-03-05 03:45:49

标签: linux libpcap arp

我想实现命令tcpdump -i eth0 arp来观察我的ubuntu上接口eth0上的arp数据包。我使用libpcap,但函数pcap_next_ex的返回值始终为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;
}

2 个答案:

答案 0 :(得分:1)

如果您的代码没有太深入,我可以看到一个主要问题:

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

旁注:

1 /泄漏:您可能希望在pcap_freecode()之后使用pcap_setfilter()释放过滤器。

2 /我假设你已经在这里阅读官方tuto:

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

......如果情况并非如此,那么建议您先这样做。我引用:

  

关于混杂与非混杂嗅探的说明:两者   技术风格迥然不同。在标准,非滥交   嗅探,主机只嗅探与之直接相关的流量   它。仅挑选来自主机的流量,来自主机或通过主机路由   由嗅探器。另一方面,混杂模式嗅探所有   电线上的交通。在非交换环境中,这可能就是全部   网络流量。 [...更多关于promisc vs non-promisc的内容]

编辑:

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

  • 您永远不会致电pcap_create()
  • 您永远不会致电pcap_set_promisc(),我们已经谈过此事
  • 您永远不会致电pcap_activate(),这可能是此处的核心问题

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

目前,我能给你的最佳建议 - 否则这将是你和我之间的实时调试会议,是:

1 /使用官方教程中的代码阅读和播放/调整:

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()打开时,捕获句柄不在{ {1}}非阻塞&#39;&#39;模式。

...然后,因为我没有使用带有超时的臭味阻塞/阻塞,繁忙的循环:

  • non-blocking mode''; a call to pcap_set-nonblock() is required in order to put it into
  • pcap_setnonblock()

...然后只有这样:   - pcap_get_selectable_fd()   - 然后是pcap_compile()   - 然后正如我提到的pcap_setfilter()   - 然后文件上的select()或系列&#39; des&#39;我从pcap_freecode()转到pcap_get_selectable_fd(),但这是另一个主题。

pcap_dispatch()是一个旧的API,从80年代开始,它真的非常 非常敏感。但不要气馁!这很棒 - 一旦你做对了。

答案 1 :(得分:0)

如果你这样做可能会更好。

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)上运行时,它几乎总是评估为“假”,因为该值看起来像0x0008,而不是0x0800, ARP数据包 - ARP类型是big-endian,因此它们在小端机器上看起来是字节交换的。这意味着它将评估为0,并且字节交换0给出零,因此if条件将评估为“false”,并且不会调用打印代码。

如果你解决了这个问题,你仍然会有很多超时,除非有一个泛滥的ARP数据包,但至少你会得到偶尔打印出来的ARP数据包。 (我建议在超时时不打印任何内容;基于pcap的程序执行实时捕获应该会发生超时,并且不应该将它们报告为异常事件。)