diff --git a/.gitignore b/.gitignore index 2f4d92b..56c0441 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ target examples/bind-shell/key-generator/pubkey -examples/bind-shell/key-generator/privkey \ No newline at end of file +examples/bind-shell/key-generator/privkey +core +**/core \ No newline at end of file diff --git a/core b/core deleted file mode 100644 index ba7a4fe..0000000 Binary files a/core and /dev/null differ diff --git a/nl-sys/src/bridge.c b/nl-sys/src/bridge.c index c03b2f5..7cabed6 100644 --- a/nl-sys/src/bridge.c +++ b/nl-sys/src/bridge.c @@ -19,4 +19,7 @@ int netlink_route() { return NETLINK_ROUTE; +} +int netlink_fib_lookup() { + return NETLINK_FIB_LOOKUP; } \ No newline at end of file diff --git a/nl-sys/src/error.rs b/nl-sys/src/error.rs index c0ede71..3215f7c 100644 --- a/nl-sys/src/error.rs +++ b/nl-sys/src/error.rs @@ -39,7 +39,7 @@ impl Display for Error { std::str::from_utf8(error_msg_ptr.to_bytes()).unwrap() }; - write!(f, "nternal libnl error: {error_msg_utf8}") + write!(f, "internal libnl error: {error_msg_utf8}") } } diff --git a/nl-sys/src/lib.rs b/nl-sys/src/lib.rs new file mode 100644 index 0000000..9335930 --- /dev/null +++ b/nl-sys/src/lib.rs @@ -0,0 +1,98 @@ +// Copyright (C) 2023 Andrew Rioux +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use std::net::Ipv4Addr; + +pub mod nl_ffi; +pub mod netlink; +pub mod route; +pub mod error; + +// from bridge.c +extern "C" { + pub(crate) fn netlink_route() -> libc::c_int; + pub(crate) fn netlink_fib_lookup() -> libc::c_int; +} + +/* fn main() -> error::Result<()> { + let sock = netlink::Socket::new(netlink::SocketType::Routing)?; + let lookup_sock = netlink::Socket::new(netlink::SocketType::Lookup)?; + + let links = sock.get_links()?; + let routes = sock.get_routes()?; + + let target_ip = "1.1.1.1".parse::().unwrap(); + let target_ip_num = u32::from(target_ip); + + let mut routes2 = routes.iter().collect::>(); + println!("Routes: {}", routes2.len()); + routes2.sort_by(|r1, r2| { + r2.dst().map(|a| a.cidrlen()) + .partial_cmp(&r1.dst().map(|a| a.cidrlen())) + .unwrap_or(std::cmp::Ordering::Equal) + }); + + let routes3 = routes2.iter().filter(|route| { + let Some(dst) = route.dst() else { return false }; + + let mask = if dst.cidrlen() != 0 { + (0xFFFFFFFFu32.overflowing_shr(32 - dst.cidrlen())).0.overflowing_shl(32 - dst.cidrlen()).0 + } else { + 0 + }; + + let Ok(dst_addr): Result = dst.try_into() else { return false }; + let dst_addr = u32::from(dst_addr); + + (mask & dst_addr) == (mask & target_ip_num) + }); + + for route in routes3 { + let link = netlink::get_link_by_index(&links, route.ifindex()); + println!( + "route: src: {:?}, dst: {:?}, link: {}, {:?}", + route.src().map(|s| (s.hw_address(), s.cidrlen())), + route.dst().map(|s| (s.hw_address(), s.cidrlen())), + route.ifindex(), + link.map(|l| l.name()) + ); + + for hops in route.hop_iter() { + println!( + "\tgateway: {:?}, ifindex: {}", + hops.gateway().map(|g| g.hw_address()), + hops.ifindex() + ); + } + } + + let src_ip = route::Addr::from("172.17.0.1".parse::().unwrap()); + + let neighs = sock.get_neigh()?; + + let target_neigh = route::get_neigh_for_addr(&neighs, &links, &src_ip); + + if let Some((link, neigh)) = target_neigh { + println!( + "link: {}; src mac: {:?}; src ip: {:?}; dst mac: {:?}", + link.name(), + link.addr().hw_address(), + neigh.dst().hw_address(), + neigh.lladdr().hw_address() + ); + } + + Ok(()) +} */ \ No newline at end of file diff --git a/nl-sys/src/main.rs b/nl-sys/src/main.rs deleted file mode 100644 index 291e85b..0000000 --- a/nl-sys/src/main.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2023 Andrew Rioux -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -mod nl_ffi; -mod netlink; -mod route; -mod error; - -// from bridge.c -extern "C" { - pub(crate) fn netlink_route() -> libc::c_int; -} - -fn main() -> error::Result<()> { - let sock = netlink::Socket::new()?; - - let links = sock.get_links()?; - - for link in links.iter() { - println!("Link: {}", link.name()); - } - - Ok(()) -} \ No newline at end of file diff --git a/nl-sys/src/netlink.rs b/nl-sys/src/netlink.rs index 7291edd..dc8817d 100644 --- a/nl-sys/src/netlink.rs +++ b/nl-sys/src/netlink.rs @@ -15,16 +15,30 @@ use std::{ptr, marker::PhantomData}; -use libc::AF_UNSPEC; +use libc::{AF_UNSPEC, AF_INET}; -use crate::{nl_ffi::*, error}; +use crate::{nl_ffi::*, error, route::{Link, Neigh, Route}}; pub struct Socket { - sock: *mut nl_sock + pub(crate) sock: *mut nl_sock +} + +pub enum SocketType { + Routing, + Lookup +} + +impl SocketType { + pub fn to_proto(&self) -> i32 { + unsafe { match self { + Self::Routing => crate::netlink_route(), + Self::Lookup => crate::netlink_fib_lookup() + } } + } } impl Socket { - pub fn new() -> error::Result { + pub fn new(stype: SocketType) -> error::Result { unsafe { let sock = Socket { sock: nl_socket_alloc() }; @@ -37,7 +51,7 @@ impl Socket { } } - pub fn get_links(&self) -> error::Result> { + pub fn get_links(&self) -> error::Result> { unsafe { let mut link_cache = ptr::null_mut::(); @@ -53,17 +67,70 @@ impl Socket { }) } } + + pub fn get_neigh(&self) -> error::Result> { + unsafe { + let mut neigh_cache = ptr::null_mut::(); + + let ret = rtnl_neigh_alloc_cache(self.sock, &mut neigh_cache as *mut _); + + if ret < 0 { + return Err(error::Error::new(ret)); + } + + Ok(Cache { + cache: neigh_cache, + dt: PhantomData + }) + } + } + + pub fn get_routes(&self) -> error::Result> { + unsafe { + let mut route_cache = ptr::null_mut::(); + + let ret = rtnl_route_alloc_cache(self.sock, AF_INET, 0, &mut route_cache as *mut _); + + if ret < 0 { + return Err(error::Error::new(ret)); + } + + Ok(Cache { + cache: route_cache, + dt: PhantomData + }) + } + } +} + +pub fn get_link_by_index(cache: &Cache, index: i32) -> Option { + unsafe { + let link = rtnl_link_get(cache.cache, index); + + if link.is_null() { + return None; + } + + Some(Link { link }) + } } pub struct Cache where T: From<*mut nl_object> { - cache: *mut nl_cache, + pub(crate) cache: *mut nl_cache, dt: PhantomData } impl> Cache { + pub(crate) fn new(cache: *mut nl_cache) -> Cache { + Cache { + cache, + dt: PhantomData + } + } + pub fn iter(&self) -> CacheIter<'_, T> { let cache_size = unsafe { nl_cache_nitems(self.cache) diff --git a/nl-sys/src/nl_ffi.rs b/nl-sys/src/nl_ffi.rs index e9df0a9..330a97d 100644 --- a/nl-sys/src/nl_ffi.rs +++ b/nl-sys/src/nl_ffi.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use libc::{c_int, c_void, c_char}; +use libc::{c_int, c_void, c_char, c_uint}; macro_rules! nl_obj { ($name:ident) => { @@ -28,9 +28,14 @@ macro_rules! nl_obj { nl_obj!(nl_sock); nl_obj!(nl_cache); -nl_obj!(rtnl_link); nl_obj!(nl_addr); nl_obj!(nl_object); +nl_obj!(nl_list_head); +nl_obj!(rtnl_link); +nl_obj!(rtnl_neigh); +nl_obj!(rtnl_route); +nl_obj!(rtnl_nexthop); +nl_obj!(flnl_request); // from libnl and libnl-route extern "C" { @@ -38,17 +43,44 @@ extern "C" { pub fn nl_socket_free(sock: *mut nl_sock); pub fn nl_socket_get_local_port(sock: *const nl_sock) -> u32; pub fn nl_connect(sock: *mut nl_sock, protocol: c_int) -> c_int; - pub fn nl_geterror(error: c_int) -> *const c_char; + pub fn nl_object_put(obj: *mut nl_object) -> c_void; + + pub fn nl_addr_get_len(addr: *mut nl_addr) -> c_uint; + pub fn nl_addr_get_binary_addr(addr: *mut nl_addr) -> *mut c_void; + pub fn nl_addr_parse(addrstr: *const i8, hint: c_int, result: *mut *mut nl_addr) -> c_int; + pub fn nl_addr_put(addr: *mut nl_addr) -> c_void; + pub fn nl_addr_get_family(addr: *mut nl_addr) -> c_int; + pub fn nl_addr_get_prefixlen(addr: *mut nl_addr) -> c_uint; + pub fn nl_cache_foreach(cache: *mut nl_cache, cb: extern "C" fn(*mut nl_object, *mut c_void), arg: *mut c_void) -> c_void; pub fn nl_cache_put(cache: *mut nl_cache) -> c_void; pub fn nl_cache_nitems(cache: *mut nl_cache) -> c_int; pub fn nl_cache_get_first(cache: *mut nl_cache) -> *mut nl_object; pub fn nl_cache_get_next(obj: *mut nl_object) -> *mut nl_object; + pub fn nl_cache_destroy_and_free(obj: *mut nl_cache) -> c_void; + pub fn rtnl_neigh_alloc_cache(sock: *mut nl_sock, result: *mut *mut nl_cache) -> c_int; + pub fn rtnl_neigh_get(cache: *mut nl_cache, ifindex: c_int, dst: *mut nl_addr) -> *mut rtnl_neigh; + pub fn rtnl_neigh_get_dst(neigh: *mut rtnl_neigh) -> *mut nl_addr; + pub fn rtnl_neigh_get_lladdr(neigh: *mut rtnl_neigh) -> *mut nl_addr; + + pub fn rtnl_link_get(cache: *mut nl_cache, index: c_int) -> *mut rtnl_link; pub fn rtnl_link_alloc_cache(sock: *mut nl_sock, family: c_int, result: *mut *mut nl_cache) -> c_int; - pub fn rtnl_link_get_by_name(cache: *mut nl_cache, name: *const c_char) -> *mut rtnl_link; pub fn rtnl_link_get_addr(link: *mut rtnl_link) -> *mut nl_addr; pub fn rtnl_link_get_name(link: *mut rtnl_link) -> *const c_char; + pub fn rtnl_link_get_ifindex(link: *mut rtnl_link) -> c_int; + pub fn rtnl_link_get_type(link: *mut rtnl_link) -> *const c_char; + + pub fn rtnl_route_alloc_cache(sock: *mut nl_sock, family: c_int, flags: c_int, result: *mut *mut nl_cache) -> c_int; + pub fn rtnl_route_get_src(route: *mut rtnl_route) -> *mut nl_addr; + pub fn rtnl_route_get_dst(route: *mut rtnl_route) -> *mut nl_addr; + pub fn rtnl_route_get_iif(route: *mut rtnl_route) -> c_int; + pub fn rtnl_route_get_pref_src(route: *mut rtnl_route) -> *mut nl_addr; + pub fn rtnl_route_get_nnexthops(route: *mut rtnl_route) -> c_int; + pub fn rtnl_route_nexthop_n(route: *mut rtnl_route, ind: c_int) -> *mut rtnl_nexthop; + + pub fn rtnl_route_nh_get_gateway(hop: *mut rtnl_nexthop) -> *mut nl_addr; + pub fn rtnl_route_nh_get_ifindex(hop: *mut rtnl_nexthop) -> c_int; } diff --git a/nl-sys/src/route.rs b/nl-sys/src/route.rs index c20d5bf..29575c2 100644 --- a/nl-sys/src/route.rs +++ b/nl-sys/src/route.rs @@ -13,12 +13,16 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use std::ffi::CStr; +use std::{ffi::{CStr, CString}, net::Ipv4Addr}; + +use libc::{c_int, AF_INET, AF_UNIX, AF_UNSPEC, c_uint}; + +use crate::{error, netlink::{Cache, Socket}}; use super::nl_ffi::*; pub struct Link { - link: *mut rtnl_link + pub(crate) link: *mut rtnl_link } impl Link { @@ -29,6 +33,41 @@ impl Link { std::str::from_utf8(name_rs.to_bytes()).unwrap().to_owned() } } + + pub fn addr(&self) -> Addr { + unsafe { + Addr { addr: rtnl_link_get_addr(self.link) } + } + } + + pub fn ltype(&self) -> Option { + unsafe { + let ltype = rtnl_link_get_type(self.link); + if ltype.is_null() { + return None; + } + let ltype_rs = CStr::from_ptr(ltype); + Some(std::str::from_utf8(ltype_rs.to_bytes()).unwrap().to_owned()) + } + } + + pub fn ifindex(&self) -> c_int { + unsafe { + rtnl_link_get_ifindex(self.link) + } + } + + pub fn get_neigh(&self, neigh_table: &Cache, addr: &Addr) -> Option { + unsafe { + let neigh = rtnl_neigh_get(neigh_table.cache, self.ifindex(), addr.addr); + + if neigh.is_null() { + return None; + } + + Some(Neigh { neigh }) + } + } } impl From<*mut nl_object> for Link { @@ -37,4 +76,253 @@ impl From<*mut nl_object> for Link { link: value as *mut _ } } +} + +pub fn get_neigh_for_addr(neighs: &Cache, links: &Cache, addr: &Addr) -> Option<(Link, Neigh)> { + for link in links.iter() { + let Some(neigh) = link.get_neigh(&neighs, addr) else { continue; }; + return Some((link, neigh)); + } + + None +} + +pub struct Neigh { + neigh: *mut rtnl_neigh +} + +impl Neigh { + pub fn dst(&self) -> Addr { + unsafe { + let addr = rtnl_neigh_get_dst(self.neigh); + Addr { addr } + } + } + + pub fn lladdr(&self) -> Addr { + unsafe { + let addr = rtnl_neigh_get_lladdr(self.neigh); + Addr { addr } + } + } +} + +impl From<*mut nl_object> for Neigh { + fn from(value: *mut nl_object) -> Self { + Self { + neigh: value as *mut _ + } + } +} + +pub struct Addr { + addr: *mut nl_addr +} + +impl Addr { + pub fn len(&self) -> u32 { + unsafe { + nl_addr_get_len(self.addr) + } + } + + pub fn hw_address(&self) -> Vec { + unsafe { + let hw_address_ptr = nl_addr_get_binary_addr(self.addr) as *const u8; + let hw_address_slice = std::slice::from_raw_parts(hw_address_ptr, self.len() as usize); + + hw_address_slice.to_vec() + } + } + + pub fn atype(&self) -> c_int { + unsafe { + nl_addr_get_family(self.addr) + } + } + + pub fn cidrlen(&self) -> c_uint { + unsafe { + nl_addr_get_prefixlen(self.addr) + } + } +} + +impl From for Addr { + fn from(value: Ipv4Addr) -> Self { + unsafe { + let mut addr = std::ptr::null_mut::(); + let value = CString::new(format!("{value}")).unwrap(); + + // we can ignore the return code because it is guaranteed to not be invalid + nl_addr_parse(value.as_ptr(), AF_INET, &mut addr as *mut _); + + Addr { addr } + } + } +} + +impl TryFrom for Ipv4Addr { + type Error = error::Error; + + fn try_from(value: Addr) -> Result { + if value.len() != 4 { + return Err(error::Error::new(15 /* NL_AF_MISMATCH */)); + } + + let addr = value.hw_address(); + Ok(Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3])) + } +} + +pub struct Route { + route: *mut rtnl_route +} + +impl Route { + pub fn src(&self) -> Option { + unsafe { + let addr = rtnl_route_get_src(self.route); + + if addr.is_null() { + return None; + } + + Some(Addr { addr }) + } + } + + pub fn dst(&self) -> Option { + unsafe { + let addr = rtnl_route_get_dst(self.route); + + if addr.is_null() { + return None; + } + + Some(Addr { addr }) + } + } + + pub fn ifindex(&self) -> c_int { + unsafe { + rtnl_route_get_iif(self.route) + } + } + + pub fn nexthop_len(&self) -> c_int { + unsafe { + rtnl_route_get_nnexthops(self.route) + } + } + + pub fn nexthop(&self, ind: i32) -> Option { + unsafe { + let nexthop = rtnl_route_nexthop_n(self.route, ind); + if nexthop.is_null() { + return None; + } + Some(Nexthop { nexthop }) + } + } + + pub fn hop_iter(&self) -> NexthopIter<'_> { + NexthopIter { route: &self, index: 0 } + } +} + +impl From<*mut nl_object> for Route { + fn from(value: *mut nl_object) -> Self { + Route { + route: value as *mut _ + } + } +} + +pub struct Nexthop { + nexthop: *mut rtnl_nexthop +} + +impl Nexthop { + pub fn gateway(&self) -> Option { + unsafe { + let addr = rtnl_route_nh_get_gateway(self.nexthop); + + if addr.is_null() { + return None; + } + + Some(Addr { addr }) + } + } + + pub fn ifindex(&self) -> i32 { + unsafe { + rtnl_route_nh_get_ifindex(self.nexthop) + } + } +} + +pub struct NexthopIter<'a> { + route: &'a Route, + index: i32 +} + +impl Iterator for NexthopIter<'_> { + type Item = Nexthop; + + fn next(&mut self) -> Option { + let next = self.route.nexthop(self.index); + + if next.is_none() { + return None; + } + + self.index += 1; + + next + } + + fn size_hint(&self) -> (usize, Option) { + (self.route.nexthop_len() as usize, Some(self.route.nexthop_len() as usize)) + } +} + +pub fn get_route_for_ip(routes: &Cache, ip: Ipv4Addr) -> Option { + let mut sorted_routes = routes.iter().collect::>(); + + sorted_routes.sort_by(|r1, r2| { + r2.dst().map(|a| a.cidrlen()) + .partial_cmp(&r1.dst().map(|a| a.cidrlen())) + .unwrap_or(std::cmp::Ordering::Equal) + }); + + let ip_int = u32::from(ip); + + sorted_routes + .iter() + .find(|route| { + let Some(dst) = route.dst() else { return false }; + + let mask = if dst.cidrlen() != 0 { + (0xFFFFFFFFu32.overflowing_shr(32 - dst.cidrlen())).0.overflowing_shl(32 - dst.cidrlen()).0 + } else { + 0 + }; + + let Ok(dst_addr): Result = dst.try_into() else { return false }; + let dst_addr: u32 = dst_addr.into(); + + (mask & dst_addr) == (mask & ip_int) + }) + .map(|route| { + route + .hop_iter() + .next() + }) + .flatten() + .map(|hop| hop.gateway()) + .flatten() + .map(|gateway| gateway.try_into().ok()) + .flatten() } \ No newline at end of file