162306a36Sopenharmony_ci/* Copyright (c) 2016,2017 Facebook
262306a36Sopenharmony_ci *
362306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or
462306a36Sopenharmony_ci * modify it under the terms of version 2 of the GNU General Public
562306a36Sopenharmony_ci * License as published by the Free Software Foundation.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include <stddef.h>
862306a36Sopenharmony_ci#include <string.h>
962306a36Sopenharmony_ci#include <linux/bpf.h>
1062306a36Sopenharmony_ci#include <linux/if_ether.h>
1162306a36Sopenharmony_ci#include <linux/if_packet.h>
1262306a36Sopenharmony_ci#include <linux/ip.h>
1362306a36Sopenharmony_ci#include <linux/ipv6.h>
1462306a36Sopenharmony_ci#include <linux/in.h>
1562306a36Sopenharmony_ci#include <linux/udp.h>
1662306a36Sopenharmony_ci#include <linux/tcp.h>
1762306a36Sopenharmony_ci#include <linux/pkt_cls.h>
1862306a36Sopenharmony_ci#include <sys/socket.h>
1962306a36Sopenharmony_ci#include <bpf/bpf_helpers.h>
2062306a36Sopenharmony_ci#include <bpf/bpf_endian.h>
2162306a36Sopenharmony_ci#include "test_iptunnel_common.h"
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistruct {
2462306a36Sopenharmony_ci	__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
2562306a36Sopenharmony_ci	__uint(max_entries, 256);
2662306a36Sopenharmony_ci	__type(key, __u32);
2762306a36Sopenharmony_ci	__type(value, __u64);
2862306a36Sopenharmony_ci} rxcnt SEC(".maps");
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct {
3162306a36Sopenharmony_ci	__uint(type, BPF_MAP_TYPE_HASH);
3262306a36Sopenharmony_ci	__uint(max_entries, MAX_IPTNL_ENTRIES);
3362306a36Sopenharmony_ci	__type(key, struct vip);
3462306a36Sopenharmony_ci	__type(value, struct iptnl_info);
3562306a36Sopenharmony_ci} vip2tnl SEC(".maps");
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic __always_inline void count_tx(__u32 protocol)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	__u64 *rxcnt_count;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	rxcnt_count = bpf_map_lookup_elem(&rxcnt, &protocol);
4262306a36Sopenharmony_ci	if (rxcnt_count)
4362306a36Sopenharmony_ci		*rxcnt_count += 1;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic __always_inline int get_dport(void *trans_data, void *data_end,
4762306a36Sopenharmony_ci				     __u8 protocol)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	struct tcphdr *th;
5062306a36Sopenharmony_ci	struct udphdr *uh;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	switch (protocol) {
5362306a36Sopenharmony_ci	case IPPROTO_TCP:
5462306a36Sopenharmony_ci		th = (struct tcphdr *)trans_data;
5562306a36Sopenharmony_ci		if (th + 1 > data_end)
5662306a36Sopenharmony_ci			return -1;
5762306a36Sopenharmony_ci		return th->dest;
5862306a36Sopenharmony_ci	case IPPROTO_UDP:
5962306a36Sopenharmony_ci		uh = (struct udphdr *)trans_data;
6062306a36Sopenharmony_ci		if (uh + 1 > data_end)
6162306a36Sopenharmony_ci			return -1;
6262306a36Sopenharmony_ci		return uh->dest;
6362306a36Sopenharmony_ci	default:
6462306a36Sopenharmony_ci		return 0;
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic __always_inline void set_ethhdr(struct ethhdr *new_eth,
6962306a36Sopenharmony_ci				       const struct ethhdr *old_eth,
7062306a36Sopenharmony_ci				       const struct iptnl_info *tnl,
7162306a36Sopenharmony_ci				       __be16 h_proto)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	memcpy(new_eth->h_source, old_eth->h_dest, sizeof(new_eth->h_source));
7462306a36Sopenharmony_ci	memcpy(new_eth->h_dest, tnl->dmac, sizeof(new_eth->h_dest));
7562306a36Sopenharmony_ci	new_eth->h_proto = h_proto;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic __always_inline int handle_ipv4(struct xdp_md *xdp)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	void *data_end = (void *)(long)xdp->data_end;
8162306a36Sopenharmony_ci	void *data = (void *)(long)xdp->data;
8262306a36Sopenharmony_ci	struct iptnl_info *tnl;
8362306a36Sopenharmony_ci	struct ethhdr *new_eth;
8462306a36Sopenharmony_ci	struct ethhdr *old_eth;
8562306a36Sopenharmony_ci	struct iphdr *iph = data + sizeof(struct ethhdr);
8662306a36Sopenharmony_ci	__u16 *next_iph;
8762306a36Sopenharmony_ci	__u16 payload_len;
8862306a36Sopenharmony_ci	struct vip vip = {};
8962306a36Sopenharmony_ci	int dport;
9062306a36Sopenharmony_ci	__u32 csum = 0;
9162306a36Sopenharmony_ci	int i;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	if (iph + 1 > data_end)
9462306a36Sopenharmony_ci		return XDP_DROP;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	dport = get_dport(iph + 1, data_end, iph->protocol);
9762306a36Sopenharmony_ci	if (dport == -1)
9862306a36Sopenharmony_ci		return XDP_DROP;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	vip.protocol = iph->protocol;
10162306a36Sopenharmony_ci	vip.family = AF_INET;
10262306a36Sopenharmony_ci	vip.daddr.v4 = iph->daddr;
10362306a36Sopenharmony_ci	vip.dport = dport;
10462306a36Sopenharmony_ci	payload_len = bpf_ntohs(iph->tot_len);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	tnl = bpf_map_lookup_elem(&vip2tnl, &vip);
10762306a36Sopenharmony_ci	/* It only does v4-in-v4 */
10862306a36Sopenharmony_ci	if (!tnl || tnl->family != AF_INET)
10962306a36Sopenharmony_ci		return XDP_PASS;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(struct iphdr)))
11262306a36Sopenharmony_ci		return XDP_DROP;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	data = (void *)(long)xdp->data;
11562306a36Sopenharmony_ci	data_end = (void *)(long)xdp->data_end;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	new_eth = data;
11862306a36Sopenharmony_ci	iph = data + sizeof(*new_eth);
11962306a36Sopenharmony_ci	old_eth = data + sizeof(*iph);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	if (new_eth + 1 > data_end ||
12262306a36Sopenharmony_ci	    old_eth + 1 > data_end ||
12362306a36Sopenharmony_ci	    iph + 1 > data_end)
12462306a36Sopenharmony_ci		return XDP_DROP;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	set_ethhdr(new_eth, old_eth, tnl, bpf_htons(ETH_P_IP));
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	iph->version = 4;
12962306a36Sopenharmony_ci	iph->ihl = sizeof(*iph) >> 2;
13062306a36Sopenharmony_ci	iph->frag_off =	0;
13162306a36Sopenharmony_ci	iph->protocol = IPPROTO_IPIP;
13262306a36Sopenharmony_ci	iph->check = 0;
13362306a36Sopenharmony_ci	iph->tos = 0;
13462306a36Sopenharmony_ci	iph->tot_len = bpf_htons(payload_len + sizeof(*iph));
13562306a36Sopenharmony_ci	iph->daddr = tnl->daddr.v4;
13662306a36Sopenharmony_ci	iph->saddr = tnl->saddr.v4;
13762306a36Sopenharmony_ci	iph->ttl = 8;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	next_iph = (__u16 *)iph;
14062306a36Sopenharmony_ci#pragma clang loop unroll(full)
14162306a36Sopenharmony_ci	for (i = 0; i < sizeof(*iph) >> 1; i++)
14262306a36Sopenharmony_ci		csum += *next_iph++;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	iph->check = ~((csum & 0xffff) + (csum >> 16));
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	count_tx(vip.protocol);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	return XDP_TX;
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic __always_inline int handle_ipv6(struct xdp_md *xdp)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	void *data_end = (void *)(long)xdp->data_end;
15462306a36Sopenharmony_ci	void *data = (void *)(long)xdp->data;
15562306a36Sopenharmony_ci	struct iptnl_info *tnl;
15662306a36Sopenharmony_ci	struct ethhdr *new_eth;
15762306a36Sopenharmony_ci	struct ethhdr *old_eth;
15862306a36Sopenharmony_ci	struct ipv6hdr *ip6h = data + sizeof(struct ethhdr);
15962306a36Sopenharmony_ci	__u16 payload_len;
16062306a36Sopenharmony_ci	struct vip vip = {};
16162306a36Sopenharmony_ci	int dport;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	if (ip6h + 1 > data_end)
16462306a36Sopenharmony_ci		return XDP_DROP;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	dport = get_dport(ip6h + 1, data_end, ip6h->nexthdr);
16762306a36Sopenharmony_ci	if (dport == -1)
16862306a36Sopenharmony_ci		return XDP_DROP;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	vip.protocol = ip6h->nexthdr;
17162306a36Sopenharmony_ci	vip.family = AF_INET6;
17262306a36Sopenharmony_ci	memcpy(vip.daddr.v6, ip6h->daddr.s6_addr32, sizeof(vip.daddr));
17362306a36Sopenharmony_ci	vip.dport = dport;
17462306a36Sopenharmony_ci	payload_len = ip6h->payload_len;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	tnl = bpf_map_lookup_elem(&vip2tnl, &vip);
17762306a36Sopenharmony_ci	/* It only does v6-in-v6 */
17862306a36Sopenharmony_ci	if (!tnl || tnl->family != AF_INET6)
17962306a36Sopenharmony_ci		return XDP_PASS;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(struct ipv6hdr)))
18262306a36Sopenharmony_ci		return XDP_DROP;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	data = (void *)(long)xdp->data;
18562306a36Sopenharmony_ci	data_end = (void *)(long)xdp->data_end;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	new_eth = data;
18862306a36Sopenharmony_ci	ip6h = data + sizeof(*new_eth);
18962306a36Sopenharmony_ci	old_eth = data + sizeof(*ip6h);
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (new_eth + 1 > data_end || old_eth + 1 > data_end ||
19262306a36Sopenharmony_ci	    ip6h + 1 > data_end)
19362306a36Sopenharmony_ci		return XDP_DROP;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	set_ethhdr(new_eth, old_eth, tnl, bpf_htons(ETH_P_IPV6));
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	ip6h->version = 6;
19862306a36Sopenharmony_ci	ip6h->priority = 0;
19962306a36Sopenharmony_ci	memset(ip6h->flow_lbl, 0, sizeof(ip6h->flow_lbl));
20062306a36Sopenharmony_ci	ip6h->payload_len = bpf_htons(bpf_ntohs(payload_len) + sizeof(*ip6h));
20162306a36Sopenharmony_ci	ip6h->nexthdr = IPPROTO_IPV6;
20262306a36Sopenharmony_ci	ip6h->hop_limit = 8;
20362306a36Sopenharmony_ci	memcpy(ip6h->saddr.s6_addr32, tnl->saddr.v6, sizeof(tnl->saddr.v6));
20462306a36Sopenharmony_ci	memcpy(ip6h->daddr.s6_addr32, tnl->daddr.v6, sizeof(tnl->daddr.v6));
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	count_tx(vip.protocol);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	return XDP_TX;
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ciSEC("xdp")
21262306a36Sopenharmony_ciint _xdp_tx_iptunnel(struct xdp_md *xdp)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	void *data_end = (void *)(long)xdp->data_end;
21562306a36Sopenharmony_ci	void *data = (void *)(long)xdp->data;
21662306a36Sopenharmony_ci	struct ethhdr *eth = data;
21762306a36Sopenharmony_ci	__u16 h_proto;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	if (eth + 1 > data_end)
22062306a36Sopenharmony_ci		return XDP_DROP;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	h_proto = eth->h_proto;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	if (h_proto == bpf_htons(ETH_P_IP))
22562306a36Sopenharmony_ci		return handle_ipv4(xdp);
22662306a36Sopenharmony_ci	else if (h_proto == bpf_htons(ETH_P_IPV6))
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci		return handle_ipv6(xdp);
22962306a36Sopenharmony_ci	else
23062306a36Sopenharmony_ci		return XDP_DROP;
23162306a36Sopenharmony_ci}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_cichar _license[] SEC("license") = "GPL";
234