1
use nix::{ifaddrs::getifaddrs, net::if_::InterfaceFlags};
2
use std::{
3
    collections::HashSet,
4
    net::{IpAddr, SocketAddr},
5
    sync::Arc,
6
};
7
use tokio::{
8
    net::UdpSocket,
9
    time::{Duration as TokioDuration, interval},
10
};
11

            
12
use crate::{
13
    proto::Whod,
14
    server::{ignore_list::IgnoreList, rwhod::rwhod_status::generate_rwhod_status_update},
15
};
16

            
17
/// Default port for rwhod communication.
18
pub const RWHOD_BROADCAST_PORT: u16 = 513;
19

            
20
#[derive(Debug, Clone)]
21
pub 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.
32
1
pub fn determine_relevant_interfaces() -> anyhow::Result<Vec<RwhodSendTarget>> {
33
1
    getifaddrs().map_err(|e| e.into()).map(|ifaces| {
34
1
        ifaces
35
            // interface must be up
36
6
            .filter(|iface| iface.flags.contains(InterfaceFlags::IFF_UP))
37
            // interface must be broadcast or point-to-point
38
6
            .filter(|iface| {
39
6
                iface
40
6
                    .flags
41
6
                    .intersects(InterfaceFlags::IFF_BROADCAST | InterfaceFlags::IFF_POINTOPOINT)
42
6
            })
43
3
            .filter_map(|iface| {
44
3
                let neighbor_addr = if iface.flags.contains(InterfaceFlags::IFF_BROADCAST) {
45
3
                    iface.broadcast
46
                } else if iface.flags.contains(InterfaceFlags::IFF_POINTOPOINT) {
47
                    iface.destination
48
                } else {
49
                    None
50
                };
51

            
52
3
                match neighbor_addr {
53
2
                    Some(addr) => addr
54
2
                        .as_sockaddr_in()
55
2
                        .map(|sa| IpAddr::V4(sa.ip()))
56
2
                        .or_else(|| addr.as_sockaddr_in6().map(|sa| IpAddr::V6(sa.ip())))
57
2
                        .map(|ip_addr| RwhodSendTarget {
58
1
                            name: iface.interface_name,
59
1
                            addr: ip_addr,
60
1
                        }),
61
1
                    None => None,
62
                }
63
3
            })
64
            // keep first occurrence per interface name
65
1
            .scan(HashSet::new(), |seen, n| {
66
1
                if seen.insert(n.name.clone()) {
67
1
                    Some(n)
68
                } else {
69
                    None
70
                }
71
1
            })
72
1
            .collect::<Vec<RwhodSendTarget>>()
73
1
    })
74
1
}
75

            
76
pub 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

            
103
pub 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)]
135
mod tests {
136
    use super::*;
137

            
138
    #[test]
139
1
    fn test_determine_relevant_interfaces() {
140
1
        let interfaces = determine_relevant_interfaces().unwrap();
141
1
        for interface in interfaces {
142
1
            println!("Interface: {} Address: {}", interface.name, interface.addr);
143
1
        }
144
1
    }
145
}