2

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;
}
hel
  • 581
  • 10
  • 26

2 Answers2

1

Without getting too deep in your code, I can see a major problem:

In your use of pcap_open_live(), you do not set promiscuous mode: the third parameter should be non-zero. If the ARP request is not targeted to your interface IP, pcap will not see it without promiscuous mode. tcpdump does, unless specifically told not to do so by using the --no-promiscuous-mode, use promisc (and hence will require CAP_NET_ADMIN privilege, which you'll get by sudo, which your program will require too).

Side note:

1/ Leak: you may want to free your filter using pcap_freecode() after your pcap_setfilter().

2/ I assume you've read the official tuto here:

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

...if that's not the case you'd be well advised to do that first. I quote:

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]

EDIT:

Actually, looking deeper to you code compared to my code running for +1 year at production level (both in-house and at the customer) I can see many more things that could be wrong:

  • You never call pcap_create()
  • You never call pcap_set_promisc(), we've talked about this already
  • You never call pcap_activate(), this may be the core issue here

...pcap is very touchy about the sequence order of operations to first get a pcap_t handle, and then operate on it.

At the moment, the best advice I can give you - otherwise this is going to a live debugging session between you and me, are:

1/ read and play/tweak with the code from the official tutorial:

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

This is mandatory.

2/ FWIW, my - definitely working - sequence of operations is this:

  • pcap_lookupnet()
  • pcap_create()
  • pcap_set_promisc()
  • pcap_set_snaplen(), you may or may not need this
  • pcap_set_buffer_size(), you may or may not need this
  • pcap_activate() with a note: Very important: first activate, then set non-blocking from PCAP_SETNONBLOCK(3PCAP): When first activated with pcap_activate() or opened with pcap_open_live() , a capture handle is not in non-blocking mode''; a call to pcap_set-nonblock() is required in order to put it intonon-blocking'' mode.

...and then, because I do not use stinking blocking/blocking with timeout, busy looping:

  • pcap_setnonblock()
  • pcap_get_selectable_fd()

...then and only then: - pcap_compile() - followed by a pcap_setfilter() - and then as I mentioned a pcap_freecode() - and then a select() or family on the file'des' I get from pcap_get_selectable_fd(), to pcap_dispatch(), but this is another topic.

pcap is an old API starting back in the 80's, and its really very very touchy. But don't get discouraged! It's great - once you get it right.

jbm
  • 3,063
  • 1
  • 16
  • 25
  • Using `tcpdump -i eth0 arp` in the same time, I observe arp request and reply in the ip of interface eth0, but no packets oberseved with my program. So I don't know what's wrong. Thanks for you answer! – hel Mar 05 '16 at 16:11
  • The output of my program indicates `pcap_next_ex` always return `0`. – hel Mar 05 '16 at 16:13
  • And you do not `sudo` your tcpdump??? If so, there's something else wrong in your code. May give it a try. In the meantime, please set the promisc parameter to your `pcap_open_live()`, or make it an option to your main(). 99% of the time you do want promisc. – jbm Mar 05 '16 at 16:15
  • @hel "The output of my program indicates pcap_next_ex always return 0" Yes, I saw that, thanks. Using `pcap_next_ex()` is not the way to go for a half-serious pcap program anyway, but that would be the topic for another question. – jbm Mar 05 '16 at 16:19
  • I retry it with `desrc = pcap_open_live("eth0", MAXBYTES2CAPTURE, 1, 512, err_buf);` and `sudo tcpdump -i eth0 arp`. And it's the same. – hel Mar 05 '16 at 16:22
  • I make the program according to the sequence `pcap_lookupnet pcap_open_live pcap_compile pcap_setfilter pcap_next_ex`, which can also be seen [http://www.tcpdump.org/pcap.html] (You can find it by searching `pcap_open_live` in that page ). I don't think the problem is the sequence called. – hel Mar 06 '16 at 01:31
  • "You never call `pcap_create()` ... You never call `pcap_activate()`" Not a problem - he's using `pcap_open_live()`, which is the old API for opening a device for capturing, instead of using `pcap_create()`, various "set" calls, and `pcap_activate()`. `pcap_open_live()` is still supported, although it can't do everything you can do with `pcap_create()`, various "set" calls, and `pcap_activate()`. It *can*, however, set promiscuous mode, which is what the person who asked the question *wasn't* making it do. –  Mar 06 '16 at 01:39
0

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

  • Yeah, it works! I have 2 questions. In the code above, "eth0" is directly given as the first parameter of `pcap_open_live`. If I want to observe "eth0" , rather than "eth1" "eth2".. on my host(multiple interfaces), How can I make it with `pcap_lookupdev`, or other ways avaliable? Is there any good way with libpcap to observe arp packets? I think my program is a little complex and messy. – hel Mar 06 '16 at 02:44
  • If you want to observe "eth0", you're doing exactly what you should do - you're passing "eth0" as the argument to `pcap_open_live()`. What are you *really* asking here? –  Mar 06 '16 at 03:25
  • 1
    "Is there any good way with libpcap to observe arp packets? I think my program is a little complex and messy." Have you taken a look at tcpdump's `print-arp.c` - or Wireshark's `packet-arp.c`? Your program's handling of ARP packets is a *lot* simpler than the more complete ARP packet analysis done by both of those programs. And their code to do capturing is more complex, too. –  Mar 06 '16 at 03:26
  • (Also, if you have questions after your original question is answered, and they're asking about stuff other than what your original question was asking - that's the case with both of your questions - you might want to ask them as separate questions. Q&A sites aren't forums, they're more like crowdsourced FAQs, and if each of your questions is asked separately, somebody who wants an answer to, for example, your *second* question, but not your *original* question, might be more likely to find it if they search the Q&A site.) –  Mar 06 '16 at 04:32
  • Thanks for your answer. I see. – hel Mar 06 '16 at 04:39