feat: tested nl-sys, and it links

This commit is contained in:
Andrew Rioux 2025-01-20 14:40:29 -05:00
parent 6b33f1a5ba
commit c2b5ea37d0
Signed by: andrew.rioux
GPG Key ID: 9B8BAC47C17ABB94
25 changed files with 3615 additions and 32 deletions

444
Cargo.lock generated
View File

@ -2,6 +2,450 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "cc"
version = "1.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cmake"
version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e"
dependencies = [
"cc",
]
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"wasi",
"windows-sys 0.52.0",
]
[[package]]
name = "nl-sys"
version = "0.1.0"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "packets"
version = "0.1.0"
[[package]]
name = "pcap-sys"
version = "0.1.0"
dependencies = [
"cmake",
"errno",
"fs_extra",
"futures",
"libc",
"packets",
"tokio",
"tokio-stream",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "socket2"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "sparse-beacon"
version = "0.7.0"
dependencies = [
"nl-sys",
]
[[package]]
name = "syn"
version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
dependencies = [
"backtrace",
"libc",
"mio",
"pin-project-lite",
"socket2",
"tokio-macros",
"windows-sys 0.52.0",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-stream"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View File

@ -1,6 +1,9 @@
[workspace]
members = [
"sparse-beacon"
"sparse-beacon",
"nl-sys",
"packets",
"pcap-sys"
]
resolver = "2"
package.version = "2.0.0"

View File

@ -45,7 +45,8 @@
};
system-libs = import ./system-libs.nix {
inherit pkgs libpcap-src winpcap-installer freebsd-libs-packed;
inherit pkgs libnl-src libpcap-src winpcap-installer
freebsd-libs-packed;
};
inherit (system-libs)
bsd-toolchain winpcap-drivers freebsd-libs libnl libpcap-linux
@ -91,8 +92,9 @@
SPARSE_BUILD_WINPCAP_LIBS = "${winpcap-libs}/Lib";
SPARSE_BUILD_WINPCAP_DRIVERS = "${winpcap-drivers}/npf.sys";
SPARSE_BUILD_LIBPCAP = libpcap-src;
SPARSE_BUILD_LIBNL = libnl-src;
SPARSE_BUILD_LIBPCAP_LINUX = libpcap-linux;
SPARSE_BUILD_LIBPCAP_FREEBSD = libpcap-freebsd;
SPARSE_BUILD_LIBNL = libnl;
FREEBSD_LIBS = freebsd-libs;

12
nl-sys/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "nl-sys"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libc = "0.2.169"
[build-dependencies]
cc = "1.0"

3
nl-sys/README.md Normal file
View File

@ -0,0 +1,3 @@
# nl-sys
This library provides Rust wrappers around the netlink socket protocol that the Linux kernel uses in order to provide access to routing tables and interface information

30
nl-sys/build.rs Normal file
View File

@ -0,0 +1,30 @@
// Copyright (C) 2025 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/>.
fn main() -> std::io::Result<()> {
cc::Build::new()
.compiler("clang")
.file("src/bridge.c")
.compile("bridge");
println!(
"cargo:rustc-link-search=native={}/lib",
std::env::var("SPARSE_BUILD_LIBNL").unwrap()
);
println!("cargo:rustc-link-lib=static=nl-3");
println!("cargo:rustc-link-lib=static=nl-route-3");
Ok(())
}

20
nl-sys/src/bridge.c Normal file
View File

@ -0,0 +1,20 @@
/**
* 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/>.
*/
#include <linux/netlink.h>
int netlink_route() { return NETLINK_ROUTE; }

48
nl-sys/src/error.rs Normal file
View File

@ -0,0 +1,48 @@
// 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::{ffi::CStr, fmt::Display};
use libc::c_int;
use crate::nl_ffi::nl_geterror;
#[derive(Debug)]
#[repr(transparent)]
pub struct Error {
error_code: c_int,
}
impl Error {
pub(crate) fn new(error_code: c_int) -> Self {
Error { error_code }
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let error_msg_utf8 = unsafe {
let error_msg = nl_geterror(self.error_code);
let error_msg_ptr = CStr::from_ptr(error_msg);
std::str::from_utf8(error_msg_ptr.to_bytes()).unwrap()
};
write!(f, "internal libnl error: {error_msg_utf8}")
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;

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

@ -0,0 +1,24 @@
// 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/>.
pub mod error;
pub mod netlink;
pub mod nl_ffi;
pub mod route;
// from bridge.c
extern "C" {
pub(crate) fn netlink_route() -> libc::c_int;
}

202
nl-sys/src/netlink.rs Normal file
View File

@ -0,0 +1,202 @@
// 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::{marker::PhantomData, ptr};
use libc::{AF_INET, AF_UNSPEC};
use crate::{
error,
nl_ffi::*,
route::{Link, Neigh, Route, RtAddr},
};
/// A netlink socket used to communicate with the kernel
pub struct Socket {
pub(crate) sock: *mut nl_sock,
}
impl Socket {
/// Establish a new connection with the Linux kernel
pub fn new() -> error::Result<Self> {
unsafe {
let sock = Socket {
sock: nl_socket_alloc(),
};
let ret = nl_connect(sock.sock, crate::netlink_route());
if ret < 0 {
return Err(error::Error::new(ret));
}
Ok(sock)
}
}
pub fn get_links(&self) -> error::Result<Cache<Link>> {
unsafe {
let mut link_cache = ptr::null_mut::<nl_cache>();
let ret = rtnl_link_alloc_cache(self.sock, AF_UNSPEC, &mut link_cache as *mut _);
if ret < 0 {
return Err(error::Error::new(ret));
}
Ok(Cache {
cache: link_cache,
dt: PhantomData,
})
}
}
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_addrs(&self) -> error::Result<Cache<RtAddr>> {
unsafe {
let mut addr_cache = ptr::null_mut::<nl_cache>();
let ret = rtnl_addr_alloc_cache(self.sock, &mut addr_cache as *mut _);
if ret < 0 {
return Err(error::Error::new(ret));
}
Ok(Cache {
cache: addr_cache,
dt: PhantomData,
})
}
}
}
impl Drop for Socket {
fn drop(&mut self) {
unsafe {
nl_close(self.sock);
}
}
}
/// Tries to get a link by the specified ifindex
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 })
}
}
/// Represents the nl_cache in the libnl library, which is itself a general
/// collection of nl_objects
pub struct Cache<T>
where
T: From<*mut nl_object>,
{
pub(crate) cache: *mut nl_cache,
dt: PhantomData<T>,
}
impl<T: From<*mut nl_object>> Cache<T> {
pub fn iter(&self) -> CacheIter<'_, T> {
let cache_size = unsafe { nl_cache_nitems(self.cache) } as usize;
CacheIter {
obj: unsafe { nl_cache_get_first(self.cache) },
cache_size,
index: 0,
item_type: PhantomData {},
}
}
}
impl<T: From<*mut nl_object>> Drop for Cache<T> {
fn drop(&mut self) {
unsafe {
nl_cache_put(self.cache);
}
}
}
/// Iterates over caches and provides an easy way to work with them
pub struct CacheIter<'a, T> {
obj: *mut nl_object,
cache_size: usize,
index: usize,
item_type: PhantomData<&'a T>,
}
impl<T: From<*mut nl_object>> Iterator for CacheIter<'_, T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.index >= self.cache_size {
return None;
}
self.index += 1;
let obj = self.obj;
self.obj = unsafe { nl_cache_get_next(obj) };
if obj.is_null() {
continue;
}
break Some(T::from(obj));
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.cache_size, Some(self.cache_size))
}
}

111
nl-sys/src/nl_ffi.rs Normal file
View File

@ -0,0 +1,111 @@
// 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 libc::{c_char, c_int, c_uint, c_void};
macro_rules! nl_obj {
($name:ident) => {
#[repr(C)]
#[allow(non_camel_case_types)]
pub struct $name {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
};
}
nl_obj!(nl_sock);
nl_obj!(nl_cache);
nl_obj!(nl_addr);
nl_obj!(nl_object);
nl_obj!(nl_list_head);
nl_obj!(rtnl_addr);
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" {
pub fn nl_socket_alloc() -> *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_connect(sock: *mut nl_sock, protocol: c_int) -> c_int;
pub fn nl_close(sock: *mut nl_sock) -> c_void;
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_addr_alloc_cache(sock: *mut nl_sock, result: *mut *mut nl_cache) -> c_int;
pub fn rtnl_addr_get_ifindex(addr: *mut rtnl_addr) -> c_int;
pub fn rtnl_addr_get_family(addr: *mut rtnl_addr) -> c_int;
pub fn rtnl_addr_get_local(addr: *mut rtnl_addr) -> *mut nl_addr;
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_neigh_get_ifindex(neigh: *mut rtnl_neigh) -> c_int;
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_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;
}

590
nl-sys/src/route.rs Normal file
View File

@ -0,0 +1,590 @@
// 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::{
ffi::{CStr, CString},
fmt::Debug,
net::Ipv4Addr,
};
use libc::{c_int, c_uint, AF_INET, AF_LLC};
use crate::{
error,
netlink::{self, Cache},
};
use super::nl_ffi::*;
/// Represents an address assigned to a link
pub struct RtAddr {
addr: *mut rtnl_addr,
}
impl RtAddr {
pub fn local(&self) -> Option<Addr> {
unsafe {
let addr = rtnl_addr_get_local(self.addr);
if addr.is_null() {
return None;
}
Some(Addr { addr })
}
}
pub fn ifindex(&self) -> i32 {
unsafe { rtnl_addr_get_ifindex(self.addr) }
}
pub fn family(&self) -> i32 {
unsafe { rtnl_addr_get_family(self.addr) }
}
}
impl From<*mut nl_object> for RtAddr {
fn from(value: *mut nl_object) -> Self {
RtAddr {
addr: value as *mut _,
}
}
}
/// Represents a network link, which can represent a network device
pub struct Link {
pub(crate) link: *mut rtnl_link,
}
impl Link {
/// Returns the network link name, e.g. eth0
pub fn name(&self) -> String {
unsafe {
let name = rtnl_link_get_name(self.link);
if name.is_null() {
return "".to_string();
}
let name_rs = CStr::from_ptr(name);
std::str::from_utf8(name_rs.to_bytes()).unwrap().to_owned()
}
}
/// Provides the address of the link. Can change based on the type of link,
/// representing MAC addresses or IP addresses
pub fn addr(&self) -> Addr {
unsafe {
Addr {
addr: rtnl_link_get_addr(self.link),
}
}
}
/// Determines the type of link. Ethernet devices are "veth or eth"
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()).ok()?.to_owned())
}
}
/// Determines the index of the interface in the kernel table
pub fn ifindex(&self) -> c_int {
unsafe { rtnl_link_get_ifindex(self.link) }
}
/// Tries to get the neighbor for this link, which can provide the destination address and the
/// link layer address (lladdr)
pub fn get_neigh(&self, neigh_table: &Cache<Neigh>, addr: &Addr) -> Option<[u8; 6]> {
unsafe {
let neigh = rtnl_neigh_get(neigh_table.cache, self.ifindex(), addr.addr);
if neigh.is_null() {
return None;
}
Neigh { neigh }.lladdr().hw_address().try_into().ok()
}
}
}
impl Debug for Link {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Link")
.field("name", &self.name())
.field("ifindex", &self.ifindex())
.finish()
}
}
impl From<*mut nl_object> for Link {
fn from(value: *mut nl_object) -> Self {
Self {
link: value as *mut _,
}
}
}
pub fn get_macs_and_src_for_ip(
addrs: &Cache<RtAddr>,
routes: &Cache<Route>,
neighs: &Cache<Neigh>,
links: &Cache<Link>,
addr: Ipv4Addr,
) -> Option<(String, i32, Ipv4Addr, [u8; 6], [u8; 6], u8)> {
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(addr);
let route = 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)
})?;
let link_ind = route.hop_iter().next()?.ifindex();
#[cfg(debug_assertions)]
{
println!("Link index: {link_ind}\n");
for link in links.iter() {
println!(
"Link {}: {:?} ({})",
link.name(),
link.addr(),
link.ifindex()
);
println!("\tAddrs:");
for addr in addrs.iter().filter(|addr| addr.ifindex() == link.ifindex()) {
if let Some(a) = addr.local() {
println!("\t\t{:?}", a)
}
}
println!("\tNeighbors:");
for neigh in neighs
.iter()
.filter(|neigh| neigh.ifindex() == link.ifindex())
{
println!("\t\t{:?}, {:?}", neigh.dst(), neigh.lladdr());
}
}
}
let link = netlink::get_link_by_index(links, link_ind)?;
let neigh = neighs
.iter()
.find(|n| n.ifindex() == link.ifindex())
.map(|n| n.lladdr().hw_address().try_into().ok())
.flatten()
.unwrap_or([0xFFu8; 6]);
let srcip = addrs.iter().find(|a| a.ifindex() == link.ifindex())?;
Some((
link.name(),
link_ind,
(&srcip.local()?).try_into().ok()?,
link.addr().hw_address().try_into().ok()?,
neigh,
route.dst().unwrap().cidrlen() as u8,
))
}
/// Gets the neighbor record for the source IP specified, or get the default address
pub fn get_neigh_for_addr(
routes: &Cache<Route>,
neighs: &Cache<Neigh>,
links: &Cache<Link>,
addr: &Addr,
) -> Option<(Ipv4Addr, Link, [u8; 6])> {
for link in links.iter() {
let Some(neigh) = link.get_neigh(&neighs, addr) else {
continue;
};
return Some((addr.try_into().ok()?, link, neigh));
}
// No good neighbors were found above, try to use the default address
if let Some(def_neigh) = get_default_route(routes) {
println!("Found default route, trying to get link for it");
if let Some((laddr, link, neigh)) = neighs
.iter()
.filter_map(|n| {
let Some(link) = netlink::get_link_by_index(links, n.ifindex()) else {
return None;
};
let Some(first_hop) = def_neigh.hop_iter().next() else {
return None;
};
if n.ifindex() != first_hop.ifindex() {
return None;
}
Some(((&first_hop.gateway()?).try_into().ok()?, link, n.lladdr()))
})
.next()
{
return Some((laddr, link, neigh.hw_address().try_into().ok()?));
}
}
None
}
/// Given the routes cache, returns the default route among them
pub fn get_default_route(routes: &Cache<Route>) -> Option<Route> {
routes
.iter()
.find(|r| r.dst().map(|a| a.cidrlen()).unwrap_or(33) == 0)
}
/// A struct representing the neighbor of a link
pub struct Neigh {
neigh: *mut rtnl_neigh,
}
impl Neigh {
/// Pull up the destination address for this neighbor record
pub fn dst(&self) -> Addr {
unsafe {
let addr = rtnl_neigh_get_dst(self.neigh);
Addr { addr }
}
}
// Bring up the link local address for the neighbor link
pub fn lladdr(&self) -> Addr {
unsafe {
let addr = rtnl_neigh_get_lladdr(self.neigh);
Addr { addr }
}
}
pub fn ifindex(&self) -> i32 {
unsafe { rtnl_neigh_get_ifindex(self.neigh) }
}
}
impl From<*mut nl_object> for Neigh {
fn from(value: *mut nl_object) -> Self {
Self {
neigh: value as *mut _,
}
}
}
/// Represents "an address"
/// IPv4? IPv6? MAC? Whatever the "any" or "lo" devices use? Yes!
pub struct Addr {
addr: *mut nl_addr,
}
impl Addr {
/// Returns the number of bytes that are in the address
pub fn len(&self) -> u32 {
unsafe { nl_addr_get_len(self.addr) }
}
/// Returns the address, which can be interpreted based on the results of [`Addr::atype`]
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()
}
}
// Determines the type of data in [`Addr::hw_address`]
pub fn atype(&self) -> Option<c_int> {
if self.addr.is_null() {
None
} else {
Some(unsafe { nl_addr_get_family(self.addr) })
}
}
/// Returns the length of the subnet mask applying to this address
pub fn cidrlen(&self) -> c_uint {
unsafe { nl_addr_get_prefixlen(self.addr) }
}
}
impl Debug for Addr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let res = match self.atype() {
Some(AF_INET) => {
let octets = self.hw_address();
f.debug_struct("Addr")
.field(
"addr",
&format!(
"{}.{}.{}.{}/{}",
octets[0],
octets[1],
octets[2],
octets[3],
self.cidrlen()
),
)
.finish()
}
Some(AF_LLC) => {
let octets = self.hw_address();
f.debug_struct("Addr")
.field(
"addr",
&format!(
"{:02X?}:{:02X?}:{:02X?}:{:02X?}:{:02X?}:{:02X?}",
octets[0], octets[1], octets[2], octets[3], octets[4], octets[5],
),
)
.finish()
}
None => f
.debug_struct("Addr")
.field("addr", &"unknown")
.field("atype", &"unknown")
.finish(),
_ => f
.debug_struct("Addr")
.field("addr", &self.hw_address())
.field("atype", &self.atype())
.finish(),
};
res
}
}
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]))
}
}
/// Represents a route in the kernel routing table
pub struct Route {
route: *mut rtnl_route,
}
impl Route {
/// Represents the destination of the 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 })
}
}
/// Represents the destination of the route
pub fn dst(&self) -> Option<Addr> {
unsafe {
let addr = rtnl_route_get_dst(self.route);
if addr.is_null() {
return None;
}
Some(Addr { addr })
}
}
/// Returns the amount of hops are in this route
pub fn nexthop_len(&self) -> c_int {
unsafe { rtnl_route_get_nnexthops(self.route) }
}
/// Gets the hop at the index specify
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 })
}
}
/// Returns an iterator representing all the hops for this route
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 _,
}
}
}
/// Represents the hops of a network route
pub struct Nexthop {
nexthop: *mut rtnl_nexthop,
}
impl Nexthop {
/// Returns the gateway used for this network hop
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 })
}
}
/// Returns the interface index for this network hop
pub fn ifindex(&self) -> i32 {
unsafe { rtnl_route_nh_get_ifindex(self.nexthop) }
}
}
/// An iterator for working with route hops
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),
)
}
}
/// Determines the source IP address to use in order to make a network request
pub fn get_srcip_for_dstip(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()
.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 = dst_addr.into();
(mask & dst_addr) == (mask & ip_int)
})
.filter_map(|route| {
route
.hop_iter()
.next()
.and_then(|hop| hop.gateway())
.or(route.dst())
})
.filter_map(|gateway| (&gateway).try_into().ok())
.next()
}

8
packets/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "packets"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

40
packets/src/error.rs Normal file
View File

@ -0,0 +1,40 @@
// 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::{error, fmt::Display};
#[derive(Debug)]
pub enum Error {
StringParse,
UnknownPacketType(u16),
PacketLengthInvalid,
}
pub type Result<T> = std::result::Result<T, Error>;
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::StringParse => write!(f, "unable to parse a string from pcap"),
Error::UnknownPacketType(ptype) => write!(f, "unknown packet type ({ptype})"),
Error::PacketLengthInvalid => write!(
f,
"received a packet with a length that mismatched the header"
),
}
}
}
impl error::Error for Error {}

806
packets/src/lib.rs Normal file
View File

@ -0,0 +1,806 @@
// 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,
sync::atomic::{AtomicU16, Ordering},
};
pub mod error;
pub struct EthernetPkt<'a> {
pub data: &'a [u8],
}
impl<'a> EthernetPkt<'a> {
pub fn destination_address(&self) -> &[u8; 6] {
self.data[0..6].try_into().unwrap()
}
pub fn source_address(&self) -> &[u8; 6] {
self.data[6..12].try_into().unwrap()
}
pub fn ether_type(&self) -> u16 {
u16::from_be_bytes(self.data[12..14].try_into().unwrap())
}
pub fn get_layer3_pkt(&'_ self) -> error::Result<Layer3Pkt<'_>> {
if self.data.len() < 14 {
return Err(error::Error::PacketLengthInvalid);
}
match self.ether_type() {
0x0800 => {
if self.data.len() < 34 {
return Err(error::Error::PacketLengthInvalid);
}
let pkt = IPv4Pkt {
data: &self.data[14..],
};
if self.data.len() < (14 + pkt.total_length()).into() {
return Err(error::Error::PacketLengthInvalid);
}
Ok(Layer3Pkt::IPv4Pkt(pkt))
}
0x0806 => {
if self.data.len() < 42 {
return Err(error::Error::PacketLengthInvalid);
}
let pkt = ARPPkt {
data: &self.data[14..],
};
Ok(Layer3Pkt::ARP(pkt))
}
p => Err(error::Error::UnknownPacketType(p)),
}
}
pub fn to_owned(&self) -> EthernetPacket {
EthernetPacket {
data: self.data.to_vec(),
}
}
}
pub enum Layer3Pkt<'a> {
IPv4Pkt(IPv4Pkt<'a>),
ARP(ARPPkt<'a>),
}
pub struct IPv4Pkt<'a> {
data: &'a [u8],
}
impl<'a> IPv4Pkt<'a> {
pub fn version(&self) -> u8 {
4
}
pub fn header_len(&self) -> u8 {
4 * (0x0F & self.data[0])
}
pub fn type_of_service(&self) -> u8 {
self.data[1] >> 2
}
pub fn ecn(&self) -> u8 {
self.data[1] & 0x03
}
pub fn total_length(&self) -> u16 {
u16::from_be_bytes(self.data[2..4].try_into().unwrap())
}
pub fn id(&self) -> u16 {
u16::from_be_bytes(self.data[4..6].try_into().unwrap())
}
pub fn dont_fragment(&self) -> bool {
self.data[6] & 0x40 != 0
}
pub fn more_fragments(&self) -> bool {
self.data[6] & 0x80 != 0
}
pub fn fragment_offset(&self) -> u16 {
u16::from_be_bytes([self.data[6] & 0x1F, self.data[7]]) * 8
}
pub fn ttl(&self) -> u8 {
self.data[8]
}
pub fn protocol(&self) -> u8 {
self.data[9]
}
pub fn header_checksum(&self) -> u16 {
u16::from_be_bytes(self.data[10..12].try_into().unwrap())
}
pub fn source_ip(&self) -> Ipv4Addr {
<&[u8] as TryInto<[u8; 4]>>::try_into(&self.data[12..16])
.unwrap()
.into()
}
pub fn dest_ip(&self) -> Ipv4Addr {
<&[u8] as TryInto<[u8; 4]>>::try_into(&self.data[16..20])
.unwrap()
.into()
}
pub fn to_owned(&self) -> IPv4Packet {
IPv4Packet {
data: self.data.to_vec(),
}
}
pub fn get_layer4_packet(&self) -> error::Result<Layer4Pkt<'a>> {
match self.protocol() {
// ICMP
0x1 => Err(error::Error::UnknownPacketType(0x1)),
// TCP
0x6 => Ok(Layer4Pkt::TCP(TCPPkt {
data: &self.data[(self.header_len() as usize)..],
})),
0x11 => Ok(Layer4Pkt::UDP(UDPPkt {
data: &self.data[(self.header_len() as usize)..],
})),
p => Err(error::Error::UnknownPacketType(p.into())),
}
}
}
pub struct ARPPkt<'a> {
data: &'a [u8],
}
impl<'a> ARPPkt<'a> {
pub fn htype(&self) -> u16 {
u16::from_be_bytes(self.data[0..2].try_into().unwrap())
}
pub fn ptype(&self) -> u16 {
u16::from_be_bytes(self.data[2..4].try_into().unwrap())
}
pub fn hwlen(&self) -> u8 {
self.data[4]
}
pub fn plen(&self) -> u8 {
self.data[5]
}
pub fn opcode(&self) -> u16 {
u16::from_be_bytes(self.data[6..8].try_into().unwrap())
}
pub fn srchwaddr(&self) -> &[u8] {
&self.data[8usize..8usize + self.hwlen() as usize]
}
pub fn srcprotoaddr(&self) -> &[u8] {
let start = self.hwlen() as usize + 8;
&self.data[start..start + self.plen() as usize]
}
pub fn targethwaddr(&self) -> &[u8] {
let start = self.hwlen() as usize + self.plen() as usize + 8;
&self.data[start..start + self.hwlen() as usize]
}
pub fn targetprotoaddr(&self) -> &[u8] {
let start = self.hwlen() as usize + self.plen() as usize + self.hwlen() as usize + 8;
&self.data[start..start + self.plen() as usize]
}
}
pub enum Layer4Pkt<'a> {
UDP(UDPPkt<'a>),
TCP(TCPPkt<'a>),
}
impl<'a> Layer4Pkt<'a> {
pub fn len(&self) -> u16 {
match self {
Layer4Pkt::UDP(pkt) => pkt.len(),
Layer4Pkt::TCP(pkt) => pkt.data.len() as u16,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub struct UDPPkt<'a> {
data: &'a [u8],
}
impl<'a> UDPPkt<'a> {
pub fn srcport(&self) -> u16 {
u16::from_be_bytes(self.data[0..2].try_into().unwrap())
}
pub fn dstport(&self) -> u16 {
u16::from_be_bytes(self.data[2..4].try_into().unwrap())
}
pub fn len(&self) -> u16 {
u16::from_be_bytes(self.data[4..6].try_into().unwrap())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn to_owned(&self) -> UDPPacket {
UDPPacket {
data: self.data.to_vec(),
}
}
pub fn get_data(&'_ self) -> &'_ [u8] {
&self.data[8..(self.len() as usize)]
}
}
pub struct TCPPkt<'a> {
data: &'a [u8],
}
impl<'a> TCPPkt<'a> {
pub fn len(&self) -> u8 {
(self.data[12] >> 4) * 4
}
pub fn srcport(&self) -> u16 {
u16::from_be_bytes(self.data[0..2].try_into().unwrap())
}
pub fn dstport(&self) -> u16 {
u16::from_be_bytes(self.data[2..4].try_into().unwrap())
}
pub fn seqnumber(&self) -> u32 {
u32::from_be_bytes(self.data[4..8].try_into().unwrap())
}
pub fn acknumber(&self) -> u32 {
u32::from_be_bytes(self.data[8..12].try_into().unwrap())
}
pub fn flags(&self) -> u8 {
self.data[13]
}
pub fn cwr(&self) -> bool {
self.flags() & 0x80 != 0
}
pub fn ece(&self) -> bool {
self.flags() & 0x40 != 0
}
pub fn urg(&self) -> bool {
self.flags() & 0x20 != 0
}
pub fn ack(&self) -> bool {
self.flags() & 0x10 != 0
}
pub fn psh(&self) -> bool {
self.flags() & 0x08 != 0
}
pub fn rst(&self) -> bool {
self.flags() & 0x04 != 0
}
pub fn syn(&self) -> bool {
self.flags() & 0x02 != 0
}
pub fn fin(&self) -> bool {
self.flags() & 0x01 != 0
}
pub fn window(&self) -> u16 {
u16::from_be_bytes(self.data[14..16].try_into().unwrap())
}
pub fn checksum(&self) -> u16 {
u16::from_be_bytes(self.data[16..18].try_into().unwrap())
}
pub fn urgent_ptr(&self) -> u16 {
u16::from_be_bytes(self.data[18..20].try_into().unwrap())
}
pub fn options(&self) -> &[u8] {
&self.data[20..(self.len() as usize)]
}
pub fn data(&self) -> &[u8] {
&self.data[(self.len() as usize)..]
}
pub fn verify_checksum<I1, I2>(&self, srcip: I1, dstip: I2) -> bool
where
I1: Into<Ipv4Addr>,
I2: Into<Ipv4Addr>,
{
let source = srcip.into();
let dest = dstip.into();
let protocol = &[0x00u8, 0x06u8];
let tcp_length = (self.data.len()) as u16;
let len: u8 = (self.len()).try_into().unwrap();
let len = len << 4;
let bytes = [
&source.octets()[..],
&dest.octets(),
protocol,
&tcp_length.to_be_bytes(),
&self.srcport().to_be_bytes()[..],
&self.dstport().to_be_bytes(),
&self.seqnumber().to_be_bytes(),
&self.acknumber().to_be_bytes(),
&[len],
&[self.flags()],
&self.window().to_be_bytes(),
&[0, 0],
&self.urgent_ptr().to_be_bytes(),
&self.options(),
&self.data(),
]
.concat();
let checksum = bytes
.chunks(2)
.map(|pair| {
if pair.len() == 1 {
(pair[0] as u32) << 8
} else {
(pair[0] as u32) << 8 | (pair[1] as u32)
}
})
.fold(0u32, |acc, e| acc + e)
+ 30;
// + 30 to intentionally deviate from
// the RFC, to make it so that I can identify the packets as sparse
// packets
let checksum = (checksum >> 16) + (checksum & 0xffff);
let checksum = ((checksum >> 16) as u16) + (checksum as u16);
checksum == self.checksum()
}
}
#[derive(Debug, Clone)]
pub struct EthernetPacket {
data: Vec<u8>,
}
impl EthernetPacket {
pub fn construct(src: [u8; 6], dst: [u8; 6], packet: &Layer3Packet) -> EthernetPacket {
match packet {
Layer3Packet::IPv4(pkt) => EthernetPacket {
data: [&dst[..], &src[..], &[0x08_u8, 0x00_u8][..], &*pkt.data].concat(),
},
Layer3Packet::ARP(pkt) => EthernetPacket {
data: [&dst[..], &src[..], &[0x08_u8, 0x06_u8][..], &*pkt.data].concat(),
},
}
}
#[inline]
pub fn pkt(&'_ self) -> EthernetPkt<'_> {
EthernetPkt { data: &self.data }
}
}
#[derive(Clone)]
pub enum Layer3Packet {
IPv4(IPv4Packet),
ARP(ARPPacket),
}
#[derive(Clone)]
pub struct ARPPacket {
data: Vec<u8>,
}
pub enum ARPMode {
Request,
Reply,
}
impl ARPMode {
fn opcode(&self) -> u16 {
match self {
Self::Request => 1,
Self::Reply => 2,
}
}
}
pub enum ARPProto {
IPv4,
}
impl ARPProto {
fn opcode(&self) -> u16 {
match self {
Self::IPv4 => 0x0800,
}
}
}
impl ARPPacket {
pub fn construct(
op: ARPMode,
proto: ARPProto,
srchwaddr: &[u8],
targethwaddr: &[u8],
srcprotoaddr: &[u8],
targetprotoaddr: &[u8],
) -> ARPPacket {
assert!(srchwaddr.len() == targethwaddr.len());
assert!(srcprotoaddr.len() == targetprotoaddr.len());
let data = [
&0x0001u16.to_be_bytes()[..],
&proto.opcode().to_be_bytes()[..],
&[srchwaddr.len() as u8],
&[srcprotoaddr.len() as u8],
&op.opcode().to_be_bytes(),
srchwaddr,
srcprotoaddr,
targethwaddr,
targetprotoaddr,
]
.concat();
ARPPacket { data }
}
}
static IPV4_ID: AtomicU16 = AtomicU16::new(0xabcd);
#[derive(Clone)]
pub struct IPv4Packet {
pub data: Vec<u8>,
}
impl IPv4Packet {
pub fn construct<I1, I2>(source: I1, dest: I2, packet: &Layer4Packet) -> IPv4Packet
where
I1: Into<Ipv4Addr>,
I2: Into<Ipv4Addr>,
{
let source = source.into();
let dest = dest.into();
let dscpen: u8 = 0;
let totallen: u16 = 20 + packet.pkt().len();
let id: u16 = IPV4_ID.fetch_add(1, Ordering::SeqCst);
let fragmentation: u16 = 0x4000;
let ttl: u8 = 64;
let protocol: u8 = match packet {
Layer4Packet::UDP(_) => 0x11,
Layer4Packet::TCP(_) => 0x6,
};
let source_upper = u16::from_be_bytes(source.octets()[0..2].try_into().unwrap());
let source_lower = u16::from_be_bytes(source.octets()[2..4].try_into().unwrap());
let dest_upper = u16::from_be_bytes(dest.octets()[0..2].try_into().unwrap());
let dest_lower = u16::from_be_bytes(dest.octets()[2..4].try_into().unwrap());
let versihldscpen = 0x4500 | dscpen as u16;
let ttlprotocol = ((ttl as u32) << 8_u8) | protocol as u32;
let checksum_part: u32 = versihldscpen as u32
+ totallen as u32
+ id as u32
+ ttlprotocol
+ fragmentation as u32
+ source_upper as u32
+ source_lower as u32
+ dest_upper as u32
+ dest_lower as u32;
let checksum: u16 = (((checksum_part & 0xFFFF) + (checksum_part >> 16)) & 0xFFFF)
.try_into()
.unwrap();
let checksum = 0xFFFF - checksum;
let data = [
&versihldscpen.to_be_bytes()[..],
&totallen.to_be_bytes(),
&id.to_be_bytes(),
&fragmentation.to_be_bytes(),
&[ttl, protocol],
&checksum.to_be_bytes(),
&source.octets(),
&dest.octets(),
match packet {
Layer4Packet::UDP(pkt) => &*pkt.data,
Layer4Packet::TCP(pkt) => &*pkt.data,
},
]
.concat();
IPv4Packet { data }
}
#[inline]
pub fn pkt(&'_ self) -> IPv4Pkt<'_> {
IPv4Pkt { data: &self.data }
}
}
#[derive(Clone)]
pub enum Layer4Packet {
UDP(UDPPacket),
TCP(TCPPacket),
}
impl Layer4Packet {
pub fn pkt(&'_ self) -> Layer4Pkt<'_> {
match self {
Layer4Packet::UDP(pkt) => Layer4Pkt::UDP(pkt.pkt()),
Layer4Packet::TCP(pkt) => Layer4Pkt::TCP(pkt.pkt()),
}
}
}
#[derive(Clone)]
pub struct UDPPacket {
data: Vec<u8>,
}
impl UDPPacket {
pub fn construct<T>(source: u16, dest: u16, data: T) -> UDPPacket
where
T: Into<Vec<u8>>,
{
let data = data.into();
UDPPacket {
data: [
&source.to_be_bytes(),
&dest.to_be_bytes(),
&(8_u16 + TryInto::<u16>::try_into(data.len()).unwrap()).to_be_bytes(),
&[0_u8, 0_u8],
&*data,
]
.concat(),
}
}
#[inline]
pub fn pkt(&'_ self) -> UDPPkt<'_> {
UDPPkt { data: &self.data }
}
}
#[derive(Clone)]
pub struct TCPPacket {
data: Vec<u8>,
}
impl TCPPacket {
#[inline]
pub fn pkt(&'_ self) -> TCPPkt<'_> {
TCPPkt { data: &self.data }
}
}
#[derive(Default, Debug)]
pub struct TCPPacketBuilder {
srcport: Option<u16>,
dstport: Option<u16>,
seqnumber: Option<u32>,
acknumber: u32,
flags: u8,
window: Option<u16>,
urgent_ptr: u16,
options: Vec<u8>,
}
macro_rules! declare_flag {
($name:ident, $offset:expr) => {
pub fn $name(mut self, enable: bool) -> Self {
if enable {
self.flags |= $offset
} else {
self.flags &= (0xFF ^ $offset)
}
self
}
};
}
impl TCPPacketBuilder {
pub fn srcport(mut self, port: u16) -> Self {
self.srcport = Some(port);
self
}
pub fn dstport(mut self, port: u16) -> Self {
self.dstport = Some(port);
self
}
pub fn seqnumber(mut self, num: u32) -> Self {
self.seqnumber = Some(num);
self
}
pub fn acknumber(mut self, num: u32) -> Self {
self.acknumber = num;
self
}
declare_flag!(cwr, 0x80);
declare_flag!(ece, 0x40);
declare_flag!(urg, 0x20);
declare_flag!(ack, 0x10);
declare_flag!(psh, 0x08);
declare_flag!(rst, 0x04);
declare_flag!(syn, 0x02);
declare_flag!(fin, 0x01);
pub fn window(mut self, window: u16) -> Self {
self.window = Some(window);
self
}
pub fn urgent_ptr(mut self, ptr: u16) -> Self {
self.urgent_ptr = ptr;
self
}
pub fn options(mut self, opts: Vec<u8>) -> Self {
self.options = opts;
self
}
pub fn build<I1, I2>(
self,
srcip: I1,
dstip: I2,
data: Vec<u8>,
checksum_offset: Option<u16>,
) -> TCPPacket
where
I1: Into<Ipv4Addr>,
I2: Into<Ipv4Addr>,
{
let source = srcip.into();
let dest = dstip.into();
let protocol = &[0x00u8, 0x06u8];
let tcp_length = (data.len() + self.options.len() + 20) as u16;
let srcport = self.srcport.unwrap();
let dstport = self.dstport.unwrap();
let seqnumber = self.seqnumber.unwrap();
let window = self.window.unwrap();
let mut options = self.options;
while options.len() % 4 != 0 {
options.push(0);
}
let len: u8 = (options.len() / 4 + 5).try_into().unwrap();
let len = len << 4;
let mut bytes = [
&source.octets()[..],
&dest.octets(),
protocol,
&tcp_length.to_be_bytes(),
&srcport.to_be_bytes()[..],
&dstport.to_be_bytes(),
&seqnumber.to_be_bytes(),
&self.acknumber.to_be_bytes(),
&[len],
&[self.flags],
&window.to_be_bytes(),
&[0, 0],
&self.urgent_ptr.to_be_bytes(),
&options,
&data,
]
.concat();
let checksum = bytes
.chunks(2)
.map(|pair| {
if pair.len() == 1 {
(pair[0] as u32) << 8
} else {
(pair[0] as u32) << 8 | (pair[1] as u32)
}
})
.fold(0u32, |acc, e| acc + e)
+ (checksum_offset.unwrap_or(0) as u32);
let checksum = (checksum >> 16) + (checksum & 0xffff);
let checksum = ((checksum >> 16) as u16) + (checksum as u16);
let checksum = (!checksum).to_be_bytes();
//bytes[16] = checksum[0];
//bytes[17] = checksum[1];
bytes[28] = checksum[0];
bytes[29] = checksum[1];
TCPPacket {
data: bytes[12..].to_vec(),
}
}
}
#[cfg(test)]
mod test {
use std::net::Ipv4Addr;
use crate::TCPPacketBuilder;
#[test]
fn test_checksum() {
let tcp_packet = TCPPacketBuilder::default()
.srcport(57116)
.dstport(54248)
.seqnumber(0xaed77262)
.acknumber(0)
.syn(true)
.window(64240)
.options(vec![
0x02, 0x04, 0x05, 0xb4, 0x04, 0x02, 0x08, 0x0a, 0xd4, 0x06, 0xdf, 0x18, 0x00, 0x00,
0x00, 0x00, 0x01, 0x03, 0x03, 0x07,
])
.build(
Ipv4Addr::new(10, 104, 18, 67),
Ipv4Addr::new(10, 104, 21, 16),
vec![],
None,
);
assert_eq!(tcp_packet.pkt().checksum(), 0x898d);
}
}

31
pcap-sys/Cargo.toml Normal file
View File

@ -0,0 +1,31 @@
# 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/>.
[package]
name = "pcap-sys"
version = "0.1.0"
edition = "2021"
[dependencies]
errno = "0.3.10"
futures = "0.3.25"
libc = "0.2.169"
tokio = { version = "1.21.2", features = ["net", "rt", "macros", "rt-multi-thread" ] }
tokio-stream = "0.1.14"
packets = { path = "../packets" }
[build-dependencies]
cmake = "0.1"
fs_extra = "1.3.0"

3
pcap-sys/README.md Normal file
View File

@ -0,0 +1,3 @@
# pcap-sys
This library provides wrappers around libpcap that when built in the dev container environment allow for full static linking against libpcap

78
pcap-sys/build.rs Normal file
View File

@ -0,0 +1,78 @@
// 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 fs_extra::dir::{copy, CopyOptions};
fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
println!("cargo:rustc-link-search=native={}", std::env::var("SPARSE_BUILD_WINPCAP").unwrap());
println!("cargo:rustc-link-search=native={}/x64", std::env::var("SPARSE_BUILD_WINPCAP").unwrap());
println!("cargo:rustc-link-lib=wpcap");
} else if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "freebsd" {
let libpcap_src = format!("{}/pcap_src", std::env::var("OUT_DIR").unwrap());
let mut options = CopyOptions::new();
options.copy_inside = true;
options.skip_exist = true;
copy(std::env::var("SPARSE_BUILD_LIBPCAP").unwrap(), &libpcap_src, &options).expect("could not copy libpcap source code to build");
let dst = cmake::Config::new(&libpcap_src)
.profile("MinSizeRel")
.define("BUILD_SHARED_LIBS", "OFF")
.define("DISABLE_BLUETOOTH", "ON")
.define("DISABLE_DAG", "ON")
.define("DISABLE_DBUS", "ON")
.define("DISABLE_DPDK", "ON")
.define("DISABLE_NETMAP", "ON")
.define("DISABLE_RDMA", "ON")
.define("DISABLE_SEPTEL", "ON")
.define("DISABLE_SNF", "ON")
.define("DISABLE_TC", "ON")
.build();
println!("cargo:rustc-link-search=native={}/lib", dst.display());
println!("cargo:rustc-link-search=native={}/lib64", dst.display());
println!("cargo:rustc-link-lib=static=pcap");
} else {
let libpcap_src = format!("{}/pcap_src", std::env::var("OUT_DIR").unwrap());
let mut options = CopyOptions::new();
options.copy_inside = true;
options.skip_exist = true;
copy(std::env::var("SPARSE_BUILD_LIBPCAP").unwrap(), &libpcap_src, &options).expect("could not copy libpcap source code to build");
let dst = cmake::Config::new(&libpcap_src)
.profile("MinSizeRel")
.define("BUILD_SHARED_LIBS", "OFF")
.define("BUILD_WITH_LIBNL", "OFF")
.define("DISABLE_BLUETOOTH", "ON")
.define("DISABLE_DAG", "ON")
.define("DISABLE_DBUS", "ON")
.define("DISABLE_DPDK", "ON")
.define("DISABLE_LINUX_USBMON", "ON")
.define("DISABLE_NETMAP", "ON")
.define("DISABLE_RDMA", "ON")
.define("DISABLE_SEPTEL", "ON")
.define("DISABLE_SNF", "ON")
.define("DISABLE_TC", "ON")
.define("PCAP_TYPE", "linux")
.build();
// panic!("hahahahah test {}", dst.display());
println!("cargo:rustc-link-search=native={}/lib", dst.display());
println!("cargo:rustc-link-search=native={}/lib64", dst.display());
println!("cargo:rustc-link-lib=static=pcap");
}
}

136
pcap-sys/src/error.rs Normal file
View File

@ -0,0 +1,136 @@
// 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 errno::Errno;
use std::{
convert::From,
error,
ffi::{self, CStr, CString},
fmt::Display,
};
#[derive(Debug)]
pub enum Error {
PcapError(CString),
PcapErrorIf(String, CString),
StringParse,
UnknownPacketType(u16),
PacketLengthInvalid,
InvalidPcapFd,
Io(std::io::Error),
Libc(Errno),
}
pub type Result<T> = std::result::Result<T, Error>;
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::PcapError(err) => {
if let Ok(err_str) = std::str::from_utf8(err.as_bytes()) {
return write!(f, "pcap error: {err_str}");
}
write!(f, "unknown pcap error")
}
Error::PcapErrorIf(ifname, err) => {
if let Ok(err_str) = std::str::from_utf8(err.as_bytes()) {
return write!(f, "pcap error on interface {ifname}: {err_str}");
}
write!(f, "unknown pcap error with interface {ifname}")
}
Error::StringParse => write!(f, "unable to parse a string from pcap"),
Error::UnknownPacketType(ptype) => write!(f, "unknown packet type ({ptype})"),
Error::PacketLengthInvalid => write!(
f,
"received a packet with a length that mismatched the header"
),
Error::InvalidPcapFd => write!(f, "internal pcap file descriptor error"),
Error::Io(io) => write!(f, "std::io error ({io})"),
Error::Libc(err) => write!(f, "libc error ({err})"),
}
}
}
impl Error {
pub fn add_ifname(self, ifname: &str) -> Self {
match self {
Error::PcapError(err) => Error::PcapErrorIf(ifname.to_string(), err),
other => other,
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::Io(err) => Some(err),
Error::Libc(err) => Some(err),
_ => None,
}
}
}
impl From<&[i8; 256]> for Error {
fn from(buf: &[i8; 256]) -> Error {
match CString::new(
buf.iter()
.take_while(|&&v| v != 0)
.map(|&v| v as u8)
.collect::<Vec<_>>(),
) {
Ok(s) => Error::PcapError(s),
Err(_) => Error::StringParse,
}
}
}
impl From<*const i8> for Error {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn from(buf: *const i8) -> Error {
Error::PcapError(unsafe { CStr::from_ptr(buf) }.to_owned())
}
}
impl From<ffi::NulError> for Error {
fn from(_err: ffi::NulError) -> Error {
Error::StringParse
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::Io(err)
}
}
impl From<Errno> for Error {
fn from(err: Errno) -> Error {
Error::Libc(err)
}
}
impl From<packets::error::Error> for Error {
fn from(err: packets::error::Error) -> Error {
use packets::error::Error as ExtE;
match err {
ExtE::PacketLengthInvalid => Error::PacketLengthInvalid,
ExtE::UnknownPacketType(t) => Error::UnknownPacketType(t),
ExtE::StringParse => Error::StringParse,
}
}
}

183
pcap-sys/src/ffi.rs Normal file
View File

@ -0,0 +1,183 @@
// 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 libc::{c_char, c_int, c_uchar, c_uint, c_ushort, c_void};
pub const DLT_NULL: i32 = 0;
pub const DLT_EN10MB: i32 = 1;
pub const DLT_EN3MB: i32 = 2;
pub const DLT_AX25: i32 = 3;
pub const DLT_PRONET: i32 = 4;
pub const DLT_CHAOS: i32 = 5;
pub const DLT_IEEE802: i32 = 6;
pub const DLT_ARCNET: i32 = 7;
pub const DLT_SLIP: i32 = 8;
pub const DLT_PPP: i32 = 9;
pub const DLT_FDDI: i32 = 10;
pub const PCAP_VERSION_MAJOR: c_int = 2;
pub const PCAP_VERSION_MINOR: c_int = 4;
pub const PCAP_ERRBUF_SIZE: usize = 256;
pub const PCAP_IF_LOOPBACK: c_int = 0x00000001;
pub const PCAP_IF_UP: c_int = 0x00000002;
pub const PCAP_IF_RUNNING: c_int = 0x00000004;
pub const PCAP_IF_WIRELESS: c_int = 0x00000008;
pub const PCAP_IF_CONNECTION_STATUS: c_int = 0x00000030;
pub const PCAP_IF_CONNECTION_STATUS_UNKNOWN: c_int = 0x00000000;
pub const PCAP_IF_CONNECTION_STATUS_CONNECTED: c_int = 0x00000010;
pub const PCAP_IF_CONNECTION_STATUS_DISCONNECTED: c_int = 0x00000020;
pub const PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE: c_int = 0x00000030;
pub const PCAP_ERROR: c_int = -1;
pub const PCAP_ERROR_BREAK: c_int = -2;
pub const PCAP_ERROR_NOT_ACTIVATED: c_int = -3;
pub const PCAP_ERROR_ACTIVATED: c_int = -4;
pub const PCAP_ERROR_NO_SUCH_DEVICE: c_int = -5;
pub const PCAP_ERROR_RFMON_NOTSUP: c_int = -6;
pub const PCAP_ERROR_NOT_RFMON: c_int = -7;
pub const PCAP_ERROR_PERM_DENIED: c_int = -8;
pub const PCAP_ERROR_IFACE_NOT_UP: c_int = -9;
pub const PCAP_ERROR_CANTSET_TSTAMP_TYPE: c_int = 10;
pub const PCAP_ERROR_PROMISC_PERM_DENIED: c_int = -11;
pub const PCAP_ERROR_TSTAMP_PRECISION_NOTSUP: c_int = -12;
pub const PCAP_WARNING: c_int = 1;
pub const PCAP_WARNING_PROMISC_NOTSUP: c_int = 2;
pub const PCAP_WARNING_TSTAMP_TYPE_NOTSUP: c_int = 3;
pub const PCAP_NETMASK_UNKNOWN: u32 = 0xffffffff;
pub const PCAP_CHAR_ENC_LOCAL: c_int = 0x00000000;
pub const PCAP_CHAR_ENC_UTF_8: c_int = 0x00000001;
pub const PCAP_TSTAMP_HOST: c_int = 0;
pub const PCAP_TSTAMP_HOST_LOWPREC: c_int = 1;
pub const PCAP_TSTAMP_HOST_HIPREC: c_int = 2;
pub const PCAP_TSTAMP_ADAPTER: c_int = 3;
pub const PCAP_TSTAMP_ADAPTER_UNSYNCED: c_int = 4;
pub const PCAP_TSTAMP_HOST_HIPREC_UNSYNCED: c_int = 5;
pub const PCAP_TSTAMP_PRECISION_MICRO: c_int = 0;
pub const PCAP_TSTAMP_PRECISION_NANO: c_int = 1;
pub const PCAP_BUF_SIZE: c_int = 1024;
pub const PCAP_SRC_FILE: c_int = 2;
pub const PCAP_SRC_IFLOCAL: c_int = 3;
pub const PCAP_SRC_IFREMOTE: c_int = 4;
pub const PCAP_SRC_FILE_STRING: &[u8] = b"file://";
pub const PCAP_SRC_IF_STRING: &[u8] = b"rpcap://";
pub const PCAP_OPENFLAG_PROMISCUOUS: c_int = 0x00000001;
pub const PCAP_OPENFLAG_DATATX_UDP: c_int = 0x00000002;
pub const PCAP_OPENFLAG_NOCAPTURE_RPCAP: c_int = 0x00000004;
pub const PCAP_OPENFLAG_NOCAPTURE_LOCAL: c_int = 0x00000008;
pub const PCAP_OPENFLAG_MAX_RESPONSIVENESS: c_int = 0x00000010;
pub const RPCAP_RMTAUTH_NULL: c_int = 0;
pub const RPCAP_RMTAUTH_PWD: c_int = 1;
pub const PCAP_SAMP_NOSAMP: c_int = 0;
pub const PCAP_SAMP_1_EVERY_N: c_int = 1;
pub const PCAP_SAMP_FIRST_AFTER_N_MS: c_int = 2;
pub const RPCAP_HOSTLIST_SIZE: c_int = 1024;
pub const BUFSIZ: c_int = 8192;
#[repr(C)]
pub struct PcapDev {
_data: [u8; 0],
}
#[repr(C)]
pub struct SockAddr {
pub sa_family: c_ushort,
pub sa_data: [u8; 14],
}
#[repr(C)]
pub struct PcapAddr {
pub next: *const PcapAddr,
pub addr: *const SockAddr,
pub netmask: *const SockAddr,
pub broadaddr: *const SockAddr,
pub dstaddr: *const SockAddr,
}
#[repr(C)]
pub struct PcapDevIf {
pub next: *const PcapDevIf,
pub name: *const c_char,
pub description: *const c_char,
pub addresses: *const PcapAddr,
pub flags: u32,
}
#[repr(C)]
pub struct bpf_insn {
code: c_ushort,
jt: c_uchar,
jf: c_uchar,
k: u32,
}
#[repr(C)]
pub struct BpfProgram {
pub bf_len: c_uint,
pub bpf_insn: *const bpf_insn,
}
#[repr(C)]
pub struct PktHeader {
pub ts: libc::timeval,
pub caplen: u32,
pub len: u32,
}
// #[link(name = "pcap", kind = "static", modifiers = "+whole-archive")]
extern "C" {
pub fn pcap_findalldevs(pcap_dev_if: *mut *const PcapDevIf, errbuf: *mut c_char) -> c_int;
pub fn pcap_freealldevs(pcap_dev_if: *const PcapDevIf);
pub fn pcap_create(source: *const c_char, errbuf: *mut c_char) -> *mut PcapDev;
pub fn pcap_close(p: *mut PcapDev);
pub fn pcap_set_promisc(dev: *mut PcapDev, promisc: c_int) -> c_int;
pub fn pcap_set_buffer_size(dev: *mut PcapDev, bufsize: c_int) -> c_int;
pub fn pcap_set_timeout(dev: *mut PcapDev, ms: c_int) -> c_int;
pub fn pcap_activate(dev: *mut PcapDev) -> c_int;
pub fn pcap_datalink(dev: *mut PcapDev) -> c_int;
pub fn pcap_geterr(dev: *mut PcapDev) -> *const c_char;
pub fn pcap_lookupnet(
dev: *const c_char,
net: *mut u32,
mask: *mut u32,
errbuf: *mut c_char,
) -> c_int;
pub fn pcap_compile(
dev: *mut PcapDev,
fp: *mut BpfProgram,
filter_exp: *const i8,
optimize: c_int,
mask: u32,
) -> c_int;
pub fn pcap_setfilter(dev: *mut PcapDev, fp: *const BpfProgram) -> c_int;
pub fn pcap_loop(
p: *mut PcapDev,
cnt: c_int,
callback: unsafe extern "C" fn(
user: *mut c_void,
header: *const PktHeader,
data: *const u8,
),
user: *mut c_void,
) -> c_int;
pub fn pcap_breakloop(p: *mut PcapDev);
pub fn pcap_sendpacket(p: *mut PcapDev, buf: *const c_uchar, size: c_int) -> c_int;
pub fn pcap_setnonblock(dev: *mut PcapDev, nonblock: c_int, errbuf: *mut c_char) -> c_int;
pub fn pcap_get_selectable_fd(p: *mut PcapDev) -> c_int;
pub fn pcap_next_ex(
p: *mut PcapDev,
header: *mut *mut PktHeader,
pkt_data: *mut *mut c_char,
) -> c_int;
}

418
pcap-sys/src/lib.rs Normal file
View File

@ -0,0 +1,418 @@
// 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::{
ffi::{CStr, CString},
ptr, slice
};
pub mod error;
mod ffi;
pub use packets;
#[cfg(target_os = "linux")]
pub mod stream;
pub mod consts {
pub use super::ffi::{
BUFSIZ, DLT_ARCNET, DLT_AX25, DLT_CHAOS, DLT_EN10MB, DLT_EN3MB, DLT_FDDI, DLT_IEEE802,
DLT_NULL, DLT_PPP, DLT_PRONET, DLT_SLIP, PCAP_BUF_SIZE, PCAP_CHAR_ENC_LOCAL,
PCAP_CHAR_ENC_UTF_8, PCAP_ERRBUF_SIZE, PCAP_ERROR, PCAP_ERROR_ACTIVATED, PCAP_ERROR_BREAK,
PCAP_ERROR_CANTSET_TSTAMP_TYPE, PCAP_ERROR_IFACE_NOT_UP, PCAP_ERROR_NOT_ACTIVATED,
PCAP_ERROR_NOT_RFMON, PCAP_ERROR_NO_SUCH_DEVICE, PCAP_ERROR_PERM_DENIED,
PCAP_ERROR_PROMISC_PERM_DENIED, PCAP_ERROR_RFMON_NOTSUP,
PCAP_ERROR_TSTAMP_PRECISION_NOTSUP, PCAP_IF_CONNECTION_STATUS,
PCAP_IF_CONNECTION_STATUS_CONNECTED, PCAP_IF_CONNECTION_STATUS_DISCONNECTED,
PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE, PCAP_IF_CONNECTION_STATUS_UNKNOWN,
PCAP_IF_LOOPBACK, PCAP_IF_RUNNING, PCAP_IF_UP, PCAP_IF_WIRELESS, PCAP_NETMASK_UNKNOWN,
PCAP_OPENFLAG_DATATX_UDP, PCAP_OPENFLAG_MAX_RESPONSIVENESS, PCAP_OPENFLAG_NOCAPTURE_LOCAL,
PCAP_OPENFLAG_NOCAPTURE_RPCAP, PCAP_OPENFLAG_PROMISCUOUS, PCAP_SAMP_1_EVERY_N,
PCAP_SAMP_FIRST_AFTER_N_MS, PCAP_SAMP_NOSAMP, PCAP_SRC_FILE, PCAP_SRC_FILE_STRING,
PCAP_SRC_IFLOCAL, PCAP_SRC_IFREMOTE, PCAP_SRC_IF_STRING, PCAP_TSTAMP_ADAPTER,
PCAP_TSTAMP_ADAPTER_UNSYNCED, PCAP_TSTAMP_HOST, PCAP_TSTAMP_HOST_HIPREC,
PCAP_TSTAMP_HOST_HIPREC_UNSYNCED, PCAP_TSTAMP_HOST_LOWPREC, PCAP_TSTAMP_PRECISION_MICRO,
PCAP_TSTAMP_PRECISION_NANO, PCAP_VERSION_MAJOR, PCAP_VERSION_MINOR, PCAP_WARNING,
PCAP_WARNING_PROMISC_NOTSUP, PCAP_WARNING_TSTAMP_TYPE_NOTSUP, RPCAP_HOSTLIST_SIZE,
RPCAP_RMTAUTH_NULL, RPCAP_RMTAUTH_PWD,
};
}
use ffi::PcapDevIf;
pub struct PcapDevIterator {
dev_if: *const PcapDevIf,
current_dev: *const PcapDevIf,
}
impl PcapDevIterator {
pub fn new() -> error::Result<PcapDevIterator> {
let mut errbuf = [0i8; ffi::PCAP_ERRBUF_SIZE];
let mut dev_if = ptr::null();
if unsafe { ffi::pcap_findalldevs(&mut dev_if, errbuf.as_mut_ptr()) } != 0 {
Err(&errbuf)?;
}
Ok(PcapDevIterator {
dev_if,
current_dev: dev_if,
})
}
}
impl Drop for PcapDevIterator {
fn drop(&mut self) {
unsafe {
ffi::pcap_freealldevs(self.dev_if);
}
}
}
impl std::iter::Iterator for PcapDevIterator {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
if self.current_dev.is_null() {
return None;
}
let current_dev = self.current_dev;
let dev_name = CStr::from_ptr((*current_dev).name)
.to_str()
.unwrap()
.to_string();
self.current_dev = (*current_dev).next;
Some(dev_name)
}
}
}
pub trait State {}
pub trait Activated: State {}
pub trait NotListening: Activated {}
pub trait Listening: Activated {}
pub trait Disabled: State {}
pub enum DevActivated {}
impl State for DevActivated {}
impl Activated for DevActivated {}
impl NotListening for DevActivated {}
pub enum DevDisabled {}
impl State for DevDisabled {}
impl Disabled for DevDisabled {}
pub enum DevListening {}
impl State for DevListening {}
impl Activated for DevListening {}
impl Listening for DevListening {}
pub struct BpfProgram {}
pub struct Interface<T: State> {
dev_name: CString,
dev: *mut ffi::PcapDev,
marker: std::marker::PhantomData<T>,
absorbed: bool,
nonblocking: bool,
}
impl<T: State> Drop for Interface<T> {
fn drop(&mut self) {
if !self.absorbed {
unsafe { ffi::pcap_close(self.dev) };
}
}
}
unsafe impl<T: State> Send for Interface<T> {}
unsafe impl<T: State> Sync for Interface<T> {}
impl<T: State> Interface<T> {
pub fn new(name: &str) -> error::Result<Interface<DevDisabled>> {
let mut errbuf = [0i8; ffi::PCAP_ERRBUF_SIZE];
let dev_name = CString::new(name)?;
let dev = unsafe { ffi::pcap_create(dev_name.as_ptr(), errbuf.as_mut_ptr()) };
if dev.is_null() {
Err(&errbuf)?;
}
Ok(Interface::<DevDisabled> {
dev_name,
dev,
marker: std::marker::PhantomData,
absorbed: false,
nonblocking: false,
})
}
pub fn lookupnet(&self) -> error::Result<(u32, u32)> {
let mut errbuf = [0i8; ffi::PCAP_ERRBUF_SIZE];
let mut net: u32 = 0;
let mut mask: u32 = 0;
if unsafe {
ffi::pcap_lookupnet(
self.dev_name.as_ptr(),
&mut net as *mut u32,
&mut mask as *mut u32,
errbuf.as_mut_ptr(),
)
} == -1
{
Err(error::Error::from(&errbuf))?;
}
Ok((net, mask))
}
pub fn set_non_blocking(&mut self, nonblocking: bool) -> error::Result<()> {
self.nonblocking = nonblocking;
let mut errbuf = [0i8; ffi::PCAP_ERRBUF_SIZE];
if unsafe { ffi::pcap_setnonblock(self.dev, i32::from(nonblocking), errbuf.as_mut_ptr()) }
== -1
{
Err(error::Error::from(&errbuf))?;
}
Ok(())
}
pub fn name(&self) -> &str {
std::str::from_utf8(self.dev_name.as_bytes()).unwrap()
}
}
impl<T: Disabled> Interface<T> {
pub fn set_promisc(&mut self, promisc: bool) -> error::Result<()> {
if unsafe { ffi::pcap_set_promisc(self.dev, i32::from(promisc)) } != 0 {
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
Ok(())
}
pub fn set_buffer_size(&mut self, bufsize: i32) -> error::Result<()> {
if unsafe { ffi::pcap_set_buffer_size(self.dev, bufsize) } != 0 {
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
Ok(())
}
pub fn set_timeout(&mut self, timeout: i32) -> error::Result<()> {
if unsafe { ffi::pcap_set_timeout(self.dev, timeout) } != 0 {
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
Ok(())
}
pub fn activate(mut self) -> error::Result<Interface<DevActivated>> {
if unsafe { ffi::pcap_activate(self.dev) } != 0 {
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
self.absorbed = true;
Ok(Interface::<DevActivated> {
dev_name: self.dev_name.clone(),
dev: self.dev,
marker: std::marker::PhantomData,
absorbed: false,
nonblocking: self.nonblocking,
})
}
}
impl<T: Activated> Interface<T> {
pub fn datalink(&self) -> i32 {
unsafe { ffi::pcap_datalink(self.dev) }
}
pub fn set_filter(
&self,
filter: &str,
optimize: bool,
mask: Option<u32>,
) -> error::Result<Box<ffi::BpfProgram>> {
let mut bpf = ffi::BpfProgram {
bf_len: 0,
bpf_insn: ptr::null(),
};
let mask = if let Some(m) = mask {
m
} else {
let (_, m) = self.lookupnet()?;
m
};
let filter = CString::new(filter)?;
if unsafe {
ffi::pcap_compile(
self.dev,
&mut bpf as *mut ffi::BpfProgram,
filter.as_ptr(),
i32::from(optimize),
mask,
)
} == -1
{
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
if unsafe { ffi::pcap_setfilter(self.dev, &bpf as *const ffi::BpfProgram) } == -1 {
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
Ok(Box::new(bpf))
}
pub fn sendpacket(&self, packet: packets::EthernetPkt) -> error::Result<()> {
if unsafe {
ffi::pcap_sendpacket(
self.dev,
packet.data.as_ptr(),
packet.data.len().try_into().unwrap(),
)
} == ffi::PCAP_ERROR
{
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
Ok(())
}
pub fn next_packet(&mut self) -> error::Result<packets::EthernetPacket> {
let mut header: *mut ffi::PktHeader = ptr::null_mut();
let mut data: *mut libc::c_char = ptr::null_mut();
if unsafe { ffi::pcap_next_ex(self.dev, &mut header as *mut _, &mut data as *mut _) < 1 } {
return unsafe { Err(ffi::pcap_geterr(self.dev))? };
}
let rdata = unsafe { slice::from_raw_parts(data as *mut u8, (*header).caplen as usize) };
if rdata.len() < 24 {
return Err(error::Error::PacketLengthInvalid);
}
Ok(packets::EthernetPkt { data: rdata }.to_owned())
}
}
struct ListenHandler<'a, F>
where
F: FnMut(&Interface<DevListening>, packets::EthernetPkt) -> error::Result<bool>,
{
packet_handler: F,
break_on_fail: bool,
fail_error: Option<error::Error>,
interface: &'a Interface<DevListening>,
}
impl<T: NotListening> Interface<T> {
pub fn listen<F>(
&self,
packet_handler: F,
break_on_fail: bool,
packet_count: i32,
) -> (Option<error::Error>, i32)
where
F: FnMut(&Interface<DevListening>, packets::EthernetPkt) -> error::Result<bool>,
{
unsafe extern "C" fn cback<F>(
user: *mut libc::c_void,
header: *const ffi::PktHeader,
data: *const u8,
) where
F: FnMut(&Interface<DevListening>, packets::EthernetPkt) -> error::Result<bool>,
{
let info = &mut *(user as *mut ListenHandler<F>);
let data = slice::from_raw_parts(data, (*header).caplen as usize);
if data.len() < 14 {
eprintln!(
" * Failed to get full packet, captured {} bytes",
data.len()
);
}
let result = (info.packet_handler)(info.interface, packets::EthernetPkt { data });
match result {
Err(e) => {
eprintln!(" * Packet handle error: {:?}", e);
if info.break_on_fail {
info.fail_error = Some(e);
ffi::pcap_breakloop(info.interface.dev);
}
}
Ok(b) => {
if b {
ffi::pcap_breakloop(info.interface.dev);
}
}
}
}
let interface = Interface {
dev_name: self.dev_name.clone(),
dev: self.dev,
marker: std::marker::PhantomData,
absorbed: true,
nonblocking: self.nonblocking,
};
let mut info = ListenHandler::<F> {
packet_handler,
break_on_fail,
fail_error: None,
interface: &interface,
};
let count = unsafe {
ffi::pcap_loop(
self.dev,
packet_count,
cback::<F>,
&mut info as *mut ListenHandler<F> as *mut libc::c_void,
)
};
(info.fail_error, count)
}
#[cfg(target_os = "linux")]
pub fn stream(mut self) -> error::Result<stream::InterfaceStream<DevActivated>> {
self.set_non_blocking(true)?;
Ok(stream::InterfaceStream {
inner: tokio::io::unix::AsyncFd::with_interest(
stream::InternalInterfaceStream::<DevActivated>::new(unsafe { std::mem::transmute(self) })?,
tokio::io::Interest::READABLE,
)?,
})
}
}

340
pcap-sys/src/stream.rs Normal file
View File

@ -0,0 +1,340 @@
// 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::{
collections::HashMap,
os::fd::{AsRawFd, RawFd},
pin::Pin,
task::{self, Poll},
};
use futures::{ready, StreamExt};
use tokio::io::unix::AsyncFd;
use tokio_stream::StreamMap;
use super::{error, ffi, packets, Activated, NotListening, Disabled, State, Interface, DevActivated, DevDisabled, PcapDevIterator};
pub(crate) struct InternalInterfaceStream<T: Activated> {
interface: Interface<T>,
fd: RawFd,
}
impl<T: Activated> InternalInterfaceStream<T> {
pub(crate) fn new(interface: Interface<T>) -> error::Result<InternalInterfaceStream<T>> {
let fd = unsafe { ffi::pcap_get_selectable_fd(interface.dev) };
if fd == -1 {
return Err(error::Error::InvalidPcapFd);
}
Ok(Self { interface, fd })
}
}
impl<T: Activated> AsRawFd for InternalInterfaceStream<T> {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
pub struct InterfaceStream<T: Activated> {
pub(crate) inner: AsyncFd<InternalInterfaceStream<T>>,
}
impl<T: Activated> InterfaceStream<T> {
pub fn sendpacket(&mut self, packet: packets::EthernetPkt) -> error::Result<()> {
self.inner.get_mut().interface.sendpacket(packet)
}
pub fn set_filter(
&mut self,
filter: &str,
optimize: bool,
mask: Option<u32>,
) -> error::Result<Box<ffi::BpfProgram>> {
self.inner
.get_mut()
.interface
.set_filter(filter, optimize, mask)
}
}
impl<T: Activated> Unpin for InterfaceStream<T> {}
impl<T: Activated> futures::Stream for InterfaceStream<T> {
type Item = error::Result<packets::EthernetPacket>;
fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Option<Self::Item>> {
let stream = Pin::into_inner(self);
loop {
let mut guard = ready!(stream.inner.poll_read_ready_mut(cx))?;
match guard.try_io(|inner| match inner.get_mut().interface.next_packet() {
Ok(p) => Ok(Ok(p)),
Err(e) => Ok(Err(e)),
}) {
Ok(result) => {
return Poll::Ready(Some(result?));
}
Err(_would_block) => continue,
}
}
}
}
pub fn new_aggregate_interface_filtered<F>(
crash: bool,
mut f: F,
) -> error::Result<AggregateInterface<DevDisabled>>
where
F: FnMut(&str) -> bool,
{
let interfaces = if crash {
PcapDevIterator::new()?
.filter(|s| (f)(s))
.map(|if_name| {
let new_name = if_name.clone();
Interface::<DevDisabled>::new(&if_name)
.map(|interface| (if_name, interface))
.map_err(|e| e.add_ifname(&new_name))
})
.collect::<error::Result<HashMap<_, _>>>()?
} else {
PcapDevIterator::new()?
.filter(|s| (f)(s))
.filter_map(|if_name| {
let new_name = if_name.clone();
Interface::<DevDisabled>::new(&if_name)
.map(|interface| (if_name, interface))
.ok()
.or_else(|| {
println!("{} failed to create device", new_name);
None
})
})
.collect::<HashMap<_, _>>()
};
Ok(AggregateInterface { interfaces, crash })
}
pub fn new_aggregate_interface(crash: bool) -> error::Result<AggregateInterface<DevDisabled>> {
new_aggregate_interface_filtered(crash, |_| true)
}
pub struct AggregateInterface<T: State> {
interfaces: HashMap<String, Interface<T>>,
crash: bool,
}
impl<T: State> AggregateInterface<T> {
pub fn set_non_blocking(&mut self, nonblocking: bool) -> error::Result<()> {
for (n, i) in self.interfaces.iter_mut() {
i.set_non_blocking(nonblocking)
.map_err(|e| e.add_ifname(n))?;
}
Ok(())
}
pub fn lookupnets(&self) -> error::Result<HashMap<&str, (u32, u32)>> {
self.interfaces
.iter()
.map(|(name, interface)| {
interface
.lookupnet()
.map(|net| (&**name, net))
.map_err(|e| e.add_ifname(&name))
})
.collect::<error::Result<_>>()
}
pub fn get_ifnames(&self) -> Vec<&str> {
self.interfaces.keys().map(|n| &**n).collect::<_>()
}
}
impl<T: Disabled> AggregateInterface<T> {
pub fn set_promisc(&mut self, promisc: bool) -> error::Result<()> {
for (n, i) in self.interfaces.iter_mut() {
i.set_promisc(promisc).map_err(|e| e.add_ifname(n))?;
}
Ok(())
}
pub fn set_buffer_size(&mut self, bufsize: i32) -> error::Result<()> {
for (n, i) in self.interfaces.iter_mut() {
i.set_buffer_size(bufsize).map_err(|e| e.add_ifname(n))?;
}
Ok(())
}
pub fn set_timeout(&mut self, timeout: i32) -> error::Result<()> {
for (n, i) in self.interfaces.iter_mut() {
i.set_timeout(timeout).map_err(|e| e.add_ifname(n))?;
}
Ok(())
}
pub fn activate(self) -> error::Result<AggregateInterface<DevActivated>> {
Ok(AggregateInterface {
interfaces: if self.crash {
self.interfaces
.into_iter()
.map(|(name, interface)| {
let new_name = name.clone();
interface
.activate()
.map(|interface| (name, interface))
.map_err(|e| e.add_ifname(&new_name))
})
.collect::<error::Result<_>>()?
} else {
self.interfaces
.into_iter()
.filter_map(|(name, interface)| {
let name_clone = name.clone();
interface
.activate()
.map(|interface| (name, interface))
.ok()
.or_else(|| {
println!("{} failed to activate", name_clone);
None
})
})
.collect::<_>()
},
crash: self.crash,
})
}
}
impl<T: Activated> AggregateInterface<T> {
pub fn datalinks(&self) -> HashMap<&str, i32> {
self.interfaces
.iter()
.map(|(name, interface)| (&**name, interface.datalink()))
.collect::<_>()
}
pub fn prune<F>(&mut self, mut f: F)
where
F: FnMut(&str, &mut Interface<T>) -> bool,
{
let to_prune = self
.interfaces
.iter_mut()
.filter_map(|(k, v)| if (f)(k, v) { Some(k.clone()) } else { None })
.collect::<Vec<_>>();
for name in to_prune {
self.interfaces.remove(&name);
}
}
pub fn set_filter(
&mut self,
filter: &str,
optimize: bool,
mask: Option<u32>,
) -> error::Result<HashMap<&str, Box<ffi::BpfProgram>>> {
if self.crash {
self.interfaces
.iter_mut()
.map(|(name, interface)| {
interface
.set_filter(filter, optimize, mask)
.map(|bpf| (&**name, bpf))
.map_err(|e| e.add_ifname(&name))
})
.collect::<error::Result<_>>()
} else {
Ok(self
.interfaces
.iter_mut()
.filter_map(|(name, interface)| {
let name_clone = name.clone();
interface
.set_filter(filter, optimize, mask)
.map(|bpf| (&**name, bpf))
.ok()
.or_else(|| {
println!("{} failed to set filter", name_clone);
None
})
})
.collect::<_>())
}
}
pub fn sendpacket(&self, ifname: &str, packet: packets::EthernetPkt) -> error::Result<()> {
if let Some(interface) = self.interfaces.get(ifname) {
interface
.sendpacket(packet)
.map_err(|e| e.add_ifname(ifname))?;
}
Ok(())
}
}
impl<T: NotListening> AggregateInterface<T> {
pub fn stream(self) -> error::Result<AggregateInterfaceStream<DevActivated>> {
Ok(AggregateInterfaceStream {
streams: self
.interfaces
.into_iter()
.map(|(ifname, interface)| {
let new_name = ifname.clone();
interface
.stream()
.map(|stream| (ifname, stream))
.map_err(|e| e.add_ifname(&new_name))
})
.collect::<error::Result<_>>()?,
})
}
}
pub struct AggregateInterfaceStream<T: Activated> {
streams: StreamMap<String, InterfaceStream<T>>,
}
impl<T: Activated> AggregateInterfaceStream<T> {
pub fn get_ifnames(&self) -> Vec<&str> {
self.streams.keys().map(|n| &**n).collect::<_>()
}
pub fn sendpacket(&mut self, ifname: &str, packet: packets::EthernetPkt) -> error::Result<()> {
if let Some(interface) = self.streams.values_mut().find(|interface| {
interface.inner.get_ref().interface.dev_name.as_bytes() == ifname.as_bytes()
}) {
interface.sendpacket(packet)?;
}
Ok(())
}
}
impl<T: Activated> futures::Stream for AggregateInterfaceStream<T> {
type Item = (String, error::Result<packets::EthernetPacket>);
fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
self.streams.poll_next_unpin(cx)
}
}

View File

@ -2,3 +2,6 @@
name = "sparse-beacon"
version = "0.7.0"
edition = "2021"
[dependencies]
nl-sys = { path = "../nl-sys" }

View File

@ -1,3 +1,16 @@
use nl_sys::{netlink, route};
fn main() {
println!("hi there");
let ip = std::net::Ipv4Addr::new(192, 168, 3, 10);
let (ifname, _, srcip, src_mac, dst_mac, _) = {
let socket = netlink::Socket::new().unwrap();
let routes = socket.get_routes().unwrap();
let neighs = socket.get_neigh().unwrap();
let links = socket.get_links().unwrap();
let addrs = socket.get_addrs().unwrap();
route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, ip).unwrap()
};
}

View File

@ -1,4 +1,4 @@
{ pkgs, libpcap-src, winpcap-installer, freebsd-libs-packed }:
{ pkgs, libnl-src, libpcap-src, winpcap-installer, freebsd-libs-packed }:
let
freebsd-libs = pkgs.stdenv.mkDerivation {
name = "freebsd-libs";
@ -44,18 +44,51 @@ in {
libnl = pkgs.stdenv.mkDerivation {
name = "libnl-static";
buildInputs = with pkgs; [ autoconf automake libtool ];
nativeBuildInputs = with pkgs; [
musl
clang
autoconf
automake
libtool
pkg-config
autoconf-archive
flex
bison
];
src = libnl-src;
configurePhase = ''
export CC=musl-clang
export CXX=musl-clang
aclocal
autoreconf -vi
./configure \
--enable-static \
--disable-shared
'';
buildPhase = ''
export CC=musl-clang
export CXX=musl-clang
make
'';
installPhase = ''
mkdir -p $out/lib
cp lib/.libs/*.a $out/lib
'';
};
libpcap-linux = pkgs.stdenv.mkDerivation {
name = "libpcap-static-linux";
buildInputs = with pkgs; [ automake bison cmake flex ];
buildInputs = with pkgs; [ clang automake bison cmake flex musl ];
src = libpcap-src;
configurePhase = ''
export CC=musl-clang
cmake \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DBUILD_SHARED_LIBS=OFF \
@ -97,6 +130,8 @@ in {
src = libpcap-src;
configurePhase = ''
export CC=x86_64-unknown-freebsd-clang
export CXX=x86_64-unknown-freebsd-clang++
cmake \
-DCMAKE_TOOLCHAIN_FILE=${bsd-toolchain} \
-DCMAKE_BUILD_TYPE=MinSizeRel \