getip/
libc_getips.rs

1//! Receive IP addresses of NICs with `libc` APIs.
2//! - On Windows, API `GetAdaptersAddresses` is used.
3//! - On unix-like systems, `getifaddrs` and `getnameinfo` are used.
4//
5//  Copyright (C) 2021 Zhang Maiyun <myzhang1029@hotmail.com>
6//
7//  This file is part of DNS updater.
8//
9//  DNS updater is free software: you can redistribute it and/or modify
10//  it under the terms of the GNU Affero General Public License as published by
11//  the Free Software Foundation, either version 3 of the License, or
12//  (at your option) any later version.
13//
14//  DNS updater is distributed in the hope that it will be useful,
15//  but WITHOUT ANY WARRANTY; without even the implied warranty of
16//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17//  GNU Affero General Public License for more details.
18//
19//  You should have received a copy of the GNU Affero General Public License
20//  along with DNS updater.  If not, see <https://www.gnu.org/licenses/>.
21//
22
23use crate::IpType;
24use crate::{Error, Result};
25#[cfg(windows)]
26use log::error;
27use log::{debug, trace};
28use std::convert::TryInto;
29use std::ffi::CStr;
30use std::mem;
31use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
32use std::ptr;
33#[cfg(unix)]
34use std::str::FromStr;
35#[cfg(windows)]
36use winapi::{
37    shared::{
38        minwindef::DWORD,
39        ntdef::{ULONG, VOID},
40        winerror, ws2def,
41        ws2def::{SOCKADDR, SOCKADDR_IN},
42        ws2ipdef::SOCKADDR_IN6,
43    },
44    um::{
45        heapapi::{GetProcessHeap, HeapAlloc, HeapFree},
46        iphlpapi::GetAdaptersAddresses,
47        iptypes::{
48            GAA_FLAG_INCLUDE_PREFIX, GAA_FLAG_SKIP_DNS_SERVER, GAA_FLAG_SKIP_MULTICAST,
49            IP_ADAPTER_ADDRESSES, IP_ADAPTER_ANYCAST_ADDRESS, IP_ADAPTER_UNICAST_ADDRESS,
50        },
51    },
52};
53#[cfg(windows)]
54const INITIAL_ALLOC_SIZE: ULONG = 15000;
55#[cfg(windows)]
56const MAX_TRIES: usize = 5;
57
58/// Fail with errno
59macro_rules! fail_os_err {
60    () => {
61        Err(Error::IoError(std::io::Error::last_os_error()))
62    };
63}
64
65/// Get an address of an interface
66#[cfg(unix)]
67fn get_addr_for_ifa_unix(addr: libc::ifaddrs, ip_type: Option<IpType>) -> Result<IpAddr> {
68    let sockaddr = addr.ifa_addr;
69    assert!(!sockaddr.is_null());
70    let family = libc::c_int::from(unsafe { *sockaddr }.sa_family);
71    // Has a IP family filter
72    if let Some(ip_type) = ip_type {
73        if (ip_type == IpType::Ipv4 && family != libc::AF_INET)
74            || (ip_type == IpType::Ipv6 && family != libc::AF_INET6)
75        {
76            trace!("Short-circuiting NoAddress: addr family does not match requested type");
77            return Err(Error::NoAddress);
78        }
79    } else if family != libc::AF_INET && family != libc::AF_INET6 {
80        trace!(
81            "Short-circuiting NoAddress: family type {:?} not address",
82            family
83        );
84        return Err(Error::NoAddress);
85    }
86    // Length for `getnameinfo`
87    let socklen: libc::socklen_t = match family {
88        libc::AF_INET => mem::size_of::<libc::sockaddr_in>(),
89        libc::AF_INET6 => mem::size_of::<libc::sockaddr_in6>(),
90        _ => unreachable!(),
91    }
92    .try_into()
93    // socklen_t should be sufficient by design
94    .unwrap_or_else(|_| unreachable!());
95    // Allocating on stack, so only when necessary
96    {
97        const MAXHOST: usize = libc::NI_MAXHOST as usize;
98        let mut host: [libc::c_char; MAXHOST] = [0; MAXHOST];
99        if unsafe {
100            libc::getnameinfo(
101                sockaddr,
102                socklen,
103                host.as_mut_ptr(),
104                libc::NI_MAXHOST,
105                ptr::null_mut(),
106                0,
107                libc::NI_NUMERICHOST,
108            )
109        } == 0
110        {
111            let address = unsafe { CStr::from_ptr(host.as_ptr()).to_bytes() };
112            let address = unsafe { std::str::from_utf8_unchecked(address) };
113            Ok(match family {
114                libc::AF_INET => IpAddr::V4(Ipv4Addr::from_str(address)?),
115                libc::AF_INET6 => IpAddr::V6(Ipv6Addr::from_str(address)?),
116                _ => unreachable!(),
117            })
118        } else {
119            fail_os_err!()
120        }
121    }
122}
123
124/// Get all assigned IP addresses of family `ip_type` on the interface named `iface_name`.
125///
126/// Both parameters can be `None`, in which case that filter is not applied.
127///
128/// If the result is an `Ok` variant, the vector is guaranteed to be non-empty.
129///
130/// # Errors
131///
132/// This function fails with the `IoError` variant if an underlying OS operation
133/// failed, or `NoAddress` if no matching addresses found.
134#[cfg(unix)]
135pub fn get_iface_addrs(ip_type: Option<IpType>, iface_name: Option<&str>) -> Result<Vec<IpAddr>> {
136    // Hold all found addresses
137    let mut result: Vec<IpAddr> = Vec::new();
138    // Save for freeifaddrs()
139    let mut save_addrs: *mut libc::ifaddrs = unsafe { mem::zeroed() };
140    if unsafe { libc::getifaddrs(&mut save_addrs) } != 0 {
141        return fail_os_err!();
142    }
143    let mut addrs = save_addrs;
144    // Walk through the linked list
145    while !addrs.is_null() {
146        let addr = unsafe { *addrs };
147        // Interface name
148        let ifa_name = unsafe { CStr::from_ptr(addr.ifa_name).to_bytes() };
149        let ifa_name = unsafe { std::str::from_utf8_unchecked(ifa_name) };
150        trace!("Got interface {:?}", ifa_name);
151        // Filter iface name
152        let address = iface_name.map_or_else(
153            || get_addr_for_ifa_unix(addr, ip_type),
154            |expected_ifa_name| {
155                if ifa_name == expected_ifa_name {
156                    get_addr_for_ifa_unix(addr, ip_type)
157                } else {
158                    Err(Error::NoAddress)
159                }
160            },
161        );
162        if let Ok(address) = address {
163            trace!(
164                "Found good addresses of type {:?} for interface {:?}: {:?}",
165                ip_type,
166                iface_name,
167                address
168            );
169            result.push(address);
170        }
171        addrs = addr.ifa_next;
172    }
173    unsafe { libc::freeifaddrs(save_addrs) };
174    if result.is_empty() {
175        debug!("No address becase none of the interfaces has a matching one");
176        Err(Error::NoAddress)
177    } else {
178        Ok(result)
179    }
180}
181
182/// Convert raw pointer `raw_addr` of type `*SOCKADDR` to Rust `IpAddr`.
183/// `raw_addr` must not be `NULL`.
184#[cfg(windows)]
185unsafe fn sockaddr_to_ipaddr(raw_addr: *mut SOCKADDR) -> IpAddr {
186    if i32::from(unsafe { *raw_addr }.sa_family) == ws2def::AF_INET {
187        #[allow(clippy::cast_ptr_alignment)]
188        let saddr_in = raw_addr.cast::<SOCKADDR_IN>();
189        let saddr_in_addr = unsafe { (*saddr_in).sin_addr.S_un.S_addr() };
190        IpAddr::V4(Ipv4Addr::from(*saddr_in_addr))
191    } else {
192        #[allow(clippy::cast_ptr_alignment)]
193        let saddr_in = raw_addr.cast::<SOCKADDR_IN6>();
194        let saddr_in_addr = unsafe { (*saddr_in).sin6_addr.u.Byte() };
195        IpAddr::V6(Ipv6Addr::from(*saddr_in_addr))
196    }
197}
198
199/// Extract all addresses from an adapter.
200/// `adapter` must not be `NULL`.
201#[cfg(windows)]
202unsafe fn extract_addresses(adapter: *mut IP_ADAPTER_ADDRESSES) -> Vec<IpAddr> {
203    let mut addresses: Vec<IpAddr> = Vec::new();
204    let mut cur_unicast: *mut IP_ADAPTER_UNICAST_ADDRESS = unsafe { *adapter }.FirstUnicastAddress;
205    while !cur_unicast.is_null() {
206        let raw_addr = unsafe { *cur_unicast }.Address.lpSockaddr;
207        assert!(!raw_addr.is_null());
208        let ipaddr = unsafe { sockaddr_to_ipaddr(raw_addr) };
209        debug!(
210            "Found good unicast address on adapter {:?}: {:?}",
211            unsafe { *adapter }.FriendlyName,
212            ipaddr
213        );
214        addresses.push(ipaddr);
215        cur_unicast = unsafe { *cur_unicast }.Next;
216    }
217    let mut cur_anycast: *mut IP_ADAPTER_ANYCAST_ADDRESS = unsafe { *adapter }.FirstAnycastAddress;
218    while !cur_anycast.is_null() {
219        let raw_addr = unsafe { *cur_anycast }.Address.lpSockaddr;
220        assert!(!raw_addr.is_null());
221        let ipaddr = unsafe { sockaddr_to_ipaddr(raw_addr) };
222        debug!(
223            "Found good anycast address on adapter {:?}: {:?}",
224            unsafe { *adapter }.FriendlyName,
225            ipaddr
226        );
227        addresses.push(ipaddr);
228        cur_anycast = unsafe { *cur_anycast }.Next;
229    }
230    addresses
231}
232
233/// Get all assigned IP addresses of family `ip_type` on the interface named `iface_name`.
234///
235/// Both parameters can be `None`, in which case that filter is not applied.
236///
237/// If the result is an `Ok` variant, the vector is guaranteed to be non-empty.
238///
239/// # Errors
240///
241/// This function fails with the `IoError` variant if an underlying OS operation
242/// failed, or `NoAddress` if no matching addresses found.
243///
244/// # See also
245///
246/// MSDN documentation on `GetAdaptersAddresses`: <https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses>.
247#[cfg(windows)]
248pub fn get_iface_addrs(ip_type: Option<IpType>, iface_name: Option<&str>) -> Result<Vec<IpAddr>> {
249    let family: u32 = match ip_type {
250        Some(IpType::Ipv4) => ws2def::AF_INET,
251        Some(IpType::Ipv6) => ws2def::AF_INET6,
252        None => ws2def::AF_UNSPEC,
253    }
254    .try_into()
255    // I know the values of those constants, so they are safe.
256    .unwrap_or_else(|_| unreachable!());
257    let flags: ULONG = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_MULTICAST;
258    // Allocate a 15 KB buffer to start with.
259    let mut allocated_size: ULONG = INITIAL_ALLOC_SIZE;
260    // Silence maybe uninitialized error
261    let mut adapter_addresses: *mut IP_ADAPTER_ADDRESSES = unsafe { mem::zeroed() };
262    let mut return_value: DWORD = 0;
263    // Try several times to query the resources as suggested by doc
264    for trial in 0..MAX_TRIES {
265        adapter_addresses = unsafe { HeapAlloc(GetProcessHeap(), 0, allocated_size as usize) }
266            .cast::<IP_ADAPTER_ADDRESSES>();
267        if adapter_addresses.is_null() {
268            error!("Raw heap allocation failed");
269            return fail_os_err!();
270        }
271        return_value = unsafe {
272            GetAdaptersAddresses(
273                family,
274                flags,
275                ptr::null_mut(),
276                adapter_addresses,
277                &mut allocated_size,
278            )
279        };
280        debug!(
281            "GetAdaptersAddresses returned {:?} on the {}th trial",
282            return_value, trial
283        );
284        if return_value == winerror::ERROR_BUFFER_OVERFLOW {
285            unsafe { HeapFree(GetProcessHeap(), 0, adapter_addresses.cast::<VOID>()) };
286        } else {
287            break;
288        }
289    }
290    let result = if return_value == winerror::NO_ERROR {
291        let mut addresses: Vec<IpAddr> = Vec::new();
292        let mut curr_adapter = adapter_addresses;
293        while !curr_adapter.is_null() {
294            let adapter_name = unsafe { *curr_adapter }.FriendlyName as *const libc::c_char;
295            let adapter_name = unsafe { CStr::from_ptr(adapter_name).to_bytes() };
296            let adapter_name = unsafe { std::str::from_utf8_unchecked(adapter_name) };
297            trace!("Examining adpater {:?}", adapter_name);
298            if let Some(expected_adapter_name) = iface_name {
299                if adapter_name == expected_adapter_name {
300                    let mut addrs = unsafe { extract_addresses(curr_adapter) };
301                    addresses.append(&mut addrs);
302                }
303            } else {
304                let mut addrs = unsafe { extract_addresses(curr_adapter) };
305                addresses.append(&mut addrs);
306            }
307            curr_adapter = unsafe { *curr_adapter }.Next;
308        }
309        if addresses.is_empty() {
310            debug!("No address becase none of the adapters has a matching one");
311            Err(Error::NoAddress)
312        } else {
313            Ok(addresses)
314        }
315    } else {
316        // Let Rust interpret the error for me
317        Err(Error::IoError(std::io::Error::from_raw_os_error(
318            // Windows system error code has range 0-15999
319            return_value.try_into().unwrap_or_else(|_| unreachable!()),
320        )))
321    };
322    unsafe {
323        HeapFree(GetProcessHeap(), 0, adapter_addresses.cast::<VOID>());
324    }
325    result
326}
327
328#[cfg(test)]
329mod test {
330    use super::get_iface_addrs;
331    use crate::Error;
332    use crate::IpType;
333
334    #[test]
335    fn test_get_iface_addrs_ipv4() {
336        // There is no reliable way to find the name of an interface
337        // So `iface_name` is None.
338        match get_iface_addrs(Some(IpType::Ipv4), None) {
339            Ok(addresses) => {
340                assert!(!addresses.is_empty(), "Addresses should not be empty");
341                for address in &addresses {
342                    assert!(address.is_ipv4(), "Address not IPv4: {:?}", address);
343                }
344            }
345            Err(error) => {
346                assert!(
347                    matches!(error, Error::NoAddress),
348                    "get_iface_addrs failed because of reasons other than NoAddress: {:?}",
349                    error
350                );
351            }
352        }
353    }
354
355    #[test]
356    fn test_get_iface_addrs_ipv6() {
357        match get_iface_addrs(Some(IpType::Ipv6), None) {
358            Ok(addresses) => {
359                assert!(!addresses.is_empty(), "Addresses should not be empty");
360                for address in &addresses {
361                    assert!(address.is_ipv6(), "Address not IPv6: {:?}", address);
362                }
363            }
364            Err(error) => {
365                assert!(
366                    matches!(error, Error::NoAddress),
367                    "get_iface_addrs failed because of reasons other than NoAddress: {:?}",
368                    error
369                );
370            }
371        }
372    }
373
374    #[test]
375    fn test_get_iface_addrs_any() {
376        match get_iface_addrs(None, None) {
377            Ok(addresses) => {
378                assert!(!addresses.is_empty(), "Addresses should not be empty");
379            }
380            Err(error) => {
381                assert!(
382                    matches!(error, Error::NoAddress),
383                    "get_iface_addrs failed because of reasons other than NoAddress: {:?}",
384                    error
385                );
386                // It does not make sense if this one if NoAddress but individual ones succeed
387                assert!(
388                    matches!(
389                        get_iface_addrs(Some(IpType::Ipv6), None),
390                        Err(Error::NoAddress)
391                    ) || matches!(
392                        get_iface_addrs(Some(IpType::Ipv4), None),
393                        Err(Error::NoAddress)
394                    ),
395                    "Individual get_iface_addrs succeeded but generic one didn't"
396                );
397            }
398        }
399    }
400}