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

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

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

            
21
/// How long to wait after a realtime update before actually reading utmp and sending a status update.
22
/// This is necessary because audit log events can arrive before the utmp record gets updated.
23
const REALTIME_UPDATE_SETTLE_DELAY: TokioDuration = TokioDuration::from_millis(500);
24

            
25
#[derive(Debug, Clone)]
26
pub struct RwhodSendTarget {
27
    /// Name of the network interface.
28
    pub name: String,
29

            
30
    /// Address to send rwhod packets to.
31
    /// This is either the broadcast address (for broadcast interfaces)
32
    /// or the point-to-point destination address (for point-to-point interfaces).
33
    pub addr: IpAddr,
34
}
35

            
36
/// Computes the broadcast address for an IPv4 address/netmask pair
37
4
fn ipv4_broadcast_address(address: Ipv4Addr, netmask: Ipv4Addr) -> Ipv4Addr {
38
4
    Ipv4Addr::from(u32::from(address) | !u32::from(netmask))
39
4
}
40

            
41
/// Find all networks network interfaces suitable for rwhod communication.
42
///
43
/// If `allowed_interfaces` is `Some`, only interfaces whose name is contained
44
/// in it are considered; otherwise all suitable interfaces are returned.
45
2
pub fn determine_relevant_interfaces(
46
2
    allowed_interfaces: Option<&HashSet<String>>,
47
2
) -> anyhow::Result<Vec<RwhodSendTarget>> {
48
2
    getifaddrs().map_err(|e| e.into()).map(|ifaces| {
49
2
        ifaces
50
            // interface must be up
51
12
            .filter(|iface| iface.flags.contains(InterfaceFlags::IFF_UP))
52
            // interface must be broadcast or point-to-point
53
12
            .filter(|iface| {
54
12
                iface
55
12
                    .flags
56
12
                    .intersects(InterfaceFlags::IFF_BROADCAST | InterfaceFlags::IFF_POINTOPOINT)
57
12
            })
58
            // interface must be in the configured allowlist, if any
59
6
            .filter(|iface| {
60
6
                allowed_interfaces
61
6
                    .is_none_or(|allowed| allowed.contains(iface.interface_name.as_str()))
62
6
            })
63
3
            .filter_map(|iface| {
64
3
                let neighbor_addr = if iface.flags.contains(InterfaceFlags::IFF_BROADCAST) {
65
                    match (
66
3
                        iface.address.as_ref().and_then(|a| a.as_sockaddr_in()),
67
3
                        iface.netmask.as_ref().and_then(|a| a.as_sockaddr_in()),
68
                    ) {
69
1
                        (Some(addr), Some(mask)) => {
70
1
                            Some(ipv4_broadcast_address(addr.ip(), mask.ip()).into())
71
                        }
72
2
                        _ => None,
73
                    }
74
                } else if iface.flags.contains(InterfaceFlags::IFF_POINTOPOINT) {
75
                    iface.destination.and_then(|addr| {
76
                        addr.as_sockaddr_in()
77
                            .map(|sa| IpAddr::V4(sa.ip()))
78
                            .or_else(|| addr.as_sockaddr_in6().map(|sa| IpAddr::V6(sa.ip())))
79
                    })
80
                } else {
81
                    None
82
                };
83

            
84
3
                neighbor_addr.map(|ip_addr| RwhodSendTarget {
85
1
                    name: iface.interface_name,
86
1
                    addr: ip_addr,
87
1
                })
88
3
            })
89
            // keep first occurrence per interface name
90
2
            .scan(HashSet::new(), |seen, n| {
91
1
                if seen.insert(n.name.clone()) {
92
1
                    Some(n)
93
                } else {
94
                    None
95
                }
96
1
            })
97
2
            .collect::<Vec<RwhodSendTarget>>()
98
2
    })
99
2
}
100

            
101
pub async fn send_rwhod_packet_to_interface(
102
    socket: Arc<UdpSocket>,
103
    interface: &RwhodSendTarget,
104
    packet: &Whod,
105
) -> anyhow::Result<()> {
106
    let serialized_packet = packet.to_bytes();
107

            
108
    // TODO: the old rwhod daemon doesn't actually ever listen to ipv6, maybe remove it
109
    let target_addr = match interface.addr {
110
        IpAddr::V4(addr) => SocketAddr::new(IpAddr::V4(addr), RWHOD_BROADCAST_PORT),
111
        IpAddr::V6(addr) => SocketAddr::new(IpAddr::V6(addr), RWHOD_BROADCAST_PORT),
112
    };
113

            
114
    tracing::debug!(
115
        "Sending rwhod packet to interface {} at address {}",
116
        interface.name,
117
        target_addr
118
    );
119

            
120
    socket
121
        .send_to(&serialized_packet, &target_addr)
122
        .await
123
        .map_err(|e| anyhow::anyhow!("Failed to send rwhod packet: {}", e))?;
124

            
125
    Ok(())
126
}
127

            
128
pub async fn rwhod_packet_sender_task(
129
    socket: Arc<UdpSocket>,
130
    interfaces: Vec<RwhodSendTarget>,
131
    ignore_list: Option<IgnoreList>,
132
    send_interval: TokioDuration,
133
    mut realtime_update_trigger: mpsc::Receiver<()>,
134
) -> anyhow::Result<()> {
135
    let mut interval = interval(send_interval);
136
    let mut trigger_closed = false;
137

            
138
    loop {
139
        if trigger_closed {
140
            interval.tick().await;
141
        } else {
142
            tokio::select! {
143
                _ = interval.tick() => {}
144
                triggered = realtime_update_trigger.recv() => {
145
                    if triggered.is_some() {
146
                        tracing::debug!("Sending an early rwhod update due to realtime trigger");
147
                        interval.reset();
148
                        tokio::time::sleep(REALTIME_UPDATE_SETTLE_DELAY).await;
149
                    } else {
150
                        tracing::warn!(
151
                            "Realtime update channel closed unexpectedly; falling back to interval-only updates"
152
                        );
153
                        trigger_closed = true;
154
                        continue;
155
                    }
156
                }
157
            }
158
        }
159

            
160
        let status_update = generate_rwhod_status_update(ignore_list.as_ref())?;
161

            
162
        tracing::debug!("Generated rwhod packet: {:?}", status_update);
163

            
164
        let packet = status_update
165
            .try_into()
166
            .map_err(|e| anyhow::anyhow!("{}", e))?;
167

            
168
        for interface in &interfaces {
169
            if let Err(e) = send_rwhod_packet_to_interface(socket.clone(), interface, &packet).await
170
            {
171
                tracing::error!(
172
                    "Failed to send rwhod packet on interface {}: {}",
173
                    interface.name,
174
                    e
175
                );
176
            }
177
        }
178
    }
179
}
180

            
181
#[cfg(test)]
182
mod tests {
183
    use super::*;
184

            
185
    #[test]
186
1
    fn test_ipv4_broadcast_address() {
187
1
        assert_eq!(
188
1
            ipv4_broadcast_address(
189
1
                Ipv4Addr::new(192, 168, 1, 2),
190
1
                Ipv4Addr::new(255, 255, 255, 0)
191
            ),
192
1
            Ipv4Addr::new(192, 168, 1, 255)
193
        );
194

            
195
1
        assert_eq!(
196
1
            ipv4_broadcast_address(Ipv4Addr::new(10, 0, 5, 200), Ipv4Addr::new(255, 0, 0, 0)),
197
1
            Ipv4Addr::new(10, 255, 255, 255)
198
        );
199

            
200
1
        assert_eq!(
201
1
            ipv4_broadcast_address(
202
1
                Ipv4Addr::new(203, 0, 113, 42),
203
1
                Ipv4Addr::new(255, 255, 255, 255)
204
            ),
205
1
            Ipv4Addr::new(203, 0, 113, 42)
206
        );
207
1
    }
208

            
209
    #[test]
210
1
    fn test_determine_relevant_interfaces() {
211
1
        let interfaces = determine_relevant_interfaces(None).unwrap();
212
1
        for interface in interfaces {
213
1
            println!("Interface: {} Address: {}", interface.name, interface.addr);
214
1
        }
215
1
    }
216

            
217
    #[test]
218
1
    fn test_determine_relevant_interfaces_with_allowlist_excludes_unlisted() {
219
1
        let allowed = HashSet::from(["definitely-not-a-real-interface".to_string()]);
220
1
        let interfaces = determine_relevant_interfaces(Some(&allowed)).unwrap();
221
1
        assert!(interfaces.is_empty());
222
1
    }
223
}