xdp_tx_iptunnel_kern.c (5713B)
1/* Copyright (c) 2016 Facebook 2 * 3 * This program is free software; you can redistribute it and/or 4 * modify it under the terms of version 2 of the GNU General Public 5 * License as published by the Free Software Foundation. 6 * 7 * This program shows how to use bpf_xdp_adjust_head() by 8 * encapsulating the incoming packet in an IPv4/v6 header 9 * and then XDP_TX it out. 10 */ 11#define KBUILD_MODNAME "foo" 12#include <uapi/linux/bpf.h> 13#include <linux/in.h> 14#include <linux/if_ether.h> 15#include <linux/if_packet.h> 16#include <linux/if_vlan.h> 17#include <linux/ip.h> 18#include <linux/ipv6.h> 19#include <bpf/bpf_helpers.h> 20#include "xdp_tx_iptunnel_common.h" 21 22struct { 23 __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); 24 __type(key, __u32); 25 __type(value, __u64); 26 __uint(max_entries, 256); 27} rxcnt SEC(".maps"); 28 29struct { 30 __uint(type, BPF_MAP_TYPE_HASH); 31 __type(key, struct vip); 32 __type(value, struct iptnl_info); 33 __uint(max_entries, MAX_IPTNL_ENTRIES); 34} vip2tnl SEC(".maps"); 35 36static __always_inline void count_tx(u32 protocol) 37{ 38 u64 *rxcnt_count; 39 40 rxcnt_count = bpf_map_lookup_elem(&rxcnt, &protocol); 41 if (rxcnt_count) 42 *rxcnt_count += 1; 43} 44 45static __always_inline int get_dport(void *trans_data, void *data_end, 46 u8 protocol) 47{ 48 struct tcphdr *th; 49 struct udphdr *uh; 50 51 switch (protocol) { 52 case IPPROTO_TCP: 53 th = (struct tcphdr *)trans_data; 54 if (th + 1 > data_end) 55 return -1; 56 return th->dest; 57 case IPPROTO_UDP: 58 uh = (struct udphdr *)trans_data; 59 if (uh + 1 > data_end) 60 return -1; 61 return uh->dest; 62 default: 63 return 0; 64 } 65} 66 67static __always_inline void set_ethhdr(struct ethhdr *new_eth, 68 const struct ethhdr *old_eth, 69 const struct iptnl_info *tnl, 70 __be16 h_proto) 71{ 72 memcpy(new_eth->h_source, old_eth->h_dest, sizeof(new_eth->h_source)); 73 memcpy(new_eth->h_dest, tnl->dmac, sizeof(new_eth->h_dest)); 74 new_eth->h_proto = h_proto; 75} 76 77static __always_inline int handle_ipv4(struct xdp_md *xdp) 78{ 79 void *data_end = (void *)(long)xdp->data_end; 80 void *data = (void *)(long)xdp->data; 81 struct iptnl_info *tnl; 82 struct ethhdr *new_eth; 83 struct ethhdr *old_eth; 84 struct iphdr *iph = data + sizeof(struct ethhdr); 85 u16 *next_iph_u16; 86 u16 payload_len; 87 struct vip vip = {}; 88 int dport; 89 u32 csum = 0; 90 int i; 91 92 if (iph + 1 > data_end) 93 return XDP_DROP; 94 95 dport = get_dport(iph + 1, data_end, iph->protocol); 96 if (dport == -1) 97 return XDP_DROP; 98 99 vip.protocol = iph->protocol; 100 vip.family = AF_INET; 101 vip.daddr.v4 = iph->daddr; 102 vip.dport = dport; 103 payload_len = ntohs(iph->tot_len); 104 105 tnl = bpf_map_lookup_elem(&vip2tnl, &vip); 106 /* It only does v4-in-v4 */ 107 if (!tnl || tnl->family != AF_INET) 108 return XDP_PASS; 109 110 /* The vip key is found. Add an IP header and send it out */ 111 112 if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(struct iphdr))) 113 return XDP_DROP; 114 115 data = (void *)(long)xdp->data; 116 data_end = (void *)(long)xdp->data_end; 117 118 new_eth = data; 119 iph = data + sizeof(*new_eth); 120 old_eth = data + sizeof(*iph); 121 122 if (new_eth + 1 > data_end || 123 old_eth + 1 > data_end || 124 iph + 1 > data_end) 125 return XDP_DROP; 126 127 set_ethhdr(new_eth, old_eth, tnl, htons(ETH_P_IP)); 128 129 iph->version = 4; 130 iph->ihl = sizeof(*iph) >> 2; 131 iph->frag_off = 0; 132 iph->protocol = IPPROTO_IPIP; 133 iph->check = 0; 134 iph->tos = 0; 135 iph->tot_len = htons(payload_len + sizeof(*iph)); 136 iph->daddr = tnl->daddr.v4; 137 iph->saddr = tnl->saddr.v4; 138 iph->ttl = 8; 139 140 next_iph_u16 = (u16 *)iph; 141#pragma clang loop unroll(full) 142 for (i = 0; i < sizeof(*iph) >> 1; i++) 143 csum += *next_iph_u16++; 144 145 iph->check = ~((csum & 0xffff) + (csum >> 16)); 146 147 count_tx(vip.protocol); 148 149 return XDP_TX; 150} 151 152static __always_inline int handle_ipv6(struct xdp_md *xdp) 153{ 154 void *data_end = (void *)(long)xdp->data_end; 155 void *data = (void *)(long)xdp->data; 156 struct iptnl_info *tnl; 157 struct ethhdr *new_eth; 158 struct ethhdr *old_eth; 159 struct ipv6hdr *ip6h = data + sizeof(struct ethhdr); 160 __u16 payload_len; 161 struct vip vip = {}; 162 int dport; 163 164 if (ip6h + 1 > data_end) 165 return XDP_DROP; 166 167 dport = get_dport(ip6h + 1, data_end, ip6h->nexthdr); 168 if (dport == -1) 169 return XDP_DROP; 170 171 vip.protocol = ip6h->nexthdr; 172 vip.family = AF_INET6; 173 memcpy(vip.daddr.v6, ip6h->daddr.s6_addr32, sizeof(vip.daddr)); 174 vip.dport = dport; 175 payload_len = ip6h->payload_len; 176 177 tnl = bpf_map_lookup_elem(&vip2tnl, &vip); 178 /* It only does v6-in-v6 */ 179 if (!tnl || tnl->family != AF_INET6) 180 return XDP_PASS; 181 182 /* The vip key is found. Add an IP header and send it out */ 183 184 if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(struct ipv6hdr))) 185 return XDP_DROP; 186 187 data = (void *)(long)xdp->data; 188 data_end = (void *)(long)xdp->data_end; 189 190 new_eth = data; 191 ip6h = data + sizeof(*new_eth); 192 old_eth = data + sizeof(*ip6h); 193 194 if (new_eth + 1 > data_end || 195 old_eth + 1 > data_end || 196 ip6h + 1 > data_end) 197 return XDP_DROP; 198 199 set_ethhdr(new_eth, old_eth, tnl, htons(ETH_P_IPV6)); 200 201 ip6h->version = 6; 202 ip6h->priority = 0; 203 memset(ip6h->flow_lbl, 0, sizeof(ip6h->flow_lbl)); 204 ip6h->payload_len = htons(ntohs(payload_len) + sizeof(*ip6h)); 205 ip6h->nexthdr = IPPROTO_IPV6; 206 ip6h->hop_limit = 8; 207 memcpy(ip6h->saddr.s6_addr32, tnl->saddr.v6, sizeof(tnl->saddr.v6)); 208 memcpy(ip6h->daddr.s6_addr32, tnl->daddr.v6, sizeof(tnl->daddr.v6)); 209 210 count_tx(vip.protocol); 211 212 return XDP_TX; 213} 214 215SEC("xdp_tx_iptunnel") 216int _xdp_tx_iptunnel(struct xdp_md *xdp) 217{ 218 void *data_end = (void *)(long)xdp->data_end; 219 void *data = (void *)(long)xdp->data; 220 struct ethhdr *eth = data; 221 __u16 h_proto; 222 223 if (eth + 1 > data_end) 224 return XDP_DROP; 225 226 h_proto = eth->h_proto; 227 228 if (h_proto == htons(ETH_P_IP)) 229 return handle_ipv4(xdp); 230 else if (h_proto == htons(ETH_P_IPV6)) 231 232 return handle_ipv6(xdp); 233 else 234 return XDP_PASS; 235} 236 237char _license[] SEC("license") = "GPL";