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