Skip to content

Commit

Permalink
Dev (#11)
Browse files Browse the repository at this point in the history
* use own struct instead of exposing Bpf directly

* checkpoint

* checkpoint

* refactor into single struct entrypoint

* fix warning

* add ipnet instead of own cidr (test broken)

* fix tests

* add proto default

* add default trait for

* feat: adds API docs, correct error handling on RuleTracker and refactors rules into its own module

We add a draft of the API docs and refactored `Rule` into its own module
this way we reduce the public API surface area and improve readability.

Furthemore, we do some checks before inserting in `RuleTracker` making
sure that in case of rule exhaustion or other non-predictable errors we
don't leave `RuleTracker` in a partial state.

* feat: add `remove_by_id` method

`remove_by_id` allows removing all the ips related to a given id instead
of having to go one by one.

* fix

* docs: add examples and expand

* feat: make number of rules/ranges configurable + benchmark draft

* Adds features to control the number of rules and port ranges to be
  able to fine-tune the map sizes
* Adds a draft for a benchmarking program + an enviroment where it would
  work using iperf3

* feat: update id size u32 -> u128 for uuid compatibility

* fix: use [u8; 16] instead of u128 and scope logging

* feat: log format improvements

* fix: PacketLog padding

* fix: uuid endianess

* feature: log as json

* fix: hide ports when not applicable

* refactor: dry logger code

* fix: ci to use latest stable

* fix: clippy

* fix: ci-run ip parsing

* fix: PacketLogger is no longer dead code

* fix: comment typos

Co-authored-by: Andrew <[email protected]>

Co-authored-by: Andrew <[email protected]>
  • Loading branch information
conectado and akrousset authored Nov 21, 2022
1 parent 4ea2766 commit 5b3e36a
Show file tree
Hide file tree
Showing 31 changed files with 1,425 additions and 604 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ jobs:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v3
- name: Install latest stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install latest nightly
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
# TODO: Not sure why rust-src is needed
components: clippy, rust-src
- name: Cache cargo modules
uses: actions/cache@v3
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"rust-analyzer.linkedProjects": ["userspace/Cargo.toml", "ebpf/Cargo.toml"]
"rust-analyzer.linkedProjects": ["userspace/Cargo.toml", "ebpf/Cargo.toml"],
"rust-analyzer.checkOnSave.extraArgs": ["--manifest-path", "userspace/Cargo.toml"]
}
1 change: 1 addition & 0 deletions ebpf/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"rust-analyzer.cargo.target": "bpfel-unknown-none",
"rust-analyzer.checkOnSave.allTargets": false,
"rust-analyzer.checkOnSave.extraArgs": ["--manifest-path", "ebpf/Cargo.toml"]
}
7 changes: 7 additions & 0 deletions ebpf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ memoffset = "0.6"
strum = { version = "0.24", default-features = false }

[features]
default = ["rules256"]
wireguard = []
rules1024 = []
rules512 = []
rules256 = []
rules128 = []
rules64 = []
rules32 = []

[[bin]]
name = "firewall-ebpf"
Expand Down
70 changes: 46 additions & 24 deletions ebpf/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,27 @@ use strum::EnumCount;

#[allow(clippy::all)]
mod bindings;
use bindings::iphdr;

use core::mem;
use firewall_common::{ConfigOpt, PacketLog, RuleStore};
use memoffset::offset_of;

use crate::bindings::{ipv6hdr, tcphdr, udphdr};
use crate::bindings::{iphdr, ipv6hdr, tcphdr, udphdr};

#[cfg(feature = "rules1024")]
const MAX_NUMBER_OF_RULES: u32 = 1024;
#[cfg(feature = "rules512")]
const MAX_NUMBER_OF_RULES: u32 = 512;
#[cfg(feature = "rules256")]
const MAX_NUMBER_OF_RULES: u32 = 256;
#[cfg(feature = "rules128")]
const MAX_NUMBER_OF_RULES: u32 = 128;
#[cfg(feature = "rules64")]
const MAX_NUMBER_OF_RULES: u32 = 64;
#[cfg(feature = "rules32")]
const MAX_NUMBER_OF_RULES: u32 = 32;

type ID = [u8; 16];

// Note: I wish we could use const values as map names
// but alas! this is not supported yet https://github.com/rust-lang/rust/issues/52393
Expand All @@ -34,20 +48,19 @@ static mut EVENTS: PerfEventArray<PacketLog> =
PerfEventArray::<PacketLog>::with_max_entries(1024, 0);

#[map(name = "SOURCE_ID_IPV4")]
static mut SOURCE_ID_IPV4: HashMap<[u8; 4], u32> =
HashMap::<[u8; 4], u32>::with_max_entries(1024, 0);
static mut SOURCE_ID_IPV4: HashMap<[u8; 4], ID> = HashMap::<[u8; 4], ID>::with_max_entries(1024, 0);

#[map(name = "RULE_MAP_IPV4")]
static mut RULE_MAP_IPV4: LpmTrie<[u8; 9], RuleStore> =
LpmTrie::<[u8; 9], RuleStore>::with_max_entries(10_000, BPF_F_NO_PREALLOC);
static mut RULE_MAP_IPV4: LpmTrie<[u8; 21], RuleStore> =
LpmTrie::<[u8; 21], RuleStore>::with_max_entries(MAX_NUMBER_OF_RULES, BPF_F_NO_PREALLOC);

#[map(name = "SOURCE_ID_IPV6")]
static mut SOURCE_ID_IPV6: HashMap<[u8; 16], u32> =
HashMap::<[u8; 16], u32>::with_max_entries(1024, 0);
static mut SOURCE_ID_IPV6: HashMap<[u8; 16], ID> =
HashMap::<[u8; 16], ID>::with_max_entries(1024, 0);

#[map(name = "RULE_MAP_IPV6")]
static mut RULE_MAP_IPV6: LpmTrie<[u8; 21], RuleStore> =
LpmTrie::<[u8; 21], RuleStore>::with_max_entries(10_000, BPF_F_NO_PREALLOC);
static mut RULE_MAP_IPV6: LpmTrie<[u8; 33], RuleStore> =
LpmTrie::<[u8; 33], RuleStore>::with_max_entries(MAX_NUMBER_OF_RULES, BPF_F_NO_PREALLOC);

// For now this just configs the default action
// However! We can use this eventually to share more runtime configs
Expand Down Expand Up @@ -86,22 +99,25 @@ unsafe fn try_ebpf_firewall(ctx: TcContext) -> Result<i32, i64> {
unsafe fn process<const N: usize, const M: usize>(
ctx: TcContext,
version: u8,
source_map: &HashMap<[u8; N], u32>,
source_map: &HashMap<[u8; N], ID>,
rule_map: &LpmTrie<[u8; M], RuleStore>,
) -> Result<i32, i64> {
let (source, dest, proto) = load_ntw_headers(&ctx, version)?;
let port = get_port(&ctx, version, proto)?;
let (dest_port, src_port) = get_port(&ctx, version, proto)?;
let class = source_class(source_map, source);
let action = get_action(class, dest, rule_map, port, proto);
let action = get_action(class, dest, rule_map, dest_port, proto);
let source = as_log_array(source);
let dest = as_log_array(dest);
let log_entry = PacketLog {
source,
dest,
action,
port,
dest_port,
src_port,
proto,
version,
class: class.unwrap_or([0; 16]),
pad: [0; 2],
};
EVENTS.output(&ctx, &log_entry, 0);
Ok(action)
Expand All @@ -126,19 +142,25 @@ fn load_ntw_headers<const N: usize>(
Ok((source, dest, next_header))
}

fn get_port(ctx: &TcContext, version: u8, proto: u8) -> Result<u16, i64> {
fn get_port(ctx: &TcContext, version: u8, proto: u8) -> Result<(u16, u16), i64> {
let ip_len = match version {
6 => IPV6_HDR_LEN,
4 => IP_HDR_LEN,
_ => unreachable!("Should only call with valid packet"),
};
let port = match proto {
let dest_port = match proto {
TCP => u16::from_be(ctx.load(ETH_HDR_LEN + ip_len + offset_of!(tcphdr, dest))?),
UDP => u16::from_be(ctx.load(ETH_HDR_LEN + ip_len + offset_of!(udphdr, dest))?),
_ => 0,
};

Ok(port)
let src_port = match proto {
TCP => u16::from_be(ctx.load(ETH_HDR_LEN + ip_len + offset_of!(tcphdr, source))?),
UDP => u16::from_be(ctx.load(ETH_HDR_LEN + ip_len + offset_of!(udphdr, source))?),
_ => 0,
};

Ok((dest_port, src_port))
}

fn as_log_array<const N: usize>(from: [u8; N]) -> [u8; 16] {
Expand All @@ -149,15 +171,15 @@ fn as_log_array<const N: usize>(from: [u8; N]) -> [u8; 16] {
}

unsafe fn source_class<const N: usize>(
source_map: &HashMap<[u8; N], u32>,
source_map: &HashMap<[u8; N], ID>,
address: [u8; N],
) -> Option<[u8; 4]> {
) -> Option<[u8; 16]> {
// Race condition if ip changes group?
source_map.get(&address).map(|x| u32::to_be_bytes(*x))
source_map.get(&address).copied()
}

fn get_action<const N: usize, const M: usize>(
group: Option<[u8; 4]>,
group: Option<[u8; 16]>,
address: [u8; N],
rule_map: &LpmTrie<[u8; M], RuleStore>,
port: u16,
Expand Down Expand Up @@ -198,15 +220,15 @@ fn is_stored(rule_store: &Option<&RuleStore>, port: u16) -> bool {
}

fn get_key<const N: usize, const M: usize>(
group: Option<[u8; 4]>,
group: Option<[u8; 16]>,
proto: u8,
address: [u8; N],
) -> [u8; M] {
// TODO: Could use MaybeUninit
let group = group.unwrap_or_default();
let mut res = [0; M];
let (res_left, res_address) = res.split_at_mut(5);
let (res_group, res_proto) = res_left.split_at_mut(4);
let (res_left, res_address) = res.split_at_mut(17);
let (res_group, res_proto) = res_left.split_at_mut(16);
res_group.copy_from_slice(&group);
res_proto[0] = proto;
res_address.copy_from_slice(&address);
Expand Down
1 change: 1 addition & 0 deletions userspace/benchmark/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
peers/
15 changes: 15 additions & 0 deletions userspace/benchmark/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM lscr.io/linuxserver/wireguard:latest


RUN \
echo "**** install dependencies ****" && \
apt-get update && \
apt-get install -y --no-install-recommends \
iperf3 && \
echo "**** cleanup ****" && \
apt-get autoremove && \
apt-get clean && \
rm -rf \
/tmp/* \
/var/lib/apt/lists/* \
/var/tmp/*
70 changes: 70 additions & 0 deletions userspace/benchmark/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
version: '3.8'

services:
wireguard_server:
build: .
image: fw-benchmark:latest
volumes:
- ../target/release/examples/benchmark:/benchmark
- ./peers/:/config/:rw
environment:
- PEERS=2
- SERVERURL=172.8.0.2
- LOG_CONFS=false
- PUID=1000
- PGID=1000
- RUST_LOG=trace
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
# Not sure what capabilities are required to create eBPF maps
privileged: true
networks:
internal:
ipv4_address: 172.8.0.100
command: /benchmark --iface wg0

peer1:
image: fw-benchmark:latest
volumes:
- ./peers/peer1/peer1.conf:/config/wg0.conf
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
environment:
- PUID=1000
- PGID=1000
networks:
- internal
command: iperf3 -s -B 10.13.13.2
#command: ping 10.13.13.3

peer2:
image: fw-benchmark:latest
volumes:
- ./peers/peer2/peer2.conf:/config/wg0.conf
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
environment:
- PUID=1000
- PGID=1000
networks:
- internal
command: iperf3 -c 10.13.13.2 -b 1G
#command: ping 10.13.13.2

networks:
internal:
ipam:
config:
- subnet: 172.8.0.0/16
12 changes: 6 additions & 6 deletions userspace/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ services:
build: .
image: wireguard-test:latest
volumes:
- ../target/debug/examples/logger-firewall:/logger-firewall:ro
- ../target/debug/examples/logger-firewall:/logger-firewall
- ./peers/:/config/:rw
environment:
- PEERS=3
Expand All @@ -32,7 +32,7 @@ services:
peer1:
image: wireguard-test:latest
volumes:
- ./peers/peer1/peer1.conf:/config/wg0.conf:ro
- ./peers/peer1/peer1.conf:/config/wg0.conf
cap_add:
- NET_ADMIN
- SYS_MODULE
Expand All @@ -46,12 +46,12 @@ services:
networks:
internal:
ipv4_address: 172.8.0.3
command: /bin/sh -c "sleep 5 && echo peer1! && echo howdy | nc -nv fafa::3 5500"
command: /bin/sh -c "sleep 10 && echo peer1! && echo howdy | nc -nvvv fafa::3 5500"

peer2:
image: wireguard-test:latest
volumes:
- ./peers/peer2/peer2.conf:/config/wg0.conf:ro
- ./peers/peer2/peer2.conf:/config/wg0.conf
cap_add:
- NET_ADMIN
- SYS_MODULE
Expand All @@ -65,12 +65,12 @@ services:
networks:
internal:
ipv4_address: 172.8.0.4
command: /bin/sh -c "sleep 5 && echo peer2! && while true; do echo "hi" | nc -lv fafa::3 5500; done"
command: /bin/sh -c "sleep 5 && echo peer2! && while true; do echo "hi" | nc -nlvvv fafa::3 5500; done"

peer3:
image: wireguard-test:latest
volumes:
- ./peers/peer3/peer3.conf:/config/wg0.conf:ro
- ./peers/peer3/peer3.conf:/config/wg0.conf
cap_add:
- NET_ADMIN
- SYS_MODULE
Expand Down
14 changes: 12 additions & 2 deletions userspace/firewall-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,24 @@ version = "0.1.0"
edition = "2021"

[features]
default = []
user = ["aya", "thiserror"]
default = ["maxranges256"]
user = ["aya", "thiserror", "num-derive", "num-traits", "serde"]
maxranges1024 = []
maxranges512 = []
maxranges256 = []
maxranges128 = []
maxranges64 = []
maxranges32 = []
maxranges16 = []

[dependencies]
strum = { version = "0.24", default-features = false }
strum_macros = { version = "0.24", default-features = false }
aya = { git = "https://github.com/aya-rs/aya.git", rev = "23c0460ab36e35d15a8b2d2246ebfb1e9544beeb", optional = true }
thiserror = { version = "1", optional = true }
num-derive = { version = "0.3", optional = true }
num-traits = { version = "0.2", optional = true }
serde = {version = "1.0", features = ["derive"], optional = true}

[dev-dependencies]
test-case = "2.2"
Expand Down
Loading

0 comments on commit 5b3e36a

Please sign in to comment.