From 333f9e969e92fa79e499e824ec7f0424884bf505 Mon Sep 17 00:00:00 2001 From: Nathaniel Bennett Date: Fri, 1 Nov 2024 05:06:18 -0400 Subject: [PATCH] Add routing --- src/lib.rs | 217 ++++++++++++++++++++++++++++++++- src/libc_extra.rs | 127 +++++++++++++++---- src/macos/feth.rs | 4 +- src/{sysctl.rs => rtmsg.rs} | 235 +++++++++++++++++++++++++++++++++-- src/rtnetlink.rs | 237 +++++++++++++++++++++++++++++++----- src/wintun.rs | 2 +- 6 files changed, 746 insertions(+), 76 deletions(-) rename src/{sysctl.rs => rtmsg.rs} (57%) diff --git a/src/lib.rs b/src/lib.rs index 5d84b57..f72119e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -167,7 +167,7 @@ mod rtnetlink; target_os = "netbsd", target_os = "openbsd" ))] -mod sysctl; +mod rtmsg; #[cfg(not(target_os = "windows"))] mod tap; #[cfg(any(not(target_os = "windows"), feature = "wintun"))] @@ -209,7 +209,7 @@ use crate::libc_extra::*; #[cfg(target_os = "linux")] use rtnetlink::{ AddressAttr, AddressAttrRef, NetlinkRequest, NetlinkResponseRef, NlmsgDeleteAddress, - NlmsgGetAddress, NlmsgNewAddress, NlmsgPayload, NlmsgPayloadRef, + NlmsgGetAddress, NlmsgNewAddress, NlmsgNewRoute, NlmsgPayload, NlmsgPayloadRef, RouteAttr, }; #[cfg(any( target_os = "dragonfly", @@ -218,7 +218,7 @@ use rtnetlink::{ target_os = "netbsd", target_os = "openbsd" ))] -use sysctl::*; +use rtmsg::*; #[cfg(target_os = "linux")] const NETLINK_MAX_RECV: usize = 65536; @@ -328,6 +328,63 @@ impl AddressInfoV6 { } } +/* +/// Information associated with an interface IP address. +#[derive(Clone, Debug)] +pub enum RouteInfo { + V4(RouteInfoV4), + V6(RouteInfoV6), +} + +/// An information type used to add an IPv4 address and its associated information (desination +/// address, netmask, etc.) to an interface. +#[derive(Clone, Debug)] +pub struct RouteInfoV4 { + addr: Ipv4Addr, + brd: Option, + dst: Option, + netmask: Option, +} + +/// An information type used to add an IPv4 address and its associated information (desination +/// address, netmask, etc.) to an interface. +#[derive(Clone, Debug)] +pub struct RouteInfoV6 { + addr: Ipv6Addr, + brd: Option, + dst: Option, + netmask: Option, +} +*/ + +/// An information type used to add a route and its associated information (netmask, table ID, etc) +/// to an interface. +#[derive(Clone, Debug)] +pub struct AddRoute { + addr: IpAddr, + netmask: Option, + // Output interface is the TUN/TAP device by default +// Linux-specific: +// table_id: Option, // defaults to RT_TABLE_MAIN +// scope: Option, // defaults to RT_SCOPE_UNIVERSE +} + +impl AddRoute { + pub fn new(addr: IpAddr) -> Self { + Self { + addr, + netmask: None, +// table_id: None, +// scope: None, + } + } + + #[inline] + pub fn set_netmask(&mut self, netmask: Netmask) { + self.netmask = Some(netmask); + } +} + /// An information type used to add an address and its associated information (desination address, /// netmask, etc.) to an interface. #[derive(Clone, Debug)] @@ -996,7 +1053,7 @@ impl Interface { libc::PF_ROUTE, 0, libc::AF_UNSPEC, // address family - libc::NET_RT_IFLIST, + libc::NET_RT_IFLIST, // NET_RT_DUMP for route info if_index as i32, ]; @@ -1046,7 +1103,7 @@ impl Interface { let if_list = IfList::new(buf.as_slice()); for message in if_list { match message? { - SysctlMessage::NewAddress(new_addr) => { + RtMsgRef::NewAddress(new_addr) => { let mut dst = None; let mut mask = None; let mut addr = None; @@ -1150,7 +1207,6 @@ impl Interface { #[cfg(target_os = "linux")] fn add_addr_impl(&self, req: AddAddress) -> io::Result<()> { let index = self.index()?; - // BUG: netmask unused let (family, default_prefixlen) = match req { AddAddress::V4(_) => (libc::AF_INET, 32), @@ -1674,7 +1730,156 @@ impl Interface { } } + #[cfg(not(target_os = "windows"))] + #[inline] + pub(crate) fn add_route>(&self, req: A) -> io::Result<()> { + self.add_route_impl(req.into()) + } + + // See the following for Netlink examples: + // https://olegkutkov.me/2018/02/14/monitoring-linux-networking-state-using-netlink/ + + #[cfg(target_os = "linux")] + fn add_route_impl(&self, req: AddRoute) -> io::Result<()> { + let index = self.index()?; + + let family = match req.addr { + IpAddr::V4(_) => libc::AF_INET as u8, + IpAddr::V6(_) => libc::AF_INET6 as u8, + }; + + let fd = unsafe { libc::socket(libc::AF_NETLINK, libc::SOCK_RAW, libc::NETLINK_ROUTE) }; + if fd < 0 { + return Err(io::Error::last_os_error()); + } + + let local = sockaddr_nl { + nl_family: libc::AF_NETLINK as u16, + nl_pad: 0, + nl_pid: 0, + nl_groups: 0, + }; + let local_len = mem::size_of::() as libc::socklen_t; + + if unsafe { libc::bind(fd, ptr::addr_of!(local) as *const libc::sockaddr, local_len) } < 0 { + let err = io::Error::last_os_error(); + Self::close_fd(fd); + return Err(err); + } + + let nlreq = NetlinkRequest { + flags: (libc::NLM_F_CREATE | libc::NLM_F_EXCL | libc::NLM_F_REQUEST | libc::NLM_F_ACK) + as u16, + seq: 1, + pid: 0, + payload: NlmsgPayload::NewRoute(NlmsgNewRoute { + family, + dst_len: req.netmask.unwrap_or(32), + src_len: 0, + tos: 0, + table_id: libc::RT_TABLE_MAIN, + protocol: libc::RTPROT_STATIC, // Administrator configured + scope: libc::RT_SCOPE_UNIVERSE, + route_type: libc::RTN_UNICAST, + flags: 0, + attrs: vec![RouteAttr::Destination(req.addr), RouteAttr::OutputInterface(index as i32)], + }) + }; + + let mut req_bytes = nlreq.serialize(); + + let mut iov = libc::iovec { + iov_base: req_bytes.as_mut_ptr() as *mut libc::c_void, + iov_len: req_bytes.len(), + }; + + let mut dst_addr = sockaddr_nl { + nl_family: libc::AF_NETLINK as u16, + nl_pad: 0, + nl_pid: 0, + nl_groups: 0, + }; + + let msg = libc::msghdr { + msg_name: ptr::addr_of_mut!(dst_addr) as *mut libc::c_void, + msg_namelen: mem::size_of::() as u32, + msg_iov: ptr::addr_of_mut!(iov), + msg_iovlen: 1, + msg_control: ptr::null_mut(), + msg_controllen: 0, + msg_flags: 0, + }; + + let res = unsafe { libc::sendmsg(fd, ptr::addr_of!(msg), 0) }; + if res < 0 { + let err = io::Error::last_os_error(); + Self::close_fd(fd); + return Err(err); + } + + let mut buf = Vec::::new(); + buf.reserve_exact(NETLINK_MAX_RECV); + + let len = unsafe { + libc::recv( + fd, + buf.as_mut_ptr() as *mut libc::c_void, + NETLINK_MAX_RECV, + 0, + ) + }; + + if len < 0 { + let err = io::Error::last_os_error(); + Self::close_fd(fd); + return Err(err); + } + + unsafe { + buf.set_len(len as usize); + } + + Self::close_fd(fd); + + let resp = NetlinkResponseRef::new(buf.as_slice()); + let msg = resp.messages().next().ok_or(io::Error::new( + io::ErrorKind::InvalidData, + "netlink ADD_ROUTE request returned no response", + ))??; + + match msg.payload() { + NlmsgPayloadRef::Error(e) => { + if e.errno() == 0 { + Ok(()) + } else { + Err(io::Error::from_raw_os_error(e.errno())) + } + } + _ => Err(io::Error::new( + io::ErrorKind::InvalidData, + "netlink ADD_ROUTE request returned unexpected response type", + )), + } + } + + /* + #[cfg(not(target_os = "windows"))] + #[inline] + pub(crate) fn routes>(&self, req: A) -> io::Result<()> { + self.routes_impl(req.into()) + } + #[cfg(target_os = "linux")] + fn routes_impl(&self) -> io::Result { + + } + + fn remove_route(&self, addr: IpAddr) -> io::Result<()> { + + } + */ + + #[cfg(not(target_os = "windows"))] #[inline] fn close_fd(fd: RawFd) { unsafe { diff --git a/src/libc_extra.rs b/src/libc_extra.rs index 2f286f7..fef1d01 100644 --- a/src/libc_extra.rs +++ b/src/libc_extra.rs @@ -630,46 +630,25 @@ pub struct ifa_msghdr { pub ifam_metric: libc::c_int, } -/* + #[cfg(target_os = "macos")] pub use libc::rt_msghdr; -#[cfg(any(target_os = "dragonfly", target_os = "openbsd"))] +#[cfg(target_os = "dragonfly")] #[derive(Clone, Copy)] #[repr(C)] pub struct rt_msghdr { pub rtm_msglen: libc::c_ushort, pub rtm_version: libc::c_uchar, pub rtm_type: libc::c_uchar, - #[cfg(target_os = "openbsd")] - pub rtm_hdrlen: libc::c_ushort, pub rtm_index: libc::c_ushort, - #[cfg(target_os = "openbsd")] - pub rtm_tableid: libc::c_ushort, - #[cfg(target_os = "openbsd")] - pub rtm_priority: libc::c_uchar, - #[cfg(target_os = "openbsd")] - pub rtm_mpls: libc::c_uchar, - #[cfg(target_os = "freebsd")] - pub _rtm_spare1: libc::c_ushort, - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] pub rtm_flags: libc::c_int, pub rtm_addrs: libc::c_int, - #[cfg(target_os = "openbsd")] - pub rtm_flags: libc::c_int, - #[cfg(target_os = "openbsd")] - pub rtm_fmask: libc::c_int, pub rtm_pid: libc::pid_t, pub rtm_seq: libc::c_int, pub rtm_errno: libc::c_int, - #[cfg(target_os = "dragonfly")] pub rtm_use: libc::c_int, - #[cfg(target_os = "freebsd")] - pub rtm_fmask: libc::c_int, - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] pub rtm_inits: libc::c_ulong, - #[cfg(target_os = "openbsd")] - pub rtm_inits: libc::c_uint, pub rtm_rmx: rt_metrics, } @@ -710,6 +689,28 @@ pub struct rt_msghdr { pub rtm_rmx: rt_metrics, } +#[cfg(target_os = "openbsd")] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct rt_msghdr { + pub rtm_msglen: libc::c_ushort, + pub rtm_version: libc::c_uchar, + pub rtm_type: libc::c_uchar, + pub rtm_hdrlen: libc::c_ushort, + pub rtm_index: libc::c_ushort, + pub rtm_tableid: libc::c_ushort, + pub rtm_priority: libc::c_uchar, + pub rtm_mpls: libc::c_uchar, + pub rtm_addrs: libc::c_int, + pub rtm_flags: libc::c_int, + pub rtm_fmask: libc::c_int, + pub rtm_pid: libc::pid_t, + pub rtm_seq: libc::c_int, + pub rtm_errno: libc::c_int, + pub rtm_inits: libc::c_uint, + pub rtm_rmx: rt_metrics, +} + #[cfg(target_os = "dragonfly")] #[derive(Clone, Copy)] #[repr(C)] @@ -786,7 +787,7 @@ pub struct rt_metrics { pub rmx_expire: libc::time_t, pub rmx_pktsent: libc::time_t, } -*/ + #[cfg(target_os = "macos")] #[repr(C)] @@ -831,6 +832,79 @@ pub struct sockaddr_ndrv { pub snd_name: [libc::c_uchar; libc::IFNAMSIZ], } +#[cfg(target_os = "dragonfly")] +#[repr(C)] +pub struct sockaddr_dl { + pub sdl_len: libc::c_uchar, + pub sdl_family: libc::c_uchar, + pub sdl_index: libc::c_ushort, + pub sdl_type: libc::c_uchar, + pub sdl_nlen: libc::c_uchar, + pub sdl_alen: libc::c_uchar, + pub sdl_slen: libc::c_uchar, + pub sdl_data: [libc::c_char; 12], + pub sdl_rcf: libc::c_ushort, + pub sdl_route: [libc::c_ushort; 16], +} + +#[cfg(target_os = "freebsd")] +#[repr(C)] +pub struct sockaddr_dl { + pub sdl_len: libc::c_uchar, + pub sdl_family: libc::c_uchar, + pub sdl_index: libc::c_ushort, + pub sdl_type: libc::c_uchar, + pub sdl_nlen: libc::c_uchar, + pub sdl_alen: libc::c_uchar, + pub sdl_slen: libc::c_uchar, + pub sdl_data: [libc::c_char; 46], +} + +#[cfg(target_os = "macos")] +#[repr(C)] +pub struct sockaddr_dl { + pub sdl_len: libc::c_uchar, + pub sdl_family: libc::c_uchar, + pub sdl_index: libc::c_ushort, + pub sdl_type: libc::c_uchar, + pub sdl_nlen: libc::c_uchar, + pub sdl_alen: libc::c_uchar, + pub sdl_slen: libc::c_uchar, + pub sdl_data: [libc::c_char; 12], +} + +#[cfg(target_os = "netbsd")] +#[repr(C)] +pub struct sockaddr_dl { + pub sdl_len: libc::c_uchar, + pub sdl_family: libc::c_uchar, + pub sdl_index: libc::c_ushort, + pub sdl_addr: dl_addr, +} + +#[cfg(target_os = "netbsd")] +#[repr(C)] +pub struct dl_addr { + pub dl_type: libc::c_uchar, + pub dl_nlen: libc::c_uchar, + pub dl_alen: libc::c_uchar, + pub dl_slen: libc::c_uchar, + pub dl_data: [libc::c_char; 24], +} + +#[cfg(target_os = "openbsd")] +#[repr(C)] +struct sockaddr_dl { + pub sdl_len: libc::c_uchar, + pub sdl_family: libc::c_uchar, + pub sdl_index: u16, + pub sdl_type: libc::c_uchar, + pub sdl_nlen: libc::c_uchar, + pub sdl_alen: libc::c_uchar, + pub sdl_slen: libc::c_uchar, + pub sdl_data: [libc::c_char; 24], +} + // #[cfg(target_os = "macos")] #[allow(unused)] @@ -987,10 +1061,13 @@ pub const RTM_NEWADDR: libc::c_int = 0xc; #[allow(unused)] pub const RTM_NEWADDR: libc::c_int = 0x16; -#[cfg(target_os = "macos")] +#[cfg(any(target_os = "dragonfly", target_os = "macos"))] pub const AF_LINK: libc::c_int = 18; #[cfg(target_os = "macos")] pub const AF_NDRV: libc::c_int = 27; +#[cfg(target_os = "dragonfly")] +pub const IFT_ETHER: libc::c_uchar = 0x8; + #[allow(unused)] pub const ND6_INFINITE_LIFETIME: u32 = u32::MAX; diff --git a/src/macos/feth.rs b/src/macos/feth.rs index 275f1a8..5acfc3f 100644 --- a/src/macos/feth.rs +++ b/src/macos/feth.rs @@ -313,7 +313,7 @@ impl FethTap { }) } - /// Determines whether Link Receive Offload (LRO) is enabled for all TAP (feth) devices. + /// Determines whether LinkAddr Receive Offload (LRO) is enabled for all TAP (feth) devices. pub fn lro() -> io::Result { let mut lro = 0u32; let mut lro_len = mem::size_of_val(&lro); @@ -332,7 +332,7 @@ impl FethTap { } } - /// Enables or disables Link Receive Offload for all TAP (feth) devices. + /// Enables or disables LinkAddr Receive Offload for all TAP (feth) devices. pub fn set_lro(lro_enabled: bool) -> io::Result<()> { let mut lro = match lro_enabled { true => 1i32, diff --git a/src/sysctl.rs b/src/rtmsg.rs similarity index 57% rename from src/sysctl.rs rename to src/rtmsg.rs index fd199f2..4c5fcf4 100644 --- a/src/sysctl.rs +++ b/src/rtmsg.rs @@ -8,11 +8,126 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +// TODO: this should be named to `pf_route.rs` + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use std::{io, mem}; +use std::{io, mem, ptr}; use crate::libc_extra::*; +const fn ROUNDUP(len: usize) -> usize { + if len > 0 { + 1 + ((len - 1) | (mem::size_of::() - 1)) + } else { + mem::size_of::() + } +} + +fn v4_to_sockaddr(ipv4: Ipv4Addr) -> libc::sockaddr_in { + libc::sockaddr_in { + sin_family: libc::AF_INET as libc::sa_family_t, + sin_port: 0, + sin_addr: libc::in_addr { + s_addr: u32::from(ipv4).to_be(), + }, + sin_zero: [0u8; 8], + } +} + +fn v6_to_sockaddr(ipv6: Ipv6Addr) -> libc::sockaddr_in6 { + libc::sockaddr_in6 { + sin6_family: libc::AF_INET6 as libc::sa_family_t, + sin6_port: 0, + sin6_flowinfo: 0, + sin6_addr: u128::from(ipv6).to_be_bytes(), + } +} + +#[cfg(target_os = "dragonfly")] +fn dl_to_sockaddr(link: LinkAddr) -> io::Result { + let mac_bytes: [u8; 6] = link.addr.into(); + + sockaddr_dl { + sdl_len: mem::size_of::() as libc::c_uchar, + sdl_family: AF_LINK as libc::c_uchar, + sdl_index: link.iface.map(|i| i.index()?).unwrap_or(0) as libc::c_ushort, + sdl_type: IFT_ETHER, + sdl_nlen: 0, + sdl_alen: 6, + sdl_slen: 0, + sdl_data: array::from_fn(|i| if i < 6 { mac_bytes[i] } else { 0 }), + sdl_rcf: 0, + sdl_route: [0; 16], + } +} + +#[cfg(target_os = "freebsd")] +fn dl_to_sockaddr(link: LinkAddr) -> io::Result { + let mac_bytes: [u8; 6] = link.addr.into(); + + sockaddr_dl { + sdl_len: mem::size_of::() as libc::c_uchar, + sdl_family: AF_LINK as libc::c_uchar, + sdl_index: link.iface.map(|i| i.index()?).unwrap_or(0) as libc::c_ushort, + sdl_type: IFT_ETHER, + sdl_nlen: 0, + sdl_alen: 6, + sdl_slen: 0, + sdl_data: array::from_fn(|i| if i < 6 { mac_bytes[i] } else { 0 }), + } +} + +#[cfg(target_os = "macos")] +fn dl_to_sockaddr(link: LinkAddr) -> io::Result { + let mac_bytes: [u8; 6] = link.addr.into(); + + sockaddr_dl { + sdl_len: mem::size_of::() as libc::c_uchar, + sdl_family: AF_LINK as libc::c_uchar, + sdl_index: link.iface.map(|i| i.index()?).unwrap_or(0) as libc::c_ushort, + sdl_type: IFT_ETHER, + sdl_nlen: 0, + sdl_alen: 6, + sdl_slen: 0, + sdl_data: array::from_fn(|i| if i < 6 { mac_bytes[i] } else { 0 }), + } +} + + +#[cfg(target_os = "netbsd")] +fn dl_to_sockaddr(link: LinkAddr) -> io::Result { + let mac_bytes: [u8; 6] = link.addr.into(); + + sockaddr_dl { + sdl_len: mem::size_of::() as libc::c_uchar, + sdl_family: AF_LINK as libc::c_uchar, + sdl_index: link.iface.map(|i| i.index()?).unwrap_or(0) as libc::c_ushort, + sdl_addr: dl_addr { + dl_type: IFT_ETHER, + dl_nlen: 0, + dl_alen: 6, + dl_slen: 0, + dl_data: array::from_fn(|i| if i < 6 { mac_bytes[i] } else { 0 }), + }, + } +} + +#[cfg(target_os = "openbsd")] +fn dl_to_sockaddr(link: LinkAddr) -> io::Result { + let mac_bytes: [u8; 6] = link.addr.into(); + + sockaddr_dl { + sdl_len: mem::size_of::() as libc::c_uchar, + sdl_family: AF_LINK as libc::c_uchar, + sdl_index: link.iface.map(|i| i.index()?).unwrap_or(0) as libc::c_ushort, + sdl_type: IFT_ETHER, + sdl_nlen: 0, + sdl_alen: 6, + sdl_slen: 0, + sdl_data: array::from_fn(|i| if i < 6 { mac_bytes[i] } else { 0 }), + } +} + #[derive(Clone, Copy, Debug)] pub struct SysctlParseError { error: &'static str, @@ -35,6 +150,106 @@ impl From for io::Error { } } +#[derive(Clone, Copy)] +pub struct LinkAddr { + pub addr: MacAddr, + pub iface: Option, +} + +#[derive(Clone)] +pub enum Gateway { + Ip(IpAddr), + LinkAddr(LinkAddr), + Iface(Interface), // `route` would use `getifaddrs` to convert Interface to sockaddr_dl... +} + +#[derive(Clone)] +pub enum Addr { + Ip(IpAddr), + Link(LinkAddr), +} + + +// destination/netmask are together +// default destination is 0.0.0.0/0 +// -interface specified if gateway not needed +// gateway addr otherwise +// "if the interface is point-to-point the name of the interface itself may be given, in which case the route remains valid even if addresses change" +// -ifscope scopes src/dst to a specific interface +// -link indicates link-level addresses, numerically specified +// + +// Routes have associated flags which influence operation of the protocols +// when sending to destinations matched by the routes. These flags may be +// set (or sometimes cleared) by indicating the following corresponding mod- +// ifiers: + +// -cloning RTF_CLONING - generates a new route on use +// -xresolve RTF_XRESOLVE - emit mesg on use (for external lookup) +// -iface ~RTF_GATEWAY - destination is directly reachable +// -static RTF_STATIC - manually added route +// -nostatic ~RTF_STATIC - pretend route added by kernel or daemon +// -reject RTF_REJECT - emit an ICMP unreachable when matched +// -blackhole RTF_BLACKHOLE - silently discard pkts (during updates) +// -proto1 RTF_PROTO1 - set protocol specific routing flag #1 +// -proto2 RTF_PROTO2 - set protocol specific routing flag #2 +// -llinfo RTF_LLINFO - validly translates proto addr to link addr + +// -ifp/ifa used to determine the interface of the destination/gateway pair +// different from -interface +pub struct RtMsgV4 { + header: rt_msghdr, + destination: Option, + gateway: Option, // Can be an Interface as well... + netmask: Option, + genmask: Option, + if_name: Option, + if_addr: Option, // Is this actually MacAddr || IpAddr? +// author: Option, +// broadcast: Option, +} + +impl RtMsg { + pub fn serialize(&self) -> Vec { + let mut v = vec![0; mem::size_of::()]; + ptr::copy_nonoverlapping(ptr::addr_of!(self.header), ptr::addr_of_mut!(v), 1); + + if let Some(dst) = self.destination.as_ref() { + match dst { + Addr::Ip(IpAddr::V4(ipv4)) => Self::serialize_ipv4(&mut v, ipv4), + Addr::Ip(IpAddr::V6(ipv6)) => Self::serialize_ipv6(&mut v, ipv6), + Addr::Link(link) => Self::serialize_link(&mut v, link), + } + } + + + } + + fn serialize_ipv4(v: &mut Vec, ipv4: Ipv4Addr) { + let sa = v4_to_sockaddr(ipv4); + let sa_buf: [u8; mem::size_of::()] = unsafe { mem::transmute(sa) }; + v.extend(sa_buf); + const PADDING: usize = ROUNDUP(mem::size_of::()) - mem::size_of::(); + v.extend([0; PADDING]); + } + + fn serialize_ipv6(v: &mut Vec, ipv6: Ipv6Addr) { + let sa = v6_to_sockaddr(ipv6); + let sa_buf: [u8; mem::size_of::()] = unsafe { mem::transmute(sa) }; + v.extend(sa_buf); + const PADDING: usize = ROUNDUP(mem::size_of::()) - mem::size_of::(); + v.extend([0; PADDING]); + } + + fn serialize_link(v: &mut Vec, link: LinkAddr) { + let sa = dl_to_sockaddr(link); + let sa_buf: [u8; mem::size_of::()] = unsafe { mem::transmute(sa) }; + v.extend(sa_buf); + const PADDING: usize = ROUNDUP(mem::size_of::()) - mem::size_of::(); + v.extend([0; PADDING]); + } +} + pub struct IfList<'a> { data: &'a [u8], } @@ -46,7 +261,7 @@ impl<'a> IfList<'a> { } impl<'a> Iterator for IfList<'a> { - type Item = Result, SysctlParseError>; + type Item = Result, SysctlParseError>; fn next(&mut self) -> Option { if self.data.is_empty() { @@ -92,30 +307,30 @@ impl<'a> Iterator for IfList<'a> { addr_data = rem_data; } - Some(Ok(SysctlMessage::NewAddress(SysctlNewAddress { + Some(Ok(RtMsgRef::NewAddress(NewAddressRef { header: msghdr, addr_data, }))) } - ty => Some(Ok(SysctlMessage::Unknown(ty))), + ty => Some(Ok(RtMsgRef::Unknown(ty))), } } } #[non_exhaustive] -pub enum SysctlMessage<'a> { +pub enum RtMsgRef<'a> { // InterfaceInfo, - NewAddress(SysctlNewAddress<'a>), + NewAddress(NewAddressRef<'a>), // Announce, Unknown(i32), } -pub struct SysctlNewAddress<'a> { +pub struct NewAddressRef<'a> { header: ifa_msghdr, addr_data: &'a [u8], } -impl<'a> SysctlNewAddress<'a> { +impl<'a> NewAddressRef<'a> { #[inline] pub fn index(&self) -> libc::c_ushort { self.header.ifam_index @@ -294,13 +509,13 @@ mod tests_macos { let if_list = IfList::new(sysctl_out.as_slice()); for msg in if_list { match msg.unwrap() { - SysctlMessage::NewAddress(new_addr) => { + RtMsgRef::NewAddress(new_addr) => { for addr in new_addr.addrs() { assert_eq!(addr.unwrap(), expected[idx]); idx += 1; } } - SysctlMessage::Unknown(_) => (), + RtMsgRef::Unknown(_) => (), } } diff --git a/src/rtnetlink.rs b/src/rtnetlink.rs index feac6d4..17d22c3 100644 --- a/src/rtnetlink.rs +++ b/src/rtnetlink.rs @@ -92,6 +92,7 @@ impl NetlinkRequest { NlmsgPayload::GetAddress(_) => libc::RTM_GETADDR, NlmsgPayload::DeleteAddress(_) => libc::RTM_DELADDR, NlmsgPayload::NewRoute(_) => libc::RTM_NEWROUTE, + NlmsgPayload::GetRoute(_) => libc::RTM_GETROUTE, NlmsgPayload::DeleteRoute(_) => libc::RTM_DELROUTE, NlmsgPayload::NewNeighbor(_) => libc::RTM_NEWNEIGH, NlmsgPayload::DeleteNeighbor(_) => libc::RTM_DELNEIGH, @@ -124,6 +125,8 @@ pub enum NlmsgPayload { DeleteAddress(NlmsgDeleteAddress), /// An `RTM_NEWROUTE` request. NewRoute(NlmsgNewRoute), + /// An `RTM_GETROUTE` request. + GetRoute(NlmsgGetRoute), /// An `RTM_DELROUTE` request. DeleteRoute(NlmsgDeleteRoute), /// An `RTM_NEWNEIGH` request. @@ -136,16 +139,17 @@ impl NlmsgPayload { #[inline] fn serialize(&self, buf: &mut Vec) { match self { - NlmsgPayload::NewAddress(nlmsg_new_address) => nlmsg_new_address.serialize(buf), - NlmsgPayload::GetAddress(nlmsg_get_address) => nlmsg_get_address.serialize(buf), - NlmsgPayload::DeleteAddress(nlmsg_delete_address) => { - nlmsg_delete_address.serialize(buf) + NlmsgPayload::NewAddress(new_addr) => new_addr.serialize(buf), + NlmsgPayload::GetAddress(get_addr) => get_addr.serialize(buf), + NlmsgPayload::DeleteAddress(del_addr) => { + del_addr.serialize(buf) } - NlmsgPayload::NewRoute(nlmsg_new_route) => nlmsg_new_route.serialize(buf), - NlmsgPayload::DeleteRoute(nlmsg_delete_route) => nlmsg_delete_route.serialize(buf), - NlmsgPayload::NewNeighbor(nlmsg_new_neighbor) => nlmsg_new_neighbor.serialize(buf), - NlmsgPayload::DeleteNeighbor(nlmsg_delete_neighbor) => { - nlmsg_delete_neighbor.serialize(buf) + NlmsgPayload::NewRoute(new_rt) => new_rt.serialize(buf), + NlmsgPayload::GetRoute(get_rt) => get_rt.serialize(buf), + NlmsgPayload::DeleteRoute(del_rt) => del_rt.serialize(buf), + NlmsgPayload::NewNeighbor(new_neigh) => new_neigh.serialize(buf), + NlmsgPayload::DeleteNeighbor(del_neigh) => { + del_neigh.serialize(buf) } } } @@ -393,33 +397,121 @@ impl AddressAttr { } } +#[derive(Clone, Debug)] +pub struct NlmsgGetRoute { + pub family: u8, + pub dst_len: u8, + pub src_len: u8, + pub tos: u8, + pub table_id: u8, + pub protocol: u8, + pub scope: u8, + pub route_type: u8, + pub flags: u32, +} + +impl NlmsgGetRoute { + fn serialize(&self, buf: &mut Vec) { + const RTMSG_LEN: usize = mem::size_of::(); + const RTMSG_PADDING: usize = NLMSG_ALIGN(RTMSG_LEN) - RTMSG_LEN; + + let route_msg = rtmsg { + rtm_family: self.family, + rtm_dst_len: self.dst_len, + rtm_src_len: self.src_len, + rtm_tos: self.tos, + rtm_table: self.table_id, + rtm_protocol: self.protocol, + rtm_scope: self.scope, + rtm_type: self.route_type, + rtm_flags: self.flags, + }; + + let rtmsg_bytes: [u8; RTMSG_LEN] = unsafe { mem::transmute(route_msg) }; + buf.extend(&rtmsg_bytes); + buf.extend(&[0u8; RTMSG_PADDING]); + } +} + #[derive(Clone, Debug)] pub struct NlmsgNewRoute { - pub iface_idx: i32, - pub state: u16, - pub flags: u8, - pub arp_type: u8, + pub family: u8, + pub dst_len: u8, + pub src_len: u8, + pub tos: u8, + pub table_id: u8, + pub protocol: u8, + pub scope: u8, + pub route_type: u8, + pub flags: u32, pub attrs: Vec, } impl NlmsgNewRoute { fn serialize(&self, buf: &mut Vec) { - todo!() + const RTMSG_LEN: usize = mem::size_of::(); + const RTMSG_PADDING: usize = NLMSG_ALIGN(RTMSG_LEN) - RTMSG_LEN; + + let route_msg = rtmsg { + rtm_family: self.family, + rtm_dst_len: self.dst_len, + rtm_src_len: self.src_len, + rtm_tos: self.tos, + rtm_table: self.table_id, + rtm_protocol: self.protocol, + rtm_scope: self.scope, + rtm_type: self.route_type, + rtm_flags: self.flags, + }; + + let rtmsg_bytes: [u8; RTMSG_LEN] = unsafe { mem::transmute(route_msg) }; + buf.extend(&rtmsg_bytes); + buf.extend(&[0u8; RTMSG_PADDING]); + + for attr in self.attrs.iter() { + attr.serialize(buf); + } } } #[derive(Clone, Debug)] pub struct NlmsgDeleteRoute { - pub iface_idx: i32, - pub state: u16, - pub flags: u8, - pub arp_type: u8, + pub family: u8, + pub dst_len: u8, + pub src_len: u8, + pub tos: u8, + pub table_id: u8, + pub protocol: u8, + pub scope: u8, + pub route_type: u8, + pub flags: u32, pub attrs: Vec, } impl NlmsgDeleteRoute { fn serialize(&self, buf: &mut Vec) { - todo!() + const RTMSG_LEN: usize = mem::size_of::(); + const RTMSG_PADDING: usize = NLMSG_ALIGN(RTMSG_LEN) - RTMSG_LEN; + + let route_msg = rtmsg { + rtm_family: self.family, + rtm_dst_len: self.dst_len, + rtm_src_len: self.src_len, + rtm_tos: self.tos, + rtm_table: self.table_id, + rtm_protocol: self.protocol, + rtm_scope: self.scope, + rtm_type: self.route_type, + rtm_flags: self.flags, + }; + + let rtmsg_bytes: [u8; RTMSG_LEN] = unsafe { mem::transmute(route_msg) }; + buf.extend(&rtmsg_bytes); + buf.extend(&[0u8; RTMSG_PADDING]); + + for attr in self.attrs.iter() { + attr.serialize(buf); + } } } @@ -466,8 +558,89 @@ pub enum RouteAttr { } impl RouteAttr { + fn rta_type(&self) -> u16 { + match self { + RouteAttr::Unspec(_) => libc::RTA_UNSPEC, + RouteAttr::Destination(_) => libc::RTA_DST, + RouteAttr::Source(_) => libc::RTA_SRC, + RouteAttr::InputInterface(_) => libc::RTA_IIF, + RouteAttr::OutputInterface(_) => libc::RTA_OIF, + RouteAttr::Gateway(_) => libc::RTA_GATEWAY, + RouteAttr::Priority(_) => libc::RTA_PRIORITY, + RouteAttr::PreferredSource(_) => libc::RTA_PREFSRC, + RouteAttr::Metric(_) => libc::RTA_METRICS, + RouteAttr::Flow(_) => libc::RTA_FLOW, + RouteAttr::CacheInfo(_) => libc::RTA_CACHEINFO, + RouteAttr::TableId(_) => libc::RTA_TABLE, + RouteAttr::Mark(_) => libc::RTA_MARK, + RouteAttr::MfcStats(_) => libc::RTA_MFC_STATS, + RouteAttr::Via(_) => libc::RTA_VIA, + RouteAttr::NewDestination(_) => libc::RTA_NEWDST, + RouteAttr::Ipv6Preference(_) => libc::RTA_PREF, + RouteAttr::Expires(_) => libc::RTA_EXPIRES, + RouteAttr::Unknown(ty, _) => *ty, + } + } + fn serialize(&self, buf: &mut Vec) { - todo!() + let rta_type = self.rta_type(); + + match self { + Self::Destination(a) | Self::Source(a) | Self::Gateway(a) | Self::PreferredSource(a) | Self::NewDestination(a) => { + match *a { + IpAddr::V4(v4) => { + buf.extend(8u16.to_ne_bytes()); + buf.extend(rta_type.to_ne_bytes()); + buf.extend(u32::from(v4).to_be_bytes()); + } + IpAddr::V6(v6) => { + buf.extend(20u16.to_ne_bytes()); + buf.extend(rta_type.to_ne_bytes()); + buf.extend(u128::from(v6).to_be_bytes()); + } + } + // let padded_len = NLMSG_ALIGN(rta_len as usize) - rta_len as usize; + // buf.extend(iter::repeat(0).take(padded_len)); + } + Self::Unspec(v) | Self::Unknown(_, v) | Self::Via(v) => { + let rta_len = 4u16 + v.len() as u16; + buf.extend(rta_len.to_ne_bytes()); + buf.extend(rta_type.to_ne_bytes()); + buf.extend(v); + let padded_len = NLMSG_ALIGN(rta_len as usize) - rta_len as usize; + buf.extend(iter::repeat(0).take(padded_len)); + } + Self::InputInterface(i) | Self::OutputInterface(i) | Self::Priority(i) | Self::Metric(i) | Self::Flow(i) | Self::Mark(i) | Self::Expires(i) | Self::TableId(i) => { + buf.extend(8u16.to_ne_bytes()); + buf.extend(rta_type.to_ne_bytes()); + buf.extend(i.to_ne_bytes()); + } + Self::Ipv6Preference(i) => { + buf.extend(5u16.to_ne_bytes()); + buf.extend(rta_type.to_ne_bytes()); + buf.push(*i as u8); + let padded_len = NLMSG_ALIGN(5) - 5; + buf.extend(iter::repeat(0).take(padded_len)); + } + Self::CacheInfo(c) => { + let rta_len = (mem::size_of_val(c) + 4) as u16; + buf.extend(rta_len.to_ne_bytes()); + buf.extend(rta_type.to_ne_bytes()); + let cache_bytes: [u8; mem::size_of::()] = unsafe { mem::transmute_copy(c) }; + buf.extend(&cache_bytes); + let padded_len = NLMSG_ALIGN(rta_len as usize) - rta_len as usize; + buf.extend(iter::repeat(0).take(padded_len)); + } + Self::MfcStats(m) => { + let rta_len = (mem::size_of_val(m) + 4) as u16; + buf.extend(rta_len.to_ne_bytes()); + buf.extend(rta_type.to_ne_bytes()); + let mfc_bytes: [u8; mem::size_of::()] = unsafe { mem::transmute_copy(m) }; + buf.extend(&mfc_bytes); + let padded_len = NLMSG_ALIGN(rta_len as usize) - rta_len as usize; + buf.extend(iter::repeat(0).take(padded_len)); + } + } } } @@ -506,7 +679,7 @@ pub enum NeighborAttr { /// NTA_DST - a neighbor cache network layer destination address DestinationAddress(IpAddr), /// NTA_LLADDR - a neighbor cache link layer address - LinkAddress(MacAddr), + LinkAddrAddress(MacAddr), /// NTA_CACHEINFO - cache statistics CacheInfo(NeighborCacheInfo), /// Some other unknown RT attribute @@ -643,7 +816,7 @@ impl<'a> NlmsgRef<'a> { )); }; - NlmsgPayloadRef::GetRoute(NlmsgGetRoute::parse(route_payload)?) + NlmsgPayloadRef::GetRoute(NlmsgGetRouteRef::parse(route_payload)?) } libc::RTM_NEWNEIGH => { let Some(neighbor_payload) = data.get(PAYLOAD_START..) else { @@ -652,7 +825,7 @@ impl<'a> NlmsgRef<'a> { )); }; - NlmsgPayloadRef::GetNeighbor(NlmsgGetNeighbor::parse(neighbor_payload)?) + NlmsgPayloadRef::GetNeighbor(NlmsgGetNeighborRef::parse(neighbor_payload)?) } _ => NlmsgPayloadRef::Unknown, }, @@ -693,14 +866,14 @@ pub enum NlmsgPayloadRef<'a> { /// Informational response from an `RTM_GETADDR` request. GetAddress(NlmsgGetAddressRef<'a>), /// Informational response from an `RTM_GETROUTE` request. - GetRoute(NlmsgGetRoute<'a>), + GetRoute(NlmsgGetRouteRef<'a>), /// Informational response from an `RTM_GETNEIGHBOR` request. - GetNeighbor(NlmsgGetNeighbor<'a>), + GetNeighbor(NlmsgGetNeighborRef<'a>), /// Some other RTNetlink message Unknown, /* // /// Informational response from an `RTM_GETLINK` request. - // GetLink(), + // GetLinkAddr(), /// Informational response from an `RTM_GETRULE` request. GetRule(), /// Informational response from an `RTM_GETQDISC` request. @@ -957,7 +1130,7 @@ pub struct AddrCacheInfo { } #[derive(Clone, Copy)] -pub struct NlmsgGetNeighbor<'a> { +pub struct NlmsgGetNeighborRef<'a> { iface_idx: i32, state: u16, flags: u8, @@ -965,7 +1138,7 @@ pub struct NlmsgGetNeighbor<'a> { attr_data: &'a [u8], } -impl<'a> NlmsgGetNeighbor<'a> { +impl<'a> NlmsgGetNeighborRef<'a> { pub fn parse(data: &'a [u8]) -> Result { const NDMSG_LEN: usize = mem::size_of::(); @@ -1070,7 +1243,7 @@ impl<'a> Iterator for NeighborAttrIter<'a> { libc::NDA_LLADDR => { if attr_data.len() == 6 { let cache_info_bytes: [u8; 6] = attr_data.try_into().unwrap(); - NeighborAttrRef::LinkAddress(MacAddr::from(cache_info_bytes)) + NeighborAttrRef::LinkAddrAddress(MacAddr::from(cache_info_bytes)) } else { return Some(Err(NlParseError::new( "netlink RTM_GETNEIGH had IFA_LLADDR attribute with invalid size", @@ -1098,7 +1271,7 @@ pub enum NeighborAttrRef<'a> { /// NTA_DST - a neighbor cache network layer destination address DestinationAddress(IpAddr), /// NTA_LLADDR - a neighbor cache link layer address - LinkAddress(MacAddr), + LinkAddrAddress(MacAddr), /// NTA_CACHEINFO - cache statistics CacheInfo(NeighborCacheInfo), /// Some other unknown RT attribute @@ -1115,7 +1288,7 @@ pub struct NeighborCacheInfo { } #[derive(Clone, Copy)] -pub struct NlmsgGetRoute<'a> { +pub struct NlmsgGetRouteRef<'a> { family: u8, dst_len: u8, src_len: u8, @@ -1128,7 +1301,7 @@ pub struct NlmsgGetRoute<'a> { attr_data: &'a [u8], } -impl<'a> NlmsgGetRoute<'a> { +impl<'a> NlmsgGetRouteRef<'a> { pub fn parse(data: &'a [u8]) -> Result { const NDMSG_LEN: usize = mem::size_of::(); diff --git a/src/wintun.rs b/src/wintun.rs index f0fe575..f7b04aa 100644 --- a/src/wintun.rs +++ b/src/wintun.rs @@ -178,7 +178,7 @@ MIB_UNICASTIPADDRESS_ROW AddressRow; WintunGetAdapterLUID(Adapter, &AddressRow.InterfaceLuid); AddressRow.Address.Ipv4.sin_family = AF_INET; AddressRow.Address.Ipv4.sin_addr.S_un.S_addr = htonl((10 << 24) | (6 << 16) | (7 << 8) | (7 << 0)); /* 10.6.7.7 */ - AddressRow.OnLinkPrefixLength = 24; /* This is a /24 network */ + AddressRow.OnLinkAddrPrefixLength = 24; /* This is a /24 network */ AddressRow.DadState = IpDadStatePreferred; LastError = CreateUnicastIpAddressEntry(&AddressRow); if (LastError != ERROR_SUCCESS && LastError != ERROR_OBJECT_ALREADY_EXISTS)