Knio
Knio

Reputation: 7090

Determine if sk_buff is L2 or L3

I'm attaching an eBPF packet filter declared as:

int sock_peek_packet(struct __sk_buff *skb);

Using the python bcc library, as so:

interface = 'wg0' # or 'eth0'
b = bcc.BPF(src_file=src_path, debug=0)
fn = b.load_func('sock_peek_packet', bcc.BPF.SOCKET_FILTER)
bcc.BPF.attach_raw_socket(dev=interface, fn=fn)

When attaching to eth0, the contents of skb is an L2 ethernet frame (i.e. starts with a struct ethhdr, like how you find on examples all over the internet).

However, when attaching to wg0, the contents of skb is an L3 (IP) packet, (i.e, starts with a struct iphdr) with no proceeding ethernet header.

How can I determine at runtime which type of packet I'm getting? (in my final program I will be attaching to all interfaces on the host). skb->protocol and skb->pkt_type do not change between eth0 and wg0.

Note: currently testing on Linux-6.1.0-21-amd64 but have observed the same behavior on all other versions tested

Upvotes: 1

Views: 68

Answers (2)

Dylan Reimerink
Dylan Reimerink

Reputation: 7908

You cannot choose. This comes down to the device type. The eth0 devices is L2 aware because it actually sends out packets over the wire. But a device like wg0 is virtual, L3 packets go in, and L3 packets come out.

As far as I know, the way you can tell before hand is to see if the device has a MAC address when you inspect it via ip l. If a device has no MAC, it is an L3 device.

Upvotes: 3

pchaigno
pchaigno

Reputation: 13063

I don't believe there's a field of __sk_buff (or some helper) that can tell you at runtime in which case you are (L2 or L3). Depending on your situation, you may be able have to parse the packet to check its first header (see below).

I would however recommend to detect the device type in userspace and use an eBPF global variable to tell the eBPF program what headers to expect. You can achieve that with the method pointed out by Dylan.


Detecting at Runtime Without Help from Userspace

If you know which MAC and IP addresses to expect, you can probably do this without risking an L2 header being confused for an L3 header (or vice-versa). The following for example checks if the first header is an IPv4 header with the expected destination IP address:

void *data = (void *)(long)skb->data;
unsigned char dst_mac[ETH_ALEN];
mac_offset = data + offsetof(struct ethhdr, h_dest);
if (memcpy(mac_offset, expected_dst_mac, ETH_ALEN)) {
     // The destination MAC address doesn't match what we expect,
     // so this likely a packet on the L3 device.
}

Note you will need to add the appropriate bounds check.

This is somewhat fragile. You might receive packets with incorrect destination MAC addresses, in which case they will be treated as L3. You will need to consider that possibility and its security implications.

Upvotes: 2

Related Questions