1 /* Copyright (c) 2016 Facebook
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.
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.
11 #define KBUILD_MODNAME "foo"
12 #include <uapi/linux/bpf.h>
14 #include <linux/if_ether.h>
15 #include <linux/if_packet.h>
16 #include <linux/if_vlan.h>
18 #include <linux/ipv6.h>
19 #include "bpf_helpers.h"
20 #include "xdp_tx_iptunnel_common.h"
22 struct bpf_map_def
SEC("maps") rxcnt
= {
23 .type
= BPF_MAP_TYPE_PERCPU_ARRAY
,
24 .key_size
= sizeof(__u32
),
25 .value_size
= sizeof(__u64
),
29 struct bpf_map_def
SEC("maps") vip2tnl
= {
30 .type
= BPF_MAP_TYPE_HASH
,
31 .key_size
= sizeof(struct vip
),
32 .value_size
= sizeof(struct iptnl_info
),
33 .max_entries
= MAX_IPTNL_ENTRIES
,
36 static __always_inline
void count_tx(u32 protocol
)
40 rxcnt_count
= bpf_map_lookup_elem(&rxcnt
, &protocol
);
45 static __always_inline
int get_dport(void *trans_data
, void *data_end
,
53 th
= (struct tcphdr
*)trans_data
;
54 if (th
+ 1 > data_end
)
58 uh
= (struct udphdr
*)trans_data
;
59 if (uh
+ 1 > data_end
)
67 static __always_inline
void set_ethhdr(struct ethhdr
*new_eth
,
68 const struct ethhdr
*old_eth
,
69 const struct iptnl_info
*tnl
,
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
;
77 static __always_inline
int handle_ipv4(struct xdp_md
*xdp
)
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
);
92 if (iph
+ 1 > data_end
)
95 dport
= get_dport(iph
+ 1, data_end
, iph
->protocol
);
99 vip
.protocol
= iph
->protocol
;
100 vip
.family
= AF_INET
;
101 vip
.daddr
.v4
= iph
->daddr
;
103 payload_len
= ntohs(iph
->tot_len
);
105 tnl
= bpf_map_lookup_elem(&vip2tnl
, &vip
);
106 /* It only does v4-in-v4 */
107 if (!tnl
|| tnl
->family
!= AF_INET
)
110 /* The vip key is found. Add an IP header and send it out */
112 if (bpf_xdp_adjust_head(xdp
, 0 - (int)sizeof(struct iphdr
)))
115 data
= (void *)(long)xdp
->data
;
116 data_end
= (void *)(long)xdp
->data_end
;
119 iph
= data
+ sizeof(*new_eth
);
120 old_eth
= data
+ sizeof(*iph
);
122 if (new_eth
+ 1 > data_end
||
123 old_eth
+ 1 > data_end
||
127 set_ethhdr(new_eth
, old_eth
, tnl
, htons(ETH_P_IP
));
130 iph
->ihl
= sizeof(*iph
) >> 2;
132 iph
->protocol
= IPPROTO_IPIP
;
135 iph
->tot_len
= htons(payload_len
+ sizeof(*iph
));
136 iph
->daddr
= tnl
->daddr
.v4
;
137 iph
->saddr
= tnl
->saddr
.v4
;
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
++;
145 iph
->check
= ~((csum
& 0xffff) + (csum
>> 16));
147 count_tx(vip
.protocol
);
152 static __always_inline
int handle_ipv6(struct xdp_md
*xdp
)
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
);
164 if (ip6h
+ 1 > data_end
)
167 dport
= get_dport(ip6h
+ 1, data_end
, ip6h
->nexthdr
);
171 vip
.protocol
= ip6h
->nexthdr
;
172 vip
.family
= AF_INET6
;
173 memcpy(vip
.daddr
.v6
, ip6h
->daddr
.s6_addr32
, sizeof(vip
.daddr
));
175 payload_len
= ip6h
->payload_len
;
177 tnl
= bpf_map_lookup_elem(&vip2tnl
, &vip
);
178 /* It only does v6-in-v6 */
179 if (!tnl
|| tnl
->family
!= AF_INET6
)
182 /* The vip key is found. Add an IP header and send it out */
184 if (bpf_xdp_adjust_head(xdp
, 0 - (int)sizeof(struct ipv6hdr
)))
187 data
= (void *)(long)xdp
->data
;
188 data_end
= (void *)(long)xdp
->data_end
;
191 ip6h
= data
+ sizeof(*new_eth
);
192 old_eth
= data
+ sizeof(*ip6h
);
194 if (new_eth
+ 1 > data_end
||
195 old_eth
+ 1 > data_end
||
199 set_ethhdr(new_eth
, old_eth
, tnl
, htons(ETH_P_IPV6
));
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
;
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
));
210 count_tx(vip
.protocol
);
215 SEC("xdp_tx_iptunnel")
216 int _xdp_tx_iptunnel(struct xdp_md
*xdp
)
218 void *data_end
= (void *)(long)xdp
->data_end
;
219 void *data
= (void *)(long)xdp
->data
;
220 struct ethhdr
*eth
= data
;
223 if (eth
+ 1 > data_end
)
226 h_proto
= eth
->h_proto
;
228 if (h_proto
== htons(ETH_P_IP
))
229 return handle_ipv4(xdp
);
230 else if (h_proto
== htons(ETH_P_IPV6
))
232 return handle_ipv6(xdp
);
237 char _license
[] SEC("license") = "GPL";