From c5f8e7b221515ce043779456945d45a7098d9d9e Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 5 Apr 2021 22:40:24 -0400 Subject: [PATCH] Support ratelimiting communities --- genrules.py | 34 +++++++++++++++++++++++++++++++++- install.sh | 2 +- xdp.c | 14 ++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/genrules.py b/genrules.py index 51e547d..300900a 100755 --- a/genrules.py +++ b/genrules.py @@ -4,6 +4,7 @@ import sys import ipaddress from enum import Enum import argparse +import math IP_PROTO_ICMP = 1 @@ -288,6 +289,7 @@ with open("rules.h", "w") as out: rules4 = "" use_v6_frags = False rulecnt = 0 + ratelimitcnt = 0 lastrule = None for line in sys.stdin.readlines(): @@ -369,10 +371,38 @@ with open("rules.h", "w") as out: ty = blocks[1].strip()[:6] low_bytes = int(blocks[2].strip(") \n"), 16) if ty == "0x8006": + if first_action is not None: + assert False # Two ratelimit actions? if low_bytes == 0: first_action = "return XDP_DROP;" else: - assert False # Not yet supported + if low_bytes & (1 << 31) != 0: + assert False # Negative ratelimit? + exp = (low_bytes & (0xff << 23)) >> 23 + if exp == 0xff: + assert False # NaN/INF? + if exp <= 127: # < 1 + first_action = "return XDP_DROP;" + if exp >= 127 + 63: # The count won't even fit in 64-bits, just accept + first_action = "return XDP_PASS;" + mantissa = low_bytes & ((1 << 23) - 1) + value = 1.0 + mantissa / (2**23) + value *= 2**(exp-127) + first_action = "uint64_t secs = bpf_ktime_get_ns() / 1000000000;\n" + first_action += f"const uint32_t ratelimitidx = {ratelimitcnt};\n" + first_action += "struct ratelimit *rate = bpf_map_lookup_elem(&rate_map, &ratelimitidx);\n" + first_action += "if (rate) {\n" + first_action += "\tbpf_spin_lock(&rate->lock);\n" + first_action += "\tif (secs != rate->bucket_secs) {\n" + first_action += "\t\trate->bucket_secs = secs;\n" + first_action += "\t\trate->bucket_count = 0;\n" + first_action += "\t}\n" + first_action += f"\tif (rate->bucket_count + (data_end - pktdata) > {math.floor(value)})\n" + first_action += "\t\t{ bpf_spin_unlock(&rate->lock); return XDP_DROP; }\n" + first_action += "\trate->bucket_count += data_end - pktdata;\n" + first_action += "\tbpf_spin_unlock(&rate->lock);\n" + first_action += "}\n" + ratelimitcnt += 1 elif ty == "0x8007": if low_bytes & 1 == 0: last_action = "return XDP_PASS;" @@ -407,6 +437,8 @@ with open("rules.h", "w") as out: out.write("\n") out.write(f"#define RULECNT {rulecnt}\n") + if ratelimitcnt != 0: + out.write(f"#define RATE_CNT {ratelimitcnt}\n") if rules4 != "": out.write("#define NEED_V4_PARSE\n") out.write("#define RULES4 {\\\n" + rules4 + "}\n") diff --git a/install.sh b/install.sh index af9a3ed..58ae670 100755 --- a/install.sh +++ b/install.sh @@ -5,7 +5,7 @@ RULES="$(birdc show route table flowspec4 primary all) $(birdc show route table flowspec6 primary all)" echo "$RULES" | ./genrules.py --8021q=drop-vlan --v6frag=ignore-parse-if-rule --ihl=drop-options -clang -std=c99 -pedantic -Wall -Wextra -Wno-pointer-arith -Wno-unused-variable -O3 -emit-llvm -c xdp.c -o - | llc -O3 -march=bpf -filetype=obj -o xdp +clang -g -std=c99 -pedantic -Wall -Wextra -Wno-pointer-arith -Wno-unused-variable -O3 -emit-llvm -c xdp.c -o - | llc -O3 -march=bpf -filetype=obj -o xdp echo "Before unload drop count was:" ./dropcount.sh || echo "Not loaded" diff --git a/xdp.c b/xdp.c index 3ebec88..d9aa076 100644 --- a/xdp.c +++ b/xdp.c @@ -162,6 +162,20 @@ struct bpf_map_def SEC("maps") drop_cnt_map = { *value += 1; \ } +#ifdef RATE_CNT +struct ratelimit { + struct bpf_spin_lock lock; + uint64_t bucket_secs; + uint64_t bucket_count; +}; +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, RATE_CNT); + __u32 *key; + struct ratelimit *value; +} rate_map SEC(".maps"); +#endif + SEC("xdp_drop") #endif int xdp_drop_prog(struct xdp_md *ctx) -- 2.39.5