Reputation: 191
I have two different types of BPF programs where I am printing the ip address with bpf_printk("%pI4", &ipv4.s_addr);
. The xdp program is loaded in the loopback dev and the other is a bpf socket filter; i have a server and a client program that sends udp packets. The programs below print the ip address but each program prints (in /sys/kernel/debug/tracing/trace_pipe) the address in a different order. One is being printed as 127.0.0.1 (in the xdp program) and the other as 1.0.0.127. I understand that socket filters have access to a mirror of the kernel's skb_buff and xdp programs do not have access to this data as they are attached at an "earlier point". My question: Does the data in skb_buff data fields get "modified" to cpu endianness? What would be the cause of this different behavior?
Thank you in advance.
My env:
x86_64
clang-13
Little endian
Linux kernel 5.13
Ubuntu 21.10
// socket filter program
unsigned long long load_word(void *skb, unsigned long long off) asm("llvm.bpf.load.word");
SEC("socket/ipv4")
int filter_ipv4(struct __sk_buff *skb)
{
struct in_addr ipv4;
ipv4.s_addr = load_word(skb, BPF_NET_OFF + offsetof(struct iphdr, saddr));
bpf_printk("%pI4", &ipv4.s_addr); // prints 1.0.0.127
return -1;
}
// xdp program
SEC("xdp_prog")
int xdp_some_prog(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *ethh;
struct hdr_cursor nh;
int nh_type;
struct in_addr ipv4;
nh.pos = data;
nh_type = parse_ethhdr(&nh, data_end, ðh);
if (nh_type == bpf_htons(ETH_P_IP)) {
struct iphdr *iph;
nh_type = parse_iphdr(&nh, data_end, &iph);
if (nh_type != IPPROTO_UDP)
return XDP_PASS;
ipv4.s_addr = iph->saddr;
bpf_printk("%pI4", &ipv4.s_addr); // prints 127.0.0.1
}
return XDP_PASS;
}
#define BPF_NET_OFF (-0x100000)
#define ETH_P_IP 0x0800
#define IPPROTO_UDP 17
struct in_addr {
__u32 s_addr;
};
struct ethhdr {
unsigned char h_dest[6];
unsigned char h_source[6];
__be16 h_proto;
};
struct iphdr {
__u8 ihl: 4;
__u8 version: 4;
__u8 tos;
__be16 tot_len;
__be16 id;
__be16 frag_off;
__u8 ttl;
__u8 protocol;
__sum16 check;
__be32 saddr;
__be32 daddr;
};
struct hdr_cursor {
void *pos;
};
static __always_inline
int parse_ethhdr(struct hdr_cursor *nh, void *data_end, struct ethhdr **ethhdr)
{
struct ethhdr *eth = nh->pos;
if (eth + 1 > data_end)
return -1;
nh->pos = eth + 1;
*ethhdr = eth;
return eth->h_proto;
}
static __always_inline
int parse_iphdr(struct hdr_cursor *nh, void *data_end, struct iphdr **iphdr)
{
struct iphdr *iph = nh->pos;
int hdrsize;
if (iph + 1 > data_end)
return -1;
hdrsize = iph->ihl * 4;
/* ipv4 hdr is minimum 20 bytes */
if (hdrsize < sizeof(*iph))
return -1;
/* Variable-length IPv4 header, need to use byte-based arithmetic */
if (nh->pos + hdrsize > data_end)
return -1;
nh->pos += hdrsize;
*iphdr = iph;
return iph->protocol;
}
Upvotes: 4
Views: 1168
Reputation: 91
You are parsing the packet on your own at the XDP layer, which will contain fields in network order. Whereas at the socket filter layer, you use the load_word
helper to parse the packet, which inturn calls the bpf_ntohl()
helper, converting the IP address field from network order to host order.
Upvotes: 2