Skip to content

Instantly share code, notes, and snippets.

@geyslan
Last active May 25, 2021 11:25
Show Gist options
  • Save geyslan/f393a4e873e0d60695b5bc19aba5fb51 to your computer and use it in GitHub Desktop.
Save geyslan/f393a4e873e0d60695b5bc19aba5fb51 to your computer and use it in GitHub Desktop.
ebpf_tc_port_drop
// 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