| /* Copyright (c) 2016 Thomas Graf <tgraf@tgraf.ch> | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or | 
 |  * modify it under the terms of version 2 of the GNU General Public | 
 |  * License as published by the Free Software Foundation. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, but | 
 |  * WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 
 |  * General Public License for more details. | 
 |  */ | 
 |  | 
 | #include <stdint.h> | 
 | #include <stddef.h> | 
 | #include <linux/bpf.h> | 
 | #include <linux/ip.h> | 
 | #include <linux/in.h> | 
 | #include <linux/in6.h> | 
 | #include <linux/tcp.h> | 
 | #include <linux/udp.h> | 
 | #include <linux/icmpv6.h> | 
 | #include <linux/if_ether.h> | 
 | #include "bpf_helpers.h" | 
 | #include <string.h> | 
 |  | 
 | # define printk(fmt, ...)						\ | 
 | 		({							\ | 
 | 			char ____fmt[] = fmt;				\ | 
 | 			bpf_trace_printk(____fmt, sizeof(____fmt),	\ | 
 | 				     ##__VA_ARGS__);			\ | 
 | 		}) | 
 |  | 
 | #define CB_MAGIC 1234 | 
 |  | 
 | /* Test: Pass all packets through */ | 
 | SEC("nop") | 
 | int do_nop(struct __sk_buff *skb) | 
 | { | 
 | 	return BPF_OK; | 
 | } | 
 |  | 
 | /* Test: Verify context information can be accessed */ | 
 | SEC("test_ctx") | 
 | int do_test_ctx(struct __sk_buff *skb) | 
 | { | 
 | 	skb->cb[0] = CB_MAGIC; | 
 | 	printk("len %d hash %d protocol %d\n", skb->len, skb->hash, | 
 | 	       skb->protocol); | 
 | 	printk("cb %d ingress_ifindex %d ifindex %d\n", skb->cb[0], | 
 | 	       skb->ingress_ifindex, skb->ifindex); | 
 |  | 
 | 	return BPF_OK; | 
 | } | 
 |  | 
 | /* Test: Ensure skb->cb[] buffer is cleared */ | 
 | SEC("test_cb") | 
 | int do_test_cb(struct __sk_buff *skb) | 
 | { | 
 | 	printk("cb0: %x cb1: %x cb2: %x\n", skb->cb[0], skb->cb[1], | 
 | 	       skb->cb[2]); | 
 | 	printk("cb3: %x cb4: %x\n", skb->cb[3], skb->cb[4]); | 
 |  | 
 | 	return BPF_OK; | 
 | } | 
 |  | 
 | /* Test: Verify skb data can be read */ | 
 | SEC("test_data") | 
 | int do_test_data(struct __sk_buff *skb) | 
 | { | 
 | 	void *data = (void *)(long)skb->data; | 
 | 	void *data_end = (void *)(long)skb->data_end; | 
 | 	struct iphdr *iph = data; | 
 |  | 
 | 	if (data + sizeof(*iph) > data_end) { | 
 | 		printk("packet truncated\n"); | 
 | 		return BPF_DROP; | 
 | 	} | 
 |  | 
 | 	printk("src: %x dst: %x\n", iph->saddr, iph->daddr); | 
 |  | 
 | 	return BPF_OK; | 
 | } | 
 |  | 
 | #define IP_CSUM_OFF offsetof(struct iphdr, check) | 
 | #define IP_DST_OFF offsetof(struct iphdr, daddr) | 
 | #define IP_SRC_OFF offsetof(struct iphdr, saddr) | 
 | #define IP_PROTO_OFF offsetof(struct iphdr, protocol) | 
 | #define TCP_CSUM_OFF offsetof(struct tcphdr, check) | 
 | #define UDP_CSUM_OFF offsetof(struct udphdr, check) | 
 | #define IS_PSEUDO 0x10 | 
 |  | 
 | static inline int rewrite(struct __sk_buff *skb, uint32_t old_ip, | 
 | 			  uint32_t new_ip, int rw_daddr) | 
 | { | 
 | 	int ret, off = 0, flags = IS_PSEUDO; | 
 | 	uint8_t proto; | 
 |  | 
 | 	ret = bpf_skb_load_bytes(skb, IP_PROTO_OFF, &proto, 1); | 
 | 	if (ret < 0) { | 
 | 		printk("bpf_l4_csum_replace failed: %d\n", ret); | 
 | 		return BPF_DROP; | 
 | 	} | 
 |  | 
 | 	switch (proto) { | 
 | 	case IPPROTO_TCP: | 
 | 		off = TCP_CSUM_OFF; | 
 | 		break; | 
 |  | 
 | 	case IPPROTO_UDP: | 
 | 		off = UDP_CSUM_OFF; | 
 | 		flags |= BPF_F_MARK_MANGLED_0; | 
 | 		break; | 
 |  | 
 | 	case IPPROTO_ICMPV6: | 
 | 		off = offsetof(struct icmp6hdr, icmp6_cksum); | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	if (off) { | 
 | 		ret = bpf_l4_csum_replace(skb, off, old_ip, new_ip, | 
 | 					  flags | sizeof(new_ip)); | 
 | 		if (ret < 0) { | 
 | 			printk("bpf_l4_csum_replace failed: %d\n"); | 
 | 			return BPF_DROP; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	ret = bpf_l3_csum_replace(skb, IP_CSUM_OFF, old_ip, new_ip, sizeof(new_ip)); | 
 | 	if (ret < 0) { | 
 | 		printk("bpf_l3_csum_replace failed: %d\n", ret); | 
 | 		return BPF_DROP; | 
 | 	} | 
 |  | 
 | 	if (rw_daddr) | 
 | 		ret = bpf_skb_store_bytes(skb, IP_DST_OFF, &new_ip, sizeof(new_ip), 0); | 
 | 	else | 
 | 		ret = bpf_skb_store_bytes(skb, IP_SRC_OFF, &new_ip, sizeof(new_ip), 0); | 
 |  | 
 | 	if (ret < 0) { | 
 | 		printk("bpf_skb_store_bytes() failed: %d\n", ret); | 
 | 		return BPF_DROP; | 
 | 	} | 
 |  | 
 | 	return BPF_OK; | 
 | } | 
 |  | 
 | /* Test: Verify skb data can be modified */ | 
 | SEC("test_rewrite") | 
 | int do_test_rewrite(struct __sk_buff *skb) | 
 | { | 
 | 	uint32_t old_ip, new_ip = 0x3fea8c0; | 
 | 	int ret; | 
 |  | 
 | 	ret = bpf_skb_load_bytes(skb, IP_DST_OFF, &old_ip, 4); | 
 | 	if (ret < 0) { | 
 | 		printk("bpf_skb_load_bytes failed: %d\n", ret); | 
 | 		return BPF_DROP; | 
 | 	} | 
 |  | 
 | 	if (old_ip == 0x2fea8c0) { | 
 | 		printk("out: rewriting from %x to %x\n", old_ip, new_ip); | 
 | 		return rewrite(skb, old_ip, new_ip, 1); | 
 | 	} | 
 |  | 
 | 	return BPF_OK; | 
 | } | 
 |  | 
 | static inline int __do_push_ll_and_redirect(struct __sk_buff *skb) | 
 | { | 
 | 	uint64_t smac = SRC_MAC, dmac = DST_MAC; | 
 | 	int ret, ifindex = DST_IFINDEX; | 
 | 	struct ethhdr ehdr; | 
 |  | 
 | 	ret = bpf_skb_change_head(skb, 14, 0); | 
 | 	if (ret < 0) { | 
 | 		printk("skb_change_head() failed: %d\n", ret); | 
 | 	} | 
 |  | 
 | 	ehdr.h_proto = __constant_htons(ETH_P_IP); | 
 | 	memcpy(&ehdr.h_source, &smac, 6); | 
 | 	memcpy(&ehdr.h_dest, &dmac, 6); | 
 |  | 
 | 	ret = bpf_skb_store_bytes(skb, 0, &ehdr, sizeof(ehdr), 0); | 
 | 	if (ret < 0) { | 
 | 		printk("skb_store_bytes() failed: %d\n", ret); | 
 | 		return BPF_DROP; | 
 | 	} | 
 |  | 
 | 	return bpf_redirect(ifindex, 0); | 
 | } | 
 |  | 
 | SEC("push_ll_and_redirect_silent") | 
 | int do_push_ll_and_redirect_silent(struct __sk_buff *skb) | 
 | { | 
 | 	return __do_push_ll_and_redirect(skb); | 
 | } | 
 |  | 
 | SEC("push_ll_and_redirect") | 
 | int do_push_ll_and_redirect(struct __sk_buff *skb) | 
 | { | 
 | 	int ret, ifindex = DST_IFINDEX; | 
 |  | 
 | 	ret = __do_push_ll_and_redirect(skb); | 
 | 	if (ret >= 0) | 
 | 		printk("redirected to %d\n", ifindex); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static inline void __fill_garbage(struct __sk_buff *skb) | 
 | { | 
 | 	uint64_t f = 0xFFFFFFFFFFFFFFFF; | 
 |  | 
 | 	bpf_skb_store_bytes(skb, 0, &f, sizeof(f), 0); | 
 | 	bpf_skb_store_bytes(skb, 8, &f, sizeof(f), 0); | 
 | 	bpf_skb_store_bytes(skb, 16, &f, sizeof(f), 0); | 
 | 	bpf_skb_store_bytes(skb, 24, &f, sizeof(f), 0); | 
 | 	bpf_skb_store_bytes(skb, 32, &f, sizeof(f), 0); | 
 | 	bpf_skb_store_bytes(skb, 40, &f, sizeof(f), 0); | 
 | 	bpf_skb_store_bytes(skb, 48, &f, sizeof(f), 0); | 
 | 	bpf_skb_store_bytes(skb, 56, &f, sizeof(f), 0); | 
 | 	bpf_skb_store_bytes(skb, 64, &f, sizeof(f), 0); | 
 | 	bpf_skb_store_bytes(skb, 72, &f, sizeof(f), 0); | 
 | 	bpf_skb_store_bytes(skb, 80, &f, sizeof(f), 0); | 
 | 	bpf_skb_store_bytes(skb, 88, &f, sizeof(f), 0); | 
 | } | 
 |  | 
 | SEC("fill_garbage") | 
 | int do_fill_garbage(struct __sk_buff *skb) | 
 | { | 
 | 	__fill_garbage(skb); | 
 | 	printk("Set initial 96 bytes of header to FF\n"); | 
 | 	return BPF_OK; | 
 | } | 
 |  | 
 | SEC("fill_garbage_and_redirect") | 
 | int do_fill_garbage_and_redirect(struct __sk_buff *skb) | 
 | { | 
 | 	int ifindex = DST_IFINDEX; | 
 | 	__fill_garbage(skb); | 
 | 	printk("redirected to %d\n", ifindex); | 
 | 	return bpf_redirect(ifindex, 0); | 
 | } | 
 |  | 
 | /* Drop all packets */ | 
 | SEC("drop_all") | 
 | int do_drop_all(struct __sk_buff *skb) | 
 | { | 
 | 	printk("dropping with: %d\n", BPF_DROP); | 
 | 	return BPF_DROP; | 
 | } | 
 |  | 
 | char _license[] SEC("license") = "GPL"; |