Skip to content

Instantly share code, notes, and snippets.

@yushijinhun
Last active April 27, 2023 03:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yushijinhun/67ad055f4701fc38ccd73d1543798bd9 to your computer and use it in GitHub Desktop.
Save yushijinhun/67ad055f4701fc38ccd73d1543798bd9 to your computer and use it in GitHub Desktop.
eBPF program to filter prefixes in IPv6 RA
/**
* ra_prefix_filter.c - eBPF program to filter prefixes in IPv6 RA
* Copyright (C) 2023 Haowei Wen <yushijinhun@gmail.com>
*
* 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