use super::Adapter;
use ipnetwork::Ipv4Network;
use pnet_base::MacAddr;
use std::net::Ipv4Addr;
use std::os::windows::io::AsRawSocket;
use std::ptr;
use windows_sys::Win32::Foundation::{ERROR_BUFFER_OVERFLOW, GlobalFree, NO_ERROR};
use windows_sys::Win32::NetworkManagement::IpHelper::{
FreeMibTable, GAA_FLAG_INCLUDE_GATEWAYS, GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_DNS_SERVER,
GAA_FLAG_SKIP_MULTICAST, GetAdaptersAddresses, GetIfEntry2, GetIpNetTable2,
IF_TYPE_SOFTWARE_LOOPBACK, IP_ADAPTER_ADDRESSES_LH, MIB_IF_ROW2, MIB_IPNET_TABLE2, SendARP,
};
use windows_sys::Win32::NetworkManagement::Ndis::IfOperStatusUp;
use windows_sys::Win32::Networking::WinSock::{
AF_INET, NlnsIncomplete, SIO_TCP_INITIAL_RTO, SOCKADDR, SOCKADDR_IN, SOCKET,
TCP_INITIAL_RTO_PARAMETERS, WSAIoctl,
};
use windows_sys::Win32::System::DataExchange::{
CloseClipboard, EmptyClipboard, OpenClipboard, SetClipboardData,
};
use windows_sys::Win32::System::Memory::{GMEM_MOVEABLE, GlobalAlloc, GlobalLock, GlobalUnlock};
use windows_sys::Win32::System::Ole::CF_UNICODETEXT;
pub fn adapters() -> Vec<Adapter> {
let flags = GAA_FLAG_INCLUDE_GATEWAYS
| GAA_FLAG_SKIP_ANYCAST
| GAA_FLAG_SKIP_MULTICAST
| GAA_FLAG_SKIP_DNS_SERVER;
let mut size: u32 = 16 * 1024;
let mut buf: Vec<u64>; loop {
buf = vec![0; (size as usize).div_ceil(8)];
let ret = unsafe {
GetAdaptersAddresses(
u32::from(AF_INET),
flags,
ptr::null(),
buf.as_mut_ptr().cast(),
&mut size,
)
};
match ret {
NO_ERROR => break,
ERROR_BUFFER_OVERFLOW => continue,
_ => return Vec::new(),
}
}
let mut out = Vec::new();
let mut next = buf.as_ptr().cast::<IP_ADAPTER_ADDRESSES_LH>();
while let Some(a) = unsafe { next.as_ref() } {
next = a.Next;
let index = unsafe { a.Anonymous1.Anonymous.IfIndex };
let mut ips = Vec::new();
let mut addr = a.FirstUnicastAddress;
while let Some(u) = unsafe { addr.as_ref() } {
if let Some(ip) = unsafe { ipv4(u.Address.lpSockaddr) }
&& let Ok(net) = Ipv4Network::new(ip, u.OnLinkPrefixLength)
{
ips.push(net);
}
addr = u.Next;
}
let gateway = unsafe { a.FirstGatewayAddress.as_ref() }
.and_then(|g| unsafe { ipv4(g.Address.lpSockaddr) });
let m = &a.PhysicalAddress;
out.push(Adapter {
name: unsafe { wide_str(a.FriendlyName) },
index,
mac: (a.PhysicalAddressLength == 6)
.then(|| MacAddr::new(m[0], m[1], m[2], m[3], m[4], m[5])),
ips,
up: a.OperStatus == IfOperStatusUp,
loopback: a.IfType == IF_TYPE_SOFTWARE_LOOPBACK,
physical: is_hardware(index),
gateway,
});
}
out
}
fn is_hardware(index: u32) -> bool {
let mut row: MIB_IF_ROW2 = unsafe { std::mem::zeroed() };
row.InterfaceIndex = index;
unsafe {
GetIfEntry2(&mut row) == NO_ERROR && row.InterfaceAndOperStatusFlags._bitfield & 1 != 0
}
}
pub fn default_gateway(adapter: &Adapter) -> Option<Ipv4Addr> {
adapter.gateway
}
pub fn arp_cache(adapter: &Adapter) -> Vec<(Ipv4Addr, MacAddr)> {
let mut table: *mut MIB_IPNET_TABLE2 = ptr::null_mut();
if unsafe { GetIpNetTable2(AF_INET, &mut table) } != NO_ERROR {
return Vec::new();
}
let rows = unsafe {
std::slice::from_raw_parts((*table).Table.as_ptr(), (*table).NumEntries as usize)
};
let found = rows
.iter()
.filter(|r| {
r.InterfaceIndex == adapter.index
&& r.State > NlnsIncomplete
&& r.PhysicalAddressLength == 6
})
.map(|r| {
let ip = Ipv4Addr::from(unsafe { r.Address.Ipv4.sin_addr.S_un.S_addr }.to_ne_bytes());
let m = &r.PhysicalAddress;
(ip, MacAddr::new(m[0], m[1], m[2], m[3], m[4], m[5]))
})
.filter(|(_, mac)| *mac != MacAddr::zero())
.collect();
unsafe { FreeMibTable(table.cast()) };
found
}
pub fn send_arp(target: Ipv4Addr, source: Ipv4Addr) -> Option<MacAddr> {
let mut mac = [0u8; 8];
let mut len = mac.len() as u32;
let ret = unsafe {
SendARP(
u32::from_ne_bytes(target.octets()),
u32::from_ne_bytes(source.octets()),
mac.as_mut_ptr().cast(),
&mut len,
)
};
(ret == NO_ERROR && len == 6)
.then(|| MacAddr::new(mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]))
}
pub fn fail_fast_on_refusal(sock: &impl AsRawSocket) {
let params = TCP_INITIAL_RTO_PARAMETERS {
Rtt: 0, MaxSynRetransmissions: TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS,
};
let mut returned = 0u32;
unsafe {
WSAIoctl(
sock.as_raw_socket() as SOCKET,
SIO_TCP_INITIAL_RTO,
(&raw const params).cast(),
size_of::<TCP_INITIAL_RTO_PARAMETERS>() as u32,
ptr::null_mut(),
0,
&mut returned,
ptr::null_mut(),
None,
);
}
}
const TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS: u8 = 0xfe;
pub fn set_clipboard(text: &str) -> bool {
let wide: Vec<u16> = text.encode_utf16().chain([0]).collect();
unsafe {
if OpenClipboard(ptr::null_mut()) == 0 {
return false;
}
EmptyClipboard();
let mem = GlobalAlloc(GMEM_MOVEABLE, wide.len() * 2);
let dst = if mem.is_null() {
ptr::null_mut()
} else {
GlobalLock(mem).cast::<u16>()
};
let ok = !dst.is_null() && {
ptr::copy_nonoverlapping(wide.as_ptr(), dst, wide.len());
GlobalUnlock(mem);
!SetClipboardData(u32::from(CF_UNICODETEXT), mem).is_null()
};
if !ok && !mem.is_null() {
GlobalFree(mem);
}
CloseClipboard();
ok
}
}
unsafe fn ipv4(sa: *const SOCKADDR) -> Option<Ipv4Addr> {
let sa = unsafe { sa.as_ref() }?;
if sa.sa_family != AF_INET {
return None;
}
let sin = unsafe { &*(sa as *const SOCKADDR).cast::<SOCKADDR_IN>() };
Some(Ipv4Addr::from(
unsafe { sin.sin_addr.S_un.S_addr }.to_ne_bytes(),
))
}
unsafe fn wide_str(p: *const u16) -> String {
if p.is_null() {
return String::new();
}
let len = (0..).take_while(|&i| unsafe { *p.add(i) } != 0).count();
String::from_utf16_lossy(unsafe { std::slice::from_raw_parts(p, len) })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore = "replaces the clipboard's contents"]
fn clipboard_takes_unicode() {
assert!(set_clipboard("Sanford’s Mac mini · 192.168.1.179"));
}
}