Networking with the Standard Library

IP Addresses

An IP address uniquely identifies a network interface on an Internet Protocol network. Rust models IP addresses in std::net using Ipv4Addr (32-bit), Ipv6Addr (128-bit), and the version-agnostic enum IpAddr (IpAddr::V4(Ipv4Addr) or IpAddr::V6(Ipv6Addr)), all of which are lightweight Copy value types.

Ipv4Addr (4 octets = 32 bits):
  192 . 168 . 1 . 10   --->  [u8; 4] = [192, 168, 1, 10]  (u32 = 0xC0A8010A)

Ipv6Addr (8 hextets = 128 bits):
  2001:0db8::8a2e:0370:7334  --->  [u16; 8] or [u8; 16] (u128)

Constructing Ipv4Addr, Ipv6Addr, and IpAddr

Standard well-known addresses are available as associated const items on Ipv4Addr and Ipv6Addr:

Constant Value Meaning
Ipv4Addr::LOCALHOST 127.0.0.1 IPv4 loopback address (local host only).
Ipv4Addr::UNSPECIFIED 0.0.0.0 Wildcard bind address (all local IPv4 interfaces).
Ipv4Addr::BROADCAST 255.255.255.255 Local network broadcast address.
Ipv6Addr::LOCALHOST ::1 IPv6 loopback address.
Ipv6Addr::UNSPECIFIED :: Wildcard bind address (all local IPv6 interfaces).
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

fn main() {
    // 1. From octets or segments (compile-time const fn)
    const LOCAL_DNS: Ipv4Addr = Ipv4Addr::new(192, 168, 1, 1);
    const DOC_IPV6: Ipv6Addr = Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1);

    // 2. From raw integer representations (network byte order / big-endian)
    let from_u32 = Ipv4Addr::from_bits(0x7F00_0001);
    assert_eq!(from_u32, Ipv4Addr::LOCALHOST);

    // 3. Parsing strings via FromStr (.parse())
    let parsed_v4: IpAddr = "10.0.0.42".parse().unwrap();
    let parsed_v6: IpAddr = "fe80::1ff:fe23:4567:890a".parse().unwrap();

    println!("LOCAL_DNS: {} (octets: {:?})", LOCAL_DNS, LOCAL_DNS.octets());
    println!("DOC_IPV6:  {}", DOC_IPV6);
    println!("Parsed:    v4={}, v6={}", parsed_v4.is_ipv4(), parsed_v6.is_ipv6());
}
LOCAL_DNS: 192.168.1.1 (octets: [192, 168, 1, 1])
DOC_IPV6:  2001:db8::1
Parsed:    v4=true, v6=true

Classification and Inspection Methods

Both IpAddr and the version-specific structs provide classification methods to validate security boundaries (for example, rejecting SSRF requests targeting loopback or private RFC 1918 networks):

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

fn classify(ip: IpAddr) {
    println!(
        "{:<18} -> loopback: {:<5} unspecified: {:<5} multicast: {}",
        ip.to_string(),
        ip.is_loopback(),
        ip.is_unspecified(),
        ip.is_multicast()
    );
}

fn main() {
    classify(IpAddr::V4(Ipv4Addr::LOCALHOST));
    classify(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
    classify(IpAddr::V4(Ipv4Addr::new(224, 0, 0, 1)));

    let private_v4 = Ipv4Addr::new(10, 24, 0, 5);
    println!("10.24.0.5 is_private: {}", private_v4.is_private());

    // IPv4-mapped IPv6 addresses (::ffff:a.b.c.d)
    let mapped: Ipv6Addr = "::ffff:192.0.2.128".parse().unwrap();
    println!(
        "{} -> to_ipv4_mapped(): {:?}",
        mapped,
        mapped.to_ipv4_mapped()
    );
}
127.0.0.1          -> loopback: true  unspecified: false multicast: false
0.0.0.0            -> loopback: false unspecified: true  multicast: false
224.0.0.1          -> loopback: false unspecified: false multicast: true
10.24.0.5 is_private: true
::ffff:192.0.2.128 -> to_ipv4_mapped(): Some(192.0.2.128)