feat: tested nl-sys, and it links
This commit is contained in:
parent
6b33f1a5ba
commit
c2b5ea37d0
444
Cargo.lock
generated
444
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"sparse-beacon"
|
||||
"sparse-beacon",
|
||||
"nl-sys",
|
||||
"packets",
|
||||
"pcap-sys"
|
||||
]
|
||||
resolver = "2"
|
||||
package.version = "2.0.0"
|
||||
|
||||
@ -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
12
nl-sys/Cargo.toml
Normal 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
3
nl-sys/README.md
Normal 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
30
nl-sys/build.rs
Normal 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
20
nl-sys/src/bridge.c
Normal 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
48
nl-sys/src/error.rs
Normal 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
24
nl-sys/src/lib.rs
Normal 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
202
nl-sys/src/netlink.rs
Normal 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
111
nl-sys/src/nl_ffi.rs
Normal 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
590
nl-sys/src/route.rs
Normal 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
8
packets/Cargo.toml
Normal 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
40
packets/src/error.rs
Normal 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
806
packets/src/lib.rs
Normal 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
31
pcap-sys/Cargo.toml
Normal 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
3
pcap-sys/README.md
Normal 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
78
pcap-sys/build.rs
Normal 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
136
pcap-sys/src/error.rs
Normal 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
183
pcap-sys/src/ffi.rs
Normal 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
418
pcap-sys/src/lib.rs
Normal 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
340
pcap-sys/src/stream.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -2,3 +2,6 @@
|
||||
name = "sparse-beacon"
|
||||
version = "0.7.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
nl-sys = { path = "../nl-sys" }
|
||||
|
||||
@ -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()
|
||||
};
|
||||
}
|
||||
|
||||
@ -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,31 +44,64 @@ 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 \
|
||||
-DDISABLE_BLUETOOTH=ON \
|
||||
-DDISABLE_DAG=ON \
|
||||
-DDISABLE_DBUS=ON \
|
||||
-DDISABLE_DPDK=ON \
|
||||
-DDISABLE_NETMAP=ON \
|
||||
-DDISABLE_RDMA=ON \
|
||||
-DDISABLE_LINUX_USBMON=ON \
|
||||
-DDISABLE_SNF=ON \
|
||||
-DPCAP_TYPE=linux \
|
||||
.
|
||||
-DCMAKE_BUILD_TYPE=MinSizeRel \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DDISABLE_BLUETOOTH=ON \
|
||||
-DDISABLE_DAG=ON \
|
||||
-DDISABLE_DBUS=ON \
|
||||
-DDISABLE_DPDK=ON \
|
||||
-DDISABLE_NETMAP=ON \
|
||||
-DDISABLE_RDMA=ON \
|
||||
-DDISABLE_LINUX_USBMON=ON \
|
||||
-DDISABLE_SNF=ON \
|
||||
-DPCAP_TYPE=linux \
|
||||
.
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
@ -97,19 +130,21 @@ 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 \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DDISABLE_BLUETOOTH=ON \
|
||||
-DDISABLE_DAG=ON \
|
||||
-DDISABLE_DBUS=ON \
|
||||
-DDISABLE_DPDK=ON \
|
||||
-DDISABLE_NETMAP=ON \
|
||||
-DDISABLE_RDMA=ON \
|
||||
-DDISABLE_SNF=ON \
|
||||
-DPCAP_TYPE=bpf \
|
||||
.
|
||||
-DCMAKE_TOOLCHAIN_FILE=${bsd-toolchain} \
|
||||
-DCMAKE_BUILD_TYPE=MinSizeRel \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DDISABLE_BLUETOOTH=ON \
|
||||
-DDISABLE_DAG=ON \
|
||||
-DDISABLE_DBUS=ON \
|
||||
-DDISABLE_DPDK=ON \
|
||||
-DDISABLE_NETMAP=ON \
|
||||
-DDISABLE_RDMA=ON \
|
||||
-DDISABLE_SNF=ON \
|
||||
-DPCAP_TYPE=bpf \
|
||||
.
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user