diff --git a/nix/module.nix b/nix/module.nix index 6272610..1c5bcae 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -36,6 +36,21 @@ in { description = "Path to the ignore list for users that should be hidden from rwhod."; }; + interfaces = lib.mkOption { + type = with lib.types; nullOr (list str); + default = null; + example = [ "eth0" "wlan0" ]; + description = '' + List of network interfaces to broadcast rwhod packets on. + + If set to `null`, rwhod will try to autodetect all reasonable interfaces + to broadcast on, and use all of them. + + If set to an empty list, rwhod will not broadcast on any interfaces, + and will only listen for incoming packets. + ''; + }; + socketConfig = lib.mkOption { type = utils.systemdUtils.unitOptions.unitOption; default = { }; diff --git a/src/bin/roowhod.rs b/src/bin/roowhod.rs index d0cbcb2..2ae6792 100644 --- a/src/bin/roowhod.rs +++ b/src/bin/roowhod.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, os::fd::{AsRawFd, FromRawFd, OwnedFd}, path::PathBuf, sync::Arc, @@ -104,6 +104,7 @@ async fn main() -> anyhow::Result<()> { socket, whod_status_store.clone(), rwhod_ignore_list.clone(), + config.rwhod.interfaces.clone(), )); } else { tracing::debug!("RWHOD server is disabled in configuration"); @@ -139,10 +140,12 @@ async fn rwhod_server( socket: UdpSocket, whod_status_store: RwhodStatusStore, ignore_list: Option, + allowed_interfaces: Option>, ) -> anyhow::Result<()> { let socket = Arc::new(socket); - let interfaces = roowho2_lib::server::rwhod::determine_relevant_interfaces()?; + let interfaces = + roowho2_lib::server::rwhod::determine_relevant_interfaces(allowed_interfaces.as_ref())?; let sender_task = rwhod_packet_sender_task(socket.clone(), interfaces, ignore_list); let receiver_task = rwhod_packet_receiver_task(socket.clone(), whod_status_store); diff --git a/src/server/config.rs b/src/server/config.rs index ddcf890..d38bfae 100644 --- a/src/server/config.rs +++ b/src/server/config.rs @@ -1,4 +1,4 @@ -use std::{net::SocketAddrV4, path::PathBuf}; +use std::{collections::HashSet, path::PathBuf}; use serde::{Deserialize, Serialize}; @@ -39,17 +39,10 @@ pub struct RwhodConfig { /// Path to the ignore list for users that should be hidden from rwhod. pub ignore_list_path: Option, - /// Network interfaces to listen on (e.g., ["eth0", "wlan0"]). + /// Network interfaces to send rwhod packets on (e.g., ["eth0", "wlan0"]). /// - /// If left as `None`, the server will automatically determine relevant interfaces. - /// - /// Note that if `broadcast_addresses` is specified, this field is ignored. - pub interfaces: Option>, - - /// Broadcast addresses to send rwhod packets to. - /// - /// If left as `None`, the server will automatically determine broadcast addresses for the selected interfaces. - pub broadcast_addresses: Option>, + /// If left as `None`, the server will send on all relevant interfaces it can find. + pub interfaces: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/server/rwhod/packet_sender.rs b/src/server/rwhod/packet_sender.rs index ca100a6..21bc1f5 100644 --- a/src/server/rwhod/packet_sender.rs +++ b/src/server/rwhod/packet_sender.rs @@ -29,7 +29,12 @@ pub struct RwhodSendTarget { } /// Find all networks network interfaces suitable for rwhod communication. -pub fn determine_relevant_interfaces() -> anyhow::Result> { +/// +/// If `allowed_interfaces` is `Some`, only interfaces whose name is contained +/// in it are considered; otherwise all suitable interfaces are returned. +pub fn determine_relevant_interfaces( + allowed_interfaces: Option<&HashSet>, +) -> anyhow::Result> { getifaddrs().map_err(|e| e.into()).map(|ifaces| { ifaces // interface must be up @@ -40,6 +45,11 @@ pub fn determine_relevant_interfaces() -> anyhow::Result> { .flags .intersects(InterfaceFlags::IFF_BROADCAST | InterfaceFlags::IFF_POINTOPOINT) }) + // interface must be in the configured allowlist, if any + .filter(|iface| { + allowed_interfaces + .is_none_or(|allowed| allowed.contains(iface.interface_name.as_str())) + }) .filter_map(|iface| { let neighbor_addr = if iface.flags.contains(InterfaceFlags::IFF_BROADCAST) { iface.broadcast @@ -137,9 +147,16 @@ mod tests { #[test] fn test_determine_relevant_interfaces() { - let interfaces = determine_relevant_interfaces().unwrap(); + let interfaces = determine_relevant_interfaces(None).unwrap(); for interface in interfaces { println!("Interface: {} Address: {}", interface.name, interface.addr); } } + + #[test] + fn test_determine_relevant_interfaces_with_allowlist_excludes_unlisted() { + let allowed = HashSet::from(["definitely-not-a-real-interface".to_string()]); + let interfaces = determine_relevant_interfaces(Some(&allowed)).unwrap(); + assert!(interfaces.is_empty()); + } }