1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2017-18 David Ahern <dsahern@gmail.com> |
3 | * |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of version 2 of the GNU General Public |
6 | * License as published by the Free Software Foundation. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, but |
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
11 | * General Public License for more details. |
12 | */ |
13 | #define KBUILD_MODNAME "foo" |
14 | #include <uapi/linux/bpf.h> |
15 | #include <linux/in.h> |
16 | #include <linux/if_ether.h> |
17 | #include <linux/if_packet.h> |
18 | #include <linux/if_vlan.h> |
19 | #include <linux/ip.h> |
20 | #include <linux/ipv6.h> |
21 | |
22 | #include <bpf/bpf_helpers.h> |
23 | |
24 | #define IPV6_FLOWINFO_MASK cpu_to_be32(0x0FFFFFFF) |
25 | |
26 | struct { |
27 | __uint(type, BPF_MAP_TYPE_DEVMAP); |
28 | __uint(key_size, sizeof(int)); |
29 | __uint(value_size, sizeof(int)); |
30 | __uint(max_entries, 64); |
31 | } xdp_tx_ports SEC(".maps" ); |
32 | |
33 | /* from include/net/ip.h */ |
34 | static __always_inline int ip_decrease_ttl(struct iphdr *iph) |
35 | { |
36 | u32 check = (__force u32)iph->check; |
37 | |
38 | check += (__force u32)htons(0x0100); |
39 | iph->check = (__force __sum16)(check + (check >= 0xFFFF)); |
40 | return --iph->ttl; |
41 | } |
42 | |
43 | static __always_inline int xdp_fwd_flags(struct xdp_md *ctx, u32 flags) |
44 | { |
45 | void *data_end = (void *)(long)ctx->data_end; |
46 | void *data = (void *)(long)ctx->data; |
47 | struct bpf_fib_lookup fib_params; |
48 | struct ethhdr *eth = data; |
49 | struct ipv6hdr *ip6h; |
50 | struct iphdr *iph; |
51 | u16 h_proto; |
52 | u64 nh_off; |
53 | int rc; |
54 | |
55 | nh_off = sizeof(*eth); |
56 | if (data + nh_off > data_end) |
57 | return XDP_DROP; |
58 | |
59 | __builtin_memset(&fib_params, 0, sizeof(fib_params)); |
60 | |
61 | h_proto = eth->h_proto; |
62 | if (h_proto == htons(ETH_P_IP)) { |
63 | iph = data + nh_off; |
64 | |
65 | if (iph + 1 > data_end) |
66 | return XDP_DROP; |
67 | |
68 | if (iph->ttl <= 1) |
69 | return XDP_PASS; |
70 | |
71 | fib_params.family = AF_INET; |
72 | fib_params.tos = iph->tos; |
73 | fib_params.l4_protocol = iph->protocol; |
74 | fib_params.sport = 0; |
75 | fib_params.dport = 0; |
76 | fib_params.tot_len = ntohs(iph->tot_len); |
77 | fib_params.ipv4_src = iph->saddr; |
78 | fib_params.ipv4_dst = iph->daddr; |
79 | } else if (h_proto == htons(ETH_P_IPV6)) { |
80 | struct in6_addr *src = (struct in6_addr *) fib_params.ipv6_src; |
81 | struct in6_addr *dst = (struct in6_addr *) fib_params.ipv6_dst; |
82 | |
83 | ip6h = data + nh_off; |
84 | if (ip6h + 1 > data_end) |
85 | return XDP_DROP; |
86 | |
87 | if (ip6h->hop_limit <= 1) |
88 | return XDP_PASS; |
89 | |
90 | fib_params.family = AF_INET6; |
91 | fib_params.flowinfo = *(__be32 *)ip6h & IPV6_FLOWINFO_MASK; |
92 | fib_params.l4_protocol = ip6h->nexthdr; |
93 | fib_params.sport = 0; |
94 | fib_params.dport = 0; |
95 | fib_params.tot_len = ntohs(ip6h->payload_len); |
96 | *src = ip6h->saddr; |
97 | *dst = ip6h->daddr; |
98 | } else { |
99 | return XDP_PASS; |
100 | } |
101 | |
102 | fib_params.ifindex = ctx->ingress_ifindex; |
103 | |
104 | rc = bpf_fib_lookup(ctx, &fib_params, sizeof(fib_params), flags); |
105 | /* |
106 | * Some rc (return codes) from bpf_fib_lookup() are important, |
107 | * to understand how this XDP-prog interacts with network stack. |
108 | * |
109 | * BPF_FIB_LKUP_RET_NO_NEIGH: |
110 | * Even if route lookup was a success, then the MAC-addresses are also |
111 | * needed. This is obtained from arp/neighbour table, but if table is |
112 | * (still) empty then BPF_FIB_LKUP_RET_NO_NEIGH is returned. To avoid |
113 | * doing ARP lookup directly from XDP, then send packet to normal |
114 | * network stack via XDP_PASS and expect it will do ARP resolution. |
115 | * |
116 | * BPF_FIB_LKUP_RET_FWD_DISABLED: |
117 | * The bpf_fib_lookup respect sysctl net.ipv{4,6}.conf.all.forwarding |
118 | * setting, and will return BPF_FIB_LKUP_RET_FWD_DISABLED if not |
119 | * enabled this on ingress device. |
120 | */ |
121 | if (rc == BPF_FIB_LKUP_RET_SUCCESS) { |
122 | /* Verify egress index has been configured as TX-port. |
123 | * (Note: User can still have inserted an egress ifindex that |
124 | * doesn't support XDP xmit, which will result in packet drops). |
125 | * |
126 | * Note: lookup in devmap supported since 0cdbb4b09a0. |
127 | * If not supported will fail with: |
128 | * cannot pass map_type 14 into func bpf_map_lookup_elem#1: |
129 | */ |
130 | if (!bpf_map_lookup_elem(&xdp_tx_ports, &fib_params.ifindex)) |
131 | return XDP_PASS; |
132 | |
133 | if (h_proto == htons(ETH_P_IP)) |
134 | ip_decrease_ttl(iph); |
135 | else if (h_proto == htons(ETH_P_IPV6)) |
136 | ip6h->hop_limit--; |
137 | |
138 | memcpy(eth->h_dest, fib_params.dmac, ETH_ALEN); |
139 | memcpy(eth->h_source, fib_params.smac, ETH_ALEN); |
140 | return bpf_redirect_map(&xdp_tx_ports, fib_params.ifindex, 0); |
141 | } |
142 | |
143 | return XDP_PASS; |
144 | } |
145 | |
146 | SEC("xdp_fwd" ) |
147 | int xdp_fwd_prog(struct xdp_md *ctx) |
148 | { |
149 | return xdp_fwd_flags(ctx, 0); |
150 | } |
151 | |
152 | SEC("xdp_fwd_direct" ) |
153 | int xdp_fwd_direct_prog(struct xdp_md *ctx) |
154 | { |
155 | return xdp_fwd_flags(ctx, BPF_FIB_LOOKUP_DIRECT); |
156 | } |
157 | |
158 | char _license[] SEC("license" ) = "GPL" ; |
159 | |