Skip to main content

roowho2_lib/server/rwhod/
packet_sender.rs

1use nix::{ifaddrs::getifaddrs, net::if_::InterfaceFlags};
2use std::{
3    collections::HashSet,
4    net::{IpAddr, SocketAddr},
5    sync::Arc,
6};
7use tokio::{
8    net::UdpSocket,
9    time::{Duration as TokioDuration, interval},
10};
11
12use crate::{
13    proto::Whod,
14    server::{ignore_list::IgnoreList, rwhod::rwhod_status::generate_rwhod_status_update},
15};
16
17/// Default port for rwhod communication.
18pub const RWHOD_BROADCAST_PORT: u16 = 513;
19
20#[derive(Debug, Clone)]
21pub struct RwhodSendTarget {
22    /// Name of the network interface.
23    pub name: String,
24
25    /// Address to send rwhod packets to.
26    /// This is either the broadcast address (for broadcast interfaces)
27    /// or the point-to-point destination address (for point-to-point interfaces).
28    pub addr: IpAddr,
29}
30
31/// Find all networks network interfaces suitable for rwhod communication.
32pub fn determine_relevant_interfaces() -> anyhow::Result<Vec<RwhodSendTarget>> {
33    getifaddrs().map_err(|e| e.into()).map(|ifaces| {
34        ifaces
35            // interface must be up
36            .filter(|iface| iface.flags.contains(InterfaceFlags::IFF_UP))
37            // interface must be broadcast or point-to-point
38            .filter(|iface| {
39                iface
40                    .flags
41                    .intersects(InterfaceFlags::IFF_BROADCAST | InterfaceFlags::IFF_POINTOPOINT)
42            })
43            .filter_map(|iface| {
44                let neighbor_addr = if iface.flags.contains(InterfaceFlags::IFF_BROADCAST) {
45                    iface.broadcast
46                } else if iface.flags.contains(InterfaceFlags::IFF_POINTOPOINT) {
47                    iface.destination
48                } else {
49                    None
50                };
51
52                match neighbor_addr {
53                    Some(addr) => addr
54                        .as_sockaddr_in()
55                        .map(|sa| IpAddr::V4(sa.ip()))
56                        .or_else(|| addr.as_sockaddr_in6().map(|sa| IpAddr::V6(sa.ip())))
57                        .map(|ip_addr| RwhodSendTarget {
58                            name: iface.interface_name,
59                            addr: ip_addr,
60                        }),
61                    None => None,
62                }
63            })
64            // keep first occurrence per interface name
65            .scan(HashSet::new(), |seen, n| {
66                if seen.insert(n.name.clone()) {
67                    Some(n)
68                } else {
69                    None
70                }
71            })
72            .collect::<Vec<RwhodSendTarget>>()
73    })
74}
75
76pub async fn send_rwhod_packet_to_interface(
77    socket: Arc<UdpSocket>,
78    interface: &RwhodSendTarget,
79    packet: &Whod,
80) -> anyhow::Result<()> {
81    let serialized_packet = packet.to_bytes();
82
83    // TODO: the old rwhod daemon doesn't actually ever listen to ipv6, maybe remove it
84    let target_addr = match interface.addr {
85        IpAddr::V4(addr) => SocketAddr::new(IpAddr::V4(addr), RWHOD_BROADCAST_PORT),
86        IpAddr::V6(addr) => SocketAddr::new(IpAddr::V6(addr), RWHOD_BROADCAST_PORT),
87    };
88
89    tracing::debug!(
90        "Sending rwhod packet to interface {} at address {}",
91        interface.name,
92        target_addr
93    );
94
95    socket
96        .send_to(&serialized_packet, &target_addr)
97        .await
98        .map_err(|e| anyhow::anyhow!("Failed to send rwhod packet: {}", e))?;
99
100    Ok(())
101}
102
103pub async fn rwhod_packet_sender_task(
104    socket: Arc<UdpSocket>,
105    interfaces: Vec<RwhodSendTarget>,
106    ignore_list: Option<IgnoreList>,
107) -> anyhow::Result<()> {
108    let mut interval = interval(TokioDuration::from_secs(60));
109
110    loop {
111        interval.tick().await;
112
113        let status_update = generate_rwhod_status_update(ignore_list.as_ref())?;
114
115        tracing::debug!("Generated rwhod packet: {:?}", status_update);
116
117        let packet = status_update
118            .try_into()
119            .map_err(|e| anyhow::anyhow!("{}", e))?;
120
121        for interface in &interfaces {
122            if let Err(e) = send_rwhod_packet_to_interface(socket.clone(), interface, &packet).await
123            {
124                tracing::error!(
125                    "Failed to send rwhod packet on interface {}: {}",
126                    interface.name,
127                    e
128                );
129            }
130        }
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_determine_relevant_interfaces() {
140        let interfaces = determine_relevant_interfaces().unwrap();
141        for interface in interfaces {
142            println!("Interface: {} Address: {}", interface.name, interface.addr);
143        }
144    }
145}