Last active
April 27, 2023 03:10
-
-
Save yushijinhun/67ad055f4701fc38ccd73d1543798bd9 to your computer and use it in GitHub Desktop.
eBPF program to filter prefixes in IPv6 RA
This file contains hidden or 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
/** | |
* ra_prefix_filter.c - eBPF program to filter prefixes in IPv6 RA | |
* Copyright (C) 2023 Haowei Wen <[email protected]> | |
* | |
* This program is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU General Public License | |
* as published by the Free Software Foundation; either version 2 | |
* of the License, or (at your option) any later version. | |
* | |
* 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. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program; if not, write to the Free Software | |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
*/ | |
/** | |
* This program discards specific Prefix Information options in IPv6 | |
* Router Advertisement message, which allows you to blacklist specific | |
* prefixes in SLAAC process while keeping other prefixes untouched. | |
* | |
* Steps to use this program: | |
* | |
* (1) Compile the program: | |
* clang -O2 -g -Wall -target bpf -c ra_prefix_filter.c -o ra_prefix_filter.o | |
* | |
* (2) Load the program to the interface that receives RA messages: | |
* ip link set eth0 xdpgeneric obj ra_prefix_filter.o sec ra_prefix_filter | |
* | |
* (3) Unload the program: | |
* ip link set eth0 xdpgeneric off | |
* | |
* To turn on debug logging, uncomment the DEBUG macro defined in this file. | |
* Debug output is available in '/sys/kernel/debug/tracing/trace_pipe'. | |
* | |
* To specify the prefixes to discard, edit the 'on_prefix_information' function | |
* defined in this file. | |
*/ | |
#include <linux/bpf.h> | |
#include <linux/icmpv6.h> | |
#include <linux/if_ether.h> | |
#include <linux/ipv6.h> | |
#include <linux/pkt_cls.h> | |
#include <linux/types.h> | |
#include <bpf/bpf_endian.h> | |
#include <bpf/bpf_helpers.h> | |
// ==== UNCOMMENT THIS TO TURN ON DEBUG ==== | |
// #define DEBUG 1 | |
#ifdef DEBUG | |
#define bpf_debug(fmt, ...) \ | |
({ \ | |
char ____fmt[] = fmt; \ | |
bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \ | |
}) | |
#else | |
#define bpf_debug(fmt, ...) \ | |
{} \ | |
while (0) | |
#endif | |
#define MAX_ND_OPTIONS 256 | |
#define MAX_ND_OPTION_LEN 256 | |
#define ICMPV6_ROUTER_ADVERTISEMENT 134 | |
#define NDP_OPTION_PREFIX_INFORMATION 3 | |
struct ra_msg { | |
struct icmp6hdr icmph; | |
__be32 reachable_time; | |
__be32 retrans_timer; | |
} __attribute__((packed)); | |
struct nd_opt_hdr { | |
__u8 nd_opt_type; | |
__u8 nd_opt_len; | |
} __attribute__((packed)); | |
struct prefix_info { | |
__u8 type; | |
__u8 length; | |
__u8 prefix_len; | |
#if defined(__BIG_ENDIAN_BITFIELD) | |
__u8 onlink : 1, autoconf : 1, reserved : 6; | |
#elif defined(__LITTLE_ENDIAN_BITFIELD) | |
__u8 reserved : 6, autoconf : 1, onlink : 1; | |
#else | |
#error "Please fix <asm/byteorder.h>" | |
#endif | |
__be32 valid; | |
__be32 prefered; | |
__be32 reserved2; | |
struct in6_addr prefix; | |
} __attribute__((packed)); | |
#define IPv6(x0, x1, x2, x3, x4, x5, x6, x7) \ | |
((((__uint128_t)0x##x0) << 112) | (((__uint128_t)0x##x1) << 96) | \ | |
(((__uint128_t)0x##x2) << 80) | (((__uint128_t)0x##x3) << 64) | \ | |
(((__uint128_t)0x##x4) << 48) | (((__uint128_t)0x##x5) << 32) | \ | |
(((__uint128_t)0x##x6) << 16) | ((__uint128_t)0x##x7)) | |
static void __always_inline discard_prefix(struct prefix_info *prefix_info, | |
__sum16 *csum, __uint128_t prefix, | |
int prefixlen) { | |
if (prefix_info->prefix_len >= prefixlen) { | |
__uint128_t mask = (~(__uint128_t)0) << (128 - prefixlen); | |
__uint128_t addr = 0; | |
#pragma unroll | |
for (int i = 0; i < 16; i++) { | |
addr <<= 8; | |
addr |= prefix_info->prefix.in6_u.u6_addr8[i]; | |
} | |
if ((addr & mask) == (prefix & mask)) { | |
// Disallow | |
// Set option type to 0 to make kernel ignore it | |
prefix_info->type = 0; | |
// Update checksum | |
*csum = ~(~*csum + (-NDP_OPTION_PREFIX_INFORMATION) + 0); | |
bpf_debug("Prefix %llx:%llx/%d discarded", | |
((__u64)bpf_ntohl(prefix_info->prefix.in6_u.u6_addr32[0]) | |
<< 32) | | |
bpf_ntohl(prefix_info->prefix.in6_u.u6_addr32[1]), | |
((__u64)bpf_ntohl(prefix_info->prefix.in6_u.u6_addr32[2]) | |
<< 32) | | |
bpf_ntohl(prefix_info->prefix.in6_u.u6_addr32[3]), | |
prefix_info->prefix_len); | |
return; | |
} | |
} | |
bpf_debug("Prefix %llx:%llx/%d allowed", | |
((__u64)bpf_ntohl(prefix_info->prefix.in6_u.u6_addr32[0]) << 32) | | |
bpf_ntohl(prefix_info->prefix.in6_u.u6_addr32[1]), | |
((__u64)bpf_ntohl(prefix_info->prefix.in6_u.u6_addr32[2]) << 32) | | |
bpf_ntohl(prefix_info->prefix.in6_u.u6_addr32[3]), | |
prefix_info->prefix_len); | |
} | |
static void __always_inline | |
on_prefix_information(struct prefix_info *prefix_info, __sum16 *csum) { | |
// ==== MODIFY THIS TO CUSTOMIZE ==== | |
// Example: discard the prefix if it falls in 2001:da8:1011::/48 | |
discard_prefix(prefix_info, csum, IPv6(2001, da8, 1011, 0, 0, 0, 0, 0), 48); | |
} | |
SEC("ra_prefix_filter") | |
int ra_prefix_filter_prog(struct xdp_md *xdp) { | |
void *data = (void *)(long)xdp->data; | |
void *data_end = (void *)(long)xdp->data_end; | |
struct ethhdr *eth = data; | |
if ((void *)eth + sizeof(*eth) > data_end) | |
// Incomplete ethernet header | |
return XDP_PASS; | |
if (eth->h_proto != bpf_htons(ETH_P_IPV6)) | |
// Not an IPv6 packet | |
return XDP_PASS; | |
struct ipv6hdr *ipv6 = (void *)eth + sizeof(*eth); | |
if ((void *)ipv6 + sizeof(*ipv6) > data_end) | |
// Incomplete IPv6 header | |
return XDP_PASS; | |
if (ipv6->nexthdr != IPPROTO_ICMPV6) | |
// Not ICMPv6 | |
return XDP_PASS; | |
struct ra_msg *ra = (void *)ipv6 + sizeof(*ipv6); | |
if ((void *)ra + sizeof(*ra) > data_end) | |
// Not an complete RA message | |
return XDP_PASS; | |
if (ra->icmph.icmp6_type != ICMPV6_ROUTER_ADVERTISEMENT) | |
// Not an RA | |
return XDP_PASS; | |
struct nd_opt_hdr *opt = (void *)ra + sizeof(*ra); | |
for (int i = 0; i < MAX_ND_OPTIONS; i++) { | |
if ((void *)opt == data_end) | |
// End of packet reached | |
break; | |
if ((void *)opt + sizeof(*opt) > data_end) | |
// Incomplete option header | |
return XDP_PASS; | |
// Length of current option in bytes | |
__u16 opt_len = opt->nd_opt_len * 8; | |
if (opt_len > MAX_ND_OPTION_LEN) | |
return XDP_PASS; | |
if ((void *)opt + opt_len > data_end) | |
// Incomplete option | |
return XDP_PASS; | |
if (opt->nd_opt_type == NDP_OPTION_PREFIX_INFORMATION) { | |
struct prefix_info *prefix_info = (void *)opt; | |
// Ensure the prefix option has a correct length | |
if (opt_len == sizeof(*prefix_info)) { | |
if ((void *)prefix_info + sizeof(*prefix_info) > data_end) | |
// Redundant check to please eBPF verifier | |
return XDP_PASS; | |
__sum16 csum = ra->icmph.icmp6_cksum; | |
on_prefix_information(prefix_info, &csum); | |
ra->icmph.icmp6_cksum = csum; | |
} | |
} | |
opt = (void *)opt + opt_len; | |
} | |
return XDP_PASS; | |
} | |
char _license[] SEC("license") = "GPL"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment