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