-
Notifications
You must be signed in to change notification settings - Fork 136
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
example: Add eBPF-based Netfilter blocklist example with Rust control…
… program
- Loading branch information
1 parent
a392b91
commit 28edb48
Showing
10 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[package] | ||
name = "netfilter_blocklist" | ||
version = "0.1.0" | ||
edition.workspace = true | ||
license = "LGPL-2.1-only OR BSD-2-Clause" | ||
|
||
[build-dependencies] | ||
libbpf-cargo = { path = "../../libbpf-cargo" } | ||
|
||
[dependencies] | ||
anyhow = "1.0" | ||
clap = { version = "4.0.32", default-features = false, features = ["std", "derive", "help", "usage"] } | ||
ctrlc = "3.2" | ||
libbpf-rs = { path = "../../libbpf-rs" } | ||
plain = "0.2" |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../LICENSE |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../LICENSE.BSD-2-Clause |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../LICENSE.LGPL-2.1 |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# eBPF Netfilter Blocklist Example | ||
|
||
This project demonstrates how to use eBPF and Rust to implement a Netfilter hook that blocks specific IPv4 traffic based on a blacklist of IP addresses. The eBPF program uses an LPM Trie map to efficiently store and lookup IP addresses, and the Rust program handles the loading, configuration, and management of the eBPF program. | ||
|
||
|
||
## ⚠ Requirements ⚠ | ||
|
||
Linux kernel [version 6.4 or later](https://github.com/torvalds/linux/commit/84601d6ee68ae820dec97450934797046d62db4b) with eBPF support. | ||
|
||
```shell | ||
bpftool btf dump file /sys/kernel/btf/vmlinux format c > ./examples/netfilter_blocklist/src/bpf/vmlinux.h | ||
``` | ||
|
||
## Building | ||
|
||
```shell | ||
$ cargo build | ||
``` | ||
|
||
## Usage | ||
|
||
```shell | ||
$ sudo ./target/release/netfilter_blocklist --block-ip <target ip> --value <show in debug> --verbose | ||
``` | ||
|
||
## Trigger | ||
|
||
```shell | ||
# Start | ||
$ sudo ./target/release/netfilter_blocklist --block-ip 1.1.1.1 --value 42 --verbose | ||
|
||
# Trigger using curl | ||
curl 1.1.1.1 | ||
``` | ||
|
||
## Output | ||
|
||
```shell | ||
$ sudo cat /sys/kernel/debug/tracing/trace_pipe | ||
``` | ||
```text | ||
curl-106738 [001] ...1. 74215.769718: bpf_trace_printk: Blocked IP: 1.1.1.1, prefix length: 32, map value: 42 | ||
<idle>-0 [001] ..s3. 74216.785447: bpf_trace_printk: Blocked IP: 1.1.1.1, prefix length: 32, map value: 42 | ||
<idle>-0 [001] ..s3. 74217.801426: bpf_trace_printk: Blocked IP: 1.1.1.1, prefix length: 32, map value: 42 | ||
<idle>-0 [002] ..s3. 74218.825369: bpf_trace_printk: Blocked IP: 1.1.1.1, prefix length: 32, map value: 42 | ||
<idle>-0 [000] ..s3. 74219.849344: bpf_trace_printk: Blocked IP: 1.1.1.1, prefix length: 32, map value: 42 | ||
<idle>-0 [000] ..s3. 74220.873297: bpf_trace_printk: Blocked IP: 1.1.1.1, prefix length: 32, map value: 42 | ||
<idle>-0 [003] ..s3. 74222.889199: bpf_trace_printk: Blocked IP: 1.1.1.1, prefix length: 32, map value: 42 | ||
<idle>-0 [001] ..s3. 74227.145032: bpf_trace_printk: Blocked IP: 1.1.1.1, prefix length: 32, map value: 42 | ||
``` |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
use std::env; | ||
use std::ffi::OsStr; | ||
use std::path::PathBuf; | ||
|
||
use libbpf_cargo::SkeletonBuilder; | ||
|
||
const SRC: &str = "src/bpf/netfilter_blocklist.bpf.c"; | ||
|
||
fn main() { | ||
let out = PathBuf::from( | ||
env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set in build script"), | ||
) | ||
.join("src") | ||
.join("bpf") | ||
.join("netfilter_blocklist.skel.rs"); | ||
|
||
SkeletonBuilder::new() | ||
.source(SRC) | ||
.clang_args([ | ||
OsStr::new("-Wno-compare-distinct-pointer-types"), | ||
OsStr::new("-I"), | ||
]) | ||
.build_and_generate(&out) | ||
.unwrap(); | ||
println!("cargo:rerun-if-changed={SRC}"); | ||
} |
62 changes: 62 additions & 0 deletions
62
examples/netfilter_blocklist/src/bpf/netfilter_blocklist.bpf.c
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#include "vmlinux.h" | ||
#include <bpf/bpf_helpers.h> | ||
#include <bpf/bpf_endian.h> | ||
|
||
#define NF_DROP 0 | ||
#define NF_ACCEPT 1 | ||
|
||
int bpf_dynptr_from_skb(struct sk_buff *skb, | ||
__u64 flags, struct bpf_dynptr *ptr__uninit) __ksym; | ||
void *bpf_dynptr_slice(const struct bpf_dynptr *ptr, | ||
uint32_t offset, void *buffer, uint32_t buffer__sz) __ksym; | ||
|
||
|
||
struct lpm_key { | ||
__u32 prefixlen; | ||
__be32 addr; | ||
}; | ||
|
||
struct { | ||
__uint(type, BPF_MAP_TYPE_LPM_TRIE); | ||
__type(key, struct lpm_key); | ||
__type(value, __u32); | ||
__uint(map_flags, BPF_F_NO_PREALLOC); | ||
__uint(max_entries, 200); | ||
} block_ips SEC(".maps"); | ||
|
||
SEC("netfilter") | ||
int netfilter_local_in(struct bpf_nf_ctx *ctx) { | ||
|
||
struct sk_buff *skb = ctx->skb; | ||
struct bpf_dynptr ptr; | ||
struct iphdr *p, iph = {}; | ||
struct lpm_key key; | ||
__u32 *match_value; | ||
|
||
if (skb->len <= 20 || bpf_dynptr_from_skb(skb, 0, &ptr)) | ||
return NF_ACCEPT; | ||
p = bpf_dynptr_slice(&ptr, 0, &iph, sizeof(iph)); | ||
if (!p) | ||
return NF_ACCEPT; | ||
|
||
/* ip4 only */ | ||
if (p->version != 4) | ||
return NF_ACCEPT; | ||
|
||
/* search p->daddr in trie */ | ||
key.prefixlen = 32; | ||
key.addr = p->daddr; | ||
match_value = bpf_map_lookup_elem(&block_ips, &key); | ||
if (match_value) { | ||
/* To view log output, use: cat /sys/kernel/debug/tracing/trace_pipe */ | ||
__be32 addr_host = bpf_ntohl(key.addr); | ||
bpf_printk("Blocked IP: %d.%d.%d.%d, prefix length: %d, map value: %d\n", | ||
(addr_host >> 24) & 0xFF, (addr_host >> 16) & 0xFF, | ||
(addr_host >> 8) & 0xFF, addr_host & 0xFF, | ||
key.prefixlen, *match_value); | ||
return NF_DROP; | ||
} | ||
return NF_ACCEPT; | ||
} | ||
|
||
char _license[] SEC("license") = "GPL"; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
use std::mem::MaybeUninit; | ||
use std::net::Ipv4Addr; | ||
use std::str::FromStr; | ||
use std::sync::atomic::AtomicBool; | ||
use std::sync::atomic::Ordering; | ||
use std::sync::Arc; | ||
use std::thread::sleep; | ||
use std::time::Duration; | ||
|
||
use anyhow::Result; | ||
use clap::Parser; | ||
|
||
use libbpf_rs::skel::OpenSkel; | ||
use libbpf_rs::skel::SkelBuilder; | ||
use libbpf_rs::ErrorExt; | ||
use libbpf_rs::MapCore; | ||
use libbpf_rs::MapFlags; | ||
use libbpf_rs::NetfilterOpts; | ||
use libbpf_rs::NFPROTO_IPV4; | ||
use libbpf_rs::NF_INET_LOCAL_OUT; | ||
|
||
mod netfilter { | ||
include!(concat!( | ||
env!("CARGO_MANIFEST_DIR"), | ||
"/src/bpf/netfilter_blocklist.skel.rs" | ||
)); | ||
} | ||
|
||
use netfilter::*; | ||
|
||
/// Netfilter Blocklist Example | ||
/// | ||
/// Drop specified IP packets in netfilter hook | ||
#[derive(Debug, Parser)] | ||
struct Command { | ||
/// Add the specified IP to the blocked IP list | ||
#[arg(long, value_parser, default_value = "1.1.1.1")] | ||
block_ip: String, | ||
|
||
/// show the value in the debug info | ||
#[arg(long, value_parser, default_value = "42")] | ||
value: u32, | ||
|
||
/// Verbose debug output | ||
#[arg(short, long)] | ||
verbose: bool, | ||
} | ||
|
||
fn main() -> Result<()> { | ||
let opts = Command::parse(); | ||
|
||
// Install Ctrl-C handler | ||
let running = Arc::new(AtomicBool::new(true)); | ||
let r = running.clone(); | ||
ctrlc::set_handler(move || { | ||
r.store(false, Ordering::SeqCst); | ||
})?; | ||
|
||
let mut skel_builder = NetfilterBlocklistSkelBuilder::default(); | ||
|
||
if opts.verbose { | ||
skel_builder.obj_builder.debug(true); | ||
} | ||
|
||
// Set constants | ||
let mut open_object = MaybeUninit::uninit(); | ||
let open_skel = skel_builder.open(&mut open_object)?; | ||
|
||
// Load into kernel | ||
let skel = open_skel.load()?; | ||
|
||
let block_ip_key = types::lpm_key { | ||
prefixlen: (32 as u32), | ||
addr: Ipv4Addr::from_str(&opts.block_ip)?.to_bits().to_be(), | ||
}; | ||
|
||
let block_ip_key = unsafe { plain::as_bytes(&block_ip_key) }; | ||
let value = opts.value; | ||
|
||
skel.maps | ||
.block_ips | ||
.update(block_ip_key, &value.to_le_bytes(), MapFlags::ANY) | ||
.context("update new record to map fail")?; | ||
|
||
|
||
let local_in_netfilter_opt = NetfilterOpts { | ||
pf: NFPROTO_IPV4, | ||
hooknum: NF_INET_LOCAL_OUT, | ||
priority: -128, | ||
..NetfilterOpts::default() | ||
}; | ||
|
||
let local_in_link = skel | ||
.progs | ||
.netfilter_local_in | ||
.attach_netfilter(local_in_netfilter_opt) | ||
.unwrap(); | ||
|
||
// Block until SIGINT | ||
while running.load(Ordering::SeqCst) { | ||
sleep(Duration::new(1, 0)); | ||
} | ||
|
||
local_in_link.detach().unwrap(); | ||
|
||
Ok(()) | ||
} |