Addresses
Resolve a hostname to socket addresses
Real config files don’t carry IP addresses, they carry hostnames like
api.internal:8080. ToSocketAddrs turns those strings into an iterator
of SocketAddr you can connect to. It’s the same trait
TcpStream::connect calls under the hood when handed a string.
A hostname can resolve to multiple addresses — at least one IPv4 and one IPv6 on any modern host. Walk all of them when you want to display every option, or take the first when one working address is enough.
use std::io;
use std::net::ToSocketAddrs;
fn main() -> io::Result<()> {
for addr in "localhost:443".to_socket_addrs()? {
println!("localhost:443 → {addr}");
}
Ok(())
}
Classify an IP address
Before connecting to an address pulled from user input, a config file, or an HTTP redirect, check what kind it is. Loopback, link-local, and RFC1918 private ranges have no business being targets for outbound traffic from a server exposed on the public internet — letting them through is the SSRF vector.
IpAddr exposes is_loopback, is_multicast, and is_unspecified
on both v4 and v6. is_private and is_link_local are on Ipv4Addr
only — match the variant to reach them.
use std::net::IpAddr;
use std::str::FromStr;
fn classify(addr: IpAddr) -> &'static str {
if addr.is_loopback() { return "loopback"; }
if addr.is_multicast() { return "multicast"; }
if addr.is_unspecified() { return "unspecified"; }
if let IpAddr::V4(v4) = addr {
if v4.is_private() { return "private (RFC1918)"; }
if v4.is_link_local() { return "link-local"; }
}
"public"
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let samples = ["127.0.0.1", "10.0.0.5", "169.254.1.1", "8.8.8.8", "224.0.0.1", "::1"];
for s in samples {
let addr = IpAddr::from_str(s)?;
println!("{addr}: {}", classify(addr));
}
Ok(())
}
Bind for both IPv4 and IPv6
0.0.0.0 accepts IPv4 clients only. To accept both protocols with a single
listener, bind to the unspecified IPv6 address [::]. On Linux and macOS the
listener accepts IPv6 clients natively and IPv4 clients via IPv4-mapped
addresses (::ffff:1.2.3.4).
On Windows and FreeBSD the IPV6_V6ONLY socket option defaults to true, so
the same bind accepts IPv6 only. std::net doesn’t expose IPV6_V6ONLY;
the socket2 crate does, or you can open two listeners.
use std::io;
use std::net::TcpListener;
fn main() -> io::Result<()> {
let listener = TcpListener::bind("[::]:0")?;
let addr = listener.local_addr()?;
println!("listening on {addr}");
Ok(())
}