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