From d97fc3a33e458a092a8dfd10f32156023a94cc98 Mon Sep 17 00:00:00 2001 From: shellrow <81893184+shellrow@users.noreply.github.com> Date: Mon, 19 Feb 2024 23:20:30 +0900 Subject: [PATCH] #55 Rebranding to netdev --- Cargo.toml | 12 +-- LICENSE | 2 +- README.md | 44 ++++++----- examples/default_gateway.rs | 5 +- examples/default_interface.rs | 7 +- examples/list_interfaces.rs | 7 +- examples/serialize.rs | 2 +- src/bpf/unix.rs | 14 ++-- src/device.rs | 28 +++++++ src/gateway/linux.rs | 97 +++++++++++++++++------ src/gateway/macos.rs | 141 +++++++++++++++++++++------------- src/gateway/mod.rs | 29 +------ src/gateway/unix.rs | 4 +- src/interface/android.rs | 2 + src/interface/linux.rs | 31 ++++++++ src/interface/macos.rs | 57 +++++++++++++- src/interface/mod.rs | 80 +++++-------------- src/interface/types.rs | 4 + src/interface/unix.rs | 87 ++++++++++----------- src/interface/windows.rs | 123 ++++++++++++++++++----------- src/ip.rs | 22 ++++++ src/lib.rs | 3 +- src/socket/packet.rs | 16 ++-- src/socket/unix.rs | 6 +- 24 files changed, 511 insertions(+), 312 deletions(-) create mode 100644 src/device.rs diff --git a/Cargo.toml b/Cargo.toml index 7329543..29b913a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "default-net" -version = "0.22.0" -authors = ["shellrow "] +name = "netdev" +version = "0.23.0" +authors = ["shellrow "] edition = "2021" -description = "Cross-platform library for network interface and gateway" -repository = "https://github.com/shellrow/default-net" +description = "Cross-platform library for network interface" +repository = "https://github.com/shellrow/netdev" readme = "README.md" keywords = ["network"] categories = ["network-programming"] @@ -31,7 +31,7 @@ version = "0.48.0" features = ["Win32_Foundation","Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_NetworkManagement_Ndis"] [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] -system-configuration = "0.5.1" +system-configuration = "0.6" [dev-dependencies] serde_json = "1.0" diff --git a/LICENSE b/LICENSE index 39b7473..ff072bd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 shellrow +Copyright (c) 2024 shellrow Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d241862..f548ca7 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,26 @@ -[crates-badge]: https://img.shields.io/crates/v/default-net.svg -[crates-url]: https://crates.io/crates/default-net -[license-badge]: https://img.shields.io/crates/l/default-net.svg -[examples-url]: https://github.com/shellrow/default-net/tree/main/examples +[crates-badge]: https://img.shields.io/crates/v/netdev.svg +[crates-url]: https://crates.io/crates/netdev +[license-badge]: https://img.shields.io/crates/l/netdev.svg +[examples-url]: https://github.com/shellrow/netdev/tree/main/examples +[doc-url]: https://docs.rs/netdev/latest/netdev +[doc-interface-url]: https://docs.rs/netdev/latest/netdev/interface/struct.Interface.html [netdev-github-url]: https://github.com/shellrow/netdev -[netdev-crates-io-url]: https://crates.io/crates/netdev +[default-net-github-url]: https://github.com/shellrow/default-net +[default-net-crates-io-url]: https://crates.io/crates/default-net -# Notice -- This project has been rebranded to `netdev` and repository has been moved to the https://github.com/shellrow/netdev -- This crate has been moved to [netdev][netdev-crates-io-url] from `0.23` - -# default-net [![Crates.io][crates-badge]][crates-url] ![License][license-badge] +# netdev [![Crates.io][crates-badge]][crates-url] ![License][license-badge] -`default-net` provides a cross-platform API for network interface and gateway. +`netdev` provides a cross-platform API for network interface. + +## Key Features +- Get list of available network interfaces +- Get default network interface +- Access additional information related to network interface + +Please refer to the [Interface][doc-interface-url] struct documentation for detail. -- Get default Network Interface and Gateway information -- Get list of available Network Interfaces +## Notice +This project was rebranded from [default-net][default-net-crates-io-url] by the author myself for future expansion, continuation, and better alignment with naming conventions. ## Supported platform - Linux @@ -22,13 +28,13 @@ - Windows ## Usage -Add `default-net` to your dependencies +Add `netdev` to your dependencies ```toml:Cargo.toml [dependencies] -default-net = "0.22" +netdev = "0.23" ``` -For more details, see [examples][examples-url] or doc. +For more details, see [examples][examples-url] or [doc][doc-url]. ## Tested on - Linux @@ -45,6 +51,6 @@ For more details, see [examples][examples-url] or doc. - 13.4.1 - 11.6 - Windows - - 11 Pro 22H2 22621.1848 - - 11 21H2 22000.493 - - 10 21H2 19044.1586 \ No newline at end of file + - 11 Pro 22H2 22621.3155 + - 11 22H2 22621.3155 + - 10 21H2 19044.1586 diff --git a/examples/default_gateway.rs b/examples/default_gateway.rs index 84ccc2e..b7af122 100644 --- a/examples/default_gateway.rs +++ b/examples/default_gateway.rs @@ -1,11 +1,12 @@ // This example shows how to get the default gateway and its properties. fn main() { - match default_net::get_default_gateway() { + match netdev::get_default_gateway() { Ok(gateway) => { println!("Default Gateway"); println!("\tMAC Address: {}", gateway.mac_addr); - println!("\tIP Address: {}", gateway.ip_addr); + println!("\tIPv4: {:?}", gateway.ipv4); + println!("\tIPv6: {:?}", gateway.ipv6); } Err(e) => { println!("Error: {}", e); diff --git a/examples/default_interface.rs b/examples/default_interface.rs index 67d6db2..b9eda1d 100644 --- a/examples/default_interface.rs +++ b/examples/default_interface.rs @@ -1,7 +1,7 @@ // This example shows how to get the default network interface and its properties. fn main() { - match default_net::get_default_interface() { + match netdev::get_default_interface() { Ok(interface) => { println!("Default Interface:"); println!("\tIndex: {}", interface.index); @@ -28,10 +28,13 @@ fn main() { if let Some(gateway) = interface.gateway { println!("Default Gateway"); println!("\tMAC Address: {}", gateway.mac_addr); - println!("\tIP Address: {}", gateway.ip_addr); + println!("\tIPv4: {:?}", gateway.ipv4); + println!("\tIPv6: {:?}", gateway.ipv6); } else { println!("Default Gateway: (Not found)"); } + println!("DNS Servers: {:?}", interface.dns_servers); + println!("Default: {}", interface.default); } Err(e) => { println!("Error: {}", e); diff --git a/examples/list_interfaces.rs b/examples/list_interfaces.rs index 718e5dc..099ddd0 100644 --- a/examples/list_interfaces.rs +++ b/examples/list_interfaces.rs @@ -1,7 +1,7 @@ // This example shows all interfaces and their properties. fn main() { - let interfaces = default_net::get_interfaces(); + let interfaces = netdev::get_interfaces(); for interface in interfaces { println!("Interface:"); println!("\tIndex: {}", interface.index); @@ -28,10 +28,13 @@ fn main() { if let Some(gateway) = interface.gateway { println!("Gateway"); println!("\tMAC Address: {}", gateway.mac_addr); - println!("\tIP Address: {}", gateway.ip_addr); + println!("\tIPv4 Address: {:?}", gateway.ipv4); + println!("\tIPv6 Address: {:?}", gateway.ipv6); } else { println!("Gateway: (Not found)"); } + println!("DNS Servers: {:?}", interface.dns_servers); + println!("Default: {}", interface.default); println!(); } } diff --git a/examples/serialize.rs b/examples/serialize.rs index af78618..24c770e 100644 --- a/examples/serialize.rs +++ b/examples/serialize.rs @@ -1,6 +1,6 @@ // This example shows how to use serde feature to serialize the default network interface to JSON. fn main() { - match default_net::get_default_interface() { + match netdev::get_default_interface() { Ok(interface) => match serde_json::to_string_pretty(&interface) { Ok(json) => { println!("{}", json); diff --git a/src/bpf/unix.rs b/src/bpf/unix.rs index 2f9d617..eb0e251 100644 --- a/src/bpf/unix.rs +++ b/src/bpf/unix.rs @@ -1,6 +1,6 @@ use super::binding; use crate::interface::Interface; -use crate::socket::{DataLinkReceiver, DataLinkSender}; +use crate::socket::{FrameReceiver, FrameSender}; use std::collections::VecDeque; use std::ffi::CString; @@ -187,7 +187,7 @@ pub fn channel(interface_name: String, config: Config) -> io::Result io::Result io::Result, fd_set: libc::fd_set, write_buffer: Vec, @@ -221,7 +221,7 @@ struct DataLinkSenderImpl { timeout: Option, } -impl DataLinkSender for DataLinkSenderImpl { +impl FrameSender for FrameSenderImpl { #[inline] fn build_and_send( &mut self, @@ -315,7 +315,7 @@ impl DataLinkSender for DataLinkSenderImpl { } } -struct DataLinkReceiverImpl { +struct FrameReceiverImpl { fd: Arc, fd_set: libc::fd_set, read_buffer: Vec, @@ -324,7 +324,7 @@ struct DataLinkReceiverImpl { packets: VecDeque<(usize, usize)>, } -impl DataLinkReceiver for DataLinkReceiverImpl { +impl FrameReceiver for FrameReceiverImpl { fn next(&mut self) -> io::Result<&[u8]> { let (header_size, buffer_offset) = if self.loopback { (4, ETHERNET_HEADER_SIZE) diff --git a/src/device.rs b/src/device.rs new file mode 100644 index 0000000..fc77ec6 --- /dev/null +++ b/src/device.rs @@ -0,0 +1,28 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::mac::MacAddr; +use std::net::{Ipv4Addr, Ipv6Addr}; + +/// Structure of NetworkDevice information +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct NetworkDevice { + /// MAC address of the device + pub mac_addr: MacAddr, + /// List of IPv4 address of the device + pub ipv4: Vec, + /// List of IPv6 address of the device + pub ipv6: Vec, +} + +impl NetworkDevice { + /// Construct a new NetworkDevice instance + pub fn new() -> NetworkDevice { + NetworkDevice { + mac_addr: MacAddr::zero(), + ipv4: Vec::new(), + ipv6: Vec::new(), + } + } +} diff --git a/src/gateway/linux.rs b/src/gateway/linux.rs index d685d43..c69b85f 100644 --- a/src/gateway/linux.rs +++ b/src/gateway/linux.rs @@ -1,9 +1,12 @@ -use super::Gateway; +use crate::device::NetworkDevice; use crate::mac::MacAddr; +use std::collections::HashMap; use std::fs::read_to_string; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::str::FromStr; const PATH_PROC_NET_ROUTE: &str = "/proc/net/route"; +const PATH_PROC_NET_IPV6_ROUTE: &str = "/proc/net/ipv6_route"; const PATH_PROC_NET_ARP: &str = "/proc/net/arp"; fn convert_hex_ipv4(hex_ip: &str) -> Ipv4Addr { @@ -17,7 +20,6 @@ fn convert_hex_ipv4(hex_ip: &str) -> Ipv4Addr { Ipv4Addr::new(o1, o2, o3, o4) } -#[allow(dead_code)] fn convert_hex_ipv6(hex_ip: &str) -> Ipv6Addr { if hex_ip.len() != 32 { return Ipv6Addr::UNSPECIFIED; @@ -33,44 +35,91 @@ fn convert_hex_ipv6(hex_ip: &str) -> Ipv6Addr { Ipv6Addr::new(h1, h2, h3, h4, h5, h6, h7, h8) } -pub fn get_default_gateway(interface_name: String) -> Result { - match super::send_udp_packet() { - Ok(_) => {} - Err(e) => return Err(format!("Failed to send UDP packet {}", e)), +fn get_arp_map() -> HashMap { + let mut arp_map: HashMap = HashMap::new(); + let arp_data = read_to_string(PATH_PROC_NET_ARP); + let arp_text = match arp_data { + Ok(content) => content, + Err(_) => String::new(), + }; + let arp_table: Vec<&str> = arp_text.trim().split("\n").collect(); + for row in arp_table { + let mut fields: Vec<&str> = row.split(" ").collect(); + fields.retain(|value| *value != ""); + if fields.len() >= 6 { + // fields[0]: IP Address + // fields[3]: MAC Address (colon-separated string of hex format) + // fields[5]: Interface Name + match Ipv4Addr::from_str(fields[0]) { + Ok(ipv4_addr) => { + arp_map.insert(ipv4_addr, MacAddr::from_hex_format(fields[3])); + } + Err(_) => {} + } + } } + arp_map +} + +fn get_ipv4_gateway_map() -> HashMap { + let mut ipv4_gateway_map: HashMap = HashMap::new(); let route_data = read_to_string(PATH_PROC_NET_ROUTE); let route_text = match route_data { Ok(content) => content, Err(_) => String::new(), }; let route_table: Vec<&str> = route_text.trim().split("\n").collect(); - let mut gateway_ip: IpAddr = IpAddr::V4(Ipv4Addr::UNSPECIFIED); for row in route_table { let fields: Vec<&str> = row.split("\t").collect(); if fields.len() >= 3 { - if fields[0] == interface_name && fields[2] != "00000000" { - gateway_ip = IpAddr::V4(convert_hex_ipv4(fields[2])); + // fields[0]: Interface Name + // fields[2]: IPv4 Address 8 hex chars + if fields[2] != "00000000" { + ipv4_gateway_map.insert(fields[0].to_string(), convert_hex_ipv4(fields[2])); } } } - let arp_data = read_to_string(PATH_PROC_NET_ARP); - let arp_text = match arp_data { + ipv4_gateway_map +} + +fn get_ipv6_gateway_map() -> HashMap { + let mut ipv6_gateway_map: HashMap = HashMap::new(); + let route_data = read_to_string(PATH_PROC_NET_IPV6_ROUTE); + let route_text = match route_data { Ok(content) => content, Err(_) => String::new(), }; - let arp_table: Vec<&str> = arp_text.trim().split("\n").collect(); - for row in arp_table { - let mut fields: Vec<&str> = row.split(" ").collect(); - fields.retain(|value| *value != ""); - if fields.len() >= 6 { - if fields[0] == gateway_ip.to_string() && fields[5] == interface_name { - let gateway: Gateway = Gateway { - mac_addr: MacAddr::from_hex_format(fields[3]), - ip_addr: gateway_ip, - }; - return Ok(gateway); + let route_table: Vec<&str> = route_text.trim().split("\n").collect(); + for row in route_table { + let fields: Vec<&str> = row.split("\t").collect(); + if fields.len() >= 10 { + // fields[4]: IPv6 Address 32 hex chars without colons + // fields[9]: Interface Name + if fields[4] != "00000000000000000000000000000000" { + ipv6_gateway_map.insert(fields[9].to_string(), convert_hex_ipv6(fields[4])); } } } - Err(String::new()) + ipv6_gateway_map +} + +pub fn get_gateway_map() -> HashMap { + match super::send_udp_packet() { + Ok(_) => {} + Err(_) => {} + } + let mut gateway_map: HashMap = HashMap::new(); + let arp_map: HashMap = get_arp_map(); + for (if_name, ipv4_addr) in get_ipv4_gateway_map() { + let gateway = gateway_map.entry(if_name).or_insert(NetworkDevice::new()); + if let Some(mac_addr) = arp_map.get(&ipv4_addr) { + gateway.mac_addr = mac_addr.clone(); + } + gateway.ipv4.push(ipv4_addr); + } + for (if_name, ipv6_addr) in get_ipv6_gateway_map() { + let gateway = gateway_map.entry(if_name).or_insert(NetworkDevice::new()); + gateway.ipv6.push(ipv6_addr); + } + gateway_map } diff --git a/src/gateway/macos.rs b/src/gateway/macos.rs index bf6a11b..ed3adb8 100644 --- a/src/gateway/macos.rs +++ b/src/gateway/macos.rs @@ -1,6 +1,6 @@ #![allow(non_camel_case_types)] -use super::Gateway; +use crate::device::NetworkDevice; use crate::mac::MacAddr; use std::{ @@ -9,6 +9,8 @@ use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, }; +use libc::RTAX_MAX; + const CTL_NET: u32 = 4; const AF_INET: u32 = 2; const AF_ROUTE: u32 = 17; @@ -158,16 +160,16 @@ fn code_to_error(err: i32) -> io::Error { io::Error::new(kind, format!("rtm_errno {}", err)) } -unsafe fn sa_to_ip(sa: &sockaddr) -> Option { +fn socketaddr_to_ipaddr(sa: &sockaddr) -> Option { match sa.sa_family as u32 { AF_INET => { - let inet: &sockaddr_in = std::mem::transmute(sa); + let inet: &sockaddr_in = unsafe { std::mem::transmute(sa) }; let octets: [u8; 4] = inet.sin_addr.s_addr.to_ne_bytes(); Some(IpAddr::from(octets)) } AF_INET6 => { - let inet6: &sockaddr_in6 = std::mem::transmute(sa); - let octets: [u8; 16] = inet6.sin6_addr.__u6_addr.__u6_addr8; + let inet6: &sockaddr_in6 = unsafe { std::mem::transmute(sa) }; + let octets: [u8; 16] = unsafe { inet6.sin6_addr.__u6_addr.__u6_addr8 }; Some(IpAddr::from(octets)) } AF_LINK => None, @@ -175,48 +177,81 @@ unsafe fn sa_to_ip(sa: &sockaddr) -> Option { } } -fn message_to_route(hdr: &rt_msghdr, msg: *mut u8) -> Option { - let destination; +// https://opensource.apple.com/source/network_cmds/network_cmds-606.40.2/netstat.tproj/route.c.auto.html +// See function get_rtaddrs, macro ROUNDUP, function p_sockaddr +fn message_to_route(hdr: &rt_msghdr, msg: &[u8]) -> Option { let mut gateway = None; - let ifindex = None; - + // Check if message has destination if hdr.rtm_addrs & (1 << RTAX_DST) == 0 { return None; } - - unsafe { - let dst_sa: &sockaddr = std::mem::transmute((msg as *mut sockaddr).add(RTAX_DST as usize)); - destination = sa_to_ip(dst_sa)?; + let mut route_addresses = [None; RTAX_MAX as usize]; + let mut cur_pos = 0; + for idx in 0..RTAX_MAX as usize { + if hdr.rtm_addrs & (1 << idx) != 0 { + let buf = &msg[cur_pos..]; + let sa: &sockaddr = unsafe { &*(buf.as_ptr() as *const sockaddr) }; + route_addresses[idx] = Some(sa); + let aligned_len = if sa.sa_len == 0 { + 4 + } else { + ((sa.sa_len - 1) | 0x3) + 1 + }; + cur_pos += aligned_len as usize; + } } + let sa = match route_addresses[RTAX_DST as usize] { + Some(sa) => sa, + None => return None, + }; + let destination = socketaddr_to_ipaddr(sa)?; + let mut prefix = match destination { IpAddr::V4(_) => 32, IpAddr::V6(_) => 128, }; + // Check if message has a gateway if hdr.rtm_addrs & (1 << RTAX_GATEWAY) != 0 { - unsafe { - let gw_sa: &sockaddr = - std::mem::transmute((msg as *mut sockaddr).add(RTAX_GATEWAY as usize)); - - gateway = sa_to_ip(gw_sa); + let gw_sa = match route_addresses[RTAX_GATEWAY as usize] { + Some(sa) => sa, + None => return None, + }; + gateway = socketaddr_to_ipaddr(gw_sa); + if let Some(IpAddr::V6(ipv6gw)) = gateway { + // Unicast link local start with FE80:: + let is_unicast_ll = ipv6gw.segments()[0] == 0xfe80; + // IPv6 multicast starts with FF + let is_multicast = ipv6gw.octets()[0] == 0xff; + // lower 4 bit of byte1 encode the multicast scope + let multicast_scope = ipv6gw.octets()[1] & 0x0f; + // scope 1: interface/node-local + // scope 2: link-local + if is_unicast_ll || (is_multicast && (multicast_scope == 1 || multicast_scope == 2)) { + let segs = ipv6gw.segments(); + gateway = Some(IpAddr::V6(Ipv6Addr::new( + segs[0], 0, segs[2], segs[3], segs[4], segs[5], segs[6], segs[7], + ))) + } } } + // Check if message has netmask if hdr.rtm_addrs & (1 << RTAX_NETMASK) != 0 { - unsafe { + let sa = route_addresses[RTAX_NETMASK as usize].unwrap(); + if sa.sa_len == 0 { + prefix = 0; + } else { match destination { IpAddr::V4(_) => { - let mask_sa: &sockaddr_in = - std::mem::transmute((msg as *mut sockaddr).add(RTAX_NETMASK as usize)); - let octets: [u8; 4] = mask_sa.sin_addr.s_addr.to_ne_bytes(); - prefix = u32::from_be_bytes(octets).leading_ones() as u8; + let mask_sa: &sockaddr_in = unsafe { std::mem::transmute(sa) }; + prefix = u32::from_be(mask_sa.sin_addr.s_addr).leading_ones() as u8; } IpAddr::V6(_) => { - let mask_sa: &sockaddr_in6 = - std::mem::transmute((msg as *mut sockaddr).add(RTAX_NETMASK as usize)); - let octets: [u8; 16] = mask_sa.sin6_addr.__u6_addr.__u6_addr8; - prefix = u128::from_be_bytes(octets).leading_ones() as u8; + let mask_sa: &sockaddr_in6 = unsafe { std::mem::transmute(sa) }; + prefix = u128::from_be_bytes(unsafe { mask_sa.sin6_addr.__u6_addr.__u6_addr8 }) + .leading_ones() as u8; } } } @@ -226,7 +261,7 @@ fn message_to_route(hdr: &rt_msghdr, msg: *mut u8) -> Option { destination, prefix, gateway, - ifindex, + ifindex: Some(hdr.rtm_index as u32), }) } @@ -282,7 +317,7 @@ fn list_routes() -> io::Result> { let mut routes = vec![]; let mut offset = 0; - loop { + while offset + std::mem::size_of::() <= len as usize { let buf = &mut msgs_buf[offset..]; if buf.len() < std::mem::size_of::() { @@ -313,7 +348,7 @@ fn list_routes() -> io::Result> { } let rt_msg = &mut buf[std::mem::size_of::()..msg_len]; - if let Some(route) = message_to_route(rt_hdr, rt_msg.as_mut_ptr()) { + if let Some(route) = message_to_route(rt_hdr, rt_msg) { routes.push(route); } } @@ -418,7 +453,8 @@ fn get_arp_table() -> io::Result> { Ok(arp_map) } -fn get_default_route() -> Option { +fn get_default_routes() -> Vec { + let mut default_routes = Vec::new(); match list_routes() { Ok(routes) => { for route in routes { @@ -428,39 +464,36 @@ fn get_default_route() -> Option { && route.gateway != Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED)) && route.gateway != Some(IpAddr::V6(Ipv6Addr::UNSPECIFIED)) { - return Some(route); + default_routes.push(route); } } } Err(_) => {} } - None + default_routes } -pub fn get_default_gateway(_interface_name: String) -> Result { - if let Some(route) = get_default_route() { +pub fn get_gateway_map() -> HashMap { + let mut gateway_map: HashMap = HashMap::new(); + let routes = get_default_routes(); + let arp_map = get_arp_table().unwrap_or(HashMap::new()); + for route in routes { if let Some(gw_ip) = route.gateway { - match get_arp_table() { - Ok(arp_map) => { - if let Some(mac_addr) = arp_map.get(&gw_ip) { - let gateway = Gateway { - mac_addr: mac_addr.clone(), - ip_addr: gw_ip, - }; - return Ok(gateway); - } + let gateway = gateway_map + .entry(route.ifindex.unwrap_or(0)) + .or_insert(NetworkDevice::new()); + if let Some(mac_addr) = arp_map.get(&gw_ip) { + gateway.mac_addr = mac_addr.clone(); + } + match gw_ip { + IpAddr::V4(ipv4) => { + gateway.ipv4.push(ipv4); + } + IpAddr::V6(ipv6) => { + gateway.ipv6.push(ipv6); } - Err(_) => {} } - let gateway = Gateway { - mac_addr: MacAddr::zero(), - ip_addr: gw_ip, - }; - return Ok(gateway); - } else { - return Err(format!("Failed to get gateway IP address")); } - } else { - return Err(format!("Failed to get default route")); } + gateway_map } diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index da88517..411ff72 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -7,35 +7,12 @@ pub(crate) mod macos; #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) mod linux; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - +use crate::device::NetworkDevice; use crate::interface::{self, Interface}; -use crate::mac::MacAddr; -use std::net::{IpAddr, Ipv4Addr}; - -/// Structure of default Gateway information -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct Gateway { - /// MAC address of Gateway - pub mac_addr: MacAddr, - /// IP address of Gateway - pub ip_addr: IpAddr, -} - -impl Gateway { - /// Construct a new Gateway instance - pub fn new() -> Gateway { - Gateway { - mac_addr: MacAddr::zero(), - ip_addr: IpAddr::V4(Ipv4Addr::UNSPECIFIED), - } - } -} +use std::net::IpAddr; /// Get default Gateway -pub fn get_default_gateway() -> Result { +pub fn get_default_gateway() -> Result { let local_ip: IpAddr = match interface::get_local_ipaddr() { Some(local_ip) => local_ip, None => return Err(String::from("Local IP address not found")), diff --git a/src/gateway/unix.rs b/src/gateway/unix.rs index 0088eb9..c65866d 100644 --- a/src/gateway/unix.rs +++ b/src/gateway/unix.rs @@ -1,10 +1,10 @@ -use super::Gateway; +use crate::device::NetworkDevice; use crate::socket; use std::time::{Duration, Instant}; const TIMEOUT: u64 = 3000; -pub fn get_default_gateway(interface_name: String) -> Result { +pub fn get_default_gateway(interface_name: String) -> Result { let timeout = Duration::from_millis(TIMEOUT); let start_time = Instant::now(); let config = socket::Config { diff --git a/src/interface/android.rs b/src/interface/android.rs index c1484ce..e000a77 100644 --- a/src/interface/android.rs +++ b/src/interface/android.rs @@ -101,6 +101,8 @@ pub mod netlink { transmit_speed: None, receive_speed: None, gateway: None, + dns_servers: Vec::new(), + default: false, }; for nla in link_msg.nlas { diff --git a/src/interface/linux.rs b/src/interface/linux.rs index f07688b..1108853 100644 --- a/src/interface/linux.rs +++ b/src/interface/linux.rs @@ -1,6 +1,9 @@ use crate::interface::InterfaceType; use std::convert::TryFrom; use std::fs::read_to_string; +use std::net::IpAddr; + +const PATH_RESOLV_CONF: &str = "/etc/resolv.conf"; pub fn get_interface_type(if_name: String) -> InterfaceType { let if_type_path: String = format!("/sys/class/net/{}/type", if_name); @@ -44,3 +47,31 @@ pub fn get_interface_speed(if_name: String) -> Option { } }; } + +pub fn get_system_dns_conf() -> Vec { + let r = read_to_string(PATH_RESOLV_CONF); + match r { + Ok(content) => { + let conf_lines: Vec<&str> = content.trim().split("\n").collect(); + let mut dns_servers = Vec::new(); + for line in conf_lines { + let fields: Vec<&str> = line.split_whitespace().collect(); + if fields.len() >= 2 { + // field [0]: Configuration type (e.g., "nameserver", "domain", "search") + // field [1]: Corresponding value (e.g., IP address, domain name) + if fields[0] == "nameserver" { + if let Ok(ip) = fields[1].parse::() { + dns_servers.push(ip); + } else { + eprintln!("Invalid IP address format: {}", fields[1]); + } + } + } + } + dns_servers + } + Err(_) => { + return Vec::new(); + } + } +} diff --git a/src/interface/macos.rs b/src/interface/macos.rs index 0929c51..ba301ff 100644 --- a/src/interface/macos.rs +++ b/src/interface/macos.rs @@ -1,18 +1,29 @@ use crate::interface::InterfaceType; -use std::collections::HashMap; +use std::fs::read_to_string; +use std::{collections::HashMap, net::IpAddr}; use system_configuration::network_configuration; +const PATH_RESOLV_CONF: &str = "/etc/resolv.conf"; + fn get_if_type_from_id(type_id: String) -> InterfaceType { match type_id.as_str() { "Ethernet" => InterfaceType::Ethernet, "IEEE80211" => InterfaceType::Wireless80211, "PPP" => InterfaceType::Ppp, + "Bridge" => InterfaceType::Bridge, _ => InterfaceType::Unknown, } } -pub fn get_if_type_map() -> HashMap { - let mut map: HashMap = HashMap::new(); +#[derive(Debug)] +pub struct SCInterface { + pub name: String, + pub friendly_name: Option, + pub interface_type: InterfaceType, +} + +pub fn get_if_type_map() -> HashMap { + let mut map: HashMap = HashMap::new(); let interfaces = network_configuration::get_interfaces(); for interface in &interfaces { let if_name: String = if let Some(bsd_name) = interface.bsd_name() { @@ -25,7 +36,45 @@ pub fn get_if_type_map() -> HashMap { } else { continue; }; - map.insert(if_name, get_if_type_from_id(type_id)); + let friendly_name: Option = if let Some(name) = interface.display_name() { + Some(name.to_string()) + } else { + None + }; + let sc_if = SCInterface { + name: if_name.clone(), + friendly_name: friendly_name, + interface_type: get_if_type_from_id(type_id), + }; + map.insert(if_name, sc_if); } return map; } + +pub fn get_system_dns_conf() -> Vec { + let r = read_to_string(PATH_RESOLV_CONF); + match r { + Ok(content) => { + let conf_lines: Vec<&str> = content.trim().split("\n").collect(); + let mut dns_servers = Vec::new(); + for line in conf_lines { + let fields: Vec<&str> = line.split_whitespace().collect(); + if fields.len() >= 2 { + // field [0]: Configuration type (e.g., "nameserver", "domain", "search") + // field [1]: Corresponding value (e.g., IP address, domain name) + if fields[0] == "nameserver" { + if let Ok(ip) = fields[1].parse::() { + dns_servers.push(ip); + } else { + eprintln!("Invalid IP address format: {}", fields[1]); + } + } + } + } + dns_servers + } + Err(_) => { + return Vec::new(); + } + } +} diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 8a8ce3a..5b4eb76 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -42,7 +42,7 @@ mod android; #[cfg(any(target_os = "macos", target_os = "ios"))] mod macos; -use crate::gateway::Gateway; +use crate::device::NetworkDevice; use crate::ip::{Ipv4Net, Ipv6Net}; use crate::mac::MacAddr; use crate::sys; @@ -75,17 +75,26 @@ pub struct Interface { /// Speed in bits per second of the receive for the network interface pub receive_speed: Option, /// Default gateway for the network interface - pub gateway: Option, + pub gateway: Option, + /// DNS servers for the network interface + pub dns_servers: Vec, + /// is default interface + pub default: bool, } impl Interface { /// Construct a new default Interface instance pub fn default() -> Result { + let interfaces: Vec = interfaces(); + for iface in &interfaces { + if iface.default { + return Ok(iface.clone()); + } + } let local_ip: IpAddr = match get_local_ipaddr() { Some(local_ip) => local_ip, None => return Err(String::from("Local IP address not found")), }; - let interfaces: Vec = interfaces(); for iface in interfaces { match local_ip { IpAddr::V4(local_ipv4) => { @@ -117,6 +126,8 @@ impl Interface { transmit_speed: None, receive_speed: None, gateway: None, + dns_servers: Vec::new(), + default: false, } } /// Check if the network interface is up @@ -147,11 +158,16 @@ impl Interface { /// Get default Network Interface pub fn get_default_interface() -> Result { + let interfaces: Vec = interfaces(); + for iface in &interfaces { + if iface.default { + return Ok(iface.clone()); + } + } let local_ip: IpAddr = match get_local_ipaddr() { Some(local_ip) => local_ip, None => return Err(String::from("Local IP address not found")), }; - let interfaces: Vec = interfaces(); for iface in interfaces { match local_ip { IpAddr::V4(local_ipv4) => { @@ -169,54 +185,6 @@ pub fn get_default_interface() -> Result { Err(String::from("Default Interface not found")) } -/// Get default Network Interface index -pub fn get_default_interface_index() -> Option { - let local_ip: IpAddr = match get_local_ipaddr() { - Some(local_ip) => local_ip, - None => return None, - }; - let interfaces = interfaces(); - for iface in interfaces { - match local_ip { - IpAddr::V4(local_ipv4) => { - if iface.ipv4.iter().any(|x| x.addr == local_ipv4) { - return Some(iface.index); - } - } - IpAddr::V6(local_ipv6) => { - if iface.ipv6.iter().any(|x| x.addr == local_ipv6) { - return Some(iface.index); - } - } - } - } - None -} - -/// Get default Network Interface name -pub fn get_default_interface_name() -> Option { - let local_ip: IpAddr = match get_local_ipaddr() { - Some(local_ip) => local_ip, - None => return None, - }; - let interfaces = interfaces(); - for iface in interfaces { - match local_ip { - IpAddr::V4(local_ipv4) => { - if iface.ipv4.iter().any(|x| x.addr == local_ipv4) { - return Some(iface.name); - } - } - IpAddr::V6(local_ipv6) => { - if iface.ipv6.iter().any(|x| x.addr == local_ipv6) { - return Some(iface.name); - } - } - } - } - None -} - /// Get a list of available Network Interfaces pub fn get_interfaces() -> Vec { interfaces() @@ -236,12 +204,4 @@ mod tests { fn test_default_interface() { println!("{:#?}", get_default_interface()); } - #[test] - fn test_default_interface_index() { - println!("{:?}", get_default_interface_index()); - } - #[test] - fn test_default_interface_name() { - println!("{:?}", get_default_interface_name()); - } } diff --git a/src/interface/types.rs b/src/interface/types.rs index 71e9b7d..937daf2 100644 --- a/src/interface/types.rs +++ b/src/interface/types.rs @@ -63,6 +63,8 @@ pub enum InterfaceType { Wwanpp, /// The network interface using a mobile broadband interface for CDMA-based devices Wwanpp2, + /// Transparent bridge interface + Bridge, } impl InterfaceType { @@ -98,6 +100,7 @@ impl InterfaceType { InterfaceType::Wman => 237, InterfaceType::Wwanpp => 243, InterfaceType::Wwanpp2 => 244, + _ => u32::MAX, } } /// Returns OS-specific value of InterfaceType @@ -159,6 +162,7 @@ impl InterfaceType { InterfaceType::Tunnel => String::from("Tunnel"), InterfaceType::MultiRateSymmetricDsl => String::from("Multi-Rate Symmetric DSL"), InterfaceType::HighPerformanceSerialBus => String::from("High Performance Serial Bus"), + InterfaceType::Bridge => String::from("Bridge"), InterfaceType::Wman => String::from("WMAN"), InterfaceType::Wwanpp => String::from("WWANPP"), InterfaceType::Wwanpp2 => String::from("WWANPP2"), diff --git a/src/interface/unix.rs b/src/interface/unix.rs index f528495..914e54d 100644 --- a/src/interface/unix.rs +++ b/src/interface/unix.rs @@ -11,71 +11,70 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::os::raw::c_char; use std::str::from_utf8_unchecked; -#[cfg(any(target_os = "openbsd", target_os = "freebsd", target_os = "netbsd"))] +#[cfg(any(target_os = "macos", target_os = "ios"))] pub fn interfaces() -> Vec { + use super::macos; + + let type_map = macos::get_if_type_map(); let mut interfaces: Vec = unix_interfaces(); let local_ip: IpAddr = match super::get_local_ipaddr() { Some(local_ip) => local_ip, None => return interfaces, }; + let gateway_map = gateway::macos::get_gateway_map(); for iface in &mut interfaces { - match local_ip { - IpAddr::V4(local_ipv4) => { - if iface.ipv4.iter().any(|x| x.addr == local_ipv4) { - match gateway::unix::get_default_gateway(iface.name.clone()) { - Ok(gateway) => { - iface.gateway = Some(gateway); - } - Err(_) => {} - } - } + if let Some(sc_interface) = type_map.get(&iface.name) { + iface.if_type = sc_interface.interface_type; + iface.friendly_name = sc_interface.friendly_name.clone(); + } + if let Some(gateway) = gateway_map.get(&iface.index) { + iface.gateway = Some(gateway.clone()); + } + iface.ipv4.iter().for_each(|ipv4| { + if IpAddr::V4(ipv4.addr) == local_ip { + iface.dns_servers = macos::get_system_dns_conf(); + iface.default = true; } - IpAddr::V6(local_ipv6) => { - if iface.ipv6.iter().any(|x| x.addr == local_ipv6) { - match gateway::unix::get_default_gateway(iface.name.clone()) { - Ok(gateway) => { - iface.gateway = Some(gateway); - } - Err(_) => {} - } - } + }); + iface.ipv6.iter().for_each(|ipv6| { + if IpAddr::V6(ipv6.addr) == local_ip { + iface.dns_servers = macos::get_system_dns_conf(); + iface.default = true; } - } + }); } interfaces } -#[cfg(any(target_os = "macos", target_os = "ios"))] +#[cfg(any(target_os = "linux", target_os = "android"))] pub fn interfaces() -> Vec { - use super::macos; + use super::linux; - let type_map = macos::get_if_type_map(); let mut interfaces: Vec = unix_interfaces(); let local_ip: IpAddr = match super::get_local_ipaddr() { Some(local_ip) => local_ip, None => return interfaces, }; + let gateway_map = gateway::linux::get_gateway_map(); for iface in &mut interfaces { - iface.if_type = *type_map.get(&iface.name).unwrap_or(&InterfaceType::Unknown); + iface.if_type = linux::get_interface_type(iface.name.clone()); + let if_speed: Option = linux::get_interface_speed(iface.name.clone()); + iface.transmit_speed = if_speed; + iface.receive_speed = if_speed; + if let Some(gateway) = gateway_map.get(&iface.name) { + iface.gateway = Some(gateway.clone()); + } match local_ip { IpAddr::V4(local_ipv4) => { if iface.ipv4.iter().any(|x| x.addr == local_ipv4) { - match gateway::macos::get_default_gateway(iface.name.clone()) { - Ok(gateway) => { - iface.gateway = Some(gateway); - } - Err(_) => {} - } + iface.default = true; + iface.dns_servers = linux::get_system_dns_conf(); } } IpAddr::V6(local_ipv6) => { if iface.ipv6.iter().any(|x| x.addr == local_ipv6) { - match gateway::macos::get_default_gateway(iface.name.clone()) { - Ok(gateway) => { - iface.gateway = Some(gateway); - } - Err(_) => {} - } + iface.default = true; + iface.dns_servers = linux::get_system_dns_conf(); } } } @@ -83,24 +82,18 @@ pub fn interfaces() -> Vec { interfaces } -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(any(target_os = "openbsd", target_os = "freebsd", target_os = "netbsd"))] pub fn interfaces() -> Vec { - use super::linux; - let mut interfaces: Vec = unix_interfaces(); let local_ip: IpAddr = match super::get_local_ipaddr() { Some(local_ip) => local_ip, None => return interfaces, }; for iface in &mut interfaces { - iface.if_type = linux::get_interface_type(iface.name.clone()); - let if_speed: Option = linux::get_interface_speed(iface.name.clone()); - iface.transmit_speed = if_speed; - iface.receive_speed = if_speed; match local_ip { IpAddr::V4(local_ipv4) => { if iface.ipv4.iter().any(|x| x.addr == local_ipv4) { - match gateway::linux::get_default_gateway(iface.name.clone()) { + match gateway::unix::get_default_gateway(iface.name.clone()) { Ok(gateway) => { iface.gateway = Some(gateway); } @@ -110,7 +103,7 @@ pub fn interfaces() -> Vec { } IpAddr::V6(local_ipv6) => { if iface.ipv6.iter().any(|x| x.addr == local_ipv6) { - match gateway::linux::get_default_gateway(iface.name.clone()) { + match gateway::unix::get_default_gateway(iface.name.clone()) { Ok(gateway) => { iface.gateway = Some(gateway); } @@ -277,6 +270,8 @@ fn unix_interfaces_inner( transmit_speed: None, receive_speed: None, gateway: None, + dns_servers: Vec::new(), + default: false, }; let mut found: bool = false; for iface in &mut ifaces { diff --git a/src/interface/windows.rs b/src/interface/windows.rs index 33beb8e..fd5802e 100644 --- a/src/interface/windows.rs +++ b/src/interface/windows.rs @@ -9,11 +9,12 @@ use windows::Win32::NetworkManagement::IpHelper::{ GetAdaptersAddresses, SendARP, GAA_FLAG_INCLUDE_GATEWAYS, IP_ADAPTER_ADDRESSES_LH, }; use windows::Win32::NetworkManagement::Ndis::{IF_OPER_STATUS, NET_IF_OPER_STATUS_UP}; +use windows::Win32::Networking::WinSock::SOCKET_ADDRESS; use windows::Win32::Networking::WinSock::{ AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR_IN, SOCKADDR_IN6, }; -use crate::gateway::Gateway; +use crate::device::NetworkDevice; use crate::interface::{Interface, InterfaceType}; use crate::ip::{Ipv4Net, Ipv6Net}; use crate::mac::MacAddr; @@ -53,9 +54,33 @@ fn get_mac_through_arp(src_ip: Ipv4Addr, dst_ip: Ipv4Addr) -> MacAddr { } } +unsafe fn socket_address_to_ipaddr(addr: &SOCKET_ADDRESS) -> Option { + let sockaddr = unsafe { *addr.lpSockaddr }; + if sockaddr.sa_family == AF_INET { + let sockaddr: *mut SOCKADDR_IN = addr.lpSockaddr as *mut SOCKADDR_IN; + let a = unsafe { (*sockaddr).sin_addr.S_un.S_addr }; + let ipv4 = if cfg!(target_endian = "little") { + Ipv4Addr::from(a.swap_bytes()) + } else { + Ipv4Addr::from(a) + }; + return Some(IpAddr::V4(ipv4)); + } else if sockaddr.sa_family == AF_INET6 { + let sockaddr: *mut SOCKADDR_IN6 = addr.lpSockaddr as *mut SOCKADDR_IN6; + let a = unsafe { (*sockaddr).sin6_addr.u.Byte }; + let ipv6 = Ipv6Addr::from(a); + return Some(IpAddr::V6(ipv6)); + } + None +} + // Get network interfaces using the IP Helper API // Reference: https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses pub fn interfaces() -> Vec { + let local_ip: IpAddr = match super::get_local_ipaddr() { + Some(local_ip) => local_ip, + None => IpAddr::V4(Ipv4Addr::LOCALHOST), + }; let mut interfaces: Vec = vec![]; let mut dwsize: u32 = 2000; let mut mem = unsafe { allocate(dwsize as usize) } as *mut IP_ADAPTER_ADDRESSES_LH; @@ -148,61 +173,63 @@ pub fn interfaces() -> Vec { // Enumerate all IPs let mut cur_a = unsafe { (*cur).FirstUnicastAddress }; while !cur_a.is_null() { - let addr = unsafe { (*cur_a).Address }; + let addr: SOCKET_ADDRESS = unsafe { (*cur_a).Address }; + let ip_addr = unsafe { socket_address_to_ipaddr(&addr) }; let prefix_len = unsafe { (*cur_a).OnLinkPrefixLength }; - let sockaddr = unsafe { *addr.lpSockaddr }; - if sockaddr.sa_family == AF_INET { - let sockaddr: *mut SOCKADDR_IN = addr.lpSockaddr as *mut SOCKADDR_IN; - let a = unsafe { (*sockaddr).sin_addr.S_un.S_addr }; - let ipv4 = if cfg!(target_endian = "little") { - Ipv4Addr::from(a.swap_bytes()) - } else { - Ipv4Addr::from(a) - }; - let ipv4_net: Ipv4Net = Ipv4Net::new(ipv4, prefix_len); - ipv4_vec.push(ipv4_net); - } else if sockaddr.sa_family == AF_INET6 { - let sockaddr: *mut SOCKADDR_IN6 = addr.lpSockaddr as *mut SOCKADDR_IN6; - let a = unsafe { (*sockaddr).sin6_addr.u.Byte }; - let ipv6 = Ipv6Addr::from(a); - let ipv6_net: Ipv6Net = Ipv6Net::new(ipv6, prefix_len); - ipv6_vec.push(ipv6_net); + if let Some(ip_addr) = ip_addr { + match ip_addr { + IpAddr::V4(ipv4) => { + let ipv4_net: Ipv4Net = Ipv4Net::new(ipv4, prefix_len); + ipv4_vec.push(ipv4_net); + } + IpAddr::V6(ipv6) => { + let ipv6_net: Ipv6Net = Ipv6Net::new(ipv6, prefix_len); + ipv6_vec.push(ipv6_net); + } + } } cur_a = unsafe { (*cur_a).Next }; } // Gateway - // TODO: IPv6 support - let mut gateway_ips: Vec = vec![]; + let mut gateway_ips: Vec = vec![]; let mut cur_g = unsafe { (*cur).FirstGatewayAddress }; while !cur_g.is_null() { - let addr = unsafe { (*cur_g).Address }; - let sockaddr = unsafe { *addr.lpSockaddr }; - if sockaddr.sa_family == AF_INET { - let sockaddr: *mut SOCKADDR_IN = addr.lpSockaddr as *mut SOCKADDR_IN; - let a = unsafe { (*sockaddr).sin_addr.S_un.S_addr }; - let ipv4 = if cfg!(target_endian = "little") { - Ipv4Addr::from(a.swap_bytes()) - } else { - Ipv4Addr::from(a) - }; - gateway_ips.push(ipv4); + let addr: SOCKET_ADDRESS = unsafe { (*cur_g).Address }; + if let Some(ip_addr) = unsafe { socket_address_to_ipaddr(&addr) } { + gateway_ips.push(ip_addr); } cur_g = unsafe { (*cur_g).Next }; } - let default_gateway: Option = match gateway_ips.get(0) { - Some(gateway_ip) => { - if let Some(ip_net) = ipv4_vec.get(0) { - let mac_addr = get_mac_through_arp(ip_net.addr, *gateway_ip); - let gateway = Gateway { - mac_addr: mac_addr, - ip_addr: IpAddr::V4(*gateway_ip), - }; - Some(gateway) - } else { - None + let mut default_gateway: NetworkDevice = NetworkDevice::new(); + for gateway_ip in gateway_ips { + match gateway_ip { + IpAddr::V4(ipv4) => { + if let Some(ip_net) = ipv4_vec.get(0) { + let mac_addr = get_mac_through_arp(ip_net.addr, ipv4); + default_gateway.mac_addr = mac_addr; + default_gateway.ipv4.push(ipv4); + } + } + IpAddr::V6(ipv6) => { + if let Some(_ip_net) = ipv6_vec.get(0) { + default_gateway.ipv6.push(ipv6); + } } } - None => None, + } + // DNS Servers + let mut dns_servers: Vec = vec![]; + let mut cur_d = unsafe { (*cur).FirstDnsServerAddress }; + while !cur_d.is_null() { + let addr: SOCKET_ADDRESS = unsafe { (*cur_d).Address }; + if let Some(ip_addr) = unsafe { socket_address_to_ipaddr(&addr) } { + dns_servers.push(ip_addr); + } + cur_d = unsafe { (*cur_d).Next }; + } + let default: bool = match local_ip { + IpAddr::V4(local_ipv4) => ipv4_vec.iter().any(|x| x.addr == local_ipv4), + IpAddr::V6(local_ipv6) => ipv6_vec.iter().any(|x| x.addr == local_ipv6), }; let interface: Interface = Interface { index: index, @@ -216,7 +243,13 @@ pub fn interfaces() -> Vec { flags: flags, transmit_speed: Some(transmit_speed), receive_speed: Some(receive_speed), - gateway: default_gateway, + gateway: if default_gateway.mac_addr == MacAddr::zero() { + None + } else { + Some(default_gateway) + }, + dns_servers: dns_servers, + default: default, }; interfaces.push(interface); cur = unsafe { (*cur).Next }; diff --git a/src/ip.rs b/src/ip.rs index 829529f..51630d7 100644 --- a/src/ip.rs +++ b/src/ip.rs @@ -74,6 +74,19 @@ impl IpNet { IpNet::V6(ref a) => IpAddr::V6(a.broadcast()), } } + /// Checks if the IP Address is in the network. + pub fn contains(&self, ip: IpAddr) -> bool { + match *self { + IpNet::V4(ref a) => match ip { + IpAddr::V4(ip) => a.contains(ip), + IpAddr::V6(_) => false, + }, + IpNet::V6(ref a) => match ip { + IpAddr::V4(_) => false, + IpAddr::V6(ip) => a.contains(ip), + }, + } + } } impl From for IpNet { @@ -158,6 +171,10 @@ impl Ipv4Net { pub fn broadcast(&self) -> Ipv4Addr { Ipv4Addr::from(u32::from(self.addr) | self.hostmask_u32()) } + /// Checks if the IP Address is in the network. + pub fn contains(&self, ip: Ipv4Addr) -> bool { + self.network() == Ipv4Addr::from(u32::from(ip) & self.netmask_u32()) + } } impl fmt::Debug for Ipv4Net { @@ -243,6 +260,11 @@ impl Ipv6Net { pub fn broadcast(&self) -> Ipv6Addr { (u128::from(self.addr) | self.hostmask_u128()).into() } + /// Checks if the IP Address is in the network. + pub fn contains(&self, ip: Ipv6Addr) -> bool { + let ipv6_network: Ipv6Addr = (u128::from(ip) & self.netmask_u128()).into(); + self.network() == ipv6_network + } } impl fmt::Debug for Ipv6Net { diff --git a/src/lib.rs b/src/lib.rs index b43704b..4ecea68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,14 +9,15 @@ mod bpf; #[cfg(any(target_os = "openbsd", target_os = "freebsd", target_os = "netbsd"))] mod socket; +pub mod device; pub mod gateway; pub mod interface; pub mod ip; pub mod mac; mod sys; +pub use device::NetworkDevice; pub use gateway::get_default_gateway; -pub use gateway::Gateway; pub use interface::get_default_interface; pub use interface::get_interfaces; pub use interface::Interface; diff --git a/src/socket/packet.rs b/src/socket/packet.rs index 5c6b108..b7ad4a9 100644 --- a/src/socket/packet.rs +++ b/src/socket/packet.rs @@ -1,7 +1,7 @@ -use crate::gateway::Gateway; +use crate::device::NetworkDevice; use crate::mac::MacAddr; use std::convert::TryInto; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::net::{Ipv4Addr, Ipv6Addr}; use std::u16; pub const ETHER_TYPE_IPV4: [u8; 2] = [8, 0]; @@ -75,7 +75,7 @@ fn convert_ipv6_bytes(bytes: [u8; 16]) -> Ipv6Addr { Ipv6Addr::new(h1, h2, h3, h4, h5, h6, h7, h8) } -pub fn parse_frame(frame: &[u8]) -> Result { +pub fn parse_frame(frame: &[u8]) -> Result { let src_mac: [u8; 6] = frame[Frame::SrcMacAddr.start_index()..Frame::SrcMacAddr.end_index()] .try_into() .unwrap(); @@ -92,9 +92,10 @@ pub fn parse_frame(frame: &[u8]) -> Result { if next_header_protocol == NEXT_HEADER_ICMP { let icmp_type: u8 = frame[Frame::IcmpType.start_index()]; if icmp_type == ICMP_TYPE_TIME_EXCEEDED { - let gateway = Gateway { + let gateway = NetworkDevice { mac_addr: MacAddr::from_octets(src_mac), - ip_addr: IpAddr::V4(convert_ipv4_bytes(src_ip)), + ipv4: vec![convert_ipv4_bytes(src_ip)], + ipv6: vec![], }; return Ok(gateway); } @@ -111,9 +112,10 @@ pub fn parse_frame(frame: &[u8]) -> Result { if icmp_type == ICMPV6_TYPE_TIME_EXCEEDED { let icmp_type: u8 = frame[Frame::Icmpv6Type.start_index()]; if icmp_type == ICMPV6_TYPE_TIME_EXCEEDED { - let gateway = Gateway { + let gateway = NetworkDevice { mac_addr: MacAddr::from_octets(src_mac), - ip_addr: IpAddr::V6(convert_ipv6_bytes(src_ip)), + ipv4: vec![], + ipv6: vec![convert_ipv6_bytes(src_ip)], }; return Ok(gateway); } diff --git a/src/socket/unix.rs b/src/socket/unix.rs index 9673fdb..023cda3 100644 --- a/src/socket/unix.rs +++ b/src/socket/unix.rs @@ -15,7 +15,7 @@ pub enum ChannelType { #[non_exhaustive] pub enum Channel { - Ethernet(Box, Box), + Ethernet(Box, Box), } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -55,7 +55,7 @@ pub fn channel(interface_name: String, configuration: Config) -> io::Result) -> Option>; } -pub trait DataLinkReceiver: Send { +pub trait FrameReceiver: Send { fn next(&mut self) -> io::Result<&[u8]>; }