Search code examples
linuxlibpcaparp

how to implement tcpdump -i interface arp with libpcap


I want to implement command tcpdump -i eth0 arp to observe arp packets on interface eth0 on my ubuntu. I use libpcap, but the return value of function pcap_next_ex is always 0. With tcpdump -i eth0 arp in the same time , it can observe arp packets.

/*
 *  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;
}

Solution

  • It would probably work better if you did

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

    rather than

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

    The latter evaluates arp_header->type == 0x0800, which, when running on a little-endian machine (such as a PC), will almost always evaluate to "false", because the value will look like 0x0008, not 0x0800, in an ARP packet - ARP types are big-endian, so they'll look byte-swapped on a little-endian machine). That means it'll evaluate to 0, and byte-swapping 0 gives you zero, so that if condition will evaluate to "false", and the printing code won't be called.

    You'll still get lots of timeouts if you fix that, unless there's a flood of ARP packets, but at least you'll get the occasional ARP packet printed out. (I would advise printing nothing on a timeout; pcap-based programs doing live capturing should expect that timeouts should happen, and should not report them as unusual occurrences.)