Skip to main content

dig_ip/
family.rs

1//! The address-family primitive every other type in this crate is tagged with.
2
3use std::net::SocketAddr;
4
5/// An IP address family: IPv6 (preferred) or IPv4 (fallback).
6///
7/// This is the axis the whole crate reasons over โ€” local capability, peer reachability, dial order,
8/// and the winning connection are all expressed in terms of a [`Family`]. IPv6 sorts before IPv4
9/// everywhere so the ecosystem's IPv6-first rule (CLAUDE.md ยง5.2) falls out of the ordinary
10/// [`Ord`]/[`PartialOrd`] derive.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub enum Family {
13    /// IPv6 โ€” the preferred family.
14    V6,
15    /// IPv4 โ€” the fallback family.
16    V4,
17}
18
19impl Family {
20    /// The family of a socket address.
21    ///
22    /// An IPv4-mapped IPv6 address (`::ffff:a.b.c.d`) is classified as [`Family::V4`]: it is IPv4
23    /// reachability wearing an IPv6 costume, so treating it as V6 would let an IPv6-only host think
24    /// it can reach a v4-only peer. Canonicalizing first keeps the family tag honest.
25    pub fn of(addr: &SocketAddr) -> Family {
26        match addr {
27            SocketAddr::V6(a) if a.ip().to_ipv4_mapped().is_none() => Family::V6,
28            _ => Family::V4,
29        }
30    }
31
32    /// The preference-ordered list of both families, IPv6 first.
33    pub const PREFERENCE: [Family; 2] = [Family::V6, Family::V4];
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn classifies_plain_v6_as_v6() {
42        let addr: SocketAddr = "[2001:db8::1]:443".parse().unwrap();
43        assert_eq!(Family::of(&addr), Family::V6);
44    }
45
46    #[test]
47    fn classifies_v4_as_v4() {
48        let addr: SocketAddr = "203.0.113.1:443".parse().unwrap();
49        assert_eq!(Family::of(&addr), Family::V4);
50    }
51
52    #[test]
53    fn classifies_v4_mapped_v6_as_v4() {
54        // `::ffff:203.0.113.1` is IPv4 reachability, so it must NOT be treated as V6.
55        let addr: SocketAddr = "[::ffff:203.0.113.1]:443".parse().unwrap();
56        assert_eq!(Family::of(&addr), Family::V4);
57    }
58
59    #[test]
60    fn v6_sorts_before_v4() {
61        assert!(Family::V6 < Family::V4);
62        assert_eq!(Family::PREFERENCE, [Family::V6, Family::V4]);
63    }
64}