summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ci/ios/test-router/flake.lock8
-rw-r--r--ci/ios/test-router/flake.nix2
-rw-r--r--ci/ios/test-router/raas/Cargo.lock10
-rw-r--r--ci/ios/test-router/raas/Cargo.toml2
-rw-r--r--ci/ios/test-router/raas/src/block_list/mod.rs2
-rw-r--r--ci/ios/test-router/raas/src/block_list/rule.rs140
-rw-r--r--ci/ios/test-router/raas/src/capture/mod.rs9
-rw-r--r--ci/ios/test-router/raas/src/capture/parse.rs4
-rw-r--r--ci/ios/test-router/raas/src/web/routes.rs99
-rw-r--r--ci/ios/test-router/router-config.nix2
10 files changed, 189 insertions, 89 deletions
diff --git a/ci/ios/test-router/flake.lock b/ci/ios/test-router/flake.lock
index 064ee7a736..6a449efb7e 100644
--- a/ci/ios/test-router/flake.lock
+++ b/ci/ios/test-router/flake.lock
@@ -2,16 +2,16 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1720535198,
- "narHash": "sha256-zwVvxrdIzralnSbcpghA92tWu2DV2lwv89xZc8MTrbg=",
+ "lastModified": 1742937945,
+ "narHash": "sha256-lWc+79eZRyvHp/SqMhHTMzZVhpxkRvthsP1Qx6UCq0E=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "205fd4226592cc83fd4c0885a3e4c9c400efabb5",
+ "rev": "d02d88f8de5b882ccdde0465d8fa2db3aa1169f7",
"type": "github"
},
"original": {
"owner": "NixOS",
- "ref": "nixos-23.11",
+ "ref": "nixos-24.11",
"repo": "nixpkgs",
"type": "github"
}
diff --git a/ci/ios/test-router/flake.nix b/ci/ios/test-router/flake.nix
index 7d7593907c..0650830934 100644
--- a/ci/ios/test-router/flake.nix
+++ b/ci/ios/test-router/flake.nix
@@ -1,7 +1,7 @@
{
description = "Config for our testing router";
- inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; };
+ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; };
outputs = { self, nixpkgs }: {
nixosConfigurations.app-team-ios-lab = nixpkgs.lib.nixosSystem {
diff --git a/ci/ios/test-router/raas/Cargo.lock b/ci/ios/test-router/raas/Cargo.lock
index ec9f29813b..04cc80d840 100644
--- a/ci/ios/test-router/raas/Cargo.lock
+++ b/ci/ios/test-router/raas/Cargo.lock
@@ -422,6 +422,15 @@ dependencies = [
]
[[package]]
+name = "ipnetwork"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "is-terminal"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -752,6 +761,7 @@ dependencies = [
"axum",
"env_logger",
"futures",
+ "ipnetwork",
"log",
"mnl",
"nftnl",
diff --git a/ci/ios/test-router/raas/Cargo.toml b/ci/ios/test-router/raas/Cargo.toml
index 5bc0eacf6e..38b59f6480 100644
--- a/ci/ios/test-router/raas/Cargo.toml
+++ b/ci/ios/test-router/raas/Cargo.toml
@@ -29,4 +29,4 @@ serde = { version = "1.0.138", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1", features = [ "serde" ] }
anyhow = "1"
-
+ipnetwork = { version = "0.21", features = [ "serde" ]}
diff --git a/ci/ios/test-router/raas/src/block_list/mod.rs b/ci/ios/test-router/raas/src/block_list/mod.rs
index 8aab004126..82eece7bb1 100644
--- a/ci/ios/test-router/raas/src/block_list/mod.rs
+++ b/ci/ios/test-router/raas/src/block_list/mod.rs
@@ -6,7 +6,7 @@ static TABLE_NAME: Lazy<CString> = Lazy::new(|| CString::new("raas").unwrap());
static FORWARD_CHAIN_NAME: Lazy<CString> = Lazy::new(|| CString::new("forward").unwrap());
mod rule;
-pub use rule::BlockRule;
+pub use rule::{BlockRule, Endpoints};
#[derive(Default)]
pub struct BlockList {
diff --git a/ci/ios/test-router/raas/src/block_list/rule.rs b/ci/ios/test-router/raas/src/block_list/rule.rs
index e6ce90f20c..6604e9c3fa 100644
--- a/ci/ios/test-router/raas/src/block_list/rule.rs
+++ b/ci/ios/test-router/raas/src/block_list/rule.rs
@@ -1,14 +1,25 @@
use crate::web::routes::TransportProtocol;
use mnl::mnl_sys::libc;
-use nftnl::{expr, nft_expr, Chain, Rule};
+use nftnl::{expr, nft_expr, nft_expr_payload, Chain, Rule};
-use std::{collections::BTreeSet, iter, net::IpAddr};
+use ipnetwork::IpNetwork;
+use std::{collections::BTreeSet, iter};
#[derive(Clone, serde::Serialize)]
-pub struct BlockRule {
- pub src: IpAddr,
- pub dst: IpAddr,
- pub protocols: BTreeSet<TransportProtocol>,
+pub enum BlockRule {
+ Host {
+ endpoints: Endpoints,
+ protocols: BTreeSet<TransportProtocol>,
+ },
+ WireGuard {
+ endpoints: Endpoints,
+ },
+}
+
+#[derive(Clone, serde::Serialize)]
+pub struct Endpoints {
+ pub src: IpNetwork,
+ pub dst: IpNetwork,
}
impl BlockRule {
@@ -16,15 +27,15 @@ impl BlockRule {
&'a self,
chain: &'a Chain<'a>,
) -> Box<dyn Iterator<Item = Rule<'a>> + 'a> {
- if self.protocols.is_empty() {
- return Box::new(iter::once(self.create_nft_rule_inner(chain, None)));
+ match self {
+ BlockRule::Host { protocols, .. } if !protocols.is_empty() => {
+ let iter = protocols
+ .iter()
+ .map(|protocol| self.create_nft_rule_inner(chain, Some(*protocol)));
+ Box::new(iter)
+ }
+ _ => Box::new(iter::once(self.create_nft_rule_inner(chain, None))),
}
-
- let iter = self
- .protocols
- .iter()
- .map(|protocol| self.create_nft_rule_inner(chain, Some(*protocol)));
- Box::new(iter)
}
fn create_nft_rule_inner<'a>(
@@ -33,46 +44,42 @@ impl BlockRule {
transport_protocol: Option<TransportProtocol>,
) -> Rule<'a> {
let mut rule = Rule::new(chain);
- check_l3proto(&mut rule, self.src);
- if let Some(protocol) = transport_protocol {
- check_l4proto(&mut rule, protocol);
- };
- // Add source checking
- rule.add_expr(match self.src {
- IpAddr::V4(_) => &nft_expr!(payload ipv4 saddr),
- IpAddr::V6(_) => &nft_expr!(payload ipv6 saddr),
- });
- match self.src {
- IpAddr::V4(addr) => rule.add_expr(&nft_expr!(cmp == addr)),
- IpAddr::V6(addr) => rule.add_expr(&nft_expr!(cmp == addr)),
- };
+ match *self {
+ BlockRule::Host {
+ endpoints: Endpoints { src, dst },
+ ..
+ } => {
+ check_l3proto(&mut rule, src);
+ if let Some(protocol) = transport_protocol {
+ check_l4proto(&mut rule, protocol);
+ };
+ check_ip_addrs(&mut rule, src, dst);
+ }
+ BlockRule::WireGuard {
+ endpoints: Endpoints { src, dst },
+ } => {
+ check_l3proto(&mut rule, src);
+ check_ip_addrs(&mut rule, src, dst);
+ check_wireguard_traffic(&mut rule);
+ }
+ }
- // Add destination check
- rule.add_expr(match self.dst {
- IpAddr::V4(_) => &nft_expr!(payload ipv4 daddr),
- IpAddr::V6(_) => &nft_expr!(payload ipv6 daddr),
- });
- match self.dst {
- IpAddr::V4(addr) => rule.add_expr(&nft_expr!(cmp == addr)),
- IpAddr::V6(addr) => rule.add_expr(&nft_expr!(cmp == addr)),
- };
rule.add_expr(&nft_expr!(counter));
rule.add_expr(&expr::Verdict::Drop);
-
rule
}
}
-fn check_l3proto(rule: &mut Rule<'_>, ip: IpAddr) {
+fn check_l3proto(rule: &mut Rule<'_>, ip: IpNetwork) {
rule.add_expr(&nft_expr!(meta nfproto));
rule.add_expr(&nft_expr!(cmp == l3proto(ip)));
}
-fn l3proto(addr: IpAddr) -> u8 {
+fn l3proto(addr: IpNetwork) -> u8 {
match addr {
- IpAddr::V4(_) => libc::NFPROTO_IPV4 as u8,
- IpAddr::V6(_) => libc::NFPROTO_IPV6 as u8,
+ IpNetwork::V4(_) => libc::NFPROTO_IPV4 as u8,
+ IpNetwork::V6(_) => libc::NFPROTO_IPV6 as u8,
}
}
@@ -80,3 +87,54 @@ fn check_l4proto(rule: &mut Rule<'_>, protocol: TransportProtocol) {
rule.add_expr(&nft_expr!(meta l4proto));
rule.add_expr(&nft_expr!(cmp == protocol.as_ipproto()));
}
+
+fn check_ip_addrs(rule: &mut Rule, src: IpNetwork, dst: IpNetwork) {
+ // Add source checking
+ rule.add_expr(match src {
+ IpNetwork::V4(_) => &nft_expr!(payload ipv4 saddr),
+ IpNetwork::V6(_) => &nft_expr!(payload ipv6 saddr),
+ });
+ check_matches_prefix(rule, src);
+
+ // Add destination check
+ rule.add_expr(match dst {
+ IpNetwork::V4(_) => &nft_expr!(payload ipv4 daddr),
+ IpNetwork::V6(_) => &nft_expr!(payload ipv6 daddr),
+ });
+ check_matches_prefix(rule, dst);
+
+ fn check_matches_prefix(rule: &mut Rule, network: IpNetwork) {
+ // Compute the bitwise AND of the incoming packet IP address and the mask, and then
+ // compare this value to the bitwise AND of the rule IP address and the mask.
+ // This will match when the network prefixes of the IP address to filter and rule
+ // IP address matches. E.g. the rule IP address/network 34.117.0.0/16 will match
+ // an incoming packet IP address 34.117.105.189.
+ match network {
+ IpNetwork::V4(addr) => {
+ rule.add_expr(&nft_expr!(bitwise mask addr.mask(), xor 0x0));
+ rule.add_expr(&nft_expr!(cmp == addr.ip() & addr.mask()));
+ }
+ IpNetwork::V6(addr) => {
+ rule.add_expr(&nft_expr!(bitwise mask addr.mask(), xor 0x0));
+ rule.add_expr(&nft_expr!(cmp == addr.ip() & addr.mask()));
+ }
+ };
+ }
+}
+
+fn check_wireguard_traffic(rule: &mut Rule) {
+ rule.add_expr(&nft_expr!(meta l4proto));
+ rule.add_expr(&nft_expr!(cmp == libc::IPPROTO_UDP));
+
+ // UDP header is 8 bytes, after which we have the WireGuard header which is 4 bytes,
+ // where the first byte can be 1 to 4 (inclusive) and the last 3 bytes are 0.
+ // See: https://wiki.wireshark.org/WireGuard
+ rule.add_expr(&nft_expr_payload!(th 8, 1));
+ rule.add_expr(&nft_expr!(cmp >= 1));
+
+ rule.add_expr(&nft_expr_payload!(th 8, 1));
+ rule.add_expr(&nft_expr!(cmp <= 4));
+
+ rule.add_expr(&nft_expr_payload!(th 9, 3));
+ rule.add_expr(&nft_expr!(cmp == 0));
+}
diff --git a/ci/ios/test-router/raas/src/capture/mod.rs b/ci/ios/test-router/raas/src/capture/mod.rs
index 992da8395a..c5c04ae82f 100644
--- a/ci/ios/test-router/raas/src/capture/mod.rs
+++ b/ci/ios/test-router/raas/src/capture/mod.rs
@@ -53,7 +53,7 @@ impl PacketCodec for CloneCodec {
}
}
-const RAAS_TMP_DIR: &'static str = "raas";
+const RAAS_TMP_DIR: &str = "raas";
impl Capture {
fn capture_file_path(label: &uuid::Uuid) -> PathBuf {
@@ -94,8 +94,11 @@ impl Capture {
let mut stream = capture.stream(CloneCodec).map_err(Error::CreateStream)?;
let capture = tokio::spawn(async move {
- let (pcap_tx, pcap_rx): (_, sync_mpsc::Receiver<(PacketHeader, Box<[u8]>)>) =
- sync_mpsc::channel();
+ #[allow(clippy::type_complexity)]
+ let (pcap_tx, pcap_rx): (
+ _,
+ sync_mpsc::Receiver<(PacketHeader, Box<[u8]>)>,
+ ) = sync_mpsc::channel();
tokio::task::spawn_blocking(move || {
while let Ok((header, data)) = pcap_rx.recv() {
let packet = Packet {
diff --git a/ci/ios/test-router/raas/src/capture/parse.rs b/ci/ios/test-router/raas/src/capture/parse.rs
index 8da51d1f59..e8f96c280a 100644
--- a/ci/ios/test-router/raas/src/capture/parse.rs
+++ b/ci/ios/test-router/raas/src/capture/parse.rs
@@ -192,7 +192,7 @@ trait IpPacket: pnet_packet::Packet {
}
}
-impl<'a> IpPacket for Ipv4Packet<'a> {
+impl IpPacket for Ipv4Packet<'_> {
fn source(&self) -> IpAddr {
self.get_source().into()
}
@@ -206,7 +206,7 @@ impl<'a> IpPacket for Ipv4Packet<'a> {
}
}
-impl<'a> IpPacket for Ipv6Packet<'a> {
+impl IpPacket for Ipv6Packet<'_> {
fn source(&self) -> IpAddr {
self.get_source().into()
}
diff --git a/ci/ios/test-router/raas/src/web/routes.rs b/ci/ios/test-router/raas/src/web/routes.rs
index a2fae72739..2dfec6f21a 100644
--- a/ci/ios/test-router/raas/src/web/routes.rs
+++ b/ci/ios/test-router/raas/src/web/routes.rs
@@ -1,27 +1,29 @@
-use std::{
- collections::{BTreeMap, BTreeSet},
- net::IpAddr,
-};
-
use axum::{
extract::{Json, Path, State},
http::StatusCode,
response::IntoResponse,
};
+use ipnetwork::IpNetwork;
use mnl::mnl_sys::libc;
+use std::collections::{BTreeMap, BTreeSet};
use uuid::Uuid;
-use crate::block_list::BlockRule;
+use crate::block_list::{BlockList, BlockRule, Endpoints};
+use crate::web;
#[derive(serde::Deserialize, Clone)]
pub struct NewRule {
- pub src: IpAddr,
- pub dst: IpAddr,
+ pub src: IpNetwork,
+ pub dst: IpNetwork,
pub protocols: Option<BTreeSet<TransportProtocol>>,
+ #[serde(default)]
+ pub block_wireguard: bool,
pub label: Uuid,
}
-#[derive(serde::Deserialize, serde::Serialize, PartialOrd, Ord, PartialEq, Eq, Clone, Copy)]
+#[derive(
+ PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug, serde::Deserialize, serde::Serialize,
+)]
#[serde(rename_all = "snake_case")]
pub enum TransportProtocol {
Tcp,
@@ -43,30 +45,29 @@ impl TransportProtocol {
pub async fn add_rule(
State(state): State<super::State>,
- Json(rule): Json<NewRule>,
+ Json(json): Json<NewRule>,
) -> impl IntoResponse {
- let result = tokio::task::spawn_blocking(move || -> anyhow::Result<()> {
- let label = rule.label;
- let rule = BlockRule {
- src: rule.src,
- dst: rule.dst,
- protocols: rule.protocols.unwrap_or_default(),
- };
- let Ok(mut fw) = state.block_list.lock() else {
- return Err(anyhow::anyhow!("Firewall thread panicked"));
+ let result = access_firewall(state, move |fw| {
+ let label = json.label;
+ let src = json.src;
+ let dst = json.dst;
+
+ let rule = if json.block_wireguard {
+ BlockRule::WireGuard {
+ endpoints: Endpoints { src, dst },
+ }
+ } else {
+ BlockRule::Host {
+ endpoints: Endpoints { src, dst },
+ protocols: json.protocols.unwrap_or_default(),
+ }
};
fw.add_rule(rule.clone(), label)?;
- log::info!(
- "Successfully added a rule to block {} from {} for test {}",
- rule.src,
- rule.dst,
- label,
- );
+ log_rule(&rule, &label);
Ok(())
})
- .await
- .expect("failed to join blocking task");
+ .await;
respond_with_result(result, StatusCode::CREATED)
}
@@ -75,18 +76,28 @@ pub async fn delete_rules(
Path(label): Path<Uuid>,
State(state): State<super::State>,
) -> impl IntoResponse {
- let result = tokio::task::spawn_blocking(move || -> anyhow::Result<()> {
+ let result = access_firewall(state, move |fw| {
+ fw.clear_rules_with_label(&label)?;
+ log::info!("Successfully removed all rules for test {label}");
+ Ok(())
+ })
+ .await;
+
+ respond_with_result(result, StatusCode::OK)
+}
+
+pub async fn access_firewall<F>(state: web::State, run: F) -> anyhow::Result<()>
+where
+ F: FnOnce(&mut BlockList) -> anyhow::Result<()> + Send + 'static,
+{
+ tokio::task::spawn_blocking(move || -> anyhow::Result<()> {
let Ok(mut fw) = state.block_list.lock() else {
return Err(anyhow::anyhow!("Firewall thread panicked"));
};
-
- fw.clear_rules_with_label(&label)?;
- log::info!("Successfully removed all rules for test {label}",);
- Ok(())
+ run(&mut fw)
})
.await
- .expect("failed to join blocking task");
- respond_with_result(result, StatusCode::OK)
+ .expect("failed to join blocking task")
}
fn respond_with_result(result: anyhow::Result<()>, success_code: StatusCode) -> impl IntoResponse {
@@ -108,7 +119,25 @@ pub async fn list_all_rules(State(state): State<super::State>) -> impl IntoRespo
.expect("failed to join blocking task");
match all_rules {
- Ok(all_rules) => axum::Json(all_rules).into_response(),
+ Ok(all_rules) => Json(all_rules).into_response(),
Err(err) => (StatusCode::SERVICE_UNAVAILABLE, format!("{err}\n")).into_response(),
}
}
+
+fn log_rule(rule: &BlockRule, label: &Uuid) {
+ match rule {
+ BlockRule::Host {
+ protocols,
+ endpoints: Endpoints { src, dst },
+ } => {
+ log::info!(
+ "Successfully added a rule to block {src} from {dst} for test {label} for protocols {protocols:?}",
+ );
+ }
+ BlockRule::WireGuard {
+ endpoints: Endpoints { src, dst },
+ } => {
+ log::info!("Successfully added a rule to block {src} from {dst} WireGuard traffic for test {label}",);
+ }
+ }
+}
diff --git a/ci/ios/test-router/router-config.nix b/ci/ios/test-router/router-config.nix
index c10a817145..49c6d74614 100644
--- a/ci/ios/test-router/router-config.nix
+++ b/ci/ios/test-router/router-config.nix
@@ -33,7 +33,7 @@ in
networking.hostName = args.hostname;
networking.useDHCP = true;
- system.stateVersion = "23.11";
+ system.stateVersion = "24.11";
systemd.network.netdevs."1-lanBridge" = {
netdevConfig = {