3

GOAL: write a BPF filter which allow just UDP packets from a specific src address and attach it to and UDP socket.

PROBLEM: if I execute the program and I try to send udp packets from a VM which has the correct src IP I don't receive none of them

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#include <arpa/inet.h>
#include <linux/filter.h>

/* udp and src 192.168.56.101 */
struct sock_filter bpfcode[] = {
    { 0x28, 0, 0, 0x0000000c },
    { 0x15, 6, 0, 0x000086dd },
    { 0x15, 0, 5, 0x00000800 },
    { 0x30, 0, 0, 0x00000017 },
    { 0x15, 0, 3, 0x00000011 },
    { 0x20, 0, 0, 0x0000001a },
    { 0x15, 0, 1, 0xc0a83865 },
    { 0x6,  0, 0, 0x00040000 },
    { 0x6,  0, 0, 0x00000000 },
};

int main(void)
{
    struct sock_fprog bpf = {
        sizeof(bpfcode) / sizeof(struct sock_filter),
        bpfcode
    };
    struct sockaddr_in src = {
        .sin_family = AF_INET,
        .sin_addr.s_addr = INADDR_ANY,
        .sin_port = htons(1025)
    };
    char buf[1024];
    ssize_t res;
    int fd, ret;

    fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (fd < 0) {
        printf("error: socket\n");
        exit(EXIT_FAILURE);
    }

    ret = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
    if (ret < 0) {
        perror("error: setsockopt\n");
        close(fd);
        exit(EXIT_FAILURE);
    }

    ret = bind(fd, (struct sockaddr *)&src, sizeof(src));
    if (ret < 0) {
        printf("error: bind\n");
        close(fd);
        exit(EXIT_FAILURE);
    }

    res = recvfrom(fd, buf, sizeof(buf), 0, NULL, 0);
    printf("res = %zi\n", res);

    close(fd);

    return 0;
}
red0ct
  • 4,840
  • 3
  • 17
  • 44
Maicake
  • 1,046
  • 10
  • 34
  • 1
    Please refer to [this answer](https://stackoverflow.com/a/39548460/3716552) – Qeole Jul 25 '19 at 10:16
  • 1
    Possible duplicate of [classic BPF on Linux: filter does not work](https://stackoverflow.com/questions/39540291/classic-bpf-on-linux-filter-does-not-work) – Qeole Jul 25 '19 at 10:17
  • So if I understood well in my case since I'm creating an UDP socket the checks start from IP header and not from ethernet header because it's not a raw socket so data-link level are removed right? – Maicake Jul 25 '19 at 10:25
  • I'd modify the filter in this way. { 0x30, 0, 0, 0x00000009 }, { 0x15, 0, 3, 0x00000011 }, { 0x20, 0, 0, 0x0000000c }, { 0x15, 0, 1, 0xc0a83865 }, { 0x6, 0, 0, 0x00040000 }, { 0x6, 0, 0, 0x00000000 }, – Maicake Jul 25 '19 at 10:32
  • 1
    Exactly. I guess you created your BPF filter with `tcpdump`, which uses raw sockets and has to parse L2/L3 headers, but with your `SOCK_DGRAM, IPPROTO_UDP` you can safely assume you work with IPv4 and UDP already. It should work with the changes you propose, although you could even remove the first two instructions from the program in your comment (they check that the packet is UDP, but we know that already) and keep just the last four ones. I see you updated the offset for the source address too so you should be all good. – Qeole Jul 25 '19 at 10:44
  • Thanks again. I've also removed the first two instructions as you said but still the program just doesnt' receive a single packet. – Maicake Jul 25 '19 at 11:03
  • Hmm. You do send the packets from 192.168.56.101, right? Can you see those packets in your VM with `tcpdump udp and src 192.168.56.101`? Also contrarily to `tcpdump`, your socket is bound to a specific UDP port (1025), do you send your packets to the correct port? – Qeole Jul 25 '19 at 11:19
  • Yes in the VM (192.168.56.101) I send packets using nc -u 192.168.56.1 1025 and I can see these packets also from the VM executing tcpdump udp and src 192.168.56.101 and also from the host machine with the same commands. It's just the C program that doesn't work properly – Maicake Jul 25 '19 at 11:24
  • Your `nc` command does not have the correct IP address, is this a typo or an error on your setup? – Qeole Jul 25 '19 at 11:51
  • I sent packets from VM (101) to host (1) the host runs the C program. – Maicake Jul 25 '19 at 12:12
  • 1
    Yeah, silly me ><. Ok, found your issue. I didn't realise that your `SOCK_DGRAM` socket was actually even higher level than what I thought, your packet starts directly at the L4 header (e.g. `{ 0x28, 0, 0, 0x00000002 }, { 0x15, 0, 1, 0x00000401 }, { 0x6, 0, 0, 0x00040000 }, { 0x6, 0, 0, 0x00000000 },` as a program matches UDP dst port and works). You have to change your socket type for filtering on the IP address, see the example in the old post maybe. – Qeole Jul 25 '19 at 12:21
  • 1
    Ok so If I understand the packet consist just of UDP header + data so I can't access to IP header information. Consequently I can just filter for UDP field data. I've seen that in the other example RAW_SOCK is used. Is it mandatory to use it to filter for IP headers fields? Thanks a lot @Qeole , I really appreciate your help. – Maicake Jul 25 '19 at 12:34
  • 1
    Yeah I tried you code and just had L4 + data. That's correct, I think you need `SOCK_RAW` if you want to access L3 headers. – Qeole Jul 25 '19 at 13:08
  • how did you find that? Wireshark or some similar tool? – Maicake Jul 25 '19 at 13:11
  • 1
    I ran your code snippet and made several attempts at filtering by changing the program: tried values that I knew I could find for L3, for data, then for L4... L4 worked :). I edited your question to make the snippet compile (the `#include`s), by the way. – Qeole Jul 25 '19 at 13:13
  • Thanks again and again. It's almost time to move and try eBPF, but looking at that assembly I'm afraid XD. – Maicake Jul 25 '19 at 13:18
  • :) Don't write eBPF assembly, write your program in C and compile it to eBPF with clang/LLVM! – Qeole Jul 25 '19 at 13:21

1 Answers1

4

Summary from the discussion in the comments:

First, the socket's type is SOCK_DGRAM, so the data you get starts at L4 (UDP), not at L2 as your filter expects. Use a SOCK_RAW instead.

Then, using SOCK_RAW will give you access to L3, not L2 (you'd need to change the socket domain for that). So you need to adapt your filter somewhat:

    { 0x28, 0, 0, 0x0000000c }, // load Ethertype
    { 0x15, 6, 0, 0x000086dd }, // If IPv6 goto drop
    { 0x15, 0, 5, 0x00000800 }, // If not IPv4 (and not IPv6) goto drop
    { 0x30, 0, 0, 0x00000017 }, // Load IP protocol
    { 0x15, 0, 3, 0x00000011 }, // If not UDP goto drop
    { 0x20, 0, 0, 0x0000001a }, // Load src address
    { 0x15, 0, 1, 0xc0a83865 }, // If not 192.168.56.1.1 goto drop
    { 0x6,  0, 0, 0x00040000 }, // Pass packet
    { 0x6,  0, 0, 0x00000000 }, // Drop

Should become (credits to OP who fixed it on their own :) ):

    // UDP check is harmless but useless
    // { 0x30, 0, 0, 0x00000009 }, // Note the offset update
    // { 0x15, 0, 3, 0x00000011 },
    { 0x20, 0, 0, 0x0000000c }, // Note the offset update
    { 0x15, 0, 1, 0xc0a83865 },
    { 0x6,  0, 0, 0x00040000 },
    { 0x6,  0, 0, 0x00000000 },
Qeole
  • 8,284
  • 1
  • 24
  • 52
  • I'd like to understand better how is processed a packet when I'm using a socket with a cbpf filter. A packet arrive from the network device, now based on the type of socket the packet is processed in the stack protocols which accordingly remove lower levels headers, then cbpf act on the data resulting from these steps? Sorry if my terms are not correct. – Maicake Jul 26 '19 at 09:19
  • 1
    Yes, this is the idea, although the packet is processed in the stack anyway and it's more like “hooks” at various points of the kernel stack rather than an original decision to really prepare everything for a datagram socket. And yes, your cBPF program is run when the packet reaches the socket. – Qeole Jul 29 '19 at 14:24
  • Thanks again, https://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/056/5617/5617f1.jpg is this representation still valid? I mean the article is from 2002 – Maicake Jul 29 '19 at 16:27
  • 2
    You don't have everything on that diagram (e.g. XDP, traffic control, netfilter hooks) but yeah, the basics remain the same. As a complement you may look at [this one](https://commons.wikimedia.org/wiki/File:Netfilter-packet-flow.svg), although it's the lower levels and you don't have much about the sockets. – Qeole Jul 30 '19 at 09:23