#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, allow(unused_attributes))]
#![deny(missing_docs)]
use std::sync::OnceLock;
use rustix::net::{bind, ipproto, socket, sockopt::set_ipv6_v6only, AddressFamily, SocketType};
static INIT: OnceLock<Probe> = OnceLock::new();
const V6_PROBES: [(bool, bool); 2] = [
(true, true), (false, false), ];
pub fn ipv4() -> bool {
probe().ipv4
}
pub fn ipv6() -> bool {
probe().ipv6
}
pub fn ipv4_mapped_ipv6() -> bool {
probe().ipv4_mapped_ipv6
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Probe {
ipv4: bool,
ipv6: bool,
ipv4_mapped_ipv6: bool,
}
impl Probe {
#[inline]
pub const fn ipv4(&self) -> bool {
self.ipv4
}
#[inline]
pub const fn ipv6(&self) -> bool {
self.ipv6
}
#[inline]
pub const fn ipv4_mapped_ipv6(&self) -> bool {
self.ipv4_mapped_ipv6
}
}
pub fn probe() -> Probe {
*INIT.get_or_init(probe_in)
}
fn probe_in() -> Probe {
use std::net::{Ipv6Addr, SocketAddrV6};
let mut caps = Probe {
ipv4: false,
ipv6: false,
ipv4_mapped_ipv6: false,
};
#[cfg(windows)]
let _ = rustix::net::wsa_startup();
{
let ipv4_sock = socket(AddressFamily::INET, SocketType::STREAM, Some(ipproto::TCP));
if ipv4_sock.is_ok() {
caps.ipv4 = true;
}
}
for (is_ipv6, v6_only) in V6_PROBES {
let sock = socket(AddressFamily::INET6, SocketType::STREAM, Some(ipproto::TCP));
if let Ok(sock) = sock {
let _ = set_ipv6_v6only(&sock, v6_only);
let addr = if is_ipv6 {
SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0)
} else {
SocketAddrV6::new(
Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x01),
0,
0,
0,
)
};
let bind_result = bind(sock, &addr);
if bind_result.is_ok() {
if is_ipv6 {
caps.ipv6 = true;
} else {
caps.ipv4_mapped_ipv6 = true;
}
}
}
}
#[cfg(windows)]
let _ = rustix::net::wsa_cleanup();
caps
}
#[test]
fn test() {
let caps = probe();
println!("IPv4 enabled: {}", caps.ipv4());
println!("IPv6 enabled: {}", caps.ipv6());
println!("IPv4-mapped IPv6 enabled: {}", caps.ipv4_mapped_ipv6());
}