feat: added basic wrapper around libnl

This commit is contained in:
Andrew Rioux 2023-05-01 05:56:04 -04:00
parent da9aa2178c
commit 95b5f3ee82
9 changed files with 504 additions and 50 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
target target
examples/bind-shell/key-generator/pubkey examples/bind-shell/key-generator/pubkey
examples/bind-shell/key-generator/privkey examples/bind-shell/key-generator/privkey
core
**/core

BIN
core

Binary file not shown.

View File

@ -20,3 +20,6 @@
int netlink_route() { int netlink_route() {
return NETLINK_ROUTE; return NETLINK_ROUTE;
} }
int netlink_fib_lookup() {
return NETLINK_FIB_LOOKUP;
}

View File

@ -39,7 +39,7 @@ impl Display for Error {
std::str::from_utf8(error_msg_ptr.to_bytes()).unwrap() 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}")
} }
} }

98
nl-sys/src/lib.rs Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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::<Ipv4Addr>().unwrap();
let target_ip_num = u32::from(target_ip);
let mut routes2 = routes.iter().collect::<Vec<_>>();
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<Ipv4Addr, _> = 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::<Ipv4Addr>().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(())
} */

View File

@ -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 <http://www.gnu.org/licenses/>.
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(())
}

View File

@ -15,16 +15,30 @@
use std::{ptr, marker::PhantomData}; 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 { 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 { impl Socket {
pub fn new() -> error::Result<Self> { pub fn new(stype: SocketType) -> error::Result<Self> {
unsafe { unsafe {
let sock = Socket { sock: nl_socket_alloc() }; let sock = Socket { sock: nl_socket_alloc() };
@ -37,7 +51,7 @@ impl Socket {
} }
} }
pub fn get_links(&self) -> error::Result<Cache<crate::route::Link>> { pub fn get_links(&self) -> error::Result<Cache<Link>> {
unsafe { unsafe {
let mut link_cache = ptr::null_mut::<nl_cache>(); let mut link_cache = ptr::null_mut::<nl_cache>();
@ -53,17 +67,70 @@ impl Socket {
}) })
} }
} }
pub fn get_neigh(&self) -> error::Result<Cache<Neigh>> {
unsafe {
let mut neigh_cache = ptr::null_mut::<nl_cache>();
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<Cache<Route>> {
unsafe {
let mut route_cache = ptr::null_mut::<nl_cache>();
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<Link>, index: i32) -> Option<Link> {
unsafe {
let link = rtnl_link_get(cache.cache, index);
if link.is_null() {
return None;
}
Some(Link { link })
}
} }
pub struct Cache<T> pub struct Cache<T>
where where
T: From<*mut nl_object> T: From<*mut nl_object>
{ {
cache: *mut nl_cache, pub(crate) cache: *mut nl_cache,
dt: PhantomData<T> dt: PhantomData<T>
} }
impl<T: From<*mut nl_object>> Cache<T> { impl<T: From<*mut nl_object>> Cache<T> {
pub(crate) fn new(cache: *mut nl_cache) -> Cache<T> {
Cache {
cache,
dt: PhantomData
}
}
pub fn iter(&self) -> CacheIter<'_, T> { pub fn iter(&self) -> CacheIter<'_, T> {
let cache_size = unsafe { let cache_size = unsafe {
nl_cache_nitems(self.cache) nl_cache_nitems(self.cache)

View File

@ -13,7 +13,7 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
use libc::{c_int, c_void, c_char}; use libc::{c_int, c_void, c_char, c_uint};
macro_rules! nl_obj { macro_rules! nl_obj {
($name:ident) => { ($name:ident) => {
@ -28,9 +28,14 @@ macro_rules! nl_obj {
nl_obj!(nl_sock); nl_obj!(nl_sock);
nl_obj!(nl_cache); nl_obj!(nl_cache);
nl_obj!(rtnl_link);
nl_obj!(nl_addr); nl_obj!(nl_addr);
nl_obj!(nl_object); 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 // from libnl and libnl-route
extern "C" { extern "C" {
@ -38,17 +43,44 @@ extern "C" {
pub fn nl_socket_free(sock: *mut nl_sock); pub fn nl_socket_free(sock: *mut nl_sock);
pub fn nl_socket_get_local_port(sock: *const nl_sock) -> u32; 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_connect(sock: *mut nl_sock, protocol: c_int) -> c_int;
pub fn nl_geterror(error: c_int) -> *const c_char; 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_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_put(cache: *mut nl_cache) -> c_void;
pub fn nl_cache_nitems(cache: *mut nl_cache) -> c_int; 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_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_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_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_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_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;
} }

View File

@ -13,12 +13,16 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
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::*; use super::nl_ffi::*;
pub struct Link { pub struct Link {
link: *mut rtnl_link pub(crate) link: *mut rtnl_link
} }
impl Link { impl Link {
@ -29,6 +33,41 @@ impl Link {
std::str::from_utf8(name_rs.to_bytes()).unwrap().to_owned() 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<String> {
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<Neigh>, addr: &Addr) -> Option<Neigh> {
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 { impl From<*mut nl_object> for Link {
@ -38,3 +77,252 @@ impl From<*mut nl_object> for Link {
} }
} }
} }
pub fn get_neigh_for_addr(neighs: &Cache<Neigh>, links: &Cache<Link>, 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<u8> {
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<Ipv4Addr> for Addr {
fn from(value: Ipv4Addr) -> Self {
unsafe {
let mut addr = std::ptr::null_mut::<nl_addr>();
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<Addr> for Ipv4Addr {
type Error = error::Error;
fn try_from(value: Addr) -> Result<Self, Self::Error> {
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<Addr> {
unsafe {
let addr = rtnl_route_get_src(self.route);
if addr.is_null() {
return None;
}
Some(Addr { addr })
}
}
pub fn dst(&self) -> Option<Addr> {
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<Nexthop> {
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<Addr> {
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<Self::Item> {
let next = self.route.nexthop(self.index);
if next.is_none() {
return None;
}
self.index += 1;
next
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.route.nexthop_len() as usize, Some(self.route.nexthop_len() as usize))
}
}
pub fn get_route_for_ip(routes: &Cache<Route>, ip: Ipv4Addr) -> Option<Ipv4Addr> {
let mut sorted_routes = routes.iter().collect::<Vec<_>>();
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<Ipv4Addr, _> = 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()
}