DEV Community

Cover image for Getting network DNS addresses for a Windows host in Rust
Dandy Vica
Dandy Vica

Posted on

Getting network DNS addresses for a Windows host in Rust

As you probably know, Windows is seriously considering and investing in Rust. The Windows Rust crate is one example of this commitment.
It allows you to call most if not all Windows APIs from Rust natively.

One caveat though: this crate is full of unsafe code.

In one of my future crate, I needed to get the DNS servers of a host. In Linux, it's quite straightforward, looking into the /etc/resolv.conf file.

But in Windows, it's more complicated. One Windows API is meant to get the details of each network interface in the system. it's called
GetAdaptersAddresses.

Let's explain how I achieved this.

First create a binary crate:

$ cargo new dnssrv
Enter fullscreen mode Exit fullscreen mode

Then, add the following to the Cargo.toml file:

[dependencies.windows]
version = "0.48"
features = [
    "Win32_Foundation",
    "Win32_NetworkManagement_IpHelper",
    "Win32_NetworkManagement_Ndis",
    "Win32_Networking_WinSock",
]
Enter fullscreen mode Exit fullscreen mode

The Rust equivalent API to call is:

pub unsafe fn GetAdaptersAddresses(
    family: u32,
    flags: GET_ADAPTERS_ADDRESSES_FLAGS,
    reserved: Option<*const c_void>,
    adapteraddresses: Option<*mut IP_ADAPTER_ADDRESSES_LH>,
    sizepointer: *mut u32
) -> u32
Enter fullscreen mode Exit fullscreen mode

All the different parameters are explained in the Microsoft Windows API link.

To use the API and Windows types, add the following:

use windows::Win32::{
    Foundation::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS},
    NetworkManagement::IpHelper::{
        GetAdaptersAddresses, GAA_FLAG_INCLUDE_PREFIX,
        IP_ADAPTER_ADDRESSES_LH,
    },
    Networking::WinSock::AF_INET,
};
Enter fullscreen mode Exit fullscreen mode

The trick is how to convert raw pointer to structures. It's actually quite simple using the as_mut_ptr() function on a vector.

As explained in the Windows API page, first call the API with a low buffer length:

// first call with a empty buffer
let family = AF_INET.0 as u32;  // look for IPV4 addresses
let mut buflen = 0u32;
let mut rc =
    unsafe { GetAdaptersAddresses(family, GAA_FLAG_INCLUDE_PREFIX, None, None, &mut buflen) };
Enter fullscreen mode Exit fullscreen mode

This should return a ERROR_BUFFER_OVERFLOW return code, with the true value needed for the buffer:

// second with the actual buffer size large enough to hold data
if rc == ERROR_BUFFER_OVERFLOW.0 {
    let mut addr = vec![0u8; buflen as usize];  // beware though of the conversion between u32 and usize
    let ptr = addr.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH;

    rc = unsafe {
        GetAdaptersAddresses(
            family,
            GAA_FLAG_INCLUDE_PREFIX,
            None,
            Some(ptr),
            &mut buflen,
        )
    };
Enter fullscreen mode Exit fullscreen mode

After that it's just a matter of moving through the linked list of adapters, and for each adapter, the link list of DNS ip addresses:

    // second with the actual buffer size large enough to hold data
    if rc == ERROR_SUCCESS.0 {
        // loop through adapters and grab DNS addresses
        let mut p = ptr;

        while !p.is_null() {
            unsafe {
                let mut p_dns = (*p).FirstDnsServerAddress;

                // loop through DNS addresses for this adapter
                while !p_dns.is_null() {
                    let sockaddr = (*p_dns).Address.lpSockaddr;
                    println!("found DNS server: {:?} for adapter '{}'", (*sockaddr).sa_data, (*p).Description.display());

                    p_dns = (*p_dns).Next;
                }

                p = (*p).Next;
            }
        }
    } else {
       println!("error {} calling GetAdaptersAddresses()", rc);
    }
Enter fullscreen mode Exit fullscreen mode

You can grab the full code here: https://github.com/dandyvica/get_dns_ip

Hope this helps !

Photo by Nathan Fertig on Unsplash

Top comments (0)