1

Setting up buildsystem to allow for BPF builds

This commit is contained in:
Evan Pratten 2023-07-27 13:57:25 -04:00
parent e35fa043b3
commit c26c06d62c
18 changed files with 193 additions and 341 deletions

1
.cargo/cross-images/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/*.dockerfile.timestamp

View File

@ -0,0 +1,11 @@
# Cross image for building bpfel and bpfeb targets
FROM rust:latest
ARG TOOLCHAIN_CHANNEL
# Install the needed toolcahin components
RUN rustup install ${TOOLCHAIN_CHANNEL}
RUN rustup component add rust-src --toolchain ${TOOLCHAIN_CHANNEL}
# Install the BFP linker
RUN cargo install bpf-linker

View File

@ -5,5 +5,5 @@
"pnet",
"Protomask",
"rtnetlink"
]
],
}

32
.vscode/tasks.json vendored
View File

@ -2,24 +2,40 @@
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"command": "build",
"type": "shell",
"command": "make",
"args": [
"target/x86_64-unknown-linux-musl/debug/protomask"
],
"problemMatcher": [
"$rustc",
"$rust-panic"
],
"group": "build",
"label": "rust: cargo build"
"label": "rust: build [target: x86_64-unknown-linux-musl]"
},
{
"type": "cargo",
"command": "test",
"type": "shell",
"command": "make",
"args": [
"target/x86_64-unknown-linux-musl/release/protomask"
],
"problemMatcher": [
"$rustc",
"$rust-panic"
],
"group": "test",
"label": "rust: cargo test"
}
"group": "build",
"label": "rust: build [target: x86_64-unknown-linux-musl] [profile: release]"
},
// {
// "type": "cargo",
// "command": "test",
// "problemMatcher": [
// "$rustc",
// "$rust-panic"
// ],
// "group": "test",
// "label": "rust: cargo test"
// }
]
}

View File

@ -1,56 +1,57 @@
[package]
name = "protomask"
version = "0.2.0"
authors = ["Evan Pratten <ewpratten@gmail.com>"]
edition = "2021"
description = "A user space NAT64 implementation"
readme = "README.md"
homepage = "https://github.com/ewpratten/protomask"
documentation = "https://docs.rs/protomask"
repository = "https://github.com/ewpratten/protomask"
license = "GPL-3.0"
keywords = []
categories = []
# [package]
# name = "protomask"
# version = "0.2.0"
# authors = ["Evan Pratten <ewpratten@gmail.com>"]
# edition = "2021"
# description = "A user space NAT64 implementation"
# readme = "README.md"
# homepage = "https://github.com/ewpratten/protomask"
# documentation = "https://docs.rs/protomask"
# repository = "https://github.com/ewpratten/protomask"
# license = "GPL-3.0"
# keywords = []
# categories = []
# # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
# [dependencies]
# protomask-tun = { path = "protomask-tun", version = "0.1.0" }
# tokio = { version = "1.29.1", features = [
# "macros",
# "rt-multi-thread",
# # "process",
# "sync"
# ] }
# clap = { version = "4.3.11", features = ["derive"] }
# serde = { version = "1.0.171", features = ["derive"] }
# ipnet = { version = "2.8.0", features = ["serde"] }
# hyper = { version = "0.14.27", features = ["server", "http1", "tcp"] }
# owo-colors = { version = "3.5.0", features = ["supports-colors"] }
# toml = "0.7.6"
# log = "0.4.19"
# fern = "0.6.2"
# serde_path_to_error = "0.1.13"
# thiserror = "1.0.43"
# tun-tap = "0.1.3"
# bimap = "0.6.3"
# pnet_packet = "0.34.0"
# rtnetlink = "0.13.0"
# futures = "0.3.28"
# prometheus = "0.13.3"
# lazy_static = "1.4.0"
# [[bin]]
# name = "protomask"
# path = "src/cli/main.rs"
# [package.metadata.deb]
# section = "network"
# assets = [
# ["target/release/protomask", "/usr/local/bin/protomask", "755"],
# ["./protomask.toml", "/etc/protomask.toml", "644"],
# ["README.md", "usr/share/doc/protomask/README.md", "644"]
# ]
# conf-files = ["/etc/protomask.toml"]
# depends = []
# maintainer-scripts = "./debian/"
# systemd-units = { enable = false }
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
protomask-tun = { path = "protomask-tun", version = "0.1.0" }
tokio = { version = "1.29.1", features = [
"macros",
"rt-multi-thread",
# "process",
"sync"
] }
clap = { version = "4.3.11", features = ["derive"] }
serde = { version = "1.0.171", features = ["derive"] }
ipnet = { version = "2.8.0", features = ["serde"] }
hyper = { version = "0.14.27", features = ["server", "http1", "tcp"] }
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
toml = "0.7.6"
log = "0.4.19"
fern = "0.6.2"
serde_path_to_error = "0.1.13"
thiserror = "1.0.43"
tun-tap = "0.1.3"
bimap = "0.6.3"
pnet_packet = "0.34.0"
rtnetlink = "0.13.0"
futures = "0.3.28"
prometheus = "0.13.3"
lazy_static = "1.4.0"
[[bin]]
name = "protomask"
path = "src/cli/main.rs"
[package.metadata.deb]
section = "network"
assets = [
["target/release/protomask", "/usr/local/bin/protomask", "755"],
["./protomask.toml", "/etc/protomask.toml", "644"],
["README.md", "usr/share/doc/protomask/README.md", "644"]
]
conf-files = ["/etc/protomask.toml"]
depends = []
maintainer-scripts = "./debian/"
systemd-units = { enable = false }
[workspace]
resolver = "2"
members = ["protomask", "protomask-ebpf"]

5
Cross.toml Normal file
View File

@ -0,0 +1,5 @@
[target.bpfel-unknown-none]
image = "protomask/bpfex-unknown-none"
[target.bpfeb-unknown-none]
image = "protomask/bpfex-unknown-none"

View File

@ -1,17 +1,48 @@
# All sources used to build the protomask binary
SRC = Cargo.toml $(shell find src/ -type f -name '*.rs') $(shell find protomask-tun/src/ -type f -name '*.rs')
PROTOMASK_SRC = protomask/Cargo.toml $(shell find protomask/src/ -type f -name '*.rs')
PROTOMASK_EBPF_SRC = protomask-ebpf/Cargo.toml $(shell find protomask-ebpf/src/ -type f -name '*.rs')
# Used to auto-version things
CRATE_VERSION = $(shell sed -n -r "s/^version = \"([0-9\.]+)\"/\1/p" Cargo.toml)
CRATE_VERSION = $(shell sed -n -r "s/^version = \"([0-9\.]+)\"/\1/p" protomask/Cargo.toml)
TOOLCHAIN_CHANNEL = $(shell sed -n -r "s/^channel = \"(.+)\"/\1/p" rust-toolchain.toml)
target/x86_64-unknown-linux-musl/release/protomask: $(SRC)
cross build --target x86_64-unknown-linux-musl --release
.PHONY: clean
target/aarch64-unknown-linux-musl/release/protomask: $(SRC)
cross build --target aarch64-unknown-linux-musl --release
# x64 Protomask binary (Debug)
target/x86_64-unknown-linux-musl/debug/protomask: $(PROTOMASK_SRC) target/bpfel-unknown-none/debug/protomask-ebpf
cross build --target x86_64-unknown-linux-musl --bin protomask
target/x86_64-unknown-linux-musl/debian/protomask_${CRATE_VERSION}_amd64.deb: target/x86_64-unknown-linux-musl/release/protomask
cargo deb --target x86_64-unknown-linux-musl --no-build
# x64 Protomask binary (Release)
target/x86_64-unknown-linux-musl/release/protomask: $(PROTOMASK_SRC) target/bpfel-unknown-none/release/protomask-ebpf
cross build --target x86_64-unknown-linux-musl --bin protomask --release
target/aarch64-unknown-linux-musl/debian/protomask_${CRATE_VERSION}_arm64.deb: target/aarch64-unknown-linux-musl/release/protomask
cargo deb --target aarch64-unknown-linux-musl --no-build
# Docker image used for building bpfel and bpfeb images
.cargo/cross-images/bpfex-unknown-none.dockerfile.timestamp: .cargo/cross-images/bpfex-unknown-none.dockerfile
docker build -t protomask/bpfex-unknown-none -f $< --build-arg TOOLCHAIN_CHANNEL=$(TOOLCHAIN_CHANNEL) .cargo/cross-images
touch .cargo/cross-images/bpfex-unknown-none.dockerfile.timestamp
# Little-Endian BPF (Debug)
target/bpfel-unknown-none/debug/protomask-ebpf: $(PROTOMASK_EBPF_SRC) .cargo/cross-images/bpfex-unknown-none.dockerfile.timestamp
cross build -Z build-std=core --target bpfel-unknown-none --bin protomask-ebpf
# Little-Endian BPF (Release)
target/bpfel-unknown-none/release/protomask-ebpf: $(PROTOMASK_EBPF_SRC) .cargo/cross-images/bpfex-unknown-none.dockerfile.timestamp
cargo build -Z build-std=core --target bpfel-unknown-none --bin protomask-ebpf --release
# Big-Endian BPF (Debug)
target/bpfeb-unknown-none/debug/protomask-ebpf: $(PROTOMASK_EBPF_SRC) .cargo/cross-images/bpfex-unknown-none.dockerfile.timestamp
cross build -Z build-std=core --target bpfeb-unknown-none --bin protomask-ebpf
# Big-Endian BPF (Release)
target/bpfeb-unknown-none/release/protomask-ebpf: $(PROTOMASK_EBPF_SRC) .cargo/cross-images/bpfex-unknown-none.dockerfile.timestamp
cargo build -Z build-std=core --target bpfeb-unknown-none --bin protomask-ebpf --release
# Cleanup task
clean:
cargo clean
# target/x86_64-unknown-linux-musl/debian/protomask_${CRATE_VERSION}_amd64.deb: target/x86_64-unknown-linux-musl/release/protomask
# cargo deb --target x86_64-unknown-linux-musl --no-build
# target/aarch64-unknown-linux-musl/debian/protomask_${CRATE_VERSION}_arm64.deb: target/aarch64-unknown-linux-musl/release/protomask
# cargo deb --target aarch64-unknown-linux-musl --no-build

View File

@ -0,0 +1,5 @@
[package]
name = "protomask-ebpf"
publish = false
version = "0.0.0"
edition = "2021"

View File

@ -0,0 +1,5 @@
#![no_std]
#![no_main]
mod panic;

View File

@ -0,0 +1,6 @@
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
unsafe { core::hint::unreachable_unchecked() }
}

View File

@ -1,23 +0,0 @@
[package]
name = "protomask-tun"
version = "0.1.0"
authors = ["Evan Pratten <ewpratten@gmail.com>"]
edition = "2021"
description = "An async interface to Linux TUN devices"
readme = "README.md"
homepage = "https://github.com/ewpratten/protomask/protomask-tun"
documentation = "https://docs.rs/protomask-tun"
repository = "https://github.com/ewpratten/protomask"
license = "GPL-3.0"
keywords = []
categories = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.29.1", features = ["sync", "rt"] }
log = "0.4.19"
thiserror = "1.0.43"
tun-tap = "0.1.3"
rtnetlink = "0.13.0"
futures = "0.3.28"
ipnet = "^2.8.0"

View File

@ -1,3 +0,0 @@
# protomask-tun
An async interface to Linux TUN devices. Support library for `protomask`.

View File

@ -1,10 +0,0 @@
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error(transparent)]
NetlinkError(#[from] rtnetlink::Error),
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -1,5 +0,0 @@
mod error;
mod tun;
pub use error::{Error, Result};
pub use tun::TunDevice;

View File

@ -1,226 +0,0 @@
use std::{
io::{Read, Write},
net::IpAddr,
os::fd::{AsRawFd, FromRawFd},
};
use futures::TryStreamExt;
use ipnet::IpNet;
use tokio::{
sync::{broadcast, mpsc},
task,
};
use tun_tap::Mode;
use crate::Result;
#[derive(Debug)]
pub struct TunDevice {
device: tun_tap::Iface,
rt_handle: rtnetlink::Handle,
link_index: u32,
mtu: usize,
}
impl TunDevice {
/// Create and bring up a new TUN device
///
/// ## Name format
///
/// The name field can be any string. If `%d` is present in the string,
/// it will be replaced with a unique number.
pub async fn new(name: &str) -> Result<Self> {
// Bring up an rtnetlink connection
let (rt_connection, rt_handle, _) = rtnetlink::new_connection().map_err(|err| {
log::error!("Failed to open rtnetlink connection");
log::error!("{}", err);
err
})?;
tokio::spawn(rt_connection);
// Create the TUN device
let tun_device = tun_tap::Iface::without_packet_info(name, Mode::Tun)?;
log::debug!("Created new TUN device: {}", tun_device.name());
// Get access to the link through rtnetlink
// NOTE: I don't think there is any way this can fail, so `except` is probably OK
let tun_link = rt_handle
.link()
.get()
.match_name(tun_device.name().to_owned())
.execute()
.try_next()
.await?
.expect("Failed to access newly created TUN device");
// Bring the link up
rt_handle
.link()
.set(tun_link.header.index)
.up()
.execute()
.await
.map_err(|err| {
log::error!("Failed to bring up link");
log::error!("{}", err);
err
})?;
log::debug!("Brought {} up", tun_device.name());
// Read the link MTU
let mtu: usize =
std::fs::read_to_string(format!("/sys/class/net/{}/mtu", tun_device.name()))
.expect("Failed to read link MTU")
.strip_suffix("\n")
.unwrap()
.parse()
.unwrap();
Ok(Self {
device: tun_device,
rt_handle,
link_index: tun_link.header.index,
mtu,
})
}
/// Add an IP address to this device
pub async fn add_address(&mut self, ip_address: IpAddr, prefix_len: u8) -> Result<()> {
self.rt_handle
.address()
.add(self.link_index, ip_address, prefix_len)
.execute()
.await
.map_err(|err| {
log::error!("Failed to add address {} to link", ip_address);
log::error!("{}", err);
err
})?;
Ok(())
}
/// Remove an IP address from this device
pub async fn remove_address(&mut self, ip_address: IpAddr, prefix_len: u8) -> Result<()> {
// Find the address message that matches the given address
if let Some(address_message) = self
.rt_handle
.address()
.get()
.set_link_index_filter(self.link_index)
.set_address_filter(ip_address)
.set_prefix_length_filter(prefix_len)
.execute()
.try_next()
.await
.map_err(|err| {
log::error!("Failed to find address {} on link", ip_address);
log::error!("{}", err);
err
})?
{
// Delete the address
self.rt_handle
.address()
.del(address_message)
.execute()
.await
.map_err(|err| {
log::error!("Failed to remove address {} from link", ip_address);
log::error!("{}", err);
err
})?;
}
Ok(())
}
/// Add a route to this device
pub async fn add_route(&mut self, destination: IpNet) -> Result<()> {
match destination {
IpNet::V4(destination) => {
self.rt_handle
.route()
.add()
.v4()
.output_interface(self.link_index)
.destination_prefix(destination.addr(), destination.prefix_len())
.execute()
.await
.map_err(|err| {
log::error!("Failed to add route {} to link", destination);
log::error!("{}", err);
err
})?;
}
IpNet::V6(destination) => {
self.rt_handle
.route()
.add()
.v6()
.output_interface(self.link_index)
.destination_prefix(destination.addr(), destination.prefix_len())
.execute()
.await
.map_err(|err| {
log::error!("Failed to add route {} to link", destination);
log::error!("{}", err);
err
})?;
}
}
Ok(())
}
/// Spawns worker threads, and returns a tx/rx pair for the caller to interact with them
pub async fn spawn_worker(&self) -> (mpsc::Sender<Vec<u8>>, broadcast::Receiver<Vec<u8>>) {
// Create a channel for packets to be sent to the caller
let (tx_to_caller, rx_from_worker) = broadcast::channel(65535);
// Create a channel for packets being received from the caller
let (tx_to_worker, mut rx_from_caller) = mpsc::channel(65535);
// Clone some values for use in worker threads
let mtu = self.mtu;
let device_fd = self.device.as_raw_fd();
// Create a task that broadcasts all incoming packets
let _rx_task = task::spawn_blocking(move || {
// Build a buffer to read packets into
let mut buffer = vec![0u8; mtu];
// Create a file to access the TUN device
let mut device = unsafe { std::fs::File::from_raw_fd(device_fd) };
loop {
// Read a packet from the TUN device
let packet_len = device.read(&mut buffer[..]).unwrap();
let packet = buffer[..packet_len].to_vec();
// Broadcast the packet to all listeners
tx_to_caller.send(packet).unwrap();
}
});
// Create a task that sends all outgoing packets
let _tx_task = task::spawn(async move {
// Create a file to access the TUN device
let mut device = unsafe { std::fs::File::from_raw_fd(device_fd) };
loop {
// Wait for a packet to be sent
let packet: Vec<u8> = rx_from_caller.recv().await.unwrap();
// Write the packet to the TUN device
device.write_all(&packet[..]).unwrap();
}
});
// Create a task that sends all outgoing packets
let _tx_task = task::spawn_blocking(|| {});
// Return an rx/tx pair for the caller to interact with the workers
(tx_to_worker, rx_from_worker)
}
}

18
protomask/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "protomask"
version = "0.2.0"
authors = ["Evan Pratten <ewpratten@gmail.com>"]
edition = "2021"
description = "A user space NAT64 implementation"
readme = "README.md"
homepage = "https://github.com/ewpratten/protomask"
documentation = "https://docs.rs/protomask"
repository = "https://github.com/ewpratten/protomask"
license = "GPL-3.0"
keywords = []
categories = []
[dependencies]
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread", "sync"] }
clap = { version = "4.3.11", features = ["derive"] }

5
protomask/src/main.rs Normal file
View File

@ -0,0 +1,5 @@
#[tokio::main]
pub async fn main(){
}

15
rust-toolchain.toml Normal file
View File

@ -0,0 +1,15 @@
[toolchain]
channel = "nightly-2023-07-26"
# components = [
# "cargo",
# "clippy",
# "rust-docs",
# "rust-src",
# "rust-std",
# "rustc",
# "rustfmt",
# ]
# targets = [
# "bpfel-unknown-none",
# "bpfeb-unknown-none",
# ]