IP

Rust

Rust has a very small standard library documented at Crate std . The std.net library has types for IP addresses and for some TCP and UDP calls. There are no DNS functions, unlike the other languages considered here.

The small standard library appears to be a deliberate choice by Rust designers. There is a huge library of user-contributed 'crates' at The Rust community’s crate registry and if you search for DNS there it will return over 200 matches. The philosophy appears to be that crates.io is the defacto standard library and that crates should be pulled from there rather than cluttering up the "real" standard library.

The philosophy is contentious: see Expansion of standard library for lengthy discussions either way. I'm in favour of a large standard library, with consistency of approach, guarantee of non-breaking updates, security, etc, but that isn't going to happen with Rust. So I'll pick what seem to be the most popular crates here and in the sequel. Of course, that may change over time, or even next month.

IP types

Rust has the types IpAddr, Ipv4Addr and Ipv6Addr.

The type Ipv4Addr has a new constructor that takes four 8 bit octets (of type u8) as in


	  let addr = Ipv4Addr::new(127, 0, 0, 1);
      

The type Ipv4Addr also implements fromStr. There may be parsing errors in this, so a Result type is created as in


	  let addr: Result<Ipv4Addr, AddrParseError> = "103.79.105.27".parse();
	  match addr {
	      Ok(a) =>  println!("address {}", a),
	      Err(e) =>  println!("error {}", e)
	  }
      

Functions include is_loopback(), is_private() as in


use std::net::Ipv4Addr;

assert_eq!(Ipv4Addr::new(127, 0, 0, 1).is_loopback(), true);
assert_eq!(Ipv4Addr::new(45, 22, 13, 197).is_loopback(), false);	  

assert_eq!(Ipv4Addr::new(10, 0, 0, 1).is_private(), true);
assert_eq!(Ipv4Addr::new(10, 10, 10, 10).is_private(), true);
assert_eq!(Ipv4Addr::new(172, 16, 10, 10).is_private(), true);
assert_eq!(Ipv4Addr::new(172, 29, 45, 14).is_private(), true);
assert_eq!(Ipv4Addr::new(172, 32, 0, 2).is_private(), false);
assert_eq!(Ipv4Addr::new(192, 168, 0, 2).is_private(), true);
assert_eq!(Ipv4Addr::new(192, 169, 0, 2).is_private(), false);
      

The type Ipv6Addr has a new constructor that takes eight 16-bit segments as in


use std::net::Ipv6Addr;	  

let localhost = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);	
let newmarch = Ipv6Addr::new(0x2400, 0x3740, 0x200, 0xd900, 0, 0, 0, 0x260);
      

The type Ipv6Addr also implements fromStr. There may be parsing errors in this, so a Result type is created as in


	  let addr: Result<Ipv6Addr, AddrParseError> = "2400:3740:200:d900::260".parse();
	  match addr {
	      Ok(a) =>  println!("address {}", a),
	      Err(e) =>  println!("error {}", e)
	  }
      

Functions include is_global(), is_unicast_link_local_strict().

Get one DNS address

There is no support for DNS in the standard library. The most popular crate for DNS (as at May, 2020) seems to be trust-dns-resolver .

Creating and managing projects using crates is more complex than simply interpreting or compiling a source file. First you need to create a project


	  cargo new Resolv
      
which creates the following directory structure

Resolv
Resolv/.git
Resolv/.git/...
Resolv/.gitignore
Resolv/src
Resolv/src/main.rs
Resolv/Cargo.toml
      

To install the trust-dns-resolver crate, keep it ip to date, build it,etc the following line needs to be added to Resolv/Cargo.toml in the Dependencies section:


	  trust-dns-resolver = "0.19.5"
      

Then in the Resolv direstory, after every change to main.rs, run


	  cargo build
      

I copied the example code into src/main.js and built it - with two main results:

Despite these initial hiccups, it does look like a very solid package

The example program will return a single IP address, and IPv4 address if it exists, otherwise an IPv6 address. Adapted to get a host name from standard input, it is IP.rs illustrates these:


use trust_dns_resolver::Resolver;
use trust_dns_resolver::config::*;
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
	println!("Usage {} hostname", args[0]);
	std::process::exit(1);
    }
    let hostname = &args[1];
    
    // Construct a new Resolver with default configuration options
    // Default tries IPv4 and then IPv6 (if IPv4 fails)
    let opts = ResolverOpts::default();
    match opts.ip_strategy {
	 LookupIpStrategy::Ipv4Only => println!("IPv4 only"),
	 LookupIpStrategy::Ipv6Only => println!("IPv6 only"),
	 LookupIpStrategy::Ipv4AndIpv6 => println!("IPv4 and v6"),
	 LookupIpStrategy::Ipv6thenIpv4 => println!("IPv6 then 4"),
	 LookupIpStrategy::Ipv4thenIpv6 => println!("IPv4 then 6")
    }

    let resolver = Resolver::new(ResolverConfig::default(), opts).unwrap();

    let response = resolver.lookup_ip(hostname);
    match response {
	Ok(resp) =>
	{
	    // only one value to return
	    for a in resp.iter() {
		println!("addr {}", a);
	    }
	},
	Err(err) =>
	{
	    println!("err {}", err);
	    std::process::exit(2)
	}
    }
    std::process::exit(0);
}

Get all DNS addresses

The changes to get both IPv4 and IPv6 addresses are basically just to the options: IPs.rs illustrates these:


use trust_dns_resolver::Resolver;
use trust_dns_resolver::config::*;
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
	println!("Usage {} hostname", args[0]);
	std::process::exit(1);
    }
    let hostname = &args[1];
    
    // Construct a new Resolver with default configuration options
    // Default gets IPv4 and then IPv6
    let mut opts = ResolverOpts::default();
    match opts.ip_strategy {
	 LookupIpStrategy::Ipv4Only => println!("IPv4 only"),
	 LookupIpStrategy::Ipv6Only => println!("IPv6 only"),
	 LookupIpStrategy::Ipv4AndIpv6 => println!("IPv4 and v6"),
	 LookupIpStrategy::Ipv6thenIpv4 => println!("IPv6 then 4"),
	 LookupIpStrategy::Ipv4thenIpv6 => println!("IPv4 then 6")
    }
    // then change to find both
    opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
    
    let resolver = Resolver::new(ResolverConfig::default(), opts).unwrap();

    let response = resolver.lookup_ip(hostname);
    match response {
	Ok(resp) =>
	{
	    for a in resp.iter() {
		println!("addr {}", a);
	    }
	},
	Err(err) =>
	{
	    println!("err {}", err);
	    std::process::exit(2)
	}
    }
    std::process::exit(0);
}

Get all addresses for an interface

Again, there isn't anything in the standard library. The most popular crate that runs under Posix and Windows seems to be get_if_addrs.

The dependency entry in Cargo.toml is


get_if_addrs = "0.5.3"	  
      

The program to list all IP addresses for each NIC is simplicity itself: iface.rs illustrates these:


use get_if_addrs;

fn main() {
    for iface in get_if_addrs::get_if_addrs().unwrap() {
	println!("{:#?}", iface);
    }
}

Partial output is


Interface {
    name: "wlp2s0",
    addr: V4(
        Ifv4Addr {
            ip: 192.168.2.190,
            netmask: 255.255.255.0,
            broadcast: Some(
                192.168.2.255,
            ),
        },
    ),
}
Interface {
    name: "lo",
    addr: V6(
        Ifv6Addr {
            ip: ::1,
            netmask: ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff,
            broadcast: None,
        },
    ),
}
Interface {
    name: "wlp2s0",
    addr: V6(
        Ifv6Addr {
            ip: fd5a:1870:766c::2aa,
            netmask: ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff,
            broadcast: None,
        },
    ),
}
      


Copyright © Jan Newmarch, jan@newmarch.name
Creative Commons License
" Network Programming using Java, Go, Python, Rust, JavaScript and Julia" by Jan Newmarch is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License .
Based on a work at https://jan.newmarch.name/NetworkProgramming/ .

If you like this book, please contribute using PayPal