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}