Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement nic on windows #297

Merged
merged 8 commits into from Feb 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions heim-net/Cargo.toml
Expand Up @@ -21,6 +21,10 @@ libc = "~0.2"
[target.'cfg(unix)'.dependencies]
nix = "~0.19"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["iphlpapi"]}
widestring = "0.4"

[dev-dependencies]
heim-derive = { version = "0.1.0-rc.1", path = "../heim-derive" }
smol = "~1.2"
Expand Down
9 changes: 9 additions & 0 deletions heim-net/src/nic.rs
Expand Up @@ -35,7 +35,14 @@ impl Nic {
self.as_ref().name()
}

/// Returns NIC index (internally used by the OS to identify the NIC)
pub fn index(&self) -> Option<u32> {
self.as_ref().index()
}

/// Returns primary NIC address.
///
/// See [`nic`] for more info regarding NICs that have multiple addresses
pub fn address(&self) -> Address {
self.as_ref().address()
}
Expand Down Expand Up @@ -88,6 +95,8 @@ impl fmt::Debug for Nic {
/// Returns a stream over the [Network Interface Cards].
///
/// [Network Interface Cards]: struct.Nic.html
///
/// Depending on your platform, NICs that have multiple addresses may be enumerated several times, with a different [`address`](Nic::address) every time.
pub async fn nic() -> Result<impl Stream<Item = Result<Nic>> + Send + Sync> {
let inner = sys::nic().await?;

Expand Down
15 changes: 15 additions & 0 deletions heim-net/src/os/windows.rs
Expand Up @@ -2,6 +2,21 @@
//!
//! Available only for `cfg(target_os = "windows")`

/// Windows-specific extension for [Nic].
///
/// [Nic]: ../../struct.Nic.html
pub trait NicExt {
/// Returns NIC GUID
fn guid(&self) -> &str;
}

#[cfg(target_os = "windows")]
impl NicExt for crate::Nic {
fn guid(&self) -> &str {
self.as_ref().guid()
}
}

/// Windows-specific extension for [IoCounters].
///
/// [IoCounters]: ../../struct.IoCounters.html
Expand Down
5 changes: 5 additions & 0 deletions heim-net/src/sys/unix/nic.rs
Expand Up @@ -2,6 +2,7 @@ use std::net::SocketAddr;

use macaddr::MacAddr;
use nix::ifaddrs;
use nix::net::if_::if_nametoindex;
use nix::net::if_::InterfaceFlags;
use nix::sys::socket;

Expand All @@ -17,6 +18,10 @@ impl Nic {
self.0.interface_name.as_str()
}

pub fn index(&self) -> Option<u32> {
if_nametoindex(self.name()).ok()
}

pub fn address(&self) -> Address {
self.0
.address
Expand Down
278 changes: 255 additions & 23 deletions heim-net/src/sys/windows/nic.rs
@@ -1,57 +1,289 @@
use heim_common::prelude::*;

use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::net::SocketAddrV4;
use std::net::SocketAddrV6;

use std::ffi::CStr;
use widestring::UCStr;

use winapi::shared::minwindef::ULONG;
use winapi::shared::ntdef::NULL;
use winapi::shared::ws2def::SOCKET_ADDRESS;
use winapi::shared::ws2ipdef::SOCKADDR_IN6;
use winapi::shared::ws2def::SOCKADDR_IN;
use winapi::shared::ws2def::{AF_INET, AF_INET6};
use winapi::shared::ws2def::AF_UNSPEC;
use winapi::shared::winerror::{ERROR_BUFFER_OVERFLOW, NO_ERROR};
use winapi::shared::ifdef::IfOperStatusUp;
use winapi::um::iphlpapi::GetAdaptersAddresses;
use winapi::um::iptypes::GAA_FLAG_INCLUDE_PREFIX;
use winapi::um::iptypes::PIP_ADAPTER_ADDRESSES;
use winapi::um::iptypes::IP_ADAPTER_ADDRESSES;

use crate::Address;

#[derive(Debug)]
pub struct Nic;


#[derive(Clone, Debug)]
pub struct Nic {
index: u32,
guid: String,
friendly_name: String,
is_up: bool,
address: Option<Address>,
netmask: Option<Address>,
}


fn sockaddr_to_ipv4(sa: SOCKET_ADDRESS) -> Option<Address> {
// Check this sockaddr can fit one short and a char[14]
// (see https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2)
// This should always happen though, this is guaranteed by winapi's interface
if (sa.iSockaddrLength as usize) < std::mem::size_of::<SOCKADDR_IN>() {
return None;
}

if sa.lpSockaddr.is_null() {
return None;
}
let arr = unsafe { (*sa.lpSockaddr).sa_data };
let ip4 = Ipv4Addr::new(arr[2] as _, arr[3] as _, arr[4] as _, arr[5] as _);
let port = (arr[0] as u16) + (arr[1] as u16)*0x100;

Some(Address::Inet(
SocketAddrV4::new(ip4, port)
))
}

fn sockaddr_to_ipv6(sa: SOCKET_ADDRESS) -> Option<Address> {
// Check this sockaddr can fit a SOCKADDR_IN6 (two shorts, two longs, and a 16-byte struct)
// (see https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2)
if (sa.iSockaddrLength as usize) < std::mem::size_of::<SOCKADDR_IN6>() {
return None;
}

let p_sa6 = sa.lpSockaddr as *const SOCKADDR_IN6;
if p_sa6.is_null() {
return None;
}
let sa6 = unsafe{ *p_sa6 };

let ip6_data = unsafe{ sa6.sin6_addr.u.Byte() };
let ip6 = Ipv6Addr::from(*ip6_data);
let port = sa6.sin6_port;
let flow_info = sa6.sin6_flowinfo;
let scope_id = unsafe{ *sa6.u.sin6_scope_id() };

Some(Address::Inet6(
SocketAddrV6::new(ip6, port, flow_info, scope_id )
))
}

/// Generate an IPv4 netmask from a prefix length (Rust equivalent of ConvertLengthToIpv4Mask())
fn ipv4_netmask_from(length: u8) -> Ipv4Addr {
let mask = match length {
len if len <= 32 => u32::max_value().checked_shl(32 - len as u32).unwrap_or(0),
_ /* invalid value */ => u32::max_value(),
};
Ipv4Addr::from(mask)
}

/// Generate an IPv6 netmask from a prefix length
fn ipv6_netmask_from(length: u8) -> Ipv6Addr {
let mask = match length {
len if len <= 128 => u128::max_value().checked_shl(128 - len as u32).unwrap_or(0),
_ /* invalid value */ => u128::max_value(),
};
Ipv6Addr::from(mask)
}

fn ipv4_netmask_address_from(length: u8) -> Address {
Address::Inet(SocketAddrV4::new(ipv4_netmask_from(length), 0))
}
fn ipv6_netmask_address_from(length: u8) -> Address {
Address::Inet6(SocketAddrV6::new(ipv6_netmask_from(length), 0, 0, 0))
}





impl Nic {
pub fn name(&self) -> &str {
unimplemented!()
&self.friendly_name
}

pub fn address(&self) -> Address {
unimplemented!()
pub fn index(&self) -> Option<u32> {
Some(self.index)
}

pub fn netmask(&self) -> Option<Address> {
unimplemented!()
pub fn guid(&self) -> &str {
&self.guid
}

pub fn address(&self) -> Address {
self.address.unwrap_or_else(||
Address::Inet(SocketAddrV4::new(Ipv4Addr::new(0,0,0,0), 0))
)
}

pub fn broadcast(&self) -> Option<Address> {
unimplemented!()
pub fn netmask(&self) -> Option<Address> {
self.netmask
}

pub fn destination(&self) -> Option<Address> {
unimplemented!()
// TODO: we could implement something one day
None
}

pub fn is_up(&self) -> bool {
unimplemented!()
self.is_up
}

pub fn is_running(&self) -> bool {
unimplemented!()
// TODO: not sure how to tell on Windows
true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a reminder: after all reviews it would be nice to create tracking issues for this and above TODOs in order not to forget about these things.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://todo.jasonet.co/ I've used this in some projects to great effect. It's a bot that automatically opens issues for TODO comments when merging a PR. Thought you might be interested ^^.

}

pub fn is_broadcast(&self) -> bool {
unimplemented!()
pub fn is_loopback(&self) -> bool {
match self.address {
Some(Address::Inet(sa)) => sa.ip().is_loopback(),
Some(Address::Inet6(sa6)) => sa6.ip().is_loopback(),
_ => false
}
}

pub fn is_loopback(&self) -> bool {
unimplemented!()
pub fn is_multicast(&self) -> bool {
match self.address {
Some(Address::Inet(sa)) => sa.ip().is_multicast(),
Some(Address::Inet6(sa6)) => sa6.ip().is_multicast(),
_ => false
}
}
}

pub async fn nic() -> Result<impl Stream<Item = Result<Nic>> + Send + Sync> {
let mut results = Vec::new();

pub fn is_point_to_point(&self) -> bool {
unimplemented!()
// Step 1 - get the size of the routing infos
let family = AF_UNSPEC; // retrieve both IPv4 and IPv6 interfaces
let flags: ULONG = GAA_FLAG_INCLUDE_PREFIX;
let mut empty_list = IP_ADAPTER_ADDRESSES::default();
let mut data_size: ULONG = 0;
let res = unsafe { GetAdaptersAddresses(family as _, flags, NULL, &mut empty_list, &mut data_size) };
if res != ERROR_BUFFER_OVERFLOW {
// Unable to get the size of routing infos
let e = Error::from(std::io::Error::from_raw_os_error(res as _)).with_ffi("GetAdaptersAddresses");
return Err(e);
}

pub fn is_multicast(&self) -> bool {
unimplemented!()

// Step 2 - get the interfaces infos
let mut buffer = vec![0; data_size as usize];
let res = unsafe { GetAdaptersAddresses(family as _, flags, NULL, buffer.as_mut_ptr() as _, &mut data_size) };
if res != NO_ERROR {
// Unable to get the routing infos
let e = Error::from(std::io::Error::from_raw_os_error(res as _)).with_ffi("GetAdaptersAddresses");
return Err(e);
}


// Step 3 - walk through the list and populate our interfaces
let mut cur_iface = unsafe {
let p = buffer.as_ptr() as PIP_ADAPTER_ADDRESSES;
if p.is_null() {
// Unable to list interfaces
let e = Error::from(std::io::Error::from_raw_os_error(res as _)).with_ffi("GetAdaptersAddresses");
return Err(e);
}
*p
};

loop {
let iface_index;
let iface_guid_cstr;
let iface_fname_ucstr;
let is_up;
let mut cur_address;

unsafe {
iface_index = cur_iface.u.s().IfIndex;
iface_guid_cstr = CStr::from_ptr(cur_iface.AdapterName);
iface_fname_ucstr = UCStr::from_ptr_str(cur_iface.FriendlyName);
cur_address = *(cur_iface.FirstUnicastAddress);
is_up = cur_iface.OperStatus == IfOperStatusUp;
}
let iface_guid = iface_guid_cstr.to_str().map(|s| s.to_string()).unwrap_or_else(|_| "".into());
let iface_friendly_name = iface_fname_ucstr.to_string_lossy();


let base_nic = Nic{
index: iface_index,
friendly_name: iface_friendly_name,
guid: iface_guid,
is_up,
address: None,
netmask: None,
};

// Walk through every IP address of this interface
loop {
let this_socket_address = cur_address.Address;
let this_netmask_length = cur_address.OnLinkPrefixLength;
let this_sa_family = unsafe { (*this_socket_address.lpSockaddr).sa_family };

let (this_address, this_netmask) = match this_sa_family as i32 {
AF_INET => {
(sockaddr_to_ipv4(this_socket_address),
Some(ipv4_netmask_address_from(this_netmask_length)))
},
AF_INET6 => {
(sockaddr_to_ipv6(this_socket_address),
Some(ipv6_netmask_address_from(this_netmask_length)))
},
_ => (None, None),
};

let mut this_nic = base_nic.clone();
this_nic.address = this_address;
this_nic.netmask = this_netmask;
results.push(Ok(this_nic));

let next_address = cur_address.Next;
if next_address.is_null() {
break;
}
cur_address = unsafe { *next_address };
}

let next_item = cur_iface.Next;
if next_item.is_null() {
break;
}
cur_iface = unsafe { *next_item };

}

Ok(stream::iter(results))
}

pub async fn nic() -> Result<impl Stream<Item = Result<Nic>> + Send + Sync> {
// TODO: Stub
Ok(stream::empty())


#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_netmasks() {
assert_eq!(ipv4_netmask_from(0), Ipv4Addr::new(0,0,0,0));
assert_eq!(ipv4_netmask_from(32), Ipv4Addr::new(255,255,255,255));
assert_eq!(ipv4_netmask_from(200), Ipv4Addr::new(255,255,255,255));
assert_eq!(ipv4_netmask_from(9), Ipv4Addr::new(255,128,0,0));

assert_eq!(ipv6_netmask_from(0), Ipv6Addr::new(0,0,0,0,0,0,0,0));
assert_eq!(ipv6_netmask_from(32), Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0));
assert_eq!(ipv6_netmask_from(200), Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff));
assert_eq!(ipv6_netmask_from(9), Ipv6Addr::new(0xff80, 0, 0, 0, 0, 0, 0, 0));
}
}