Last active
May 25, 2021 11:25
-
-
Save geyslan/f393a4e873e0d60695b5bc19aba5fb51 to your computer and use it in GitHub Desktop.
ebpf_tc_port_drop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// BPF_PROG_TYPE_SCHED_CLS | |
#define KBUILD_MODNAME "tc" | |
#include <asm/types.h> | |
#include <asm/byteorder.h> | |
#include <linux/bpf.h> | |
#include <bpf/bpf_helpers.h> | |
#include <linux/pkt_cls.h> | |
#include <linux/filter.h> | |
#include <linux/in.h> | |
#include <linux/if_ether.h> | |
#include <linux/ip.h> | |
#include <linux/tcp.h> | |
#include <linux/sched.h> | |
/* Misc helper macros. */ | |
#ifndef __section | |
#define __section(x) __attribute__((section((x)), used)) | |
#endif | |
#ifndef offsetof | |
#define offsetof(x, y) __builtin_offsetof(x, y) | |
#endif | |
#define likely(x) __builtin_expect(!!(x), 1) | |
#define unlikely(x) __builtin_expect(!!(x), 0) | |
#define TASK_COMM_LEN 16 | |
#define BPF_MAP_ID_STATS 1 | |
#define PIN_GLOBAL_NS 2 | |
/* Some used BPF intrinsics. */ | |
unsigned long long load_byte(void *skb, unsigned long long off) | |
asm ("llvm.bpf.load.byte"); | |
unsigned long long load_half(void *skb, unsigned long long off) | |
asm ("llvm.bpf.load.half"); | |
/* ELF map definition */ | |
struct bpf_elf_map { | |
__u32 type; | |
__u32 size_key; | |
__u32 size_value; | |
__u32 max_elem; | |
__u32 flags; | |
__u32 id; | |
__u32 pinning; | |
__u32 inner_id; | |
__u32 inner_idx; | |
}; | |
struct bpf_elf_map __section("maps") map_port_blk = { | |
.type = BPF_MAP_TYPE_ARRAY, | |
.size_key = sizeof(__u32), | |
.size_value = sizeof(__u16), | |
.max_elem = 1, | |
.id = BPF_MAP_ID_STATS, | |
//.pinning = PIN_GLOBAL_NS, | |
}; | |
static inline void setup_map(__u16 *port) | |
{ | |
__u16 *map_port; | |
int map_key = 0; | |
if (unlikely(!port)) | |
return; | |
map_port = bpf_map_lookup_elem(&map_port_blk, &map_key); | |
if (unlikely(!map_port)) | |
return; | |
if (likely(*map_port == *port)) | |
return; | |
if (*map_port == 0) | |
bpf_map_update_elem(&map_port_blk, &map_key, port, BPF_ANY); | |
else | |
*port = *map_port; | |
} | |
static inline int is_myprogram(void) | |
{ | |
const char name[] = "myprogram"; | |
char task_name[TASK_COMM_LEN] = { 0 }; | |
const struct task_struct *t; | |
const char read_err[] = "read_error: t->comm - %d\n"; | |
const char read_suc[] = "read_success: t->comm - %d\n"; | |
int ret; | |
t = (struct task_struct *) bpf_get_current_task(); | |
ret = bpf_probe_read_kernel(task_name, TASK_COMM_LEN, t->comm); | |
if (ret < 0) { | |
bpf_trace_printk(read_err, sizeof(read_err), ret); | |
return 0; | |
} | |
/* checking success but truncated reads | |
* maybe because we can't lock the task | |
* https://elixir.bootlin.com/linux/latest/source/include/linux/sched.h#L957 | |
* https://elixir.bootlin.com/linux/latest/source/fs/exec.c#L1210 | |
*/ | |
bpf_trace_printk(read_suc, sizeof(read_suc), ret); | |
bpf_trace_printk(name, sizeof(name)); | |
for (int i = 0; i < sizeof(name); ++i) { | |
if (task_name[i] != name[i]) | |
return 0; | |
} | |
return 1; | |
} | |
static inline int block_tcp_port(struct __sk_buff *skb, __u16 blk_port) | |
{ | |
__u8 ip_proto, ip_vl; | |
__u16 dport, sport; | |
int nh_off = BPF_LL_OFF + ETH_HLEN; | |
if (unlikely(skb->protocol != __constant_htons(ETH_P_IP))) | |
return TC_ACT_OK; | |
ip_proto = load_byte(skb, nh_off + offsetof(struct iphdr, protocol)); | |
if (unlikely(ip_proto != IPPROTO_TCP)) | |
return TC_ACT_OK; | |
ip_vl = load_byte(skb, nh_off); | |
if (likely(ip_vl == 0x45)) | |
nh_off += sizeof(struct iphdr); | |
else | |
nh_off += (ip_vl & 0xF) << 2; | |
dport = load_half(skb, nh_off + offsetof(struct tcphdr, dest)); | |
sport = load_half(skb, nh_off + offsetof(struct tcphdr, source)); | |
if (unlikely(dport == blk_port || sport == blk_port)) { | |
char log[] = "blocked port: %d (source: %d dest: %d)\n"; | |
bpf_trace_printk(log, sizeof(log), blk_port, sport, dport); | |
return TC_ACT_SHOT; | |
} | |
return TC_ACT_OK; | |
} | |
__section("classifier") | |
int cls_main(struct __sk_buff *skb) | |
{ | |
__u16 port = 4040; | |
/* | |
* bpf_get_current_comm() helper not available for TC. | |
* is_my_program() tries to get task_struct->comm but fails. | |
* More information inside the implementation. | |
*/ | |
/* if (likely(!is_myprogram())) | |
return TC_ACT_OK; | |
*/ | |
setup_map(&port); | |
return block_tcp_port(skb, port); | |
} | |
char __license[] __section("license") = "GPL"; | |
/* | |
Testing | |
- Put tc.c in linux/samples/bpf kernel source path | |
- Inside linux/samples/bpf run make and these commands: | |
sudo tc qdisc add dev enp12s0 clsact | |
sudo tc filter add dev enp12s0 ingress bpf direct-action obj tc.o sec classifier | |
tc filter show dev enp12s0 ingress | |
sudo bpftool map show | |
sudo bpftool map dump id 'ID' | |
# port 80 | |
sudo bpftool map update id 'ID' key hex 00 00 00 00 value hex 50 00 | |
# port 443 | |
sudo bpftool map update id 'ID' key hex 00 00 00 00 value hex bb 01 | |
# zeroing key sets default port 4040 | |
sudo bpftool map update id 'ID' key hex 00 00 00 00 value hex 00 00 | |
sudo bpftool map dump id 'ID' | |
sudo tc qdisc del dev enp12s0 clsact | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment