iprobe/
lib.rs

1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![cfg_attr(docsrs, allow(unused_attributes))]
5#![deny(missing_docs)]
6
7use std::sync::OnceLock;
8
9use rustix::net::{bind, ipproto, socket, sockopt::set_ipv6_v6only, AddressFamily, SocketType};
10
11static INIT: OnceLock<Probe> = OnceLock::new();
12
13const V6_PROBES: [(bool, bool); 2] = [
14  (true, true),   // IPv6
15  (false, false), // IPv4-mapped
16];
17
18/// Returns `true` if the system supports IPv4 communication.
19pub fn ipv4() -> bool {
20  probe().ipv4
21}
22
23/// Returns `true` if the system supports IPv6 communication.
24pub fn ipv6() -> bool {
25  probe().ipv6
26}
27
28/// Returns `true` if the system understands
29/// IPv4-mapped IPv6.
30pub fn ipv4_mapped_ipv6() -> bool {
31  probe().ipv4_mapped_ipv6
32}
33
34/// Represents the IP stack communication capabilities of the system.
35#[derive(Debug, Copy, Clone, PartialEq, Eq)]
36pub struct Probe {
37  ipv4: bool,
38  ipv6: bool,
39  ipv4_mapped_ipv6: bool,
40}
41
42impl Probe {
43  /// Returns `true` if the system supports IPv4 communication.
44  #[inline]
45  pub const fn ipv4(&self) -> bool {
46    self.ipv4
47  }
48
49  /// Returns `true` if the system supports IPv6 communication.
50  #[inline]
51  pub const fn ipv6(&self) -> bool {
52    self.ipv6
53  }
54
55  /// Returns `true` if the system understands
56  /// IPv4-mapped IPv6.
57  #[inline]
58  pub const fn ipv4_mapped_ipv6(&self) -> bool {
59    self.ipv4_mapped_ipv6
60  }
61}
62
63/// Probes IPv4, IPv6 and IPv4-mapped IPv6 communication
64/// capabilities which are controlled by the `IPV6_V6ONLY` socket option
65/// and kernel configuration.
66///
67/// Should we try to use the IPv4 socket interface if we're only
68/// dealing with IPv4 sockets? As long as the host system understands
69/// IPv4-mapped IPv6, it's okay to pass IPv4-mapped IPv6 addrs to
70/// the IPv6 interface. That simplifies our code and is most
71/// general. Unfortunately, we need to run on kernels built without
72/// IPv6 support too. So probe the kernel to figure it out.
73pub fn probe() -> Probe {
74  *INIT.get_or_init(probe_in)
75}
76
77fn probe_in() -> Probe {
78  use std::net::{Ipv6Addr, SocketAddrV6};
79
80  let mut caps = Probe {
81    ipv4: false,
82    ipv6: false,
83    ipv4_mapped_ipv6: false,
84  };
85
86  #[cfg(windows)]
87  let _ = rustix::net::wsa_startup();
88
89  // Check IPv4 support
90  {
91    let ipv4_sock = socket(AddressFamily::INET, SocketType::STREAM, Some(ipproto::TCP));
92
93    if ipv4_sock.is_ok() {
94      caps.ipv4 = true;
95    }
96  }
97
98  // Probe IPv6 and IPv4-mapped IPv6
99  for (is_ipv6, v6_only) in V6_PROBES {
100    let sock = socket(AddressFamily::INET6, SocketType::STREAM, Some(ipproto::TCP));
101
102    if let Ok(sock) = sock {
103      // Set IPV6_V6ONLY option
104      let _ = set_ipv6_v6only(&sock, v6_only);
105
106      // Create bind address
107      let addr = if is_ipv6 {
108        SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0)
109      } else {
110        SocketAddrV6::new(
111          // ::ffff:127.0.0.1
112          Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x01),
113          0,
114          0,
115          0,
116        )
117      };
118
119      // Attempt to bind
120      let bind_result = bind(sock, &addr);
121
122      if bind_result.is_ok() {
123        if is_ipv6 {
124          caps.ipv6 = true;
125        } else {
126          caps.ipv4_mapped_ipv6 = true;
127        }
128      }
129    }
130  }
131
132  #[cfg(windows)]
133  let _ = rustix::net::wsa_cleanup();
134
135  caps
136}
137
138#[test]
139fn test() {
140  let caps = probe();
141  println!("IPv4 enabled: {}", caps.ipv4());
142  println!("IPv6 enabled: {}", caps.ipv6());
143  println!("IPv4-mapped IPv6 enabled: {}", caps.ipv4_mapped_ipv6());
144}