// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2020 Jesper Dangaard Brouer */ #include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <linux/if_ether.h> #include <stddef.h> #include <stdint.h> char _license[] SEC("license") = "GPL"; /* Userspace will update with MTU it can see on device */ volatile const int GLOBAL_USER_MTU; volatile const __u32 GLOBAL_USER_IFINDEX; /* BPF-prog will update these with MTU values it can see */ __u32 global_bpf_mtu_xdp = 0; __u32 global_bpf_mtu_tc = 0; SEC("xdp") int xdp_use_helper_basic(struct xdp_md *ctx) { __u32 mtu_len = 0; if (bpf_check_mtu(ctx, 0, &mtu_len, 0, 0)) return XDP_ABORTED; return XDP_PASS; } SEC("xdp") int xdp_use_helper(struct xdp_md *ctx) { int retval = XDP_PASS; /* Expected retval on successful test */ __u32 mtu_len = 0; __u32 ifindex = 0; int delta = 0; /* When ifindex is zero, save net_device lookup and use ctx netdev */ if (GLOBAL_USER_IFINDEX > 0) ifindex = GLOBAL_USER_IFINDEX; if (bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0)) { /* mtu_len is also valid when check fail */ retval = XDP_ABORTED; goto out; } if (mtu_len != GLOBAL_USER_MTU) retval = XDP_DROP; out: global_bpf_mtu_xdp = mtu_len; return retval; } SEC("xdp") int xdp_exceed_mtu(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; __u32 ifindex = GLOBAL_USER_IFINDEX; __u32 data_len = data_end - data; int retval = XDP_ABORTED; /* Fail */ __u32 mtu_len = 0; int delta; int err; /* Exceed MTU with 1 via delta adjust */ delta = GLOBAL_USER_MTU - (data_len - ETH_HLEN) + 1; err = bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0); if (err) { retval = XDP_PASS; /* Success in exceeding MTU check */ if (err != BPF_MTU_CHK_RET_FRAG_NEEDED) retval = XDP_DROP; } global_bpf_mtu_xdp = mtu_len; return retval; } SEC("xdp") int xdp_minus_delta(struct xdp_md *ctx) { int retval = XDP_PASS; /* Expected retval on successful test */ void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; __u32 ifindex = GLOBAL_USER_IFINDEX; __u32 data_len = data_end - data; __u32 mtu_len = 0; int delta; /* Borderline test case: Minus delta exceeding packet length allowed */ delta = -((data_len - ETH_HLEN) + 1); /* Minus length (adjusted via delta) still pass MTU check, other helpers * are responsible for catching this, when doing actual size adjust */ if (bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0)) retval = XDP_ABORTED; global_bpf_mtu_xdp = mtu_len; return retval; } SEC("xdp") int xdp_input_len(struct xdp_md *ctx) { int retval = XDP_PASS; /* Expected retval on successful test */ void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; __u32 ifindex = GLOBAL_USER_IFINDEX; __u32 data_len = data_end - data; /* API allow user give length to check as input via mtu_len param, * resulting MTU value is still output in mtu_len param after call. * * Input len is L3, like MTU and iph->tot_len. * Remember XDP data_len is L2. */ __u32 mtu_len = data_len - ETH_HLEN; if (bpf_check_mtu(ctx, ifindex, &mtu_len, 0, 0)) retval = XDP_ABORTED; global_bpf_mtu_xdp = mtu_len; return retval; } SEC("xdp") int xdp_input_len_exceed(struct xdp_md *ctx) { int retval = XDP_ABORTED; /* Fail */ __u32 ifindex = GLOBAL_USER_IFINDEX; int err; /* API allow user give length to check as input via mtu_len param, * resulting MTU value is still output in mtu_len param after call. * * Input length value is L3 size like MTU. */ __u32 mtu_len = GLOBAL_USER_MTU; mtu_len += 1; /* Exceed with 1 */ err = bpf_check_mtu(ctx, ifindex, &mtu_len, 0, 0); if (err == BPF_MTU_CHK_RET_FRAG_NEEDED) retval = XDP_PASS ; /* Success in exceeding MTU check */ global_bpf_mtu_xdp = mtu_len; return retval; } SEC("tc") int tc_use_helper(struct __sk_buff *ctx) { int retval = BPF_OK; /* Expected retval on successful test */ __u32 mtu_len = 0; int delta = 0; if (bpf_check_mtu(ctx, 0, &mtu_len, delta, 0)) { retval = BPF_DROP; goto out; } if (mtu_len != GLOBAL_USER_MTU) retval = BPF_REDIRECT; out: global_bpf_mtu_tc = mtu_len; return retval; } SEC("tc") int tc_exceed_mtu(struct __sk_buff *ctx) { __u32 ifindex = GLOBAL_USER_IFINDEX; int retval = BPF_DROP; /* Fail */ __u32 skb_len = ctx->len; __u32 mtu_len = 0; int delta; int err; /* Exceed MTU with 1 via delta adjust */ delta = GLOBAL_USER_MTU - (skb_len - ETH_HLEN) + 1; err = bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0); if (err) { retval = BPF_OK; /* Success in exceeding MTU check */ if (err != BPF_MTU_CHK_RET_FRAG_NEEDED) retval = BPF_DROP; } global_bpf_mtu_tc = mtu_len; return retval; } SEC("tc") int tc_exceed_mtu_da(struct __sk_buff *ctx) { /* SKB Direct-Access variant */ void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; __u32 ifindex = GLOBAL_USER_IFINDEX; __u32 data_len = data_end - data; int retval = BPF_DROP; /* Fail */ __u32 mtu_len = 0; int delta; int err; /* Exceed MTU with 1 via delta adjust */ delta = GLOBAL_USER_MTU - (data_len - ETH_HLEN) + 1; err = bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0); if (err) { retval = BPF_OK; /* Success in exceeding MTU check */ if (err != BPF_MTU_CHK_RET_FRAG_NEEDED) retval = BPF_DROP; } global_bpf_mtu_tc = mtu_len; return retval; } SEC("tc") int tc_minus_delta(struct __sk_buff *ctx) { int retval = BPF_OK; /* Expected retval on successful test */ __u32 ifindex = GLOBAL_USER_IFINDEX; __u32 skb_len = ctx->len; __u32 mtu_len = 0; int delta; /* Borderline test case: Minus delta exceeding packet length allowed */ delta = -((skb_len - ETH_HLEN) + 1); /* Minus length (adjusted via delta) still pass MTU check, other helpers * are responsible for catching this, when doing actual size adjust */ if (bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0)) retval = BPF_DROP; global_bpf_mtu_xdp = mtu_len; return retval; } SEC("tc") int tc_input_len(struct __sk_buff *ctx) { int retval = BPF_OK; /* Expected retval on successful test */ __u32 ifindex = GLOBAL_USER_IFINDEX; /* API allow user give length to check as input via mtu_len param, * resulting MTU value is still output in mtu_len param after call. * * Input length value is L3 size. */ __u32 mtu_len = GLOBAL_USER_MTU; if (bpf_check_mtu(ctx, ifindex, &mtu_len, 0, 0)) retval = BPF_DROP; global_bpf_mtu_xdp = mtu_len; return retval; } SEC("tc") int tc_input_len_exceed(struct __sk_buff *ctx) { int retval = BPF_DROP; /* Fail */ __u32 ifindex = GLOBAL_USER_IFINDEX; int err; /* API allow user give length to check as input via mtu_len param, * resulting MTU value is still output in mtu_len param after call. * * Input length value is L3 size like MTU. */ __u32 mtu_len = GLOBAL_USER_MTU; mtu_len += 1; /* Exceed with 1 */ err = bpf_check_mtu(ctx, ifindex, &mtu_len, 0, 0); if (err == BPF_MTU_CHK_RET_FRAG_NEEDED) retval = BPF_OK; /* Success in exceeding MTU check */ global_bpf_mtu_xdp = mtu_len; return retval; }