Skip to content

Commit

Permalink
libbpf-rs(test): Add tests for NetfilterOpts and attach_netfilter fun…
Browse files Browse the repository at this point in the history
…ctionality

* Fix netfilter-blocklist vmlinux
* Fix format in netfilter-blocklist dependencies
* Update syntax for Rust min version compatibility
* Excluding the [ FORWARD, LOCAL_OUT ] hook
  • Loading branch information
ThisSeanZhang committed Nov 2, 2024
1 parent 28edb48 commit 9aa0d80
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 45 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions examples/netfilter_blocklist/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ license = "LGPL-2.1-only OR BSD-2-Clause"

[build-dependencies]
libbpf-cargo = { path = "../../libbpf-cargo" }
vmlinux = { version = "0.0", git = "https://github.com/libbpf/vmlinux.h.git", rev = "83a228cf37fc65f2d14e4896a04922b5ee531a94" }

[[bin]]
name = "netfilter_blocklist"

[dependencies]
anyhow = "1.0"
Expand Down
6 changes: 1 addition & 5 deletions examples/netfilter_blocklist/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,10 @@ This project demonstrates how to use eBPF and Rust to implement a Netfilter hook

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
$ cargo build --release
```

## Usage
Expand Down
5 changes: 4 additions & 1 deletion examples/netfilter_blocklist/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ fn main() {
.join("bpf")
.join("netfilter_blocklist.skel.rs");

let arch = env::var("CARGO_CFG_TARGET_ARCH")
.expect("CARGO_CFG_TARGET_ARCH must be set in build script");

SkeletonBuilder::new()
.source(SRC)
.clang_args([
OsStr::new("-Wno-compare-distinct-pointer-types"),
OsStr::new("-I"),
vmlinux::include_path_root().join(arch).as_os_str(),
])
.build_and_generate(&out)
.unwrap();
Expand Down
72 changes: 36 additions & 36 deletions examples/netfilter_blocklist/src/bpf/netfilter_blocklist.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,57 @@
#define NF_ACCEPT 1

int bpf_dynptr_from_skb(struct sk_buff *skb,
__u64 flags, struct bpf_dynptr *ptr__uninit) __ksym;
__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;
uint32_t offset, void *buffer, uint32_t buffer__sz) __ksym;


struct lpm_key {
__u32 prefixlen;
__be32 addr;
__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);
__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",
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;
return NF_DROP;
}
return NF_ACCEPT;
}

char _license[] SEC("license") = "GPL";
6 changes: 4 additions & 2 deletions examples/netfilter_blocklist/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ fn main() -> Result<()> {
// Load into kernel
let skel = open_skel.load()?;

let block_ip = Ipv4Addr::from_str(&opts.block_ip)?;
let block_ip: u32 = block_ip.into();
let block_ip_key = types::lpm_key {
prefixlen: (32 as u32),
addr: Ipv4Addr::from_str(&opts.block_ip)?.to_bits().to_be(),
prefixlen: (32_u32),
addr: block_ip.to_be(),
};

let block_ip_key = unsafe { plain::as_bytes(&block_ip_key) };
Expand Down
2 changes: 1 addition & 1 deletion libbpf-rs/src/netfilter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ pub const NF_INET_LOCAL_OUT: u32 = 3;
/// Netfilter hook number for post-routing (4).
pub const NF_INET_POST_ROUTING: u32 = 4;

#[derive(Clone, Debug, Default)]
/// Options to be provided when attaching a program to a netfilter hook.
#[derive(Clone, Debug, Default)]
pub struct NetfilterOpts {
/// Protocol family for netfilter; supported values are `NFPROTO_IPV4` (2) for IPv4
/// and `NFPROTO_IPV6` (10) for IPv6.
Expand Down
29 changes: 29 additions & 0 deletions libbpf-rs/tests/bin/src/netfilter.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

#define NF_ACCEPT 1

struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 4096 /* one page */);
} ringbuf SEC(".maps");

SEC("netfilter")
int handle_netfilter(struct bpf_nf_ctx *ctx) {

int *value;

value = bpf_ringbuf_reserve(&ringbuf, sizeof(int), 0);
if (!value) {
bpf_printk("handle_netfilter: failed to reserve ring buffer space");
return 1;
}

*value = 1;
bpf_ringbuf_submit(value, 0);

bpf_printk("handle_netfilter: submitted ringbuf value");
return NF_ACCEPT;
}

char LICENSE[] SEC("license") = "GPL";
124 changes: 124 additions & 0 deletions libbpf-rs/tests/test_netfilter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#[allow(dead_code)]
mod common;

use std::process::Command;

use libbpf_rs::Map;
use libbpf_rs::NetfilterOpts;
use libbpf_rs::Object;
use test_tag::tag;

use libbpf_rs::NFPROTO_IPV4;
use libbpf_rs::NFPROTO_IPV6;

// use libbpf_rs::NF_INET_FORWARD;
use libbpf_rs::NF_INET_LOCAL_IN;
// use libbpf_rs::NF_INET_LOCAL_OUT;
use libbpf_rs::NF_INET_POST_ROUTING;
use libbpf_rs::NF_INET_PRE_ROUTING;

use crate::common::bump_rlimit_mlock;
use crate::common::get_map_mut;
use crate::common::get_prog_mut;
use crate::common::get_test_object;

// copy from test
// I wasn't sure if I could just move this function to common so I copied it
fn with_ringbuffer<F>(map: &Map, action: F) -> i32
where
F: FnOnce(),
{
let mut value = 0i32;
{
let callback = |data: &[u8]| {
plain::copy_from_bytes(&mut value, data).expect("Wrong size");
0
};

let mut builder = libbpf_rs::RingBufferBuilder::new();
builder.add(map, callback).expect("failed to add ringbuf");
let mgr = builder.build().expect("failed to build");

action();
mgr.consume().expect("failed to consume ringbuf");
}

value
}

fn test_attach_and_detach(obj: &mut Object, pf: u32, hooknum: u32, hook_desc: &str) {
let prog = get_prog_mut(obj, "handle_netfilter");
let netfilter_opt = libbpf_rs::NetfilterOpts {
pf,
hooknum,
..NetfilterOpts::default()
};
let error_message = format!(
"Failed to attach netfilter protocol {}, hook: {}",
pf, hook_desc
);
let link = prog.attach_netfilter(netfilter_opt).expect(&error_message);

let map = get_map_mut(obj, "ringbuf");

if pf == 2 {
let result = match hook_desc {
"PRE_ROUTING" | "LOCAL_IN" | "LOCAL_OUT" | "POST_ROUTING" => {
let action = || {
Command::new("sh")
.arg("-c")
.arg("echo 'Test data' | nc -u 127.0.0.1 12345")
.output()
.expect("Failed to send packet");
};
with_ringbuffer(&map, action)
}
"FORWARD" => 1,
_ => {
panic!("unknow hook")
}
};
assert_eq!(result, 1);
}
assert!(link.detach().is_ok());
}

#[tag(root)]
#[test]
fn test_netfilter() {
bump_rlimit_mlock();
let mut obj = get_test_object("netfilter.bpf.o");

// 测试所有 IPv4 hook
test_attach_and_detach(&mut obj, NFPROTO_IPV4, NF_INET_PRE_ROUTING, "PRE_ROUTING");
test_attach_and_detach(&mut obj, NFPROTO_IPV4, NF_INET_LOCAL_IN, "LOCAL_IN");
// test_attach_and_detach(&mut obj, NFPROTO_IPV4, NF_INET_FORWARD, "FORWARD");
// test_attach_and_detach(&mut obj, NFPROTO_IPV4, NF_INET_LOCAL_OUT, "LOCAL_OUT");
test_attach_and_detach(&mut obj, NFPROTO_IPV4, NF_INET_POST_ROUTING, "POST_ROUTING");

// 测试所有 IPv6 hook&map,
test_attach_and_detach(&mut obj, NFPROTO_IPV6, NF_INET_PRE_ROUTING, "PRE_ROUTING");
test_attach_and_detach(&mut obj, NFPROTO_IPV6, NF_INET_LOCAL_IN, "LOCAL_IN");
// test_attach_and_detach(&mut obj, NFPROTO_IPV6, NF_INET_FORWARD, "FORWARD");
// test_attach_and_detach(&mut obj, NFPROTO_IPV6, NF_INET_LOCAL_OUT, "LOCAL_OUT");
test_attach_and_detach(&mut obj, NFPROTO_IPV6, NF_INET_POST_ROUTING, "POST_ROUTING");
}

#[tag(root)]
#[test]
fn test_invalid_netfilter_opts() {
let mut obj = get_test_object("netfilter.bpf.o");
let prog = get_prog_mut(&mut obj, "handle_netfilter");

let invalid_opts = NetfilterOpts {
pf: 999,
hooknum: 999,
..NetfilterOpts::default()
};

let result = prog.attach_netfilter(invalid_opts);
assert!(
result.is_err(),
"Expected error for invalid NetfilterOpts, but got Ok."
);
}

0 comments on commit 9aa0d80

Please sign in to comment.