Skip to main content

stackforge_core/layer/ipv4/
routing.rs

1//! IPv4 routing utilities.
2//!
3//! Provides routing table lookup and interface selection for IPv4 packets.
4//! This is used to determine which interface to send packets on and what
5//! the next-hop address should be.
6
7use std::net::Ipv4Addr;
8
9/// Routing information for an IPv4 destination.
10#[derive(Debug, Clone, Default)]
11pub struct Ipv4Route {
12    /// The outgoing interface name.
13    pub interface: Option<String>,
14    /// The source IP address to use.
15    pub source: Option<Ipv4Addr>,
16    /// The next-hop gateway IP (if not on local network).
17    pub gateway: Option<Ipv4Addr>,
18    /// Whether the destination is on the local network.
19    pub is_local: bool,
20    /// The network mask (prefix length).
21    pub prefix_len: u8,
22    /// Route metric (lower is better).
23    pub metric: u32,
24}
25
26impl Ipv4Route {
27    /// Create an empty route (no routing info available).
28    pub fn none() -> Self {
29        Self::default()
30    }
31
32    /// Create a route for a local destination.
33    pub fn local(interface: String, source: Ipv4Addr) -> Self {
34        Self {
35            interface: Some(interface),
36            source: Some(source),
37            gateway: None,
38            is_local: true,
39            prefix_len: 32,
40            metric: 0,
41        }
42    }
43
44    /// Create a route via a gateway.
45    pub fn via_gateway(interface: String, source: Ipv4Addr, gateway: Ipv4Addr) -> Self {
46        Self {
47            interface: Some(interface),
48            source: Some(source),
49            gateway: Some(gateway),
50            is_local: false,
51            prefix_len: 0,
52            metric: 0,
53        }
54    }
55
56    /// Check if this route is valid (has interface).
57    pub fn is_valid(&self) -> bool {
58        self.interface.is_some()
59    }
60
61    /// Get the next-hop address (gateway or destination if local).
62    pub fn next_hop(&self, dst: Ipv4Addr) -> Ipv4Addr {
63        self.gateway.unwrap_or(dst)
64    }
65}
66
67/// A router that can look up routes for destinations.
68pub trait Ipv4Router {
69    /// Look up the route for a destination.
70    fn route(&self, dst: Ipv4Addr) -> Ipv4Route;
71}
72
73/// Get routing information for a destination address.
74///
75/// This uses the system routing table to determine the interface,
76/// source address, and gateway for reaching a destination.
77pub fn get_route(dst: Ipv4Addr) -> Ipv4Route {
78    // Handle special addresses
79    if dst.is_loopback() {
80        return Ipv4Route::local("lo".to_string(), Ipv4Addr::LOCALHOST);
81    }
82
83    if dst.is_broadcast() || dst == Ipv4Addr::BROADCAST {
84        return get_default_route()
85            .map(|r| Ipv4Route {
86                is_local: true,
87                ..r
88            })
89            .unwrap_or_default();
90    }
91
92    if dst.is_multicast() {
93        // Multicast uses the default route interface
94        return get_default_route()
95            .map(|r| Ipv4Route {
96                gateway: None,
97                is_local: true,
98                ..r
99            })
100            .unwrap_or_default();
101    }
102
103    // Try to find a matching interface
104    let interfaces = pnet_datalink::interfaces();
105
106    // First, check for direct connectivity
107    for iface in &interfaces {
108        if !iface.is_up() || iface.is_loopback() {
109            continue;
110        }
111
112        for ip_network in &iface.ips {
113            if let std::net::IpAddr::V4(ip) = ip_network.ip() {
114                // Check if destination is in this subnet
115                if ip_network.contains(std::net::IpAddr::V4(dst)) {
116                    return Ipv4Route {
117                        interface: Some(iface.name.clone()),
118                        source: Some(ip),
119                        gateway: None,
120                        is_local: true,
121                        prefix_len: ip_network.prefix(),
122                        metric: 0,
123                    };
124                }
125            }
126        }
127    }
128
129    // Use default gateway
130    if let Some(route) = get_default_route() {
131        return route;
132    }
133
134    Ipv4Route::none()
135}
136
137/// Get the default route (gateway).
138pub fn get_default_route() -> Option<Ipv4Route> {
139    // Try to get default interface using default-net crate
140    let default_iface = default_net::get_default_interface().ok()?;
141    let gateway = default_net::get_default_gateway().ok()?;
142
143    let interfaces = pnet_datalink::interfaces();
144    let iface = interfaces.iter().find(|i| i.name == default_iface.name)?;
145
146    // Find IPv4 address on this interface
147    let source = iface.ips.iter().find_map(|ip| {
148        if let std::net::IpAddr::V4(v4) = ip.ip() {
149            Some(v4)
150        } else {
151            None
152        }
153    })?;
154
155    let gw_ip = match gateway.ip_addr {
156        std::net::IpAddr::V4(v4) => v4,
157        _ => return None,
158    };
159
160    Some(Ipv4Route {
161        interface: Some(iface.name.clone()),
162        source: Some(source),
163        gateway: Some(gw_ip),
164        is_local: false,
165        prefix_len: 0,
166        metric: 0,
167    })
168}
169
170/// Get the source IP address for a destination.
171///
172/// This performs a routing lookup and returns the appropriate source.
173pub fn get_source_for_dst(dst: Ipv4Addr) -> Option<Ipv4Addr> {
174    get_route(dst).source
175}
176
177/// Get the interface name for a destination.
178pub fn get_interface_for_dst(dst: Ipv4Addr) -> Option<String> {
179    get_route(dst).interface
180}
181
182/// Check if a destination is on the local network.
183pub fn is_local_destination(dst: Ipv4Addr) -> bool {
184    get_route(dst).is_local
185}
186
187/// Get all available IPv4 interfaces.
188pub fn get_ipv4_interfaces() -> Vec<Ipv4Interface> {
189    pnet_datalink::interfaces()
190        .into_iter()
191        .filter_map(|iface| {
192            if !iface.is_up() {
193                return None;
194            }
195
196            let addrs: Vec<_> = iface
197                .ips
198                .iter()
199                .filter_map(|ip| {
200                    if let std::net::IpAddr::V4(v4) = ip.ip() {
201                        Some(Ipv4InterfaceAddr {
202                            address: v4,
203                            prefix_len: ip.prefix(),
204                            // Fix for `and_then` error: match directly on IpAddr
205                            broadcast: match ip.broadcast() {
206                                std::net::IpAddr::V4(v4) => Some(v4),
207                                _ => None,
208                            },
209                        })
210                    } else {
211                        None
212                    }
213                })
214                .collect();
215
216            if addrs.is_empty() {
217                return None;
218            }
219
220            // Fix for "Value used after being moved":
221            // Extract boolean flags BEFORE moving `iface.name`
222            let is_loopback = iface.is_loopback();
223            let is_up = iface.is_up();
224            let is_running = iface.is_running();
225            let is_multicast = iface.is_multicast();
226            let is_broadcast = iface.is_broadcast();
227
228            Some(Ipv4Interface {
229                name: iface.name, // Move occurs here
230                index: iface.index,
231                mac: iface.mac.map(|m| m.octets()),
232                addresses: addrs,
233                is_loopback,
234                is_up,
235                is_running,
236                is_multicast,
237                is_broadcast,
238                mtu: None, // Not available from pnet
239            })
240        })
241        .collect()
242}
243
244/// Information about an IPv4-capable interface.
245#[derive(Debug, Clone)]
246pub struct Ipv4Interface {
247    /// Interface name (e.g., "eth0", "en0").
248    pub name: String,
249    /// Interface index.
250    pub index: u32,
251    /// MAC address (if available).
252    pub mac: Option<[u8; 6]>,
253    /// IPv4 addresses on this interface.
254    pub addresses: Vec<Ipv4InterfaceAddr>,
255    /// Whether this is a loopback interface.
256    pub is_loopback: bool,
257    /// Whether the interface is up.
258    pub is_up: bool,
259    /// Whether the interface is running.
260    pub is_running: bool,
261    /// Whether multicast is enabled.
262    pub is_multicast: bool,
263    /// Whether broadcast is supported.
264    pub is_broadcast: bool,
265    /// Interface MTU.
266    pub mtu: Option<u32>,
267}
268
269impl Ipv4Interface {
270    /// Get the first (primary) IPv4 address.
271    pub fn primary_address(&self) -> Option<Ipv4Addr> {
272        self.addresses.first().map(|a| a.address)
273    }
274
275    /// Check if an address is on this interface's network.
276    pub fn contains(&self, addr: Ipv4Addr) -> bool {
277        self.addresses.iter().any(|a| {
278            let mask = prefix_to_mask(a.prefix_len);
279            (u32::from(a.address) & mask) == (u32::from(addr) & mask)
280        })
281    }
282}
283
284/// An IPv4 address on an interface.
285#[derive(Debug, Clone)]
286pub struct Ipv4InterfaceAddr {
287    /// The IPv4 address.
288    pub address: Ipv4Addr,
289    /// Network prefix length (e.g., 24 for /24).
290    pub prefix_len: u8,
291    /// Broadcast address (if applicable).
292    pub broadcast: Option<Ipv4Addr>,
293}
294
295impl Ipv4InterfaceAddr {
296    /// Get the network address.
297    pub fn network(&self) -> Ipv4Addr {
298        let mask = prefix_to_mask(self.prefix_len);
299        Ipv4Addr::from(u32::from(self.address) & mask)
300    }
301
302    /// Get the subnet mask.
303    pub fn netmask(&self) -> Ipv4Addr {
304        Ipv4Addr::from(prefix_to_mask(self.prefix_len))
305    }
306}
307
308/// Convert a prefix length to a subnet mask.
309pub fn prefix_to_mask(prefix: u8) -> u32 {
310    if prefix >= 32 {
311        0xFFFFFFFF
312    } else if prefix == 0 {
313        0
314    } else {
315        !((1u32 << (32 - prefix)) - 1)
316    }
317}
318
319/// Convert a subnet mask to a prefix length.
320pub fn mask_to_prefix(mask: Ipv4Addr) -> u8 {
321    let mask_u32 = u32::from(mask);
322    mask_u32.leading_ones() as u8
323}
324
325/// Check if an IP address matches a network/prefix.
326pub fn ip_in_network(ip: Ipv4Addr, network: Ipv4Addr, prefix: u8) -> bool {
327    let mask = prefix_to_mask(prefix);
328    (u32::from(ip) & mask) == (u32::from(network) & mask)
329}
330
331/// Calculate the broadcast address for a network.
332pub fn broadcast_address(network: Ipv4Addr, prefix: u8) -> Ipv4Addr {
333    let mask = prefix_to_mask(prefix);
334    Ipv4Addr::from(u32::from(network) | !mask)
335}
336
337/// Calculate the number of hosts in a network.
338pub fn network_host_count(prefix: u8) -> u32 {
339    if prefix >= 31 {
340        // /31 has 2 hosts, /32 has 1
341        2u32.saturating_sub(prefix as u32 - 30)
342    } else {
343        (1u32 << (32 - prefix)) - 2 // Subtract network and broadcast
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350
351    #[test]
352    fn test_prefix_to_mask() {
353        assert_eq!(prefix_to_mask(0), 0x00000000);
354        assert_eq!(prefix_to_mask(8), 0xFF000000);
355        assert_eq!(prefix_to_mask(16), 0xFFFF0000);
356        assert_eq!(prefix_to_mask(24), 0xFFFFFF00);
357        assert_eq!(prefix_to_mask(32), 0xFFFFFFFF);
358    }
359
360    #[test]
361    fn test_mask_to_prefix() {
362        assert_eq!(mask_to_prefix(Ipv4Addr::new(255, 0, 0, 0)), 8);
363        assert_eq!(mask_to_prefix(Ipv4Addr::new(255, 255, 0, 0)), 16);
364        assert_eq!(mask_to_prefix(Ipv4Addr::new(255, 255, 255, 0)), 24);
365        assert_eq!(mask_to_prefix(Ipv4Addr::new(255, 255, 255, 255)), 32);
366    }
367
368    #[test]
369    fn test_ip_in_network() {
370        let network = Ipv4Addr::new(192, 168, 1, 0);
371        assert!(ip_in_network(Ipv4Addr::new(192, 168, 1, 100), network, 24));
372        assert!(ip_in_network(Ipv4Addr::new(192, 168, 1, 255), network, 24));
373        assert!(!ip_in_network(Ipv4Addr::new(192, 168, 2, 1), network, 24));
374    }
375
376    #[test]
377    fn test_broadcast_address() {
378        assert_eq!(
379            broadcast_address(Ipv4Addr::new(192, 168, 1, 0), 24),
380            Ipv4Addr::new(192, 168, 1, 255)
381        );
382        assert_eq!(
383            broadcast_address(Ipv4Addr::new(10, 0, 0, 0), 8),
384            Ipv4Addr::new(10, 255, 255, 255)
385        );
386    }
387
388    #[test]
389    fn test_network_host_count() {
390        assert_eq!(network_host_count(24), 254);
391        assert_eq!(network_host_count(16), 65534);
392        assert_eq!(network_host_count(8), 16777214);
393    }
394
395    #[test]
396    fn test_route_loopback() {
397        let route = get_route(Ipv4Addr::LOCALHOST);
398        assert!(route.is_local);
399        assert_eq!(route.interface, Some("lo".to_string()));
400    }
401
402    #[test]
403    fn test_ipv4_route() {
404        let route = Ipv4Route::local("eth0".to_string(), Ipv4Addr::new(192, 168, 1, 100));
405        assert!(route.is_valid());
406        assert!(route.is_local);
407        assert_eq!(
408            route.next_hop(Ipv4Addr::new(192, 168, 1, 200)),
409            Ipv4Addr::new(192, 168, 1, 200)
410        );
411
412        let route = Ipv4Route::via_gateway(
413            "eth0".to_string(),
414            Ipv4Addr::new(192, 168, 1, 100),
415            Ipv4Addr::new(192, 168, 1, 1),
416        );
417        assert!(!route.is_local);
418        assert_eq!(
419            route.next_hop(Ipv4Addr::new(8, 8, 8, 8)),
420            Ipv4Addr::new(192, 168, 1, 1)
421        );
422    }
423
424    #[test]
425    fn test_get_ipv4_interfaces() {
426        let interfaces = get_ipv4_interfaces();
427        for iface in &interfaces {
428            assert!(!iface.name.is_empty());
429            assert!(!iface.addresses.is_empty());
430        }
431    }
432
433    #[test]
434    fn test_interface_addr() {
435        let addr = Ipv4InterfaceAddr {
436            address: Ipv4Addr::new(192, 168, 1, 100),
437            prefix_len: 24,
438            broadcast: Some(Ipv4Addr::new(192, 168, 1, 255)),
439        };
440
441        assert_eq!(addr.network(), Ipv4Addr::new(192, 168, 1, 0));
442        assert_eq!(addr.netmask(), Ipv4Addr::new(255, 255, 255, 0));
443    }
444}