rwhod: implement packet sender if filtering

This commit is contained in:
2026-07-20 20:01:50 +09:00
parent dada91184d
commit 4b01e27907
4 changed files with 43 additions and 15 deletions
+15
View File
@@ -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 = { };
+5 -2
View File
@@ -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<IgnoreList>,
allowed_interfaces: Option<HashSet<String>>,
) -> 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);
+4 -11
View File
@@ -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<PathBuf>,
/// 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<Vec<String>>,
/// 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<Vec<SocketAddrV4>>,
/// If left as `None`, the server will send on all relevant interfaces it can find.
pub interfaces: Option<HashSet<String>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+19 -2
View File
@@ -29,7 +29,12 @@ pub struct RwhodSendTarget {
}
/// Find all networks network interfaces suitable for rwhod communication.
pub fn determine_relevant_interfaces() -> anyhow::Result<Vec<RwhodSendTarget>> {
///
/// 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<String>>,
) -> anyhow::Result<Vec<RwhodSendTarget>> {
getifaddrs().map_err(|e| e.into()).map(|ifaces| {
ifaces
// interface must be up
@@ -40,6 +45,11 @@ pub fn determine_relevant_interfaces() -> anyhow::Result<Vec<RwhodSendTarget>> {
.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());
}
}