mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
Merge branch 'bpf-update-transport_header-when-encapsulating-udp-tunnel-in-lwt'
Leon Hwang says: ==================== bpf: Update transport_header when encapsulating UDP tunnel in lwt Currently, bpf_lwt_push_ip_encap() does not update skb->transport_header. When a driver, e.g. ice, reuses the stale skb->transport_header to offload checksum computation to NIC hardware, VxLAN packets encapsulated by bpf_lwt_push_encap() helper may be dropped due to incorrect checksum. Update skb->transport_header in bpf_lwt_push_ip_encap() whenever the encapsulated packet uses UDP, so checksum offload works correctly. Changes: v3 -> v4: * Address comments from Emil: * Make the logic of skb_set_transport_header() clearer in patch #1. * Fold the code of fexit_lwt_push_ip_encap() into test_lwt_ip_encap.c in patch #2. * Resolve assorted issues of test in patch #2. * v3: https://lore.kernel.org/bpf/20260601150203.20352-1-leon.hwang@linux.dev/ v2 -> v3: * Drop patch #1 and #2 of v2 that aim to resolve potential issues reported by sashiko (per Alexei). * Check target IP version and UDP tunnel in test (per sashiko). * v2: https://lore.kernel.org/bpf/20260529151351.69911-1-leon.hwang@linux.dev/ v1 -> v2: * Address sashiko's reviews: * Fix TOCTOU issue in lwt to avoid changing hdr after checks. * Add check iph->ihl < 5 in lwt to avoid infinite-loop in MIPS driver. * Update comment style in selftests with BPF comment style. * v1: https://lore.kernel.org/bpf/20260525142650.2569-1-leon.hwang@linux.dev/ ==================== Link: https://patch.msgid.link/20260602150931.49629-1-leon.hwang@linux.dev Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
f091801c85
|
|
@ -599,6 +599,7 @@ static int handle_gso_encap(struct sk_buff *skb, bool ipv4, int encap_len)
|
|||
|
||||
int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)
|
||||
{
|
||||
bool is_udp_tunnel;
|
||||
struct iphdr *iph;
|
||||
bool ipv4;
|
||||
int err;
|
||||
|
|
@ -612,10 +613,16 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)
|
|||
ipv4 = true;
|
||||
if (unlikely(len < iph->ihl * 4))
|
||||
return -EINVAL;
|
||||
is_udp_tunnel = iph->protocol == IPPROTO_UDP;
|
||||
if (unlikely(is_udp_tunnel && len < iph->ihl * 4 + sizeof(struct udphdr)))
|
||||
return -EINVAL;
|
||||
} else if (iph->version == 6) {
|
||||
ipv4 = false;
|
||||
if (unlikely(len < sizeof(struct ipv6hdr)))
|
||||
return -EINVAL;
|
||||
is_udp_tunnel = ((struct ipv6hdr *)iph)->nexthdr == NEXTHDR_UDP;
|
||||
if (unlikely(is_udp_tunnel && len < sizeof(struct ipv6hdr) + sizeof(struct udphdr)))
|
||||
return -EINVAL;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
@ -637,6 +644,11 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)
|
|||
if (ingress)
|
||||
skb_postpush_rcsum(skb, iph, len);
|
||||
skb_reset_network_header(skb);
|
||||
if (is_udp_tunnel) {
|
||||
size_t iph_sz = ipv4 ? iph->ihl * 4 : sizeof(struct ipv6hdr);
|
||||
|
||||
skb_set_transport_header(skb, skb_network_offset(skb) + iph_sz);
|
||||
}
|
||||
memcpy(skb_network_header(skb), hdr, len);
|
||||
bpf_compute_data_pointers(skb);
|
||||
skb_clear_hash(skb);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "network_helpers.h"
|
||||
#include "test_progs.h"
|
||||
#include "test_lwt_ip_encap.skel.h"
|
||||
|
||||
#define BPF_FILE "test_lwt_ip_encap.bpf.o"
|
||||
|
||||
|
|
@ -32,6 +33,9 @@
|
|||
#define IP6_ADDR_8 "fb08::1"
|
||||
#define IP6_ADDR_GRE "fb10::1"
|
||||
|
||||
#define IP4_ADDR_VXLAN "172.16.17.100"
|
||||
#define IP6_ADDR_VXLAN "fb11::1"
|
||||
|
||||
#define IP6_ADDR_SRC IP6_ADDR_1
|
||||
#define IP6_ADDR_DST IP6_ADDR_4
|
||||
|
||||
|
|
@ -538,3 +542,144 @@ void test_lwt_ip_encap_ipv4(void)
|
|||
if (test__start_subtest("ingress"))
|
||||
lwt_ip_encap(IPV4_ENCAP, INGRESS, "");
|
||||
}
|
||||
|
||||
/*
|
||||
* VxLAN Setup/topology:
|
||||
*
|
||||
* NS1 (IP*_ADDR_1) NS2 NS3 (IP*_ADDR_4)
|
||||
* [ping src]
|
||||
* | top route
|
||||
* veth1 (LWT encap) <<-- veth2 veth3 <<-- veth4 (ping dst)
|
||||
* | ^
|
||||
* (bottom route) | (inner pkt)
|
||||
* v bottom route |
|
||||
* veth5 -->> veth6 veth7 -->> veth8 (vxlan decap)
|
||||
* (IP*_ADDR_VXLAN)
|
||||
*
|
||||
* Add the VxLAN endpoint addresses to NS3's veth8, create standard
|
||||
* VxLAN decap devices bound to those addresses, and install routes so
|
||||
* NS1/NS2 can reach the endpoints via the bottom route. NS2 here is to
|
||||
* make sure the LWT-encap VxLAN packets are routed to NS3 correctly.
|
||||
*/
|
||||
static int setup_vxlan_routes(const char *ns3, const char *ns1, const char *ns2)
|
||||
{
|
||||
struct nstoken *nstoken;
|
||||
|
||||
nstoken = open_netns(ns3);
|
||||
if (!ASSERT_OK_PTR(nstoken, "open ns3 for vxlan"))
|
||||
return -1;
|
||||
|
||||
SYS(fail_close, "ip a add %s/32 dev veth8", IP4_ADDR_VXLAN);
|
||||
SYS(fail_close, "ip -6 a add %s/128 dev veth8", IP6_ADDR_VXLAN);
|
||||
/*
|
||||
* Standard VxLAN devices to decap the encapsulated packets. The inner
|
||||
* Ethernet frame uses a broadcast dst MAC so the IP stack accepts it
|
||||
* without ARP or FDB configuration.
|
||||
*/
|
||||
SYS(fail_close, "ip link add vxlan4 type vxlan id 1 dstport 4789 local %s dev veth8 nolearning noudpcsum",
|
||||
IP4_ADDR_VXLAN);
|
||||
SYS(fail_close, "ip link set vxlan4 up");
|
||||
SYS(fail_close, "ip link add vxlan6 type vxlan id 1 dstport 4789 local %s dev veth8 nolearning udp6zerocsumrx",
|
||||
IP6_ADDR_VXLAN);
|
||||
SYS(fail_close, "ip link set vxlan6 up");
|
||||
close_netns(nstoken);
|
||||
|
||||
SYS(fail, "ip -n %s route add %s/32 dev veth5 via %s",
|
||||
ns1, IP4_ADDR_VXLAN, IP4_ADDR_6);
|
||||
SYS(fail, "ip -n %s route add %s/32 dev veth7 via %s",
|
||||
ns2, IP4_ADDR_VXLAN, IP4_ADDR_8);
|
||||
SYS(fail, "ip -n %s -6 route add %s/128 dev veth5 via %s",
|
||||
ns1, IP6_ADDR_VXLAN, IP6_ADDR_6);
|
||||
SYS(fail, "ip -n %s -6 route add %s/128 dev veth7 via %s",
|
||||
ns2, IP6_ADDR_VXLAN, IP6_ADDR_8);
|
||||
return 0;
|
||||
|
||||
fail_close:
|
||||
close_netns(nstoken);
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void lwt_ip_encap_vxlan(bool ipv4_encap)
|
||||
{
|
||||
char ns1[NETNS_NAME_SIZE] = NETNS_BASE "-1-";
|
||||
char ns2[NETNS_NAME_SIZE] = NETNS_BASE "-2-";
|
||||
char ns3[NETNS_NAME_SIZE] = NETNS_BASE "-3-";
|
||||
const char *sec = ipv4_encap ? "encap_vxlan" : "encap_vxlan6";
|
||||
int expected_offset = ipv4_encap ? (int)sizeof(struct iphdr)
|
||||
: (int)sizeof(struct ipv6hdr);
|
||||
struct test_lwt_ip_encap *skel = NULL;
|
||||
int thdr_offset, err;
|
||||
|
||||
if (!ASSERT_OK(create_ns(ns1, NETNS_NAME_SIZE), "create ns1"))
|
||||
goto out;
|
||||
if (!ASSERT_OK(create_ns(ns2, NETNS_NAME_SIZE), "create ns2"))
|
||||
goto out;
|
||||
if (!ASSERT_OK(create_ns(ns3, NETNS_NAME_SIZE), "create ns3"))
|
||||
goto out;
|
||||
|
||||
if (!ASSERT_OK(setup_network(ns1, ns2, ns3, ""), "setup network"))
|
||||
goto out;
|
||||
|
||||
if (!ASSERT_OK(setup_vxlan_routes(ns3, ns1, ns2), "setup vxlan routes"))
|
||||
goto out;
|
||||
|
||||
skel = test_lwt_ip_encap__open();
|
||||
if (!ASSERT_OK_PTR(skel, "test_lwt_ip_encap__open"))
|
||||
goto out;
|
||||
|
||||
bpf_program__set_autoload(skel->progs.bpf_lwt_encap_gre, false);
|
||||
bpf_program__set_autoload(skel->progs.bpf_lwt_encap_gre6, false);
|
||||
bpf_program__set_autoload(skel->progs.bpf_lwt_encap_vxlan, false);
|
||||
bpf_program__set_autoload(skel->progs.bpf_lwt_encap_vxlan6, false);
|
||||
bpf_program__set_autoload(skel->progs.fexit_lwt_push_ip_encap, true);
|
||||
skel->rodata->tgt_ip_version = ipv4_encap ? 4 : 6;
|
||||
|
||||
err = test_lwt_ip_encap__load(skel);
|
||||
if (!ASSERT_OK(err, "test_lwt_ip_encap__load"))
|
||||
goto out;
|
||||
|
||||
err = test_lwt_ip_encap__attach(skel);
|
||||
if (!ASSERT_OK(err, "test_lwt_ip_encap__attach"))
|
||||
goto out;
|
||||
|
||||
/* Remove the direct NS2->DST route so packets must go via LWT encap. */
|
||||
SYS(out, "ip -n %s route del %s/32 dev veth3", ns2, IP4_ADDR_DST);
|
||||
SYS(out, "ip -n %s -6 route del %s/128 dev veth3", ns2, IP6_ADDR_DST);
|
||||
|
||||
if (ipv4_encap)
|
||||
SYS(out, "ip -n %s route add %s encap bpf xmit obj %s sec %s dev veth1",
|
||||
ns1, IP4_ADDR_DST, BPF_FILE, sec);
|
||||
else
|
||||
SYS(out, "ip -n %s -6 route add %s encap bpf xmit obj %s sec %s dev veth1",
|
||||
ns1, IP6_ADDR_DST, BPF_FILE, sec);
|
||||
|
||||
skel->bss->fexit_triggered = false;
|
||||
|
||||
if (ipv4_encap)
|
||||
SYS(out, "ip netns exec %s ping -c 1 -W1 %s", ns1, IP4_ADDR_DST);
|
||||
else
|
||||
SYS(out, "ip netns exec %s ping6 -c 1 -W1 %s", ns1, IP6_ADDR_DST);
|
||||
|
||||
if (!ASSERT_TRUE(skel->bss->fexit_triggered, "fexit_triggered"))
|
||||
goto out;
|
||||
|
||||
thdr_offset = (int)skel->bss->transport_hdr - (int)skel->bss->network_hdr;
|
||||
ASSERT_EQ(thdr_offset, expected_offset, "transport_hdr offset");
|
||||
|
||||
out:
|
||||
test_lwt_ip_encap__destroy(skel);
|
||||
SYS_NOFAIL("ip netns del %s", ns1);
|
||||
SYS_NOFAIL("ip netns del %s", ns2);
|
||||
SYS_NOFAIL("ip netns del %s", ns3);
|
||||
}
|
||||
|
||||
void test_lwt_ip_encap_vxlan_ipv4(void)
|
||||
{
|
||||
lwt_ip_encap_vxlan(IPV4_ENCAP);
|
||||
}
|
||||
|
||||
void test_lwt_ip_encap_vxlan_ipv6(void)
|
||||
{
|
||||
lwt_ip_encap_vxlan(IPV6_ENCAP);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <stddef.h>
|
||||
#include "vmlinux.h"
|
||||
#include <string.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/ipv6.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
struct grehdr {
|
||||
__be16 flags;
|
||||
|
|
@ -64,13 +62,13 @@ int bpf_lwt_encap_gre6(struct __sk_buff *skb)
|
|||
hdr.ip6hdr.nexthdr = 47; /* IPPROTO_GRE */
|
||||
hdr.ip6hdr.hop_limit = 0x40;
|
||||
/* fb01::1 */
|
||||
hdr.ip6hdr.saddr.s6_addr[0] = 0xfb;
|
||||
hdr.ip6hdr.saddr.s6_addr[1] = 1;
|
||||
hdr.ip6hdr.saddr.s6_addr[15] = 1;
|
||||
hdr.ip6hdr.saddr.in6_u.u6_addr8[0] = 0xfb;
|
||||
hdr.ip6hdr.saddr.in6_u.u6_addr8[1] = 1;
|
||||
hdr.ip6hdr.saddr.in6_u.u6_addr8[15] = 1;
|
||||
/* fb10::1 */
|
||||
hdr.ip6hdr.daddr.s6_addr[0] = 0xfb;
|
||||
hdr.ip6hdr.daddr.s6_addr[1] = 0x10;
|
||||
hdr.ip6hdr.daddr.s6_addr[15] = 1;
|
||||
hdr.ip6hdr.daddr.in6_u.u6_addr8[0] = 0xfb;
|
||||
hdr.ip6hdr.daddr.in6_u.u6_addr8[1] = 0x10;
|
||||
hdr.ip6hdr.daddr.in6_u.u6_addr8[15] = 1;
|
||||
|
||||
hdr.greh.protocol = skb->protocol;
|
||||
|
||||
|
|
@ -82,4 +80,141 @@ int bpf_lwt_encap_gre6(struct __sk_buff *skb)
|
|||
return BPF_LWT_REROUTE;
|
||||
}
|
||||
|
||||
#define VXLAN_PORT 4789
|
||||
#define VXLAN_FLAGS 0x08000000
|
||||
#define VXLAN_VNI 1
|
||||
|
||||
#define ETH_ALEN 6 /* Octets in one ethernet addr */
|
||||
#define ETH_P_IP 0x0800 /* Internet Protocol packet */
|
||||
#define ETH_P_IPV6 0x86DD /* IPv6 over bluebook */
|
||||
|
||||
static const __u8 bcast[ETH_ALEN] = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
};
|
||||
|
||||
static const __u8 srcmac[ETH_ALEN] = {
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
};
|
||||
|
||||
SEC("encap_vxlan")
|
||||
int bpf_lwt_encap_vxlan(struct __sk_buff *skb)
|
||||
{
|
||||
struct encap_hdr {
|
||||
struct iphdr iph;
|
||||
struct udphdr udph;
|
||||
struct vxlanhdr vxh;
|
||||
struct ethhdr eth;
|
||||
} __attribute__((__packed__)) hdr;
|
||||
int err;
|
||||
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
|
||||
hdr.iph.ihl = 5;
|
||||
hdr.iph.version = 4;
|
||||
hdr.iph.ttl = 0x40;
|
||||
hdr.iph.protocol = 17; /* IPPROTO_UDP */
|
||||
hdr.iph.tot_len = bpf_htons(skb->len + sizeof(hdr));
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
hdr.iph.saddr = 0x640510ac; /* 172.16.5.100 */
|
||||
hdr.iph.daddr = 0x641110ac; /* 172.16.17.100 */
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
hdr.iph.saddr = 0xac100564; /* 172.16.5.100 */
|
||||
hdr.iph.daddr = 0xac101164; /* 172.16.17.100 */
|
||||
#else
|
||||
#error "Fix your compiler's __BYTE_ORDER__?!"
|
||||
#endif
|
||||
|
||||
hdr.udph.source = bpf_htons(VXLAN_PORT);
|
||||
hdr.udph.dest = bpf_htons(VXLAN_PORT);
|
||||
hdr.udph.len = bpf_htons(skb->len + sizeof(hdr.udph) + sizeof(hdr.vxh) +
|
||||
sizeof(hdr.eth));
|
||||
|
||||
hdr.vxh.vx_flags = bpf_htonl(VXLAN_FLAGS);
|
||||
hdr.vxh.vx_vni = bpf_htonl(VXLAN_VNI << 8);
|
||||
|
||||
__builtin_memcpy(hdr.eth.h_dest, bcast, ETH_ALEN);
|
||||
__builtin_memcpy(hdr.eth.h_source, srcmac, ETH_ALEN);
|
||||
hdr.eth.h_proto = bpf_htons(ETH_P_IP);
|
||||
|
||||
err = bpf_lwt_push_encap(skb, BPF_LWT_ENCAP_IP, &hdr, sizeof(hdr));
|
||||
if (err)
|
||||
return BPF_DROP;
|
||||
|
||||
return BPF_LWT_REROUTE;
|
||||
}
|
||||
|
||||
SEC("encap_vxlan6")
|
||||
int bpf_lwt_encap_vxlan6(struct __sk_buff *skb)
|
||||
{
|
||||
struct encap_hdr {
|
||||
struct ipv6hdr ip6hdr;
|
||||
struct udphdr udph;
|
||||
struct vxlanhdr vxh;
|
||||
struct ethhdr eth;
|
||||
} __attribute__((__packed__)) hdr;
|
||||
int err;
|
||||
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
|
||||
hdr.ip6hdr.version = 6;
|
||||
hdr.ip6hdr.nexthdr = 17; /* IPPROTO_UDP */
|
||||
hdr.ip6hdr.hop_limit = 0x40;
|
||||
hdr.ip6hdr.payload_len = bpf_htons(skb->len + sizeof(hdr.udph) + sizeof(hdr.vxh) +
|
||||
sizeof(hdr.eth));
|
||||
/* fb05::1 */
|
||||
hdr.ip6hdr.saddr.in6_u.u6_addr8[0] = 0xfb;
|
||||
hdr.ip6hdr.saddr.in6_u.u6_addr8[1] = 0x05;
|
||||
hdr.ip6hdr.saddr.in6_u.u6_addr8[15] = 1;
|
||||
/* fb11::1 */
|
||||
hdr.ip6hdr.daddr.in6_u.u6_addr8[0] = 0xfb;
|
||||
hdr.ip6hdr.daddr.in6_u.u6_addr8[1] = 0x11;
|
||||
hdr.ip6hdr.daddr.in6_u.u6_addr8[15] = 1;
|
||||
|
||||
hdr.udph.source = bpf_htons(VXLAN_PORT);
|
||||
hdr.udph.dest = bpf_htons(VXLAN_PORT);
|
||||
hdr.udph.len = bpf_htons(skb->len + sizeof(hdr.udph) + sizeof(hdr.vxh) +
|
||||
sizeof(hdr.eth));
|
||||
|
||||
hdr.vxh.vx_flags = bpf_htonl(VXLAN_FLAGS);
|
||||
hdr.vxh.vx_vni = bpf_htonl(VXLAN_VNI << 8);
|
||||
|
||||
__builtin_memcpy(hdr.eth.h_dest, bcast, ETH_ALEN);
|
||||
__builtin_memcpy(hdr.eth.h_source, srcmac, ETH_ALEN);
|
||||
hdr.eth.h_proto = bpf_htons(ETH_P_IPV6);
|
||||
|
||||
err = bpf_lwt_push_encap(skb, BPF_LWT_ENCAP_IP, &hdr, sizeof(hdr));
|
||||
if (err)
|
||||
return BPF_DROP;
|
||||
|
||||
return BPF_LWT_REROUTE;
|
||||
}
|
||||
|
||||
volatile const int tgt_ip_version;
|
||||
|
||||
__u16 transport_hdr = 0;
|
||||
__u16 network_hdr = 0;
|
||||
bool fexit_triggered = false;
|
||||
|
||||
SEC("?fexit/bpf_lwt_push_ip_encap")
|
||||
int BPF_PROG(fexit_lwt_push_ip_encap, struct sk_buff *skb, void *hdr, u32 len, bool ingress,
|
||||
int retval)
|
||||
{
|
||||
struct iphdr *iph;
|
||||
|
||||
if (retval || fexit_triggered)
|
||||
return 0;
|
||||
|
||||
iph = (typeof(iph)) (skb->head + skb->network_header);
|
||||
if (iph->version != tgt_ip_version)
|
||||
return 0;
|
||||
|
||||
if ((iph->version == 4 && iph->protocol == 17 /* IPPROTO_UDP */) ||
|
||||
(iph->version == 6 && ((struct ipv6hdr *)iph)->nexthdr == 17 /* IPPROTO_UDP */)) {
|
||||
fexit_triggered = true;
|
||||
transport_hdr = skb->transport_header;
|
||||
network_hdr = skb->network_header;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user