mirror of
https://github.com/domcyrus/rustnet.git
synced 2026-01-04 13:00:04 -06:00
feat: Add experimental eBPF support for enhanced socket tracking (#11)
* feat: Add experimental eBPF support for enhanced socket tracking - Implement eBPF-based socket tracker for Linux with CO-RE support - Add minimal vmlinux header (5.5KB) instead of full 3.4MB file - Create graceful fallback mechanism to procfs when eBPF unavailable - Add comprehensive eBPF build documentation - Integrate libbpf-rs for eBPF program loading and management - Support both IPv4 and IPv6 socket tracking - Add capability checking for required permissions The eBPF feature is optional and disabled by default. When enabled, it provides faster and more accurate process-to-socket mapping on Linux systems with appropriate permissions.
This commit is contained in:
4
.github/workflows/rust.yml
vendored
4
.github/workflows/rust.yml
vendored
@@ -29,8 +29,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install libpcap
|
||||
run: sudo apt-get update && sudo apt-get install -y libpcap-dev
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y libpcap-dev libelf-dev clang llvm
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
|
||||
319
Cargo.lock
generated
319
Cargo.lock
generated
@@ -121,7 +121,7 @@ dependencies = [
|
||||
"objc2-foundation",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
@@ -161,6 +161,44 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-platform"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo_metadata"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
@@ -191,6 +229,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
@@ -585,6 +629,12 @@ version = "3.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
@@ -644,7 +694,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.6+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -805,6 +867,48 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libbpf-cargo"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "626c6fbcb5088716de86d0ccbdccedc17b13e59f41a605a3274029335e71fcbb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_metadata",
|
||||
"clap",
|
||||
"libbpf-rs",
|
||||
"libbpf-sys",
|
||||
"memmap2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libbpf-rs"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e23d252d93e246c8787198369f06806c99c5077b5295be29505295f4e5426dc4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"libbpf-sys",
|
||||
"libc",
|
||||
"vsprintf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libbpf-sys"
|
||||
version = "1.5.1+v1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "912fae30b08bcbdb861d4b85bd09c05352c0ac9d7b93765ced5ca23709e7e590"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"nix",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
@@ -870,6 +974,15 @@ version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.8"
|
||||
@@ -888,16 +1001,37 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-net"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
@@ -1061,6 +1195,12 @@ version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
@@ -1162,6 +1302,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.29.0"
|
||||
@@ -1236,7 +1382,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"getrandom 0.2.16",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -1275,12 +1421,17 @@ dependencies = [
|
||||
"aes",
|
||||
"anyhow",
|
||||
"arboard",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"clap",
|
||||
"crossbeam",
|
||||
"crossterm 0.29.0",
|
||||
"dashmap",
|
||||
"dns-lookup",
|
||||
"libbpf-cargo",
|
||||
"libbpf-rs",
|
||||
"libbpf-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"num_cpus",
|
||||
"pcap",
|
||||
@@ -1311,25 +1462,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.223"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.223"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
version = "1.0.223"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
@@ -1455,6 +1648,19 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.0.7",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
@@ -1464,6 +1670,26 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread-id"
|
||||
version = "3.3.0"
|
||||
@@ -1475,6 +1701,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.9.1"
|
||||
@@ -1519,6 +1754,38 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nu-ansi-term",
|
||||
"sharded-slab",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
@@ -1578,12 +1845,40 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "vsprintf"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aec2f81b75ca063294776b4f7e8da71d1d5ae81c2b1b149c8d89969230265d63"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.6+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f71243a3f320c00a8459e455c046ce571229c2f31fd11645d9dc095e3068ca0"
|
||||
dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
@@ -1994,6 +2289,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.13.1"
|
||||
|
||||
24
Cargo.toml
24
Cargo.toml
@@ -11,14 +11,11 @@ readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
keywords = ["network", "monitoring", "tui", "terminal", "packet-capture"]
|
||||
categories = ["command-line-utilities", "network-programming", "visualization"]
|
||||
exclude = [
|
||||
".github/",
|
||||
"scripts/",
|
||||
"tests/",
|
||||
"*.log",
|
||||
"target/",
|
||||
".gitignore",
|
||||
]
|
||||
exclude = [".github/", "scripts/", "tests/", "*.log", "target/", ".gitignore"]
|
||||
|
||||
[lib]
|
||||
name = "rustnet_monitor"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "rustnet"
|
||||
@@ -45,3 +42,14 @@ aes = "0.8"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
procfs = "0.16"
|
||||
libbpf-rs = { version = "0.25", optional = true }
|
||||
libbpf-sys = { version = "1.4", optional = true }
|
||||
bytes = { version = "1.5", optional = true }
|
||||
libc = { version = "0.2", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.build-dependencies]
|
||||
libbpf-cargo = "0.25"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
ebpf = ["libbpf-rs", "libbpf-sys", "bytes", "libc"]
|
||||
|
||||
231
EBPF_BUILD.md
Normal file
231
EBPF_BUILD.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# eBPF Build Guide
|
||||
|
||||
This document explains how to work with eBPF kernel headers in this project.
|
||||
|
||||
## Current Setup
|
||||
|
||||
We use a **minimal vmlinux header** (`vmlinux_min.h`) instead of the full kernel headers. This approach has trade-offs that should be considered:
|
||||
|
||||
**Benefits of minimal vmlinux_min.h:**
|
||||
|
||||
- **Small size**: 5.5KB (203 lines) vs 3.4MB (100K+ lines) full vmlinux.h
|
||||
- **Git-friendly**: Small file size, manageable diffs, easier to review
|
||||
- **Portable**: Works across kernel versions with CO-RE/BTF
|
||||
- **Clear dependencies**: Shows exactly which kernel structures we depend on
|
||||
|
||||
**Drawbacks of minimal vmlinux_min.h:**
|
||||
|
||||
- **Manual maintenance**: Need to update when adding new eBPF features that access different kernel structures
|
||||
- **Potential for missing definitions**: Easy to forget required types when extending functionality
|
||||
- **Development overhead**: Requires understanding of kernel internals to extract correct definitions
|
||||
|
||||
**Alternative approach (full vmlinux.h):**
|
||||
|
||||
- **Pros**: Complete kernel definitions, auto-generated, no manual maintenance, never missing types
|
||||
- **Cons**: Very large file (3.4MB), but can be gitignored and generated during build process
|
||||
|
||||
## How to Generate Full vmlinux.h (if needed)
|
||||
|
||||
If you need to generate a complete vmlinux.h file for your kernel:
|
||||
|
||||
```bash
|
||||
# Method 1: Using bpftool (requires root/CAP_BPF)
|
||||
sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
|
||||
|
||||
# Method 2: Using pahole (if available)
|
||||
pahole -J /boot/vmlinux-$(uname -r)
|
||||
pahole --btf_encode_detached vmlinux.btf /boot/vmlinux-$(uname -r)
|
||||
bpftool btf dump file vmlinux.btf format c > vmlinux.h
|
||||
|
||||
# Method 3: From kernel source
|
||||
cd /path/to/kernel/source
|
||||
make scripts_gdb
|
||||
bpftool btf dump file vmlinux format c > vmlinux.h
|
||||
```
|
||||
|
||||
## How to Create Minimal Headers from Full vmlinux.h
|
||||
|
||||
### 1. Identify Required Structures
|
||||
|
||||
First, analyze your eBPF program to find which kernel structures you access:
|
||||
|
||||
```bash
|
||||
# Find all struct references in your eBPF code
|
||||
grep -E "struct [a-zA-Z_]+" socket_tracker.bpf.c
|
||||
|
||||
# Find BPF_CORE_READ usage to see field accesses
|
||||
grep -E "BPF_CORE_READ.*\\..*" socket_tracker.bpf.c
|
||||
|
||||
# Common structures for socket tracking:
|
||||
# - struct sock (contains __sk_common)
|
||||
# - struct sock_common (network fields)
|
||||
# - struct msghdr (for sendmsg calls)
|
||||
# - struct sockaddr_in (IPv4 addresses)
|
||||
# - struct pt_regs (kprobe context)
|
||||
```
|
||||
|
||||
### 2. Extract Definitions from Full vmlinux.h
|
||||
|
||||
Use these commands to extract specific structures:
|
||||
|
||||
```bash
|
||||
# Extract a specific struct (e.g., sock_common)
|
||||
awk '/^struct sock_common {/,/^}/' vmlinux.h
|
||||
|
||||
# Extract type definitions
|
||||
grep "typedef.*__u[0-9]*\|typedef.*__be[0-9]*" vmlinux.h
|
||||
|
||||
# Extract multiple related structures
|
||||
grep -A 50 "struct sock_common {" vmlinux.h
|
||||
grep -A 20 "struct sock {" vmlinux.h
|
||||
grep -A 10 "struct msghdr {" vmlinux.h
|
||||
```
|
||||
|
||||
### 3. Create Minimal Header
|
||||
|
||||
Create a new header file with:
|
||||
|
||||
1. **Header guards and CO-RE pragma**:
|
||||
```c
|
||||
#ifndef __VMLINUX_MIN_H__
|
||||
#define __VMLINUX_MIN_H__
|
||||
|
||||
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
|
||||
#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
|
||||
#endif
|
||||
```
|
||||
|
||||
2. **Basic types** (only what you need):
|
||||
```c
|
||||
typedef unsigned char __u8;
|
||||
typedef unsigned int __u32;
|
||||
typedef __u32 __be32;
|
||||
// etc.
|
||||
```
|
||||
|
||||
3. **Required structures** with **only the fields you access**:
|
||||
```c
|
||||
struct sock_common {
|
||||
// Only include fields accessed by BPF_CORE_READ
|
||||
__be32 skc_daddr;
|
||||
__be32 skc_rcv_saddr;
|
||||
__be16 skc_dport;
|
||||
__u16 skc_num;
|
||||
// ... other fields you actually use
|
||||
};
|
||||
```
|
||||
|
||||
4. **Footer**:
|
||||
```c
|
||||
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
|
||||
#pragma clang attribute pop
|
||||
#endif
|
||||
#endif
|
||||
```
|
||||
|
||||
### 4. Automated Extraction Script
|
||||
|
||||
For complex projects, you can create a script to automate extraction:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# extract_minimal_vmlinux.sh
|
||||
|
||||
FULL_VMLINUX="vmlinux.h"
|
||||
OUTPUT="vmlinux_min.h"
|
||||
BPF_SOURCE="socket_tracker.bpf.c"
|
||||
|
||||
# Find structs used in BPF program
|
||||
STRUCTS=$(grep -oE "struct [a-zA-Z_]+" "$BPF_SOURCE" | sort -u | cut -d' ' -f2)
|
||||
|
||||
echo "Extracting structures: $STRUCTS"
|
||||
|
||||
# Start minimal header
|
||||
cat > "$OUTPUT" << 'EOF'
|
||||
#ifndef __VMLINUX_MIN_H__
|
||||
#define __VMLINUX_MIN_H__
|
||||
|
||||
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
|
||||
#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
|
||||
#endif
|
||||
|
||||
/* Basic types */
|
||||
EOF
|
||||
|
||||
# Extract basic types
|
||||
grep "typedef.*__u[0-9]*\|typedef.*__be[0-9]*\|typedef.*__kernel" "$FULL_VMLINUX" | head -20 >> "$OUTPUT"
|
||||
|
||||
echo "" >> "$OUTPUT"
|
||||
echo "/* Network structures */" >> "$OUTPUT"
|
||||
|
||||
# Extract each required struct
|
||||
for struct in $STRUCTS; do
|
||||
echo "Extracting struct $struct..."
|
||||
awk "/^struct $struct \{/,/^}/" "$FULL_VMLINUX" >> "$OUTPUT"
|
||||
echo "" >> "$OUTPUT"
|
||||
done
|
||||
|
||||
# Close header
|
||||
cat >> "$OUTPUT" << 'EOF'
|
||||
|
||||
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
|
||||
#pragma clang attribute pop
|
||||
#endif
|
||||
|
||||
#endif /* __VMLINUX_MIN_H__ */
|
||||
EOF
|
||||
|
||||
echo "Minimal vmlinux header created: $OUTPUT"
|
||||
```
|
||||
|
||||
## Testing Your Minimal Header
|
||||
|
||||
After creating your minimal header:
|
||||
|
||||
```bash
|
||||
# Test compilation
|
||||
cargo build --features ebpf
|
||||
|
||||
# If compilation fails, check for missing definitions
|
||||
# and add them to your minimal header
|
||||
|
||||
# Verify eBPF program loads (requires root)
|
||||
sudo cargo run --features ebpf
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Start minimal**: Only include structures and fields you actually access
|
||||
2. **Use CO-RE**: Always include the preserve_access_index pragma for portability
|
||||
3. **Document sources**: Note which kernel version/source your definitions came from
|
||||
4. **Test across kernels**: Verify your program works on different kernel versions
|
||||
5. **Keep synchronized**: Update minimal headers when your eBPF program changes
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Compilation Errors
|
||||
|
||||
- **Missing struct definition**: Add the struct to your minimal header
|
||||
- **Missing field**: Include the specific field in your struct definition
|
||||
- **Type errors**: Ensure all referenced types are defined
|
||||
|
||||
### Runtime Errors
|
||||
|
||||
- **BTF verification failed**: Check that field names match kernel structures
|
||||
- **Access violations**: Ensure you're accessing fields that exist in target kernel
|
||||
|
||||
### Field Access Issues
|
||||
|
||||
- **Wrong offset**: Make sure struct layout matches target kernel
|
||||
- **Missing CO-RE relocations**: Verify preserve_access_index pragma is present
|
||||
|
||||
## Why Not Use Full vmlinux.h?
|
||||
|
||||
While using the full vmlinux.h works, it has downsides:
|
||||
|
||||
- **Huge file size** (3+ MB): Slows down compilation and git operations
|
||||
- **Unclear dependencies**: Hard to see what your program actually needs
|
||||
- **Kernel-specific**: Generated for one specific kernel version
|
||||
- **Review complexity**: Impossible to review 100K+ lines in PRs
|
||||
|
||||
The minimal approach gives you the benefits of vmlinux.h (CO-RE support, exact field layouts) without the downsides.
|
||||
58
README.md
58
README.md
@@ -31,6 +31,7 @@ A cross-platform network monitoring tool built with Rust. RustNet provides real-
|
||||
- Protocol-specific cleanup (DNS: 30s, established TCP: 5min, QUIC with close frames: 1-10s)
|
||||
- Activity-based timeout adjustment for long-lived vs idle connections
|
||||
- **Process Identification**: Associate network connections with running processes
|
||||
- **Note**: With experimental eBPF support, process names are limited to 16 characters from the kernel's `comm` field and may show thread names instead of full executable names
|
||||
- **Service Name Resolution**: Identify well-known services using port numbers
|
||||
- **Cross-platform Support**: Works on Linux, macOS, Windows and potentially BSD systems
|
||||
- **Advanced Filtering**: Real-time vim/fzf-style filtering with keyword support:
|
||||
@@ -41,6 +42,25 @@ A cross-platform network monitoring tool built with Rust. RustNet provides real-
|
||||
- **Multi-threaded Processing**: Concurrent packet processing across multiple threads
|
||||
- **Optional Logging**: Detailed logging with configurable log levels (disabled by default)
|
||||
|
||||
### eBPF Enhanced Process Identification (Experimental)
|
||||
|
||||
When built with the `ebpf` feature on Linux, RustNet uses kernel eBPF programs for enhanced performance and lower overhead process identification. However, this comes with important limitations:
|
||||
|
||||
**Process Name Limitations:**
|
||||
- eBPF uses the kernel's `comm` field, which is limited to 16 characters
|
||||
- Shows the task/thread command name, not the full executable path
|
||||
- Multi-threaded applications often show thread names instead of the main process name
|
||||
|
||||
**Real-world Examples:**
|
||||
- **Firefox**: May appear as "Socket Thread", "Web Content", "Isolated Web Co", or "MainThread"
|
||||
- **Chrome**: May appear as "ThreadPoolForeg", "Chrome_IOThread", "BrokerProcess", or "SandboxHelper"
|
||||
- **Electron apps**: Often show as "electron", "node", or internal thread names
|
||||
- **System processes**: Show truncated names like "systemd-resolve" → "systemd-resolve"
|
||||
|
||||
**Fallback Behavior:**
|
||||
- When eBPF fails to load or lacks sufficient permissions, RustNet automatically falls back to standard procfs-based process identification
|
||||
- Standard mode provides full process names but with higher CPU overhead
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
@@ -50,6 +70,10 @@ A cross-platform network monitoring tool built with Rust. RustNet provides real-
|
||||
- **Linux**: `sudo apt-get install libpcap-dev` (Debian/Ubuntu) or `sudo yum install libpcap-devel` (RedHat/CentOS)
|
||||
- **macOS**: Included by default
|
||||
- **Windows**: Install Npcap and Npcap SDK (see [Windows Build Setup](#windows-build-setup) below)
|
||||
- **For eBPF support (optional, experimental - Linux only)**:
|
||||
- `sudo apt-get install libelf-dev clang llvm` (Debian/Ubuntu)
|
||||
- `sudo yum install elfutils-libelf-devel clang llvm` (RedHat/CentOS)
|
||||
- Linux kernel 4.19+ with BTF support recommended
|
||||
|
||||
### Windows Build Setup
|
||||
|
||||
@@ -108,9 +132,12 @@ cargo install rustnet-monitor
|
||||
git clone https://github.com/domcyrus/rustnet.git
|
||||
cd rustnet
|
||||
|
||||
# Build in release mode
|
||||
# Build in release mode (basic functionality)
|
||||
cargo build --release
|
||||
|
||||
# Build with experimental eBPF support for enhanced Linux performance (Linux only)
|
||||
cargo build --release --features ebpf
|
||||
|
||||
# The executable will be in target/release/rustnet
|
||||
```
|
||||
|
||||
@@ -547,6 +574,35 @@ sudo setcap cap_net_raw,cap_net_admin=eip ~/.cargo/bin/rustnet
|
||||
rustnet
|
||||
```
|
||||
|
||||
**For experimental eBPF-enabled builds (enhanced Linux performance):**
|
||||
|
||||
eBPF is an experimental feature that requires additional capabilities for kernel program loading and performance monitoring:
|
||||
|
||||
```bash
|
||||
# Build with eBPF support
|
||||
cargo build --release --features ebpf
|
||||
|
||||
# Grant full capability set for eBPF (modern kernels with CAP_BPF support)
|
||||
sudo setcap 'cap_net_raw,cap_net_admin,cap_bpf,cap_perfmon+eip' ./target/release/rustnet
|
||||
|
||||
# OR for older kernels (fallback to CAP_SYS_ADMIN)
|
||||
sudo setcap 'cap_net_raw,cap_net_admin,cap_sys_admin+eip' ./target/release/rustnet
|
||||
|
||||
# Run without sudo - eBPF programs will load automatically if capabilities are sufficient
|
||||
./target/release/rustnet
|
||||
```
|
||||
|
||||
**Capability requirements for eBPF:**
|
||||
- `CAP_NET_RAW` - Raw socket access for packet capture
|
||||
- `CAP_NET_ADMIN` - Network administration
|
||||
- `CAP_BPF` - BPF program loading (Linux 5.8+, preferred)
|
||||
- `CAP_PERFMON` - Performance monitoring (Linux 5.8+, preferred)
|
||||
- `CAP_SYS_ADMIN` - System administration (fallback for older kernels)
|
||||
|
||||
The application will automatically detect available capabilities and fall back to procfs-only mode if eBPF cannot be loaded.
|
||||
|
||||
**Note:** eBPF support is experimental and may have limitations with process name display (see [eBPF Enhanced Process Identification](#ebpf-enhanced-process-identification-experimental)).
|
||||
|
||||
**For system-wide installation:**
|
||||
|
||||
```bash
|
||||
|
||||
28
ROADMAP.md
28
ROADMAP.md
@@ -5,9 +5,31 @@ This document outlines the planned features and improvements for RustNet.
|
||||
## Platform Support
|
||||
|
||||
- **macOS Support**: Basic features need testing and fixes for macOS compatibility
|
||||
- **Windows Support**: ✅ Basic functionality working with Npcap SDK and runtime. Process identification not yet implemented for Windows
|
||||
- **Windows Support**: Basic functionality working with Npcap SDK and runtime. Process identification not yet implemented for Windows
|
||||
- **BSD Support**: Add support for FreeBSD, OpenBSD, and NetBSD
|
||||
- **Linux Process Identification Enhancement**: Investigate using **eBPF** (Extended Berkeley Packet Filter) for direct kernel-level process identification similar to macOS PKTAP. This would provide more accurate and efficient process-to-connection mapping than the current `/proc` filesystem approach, especially for high-throughput scenarios.
|
||||
- **Linux Process Identification**: **Experimental eBPF Support Implemented** - Basic eBPF-based process identification now available with `--features ebpf`. Provides efficient kernel-level process-to-connection mapping with lower overhead than procfs. Currently has limitations (see eBPF Improvements section below).
|
||||
|
||||
## eBPF Improvements (Linux)
|
||||
|
||||
The experimental eBPF support provides efficient process identification but has several areas for improvement:
|
||||
|
||||
### Current Limitations
|
||||
- **Process Names Limited to 16 Characters**: Uses kernel `comm` field, causing truncation (e.g., "Firefox" → "Socket Thread")
|
||||
- **Thread Names vs Process Names**: Shows thread command names instead of full executable names
|
||||
- **Minimal vmlinux.h Maintenance**: Current approach requires manual updates when adding new kernel structure access
|
||||
|
||||
### Planned Improvements
|
||||
- **Hybrid eBPF + Procfs Approach**: Use eBPF for connection tracking, selectively lookup full process names via procfs for better accuracy
|
||||
- **Full Executable Path Resolution**: Investigate accessing full process executable path from eBPF programs
|
||||
- **Better Process-Thread Mapping**: Improve mapping from thread IDs to parent process information
|
||||
- **vmlinux.h Strategy**: Consider switching to full auto-generated vmlinux.h for easier maintenance vs current minimal approach
|
||||
- **Enhanced BTF Support**: Better compatibility across different kernel versions and distributions
|
||||
- **Performance Optimizations**: Reduce eBPF map lookups and improve connection-to-process matching efficiency
|
||||
|
||||
### Future Enhancements
|
||||
- **Real-time Process Updates**: Track process name changes and executable updates
|
||||
- **Container Support**: Better process identification within containerized environments
|
||||
- **Security Context**: Include process security attributes (capabilities, SELinux context, etc.)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -18,7 +40,7 @@ This document outlines the planned features and improvements for RustNet.
|
||||
- **DNS Reverse Lookup**: Add optional hostname resolution (toggle between IP and hostname display)
|
||||
- **IPv6 Support**: Full IPv6 connection tracking and display, including DNS resolution, didn't test yet
|
||||
- **Search/Filter**:
|
||||
- 🔄 Regular expression support (future enhancement)
|
||||
- Regular expression support (future enhancement)
|
||||
- **Internationalization (i18n)**: Support for multiple languages in the UI
|
||||
- **Connection History**: Store and display historical connection data
|
||||
- **Export Functionality**: Export connections to CSV/JSON formats
|
||||
|
||||
71
build.rs
Normal file
71
build.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
// Only compile eBPF programs on Linux when the feature is enabled
|
||||
if cfg!(target_os = "linux") && env::var("CARGO_FEATURE_EBPF").is_ok() {
|
||||
compile_ebpf_programs();
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-changed=src/network/platform/linux_ebpf/programs/");
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "ebpf"))]
|
||||
fn compile_ebpf_programs() {
|
||||
use libbpf_cargo::SkeletonBuilder;
|
||||
use std::path::PathBuf;
|
||||
|
||||
let mut out = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
out.push("socket_tracker.skel.rs");
|
||||
|
||||
let src = "src/network/platform/linux_ebpf/programs/socket_tracker.bpf.c";
|
||||
|
||||
println!("cargo:warning=Building eBPF program using libbpf-cargo");
|
||||
|
||||
match SkeletonBuilder::new()
|
||||
.source(src)
|
||||
.clang_args([
|
||||
"-I/usr/include",
|
||||
"-I/usr/include/linux",
|
||||
"-I/usr/include/x86_64-linux-gnu",
|
||||
"-D__TARGET_ARCH_x86",
|
||||
])
|
||||
.build_and_generate(&out)
|
||||
{
|
||||
Ok(_) => {
|
||||
println!("cargo:warning=eBPF skeleton generated successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("cargo:warning=Failed to build eBPF program: {}", e);
|
||||
|
||||
// Create a placeholder skeleton file that compiles but returns None
|
||||
let placeholder_skeleton = r#"
|
||||
// Placeholder skeleton for failed compilation
|
||||
#[allow(dead_code)]
|
||||
pub mod socket_tracker {
|
||||
use anyhow::Result;
|
||||
|
||||
pub struct SocketTrackerSkel;
|
||||
|
||||
impl SocketTrackerSkel {
|
||||
pub fn open() -> Result<Self> {
|
||||
Err(anyhow::anyhow!("eBPF compilation failed"))
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
std::fs::write(&out, placeholder_skeleton).unwrap_or_else(|e| {
|
||||
println!(
|
||||
"cargo:warning=Failed to create placeholder skeleton file: {}",
|
||||
e
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-changed={}", src);
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_os = "linux", feature = "ebpf")))]
|
||||
fn compile_ebpf_programs() {
|
||||
// No-op when not on Linux or eBPF feature is not enabled
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{Result, anyhow};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
||||
9
src/lib.rs
Normal file
9
src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! RustNet Monitor Library
|
||||
//!
|
||||
//! A high-performance, cross-platform network monitoring library built with Rust.
|
||||
|
||||
pub mod app;
|
||||
pub mod config;
|
||||
pub mod filter;
|
||||
pub mod network;
|
||||
pub mod ui;
|
||||
@@ -84,6 +84,12 @@ pub struct PacketParser {
|
||||
linktype: Option<i32>, // DLT linktype - 149 means PKTAP on macOS
|
||||
}
|
||||
|
||||
impl Default for PacketParser {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PacketParser {
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> Self {
|
||||
|
||||
184
src/network/platform/linux_ebpf/loader.rs
Normal file
184
src/network/platform/linux_ebpf/loader.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
//! eBPF program loader with comprehensive error handling
|
||||
|
||||
use anyhow::Result;
|
||||
use libbpf_rs::skel::{OpenSkel, Skel, SkelBuilder};
|
||||
use log::{debug, info, warn};
|
||||
|
||||
mod socket_tracker {
|
||||
include!(concat!(env!("OUT_DIR"), "/socket_tracker.skel.rs"));
|
||||
}
|
||||
|
||||
use socket_tracker::*;
|
||||
|
||||
pub struct EbpfLoader {
|
||||
skel: Box<SocketTrackerSkel<'static>>,
|
||||
_open_object: Box<std::mem::MaybeUninit<libbpf_rs::OpenObject>>,
|
||||
}
|
||||
|
||||
impl EbpfLoader {
|
||||
/// Attempt to load eBPF programs with graceful error handling
|
||||
pub fn try_load() -> Result<Option<Self>> {
|
||||
// First check if we have necessary capabilities
|
||||
if !Self::check_capabilities() {
|
||||
info!("eBPF: Insufficient capabilities (need root or CAP_BPF), falling back to procfs");
|
||||
return Ok(None);
|
||||
} else {
|
||||
info!("eBPF: Sufficient capabilities detected, attempting to load program");
|
||||
}
|
||||
|
||||
match Self::load_program() {
|
||||
Ok(loader) => {
|
||||
info!("eBPF: Socket tracker loaded and attached successfully");
|
||||
Ok(Some(loader))
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"eBPF: Failed to load program: {}, falling back to procfs",
|
||||
e
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_program() -> Result<Self> {
|
||||
debug!("eBPF: Opening eBPF skeleton");
|
||||
let skel_builder = SocketTrackerSkelBuilder::default();
|
||||
|
||||
// Heap allocate the object to avoid lifetime issues
|
||||
let mut open_object = Box::new(std::mem::MaybeUninit::uninit());
|
||||
let open_skel = skel_builder.open(&mut open_object).map_err(|e| {
|
||||
warn!("eBPF: Failed to open skeleton: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
debug!("eBPF: Loading program into kernel");
|
||||
let mut skel = open_skel.load().map_err(|e| {
|
||||
warn!("eBPF: Failed to load program into kernel: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
debug!("eBPF: Attaching all programs");
|
||||
match skel.attach() {
|
||||
Ok(_) => {
|
||||
info!("eBPF: Programs attached successfully");
|
||||
// Verify programs are actually attached by checking their links
|
||||
let prog_names = [
|
||||
"trace_tcp_connect",
|
||||
"trace_tcp_accept",
|
||||
"trace_udp_sendmsg",
|
||||
"trace_tcp_v6_connect",
|
||||
"trace_udp_v6_sendmsg",
|
||||
];
|
||||
|
||||
for (i, prog_name) in prog_names.iter().enumerate() {
|
||||
debug!("eBPF: Checking attachment for program {}: {}", i, prog_name);
|
||||
}
|
||||
|
||||
info!("eBPF: All programs loaded and attached successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("eBPF: Failed to attach programs: {}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to 'static lifetime by boxing
|
||||
let skel_static: SocketTrackerSkel<'static> = unsafe { std::mem::transmute(skel) };
|
||||
|
||||
Ok(Self {
|
||||
skel: Box::new(skel_static),
|
||||
_open_object: open_object,
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if we have the necessary capabilities for eBPF
|
||||
fn check_capabilities() -> bool {
|
||||
use std::fs;
|
||||
|
||||
// Check if we're running as root
|
||||
if unsafe { libc::geteuid() } == 0 {
|
||||
debug!("eBPF: Running as root - all capabilities available");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for required capabilities via /proc/self/status
|
||||
if let Ok(status) = fs::read_to_string("/proc/self/status") {
|
||||
// Parse CapEff (effective capabilities) line
|
||||
if let Some(cap_line) = status.lines().find(|line| line.starts_with("CapEff:"))
|
||||
&& let Some(cap_hex) = cap_line.split_whitespace().nth(1)
|
||||
&& let Ok(cap_value) = u64::from_str_radix(cap_hex, 16)
|
||||
{
|
||||
debug!("eBPF: Current effective capabilities: 0x{:x}", cap_value);
|
||||
|
||||
// Required capabilities (bit positions):
|
||||
// CAP_NET_RAW = 13, CAP_NET_ADMIN = 12, CAP_BPF = 39, CAP_PERFMON = 38, CAP_SYS_ADMIN = 21
|
||||
let required_caps = [
|
||||
13, // CAP_NET_RAW - for packet capture
|
||||
12, // CAP_NET_ADMIN - for network administration
|
||||
21, // CAP_SYS_ADMIN - fallback for older kernels
|
||||
];
|
||||
|
||||
// Optional modern capabilities (Linux 5.8+)
|
||||
let modern_caps = [
|
||||
39, // CAP_BPF - for BPF operations
|
||||
38, // CAP_PERFMON - for performance monitoring
|
||||
];
|
||||
|
||||
// Check required capabilities
|
||||
let has_required = required_caps.iter().all(|&cap| {
|
||||
let has_cap = (cap_value & (1u64 << cap)) != 0;
|
||||
debug!(
|
||||
"eBPF: Capability {} (bit {}): {}",
|
||||
match cap {
|
||||
13 => "CAP_NET_RAW",
|
||||
12 => "CAP_NET_ADMIN",
|
||||
21 => "CAP_SYS_ADMIN",
|
||||
_ => "UNKNOWN",
|
||||
},
|
||||
cap,
|
||||
if has_cap { "present" } else { "missing" }
|
||||
);
|
||||
has_cap
|
||||
});
|
||||
|
||||
// Check modern capabilities (nice to have)
|
||||
let has_modern = modern_caps.iter().any(|&cap| {
|
||||
let has_cap = (cap_value & (1u64 << cap)) != 0;
|
||||
debug!(
|
||||
"eBPF: Modern capability {} (bit {}): {}",
|
||||
match cap {
|
||||
39 => "CAP_BPF",
|
||||
38 => "CAP_PERFMON",
|
||||
_ => "UNKNOWN",
|
||||
},
|
||||
cap,
|
||||
if has_cap { "present" } else { "missing" }
|
||||
);
|
||||
has_cap
|
||||
});
|
||||
|
||||
if has_required {
|
||||
if has_modern {
|
||||
info!("eBPF: All required and modern capabilities present");
|
||||
} else {
|
||||
info!("eBPF: Required capabilities present, using legacy mode");
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
debug!("eBPF: Missing required capabilities");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"eBPF: Insufficient capabilities - need CAP_NET_RAW, CAP_NET_ADMIN, and CAP_SYS_ADMIN (or CAP_BPF+CAP_PERFMON on newer kernels)"
|
||||
);
|
||||
false
|
||||
}
|
||||
|
||||
/// Get the socket map for lookups
|
||||
pub fn socket_map(&self) -> &libbpf_rs::Map<'_> {
|
||||
&self.skel.maps.socket_map
|
||||
}
|
||||
}
|
||||
275
src/network/platform/linux_ebpf/maps_libbpf.rs
Normal file
275
src/network/platform/linux_ebpf/maps_libbpf.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
//! eBPF map interaction utilities for libbpf-rs
|
||||
|
||||
use super::ProcessInfo;
|
||||
use anyhow::Result;
|
||||
use libbpf_rs::MapCore;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
/// Connection key matching the eBPF program structure (supports IPv4 and IPv6)
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ConnKey {
|
||||
pub saddr: [u32; 4], // IPv4 uses only saddr[0], IPv6 uses all 4
|
||||
pub daddr: [u32; 4], // IPv4 uses only daddr[0], IPv6 uses all 4
|
||||
pub sport: u16,
|
||||
pub dport: u16,
|
||||
pub proto: u8, // IPPROTO_TCP or IPPROTO_UDP
|
||||
pub family: u8, // AF_INET or AF_INET6
|
||||
}
|
||||
|
||||
/// Connection info matching the eBPF program structure
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ConnInfo {
|
||||
pub pid: u32,
|
||||
pub uid: u32,
|
||||
pub comm: [u8; 16],
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl ConnKey {
|
||||
pub fn new(src_ip: IpAddr, dst_ip: IpAddr, src_port: u16, dst_port: u16, is_tcp: bool) -> Self {
|
||||
let mut key = Self {
|
||||
saddr: [0; 4],
|
||||
daddr: [0; 4],
|
||||
sport: src_port,
|
||||
dport: dst_port,
|
||||
proto: if is_tcp { 6 } else { 17 }, // IPPROTO_TCP or IPPROTO_UDP
|
||||
family: match src_ip {
|
||||
IpAddr::V4(_) => 2, // AF_INET
|
||||
IpAddr::V6(_) => 10, // AF_INET6
|
||||
},
|
||||
};
|
||||
|
||||
match (src_ip, dst_ip) {
|
||||
(IpAddr::V4(src), IpAddr::V4(dst)) => {
|
||||
// Use little-endian to match kernel/eBPF native format
|
||||
key.saddr[0] = u32::from_le_bytes(src.octets());
|
||||
key.daddr[0] = u32::from_le_bytes(dst.octets());
|
||||
}
|
||||
(IpAddr::V6(src), IpAddr::V6(dst)) => {
|
||||
let src_bytes = src.octets();
|
||||
let dst_bytes = dst.octets();
|
||||
|
||||
// Convert 16-byte IPv6 addresses to 4 u32 values (big-endian)
|
||||
for i in 0..4 {
|
||||
let src_start = i * 4;
|
||||
let dst_start = i * 4;
|
||||
key.saddr[i] = u32::from_be_bytes([
|
||||
src_bytes[src_start],
|
||||
src_bytes[src_start + 1],
|
||||
src_bytes[src_start + 2],
|
||||
src_bytes[src_start + 3],
|
||||
]);
|
||||
key.daddr[i] = u32::from_be_bytes([
|
||||
dst_bytes[dst_start],
|
||||
dst_bytes[dst_start + 1],
|
||||
dst_bytes[dst_start + 2],
|
||||
dst_bytes[dst_start + 3],
|
||||
]);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Mixed IPv4/IPv6 - shouldn't happen in practice
|
||||
panic!("Mixed IPv4/IPv6 addresses not supported");
|
||||
}
|
||||
}
|
||||
|
||||
key
|
||||
}
|
||||
|
||||
pub fn new_v4(
|
||||
src_ip: Ipv4Addr,
|
||||
dst_ip: Ipv4Addr,
|
||||
src_port: u16,
|
||||
dst_port: u16,
|
||||
is_tcp: bool,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
IpAddr::V4(src_ip),
|
||||
IpAddr::V4(dst_ip),
|
||||
src_port,
|
||||
dst_port,
|
||||
is_tcp,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn new_v6(
|
||||
src_ip: Ipv6Addr,
|
||||
dst_ip: Ipv6Addr,
|
||||
src_port: u16,
|
||||
dst_port: u16,
|
||||
is_tcp: bool,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
IpAddr::V6(src_ip),
|
||||
IpAddr::V6(dst_ip),
|
||||
src_port,
|
||||
dst_port,
|
||||
is_tcp,
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert to bytes for map lookup
|
||||
pub fn as_bytes(&self) -> [u8; 38] {
|
||||
unsafe { std::mem::transmute(*self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConnInfo> for ProcessInfo {
|
||||
fn from(info: ConnInfo) -> Self {
|
||||
// Convert C string to Rust String
|
||||
let comm_len = info.comm.iter().position(|&x| x == 0).unwrap_or(16);
|
||||
let comm = String::from_utf8_lossy(&info.comm[..comm_len]).to_string();
|
||||
|
||||
Self {
|
||||
pid: info.pid,
|
||||
uid: info.uid,
|
||||
comm,
|
||||
timestamp: info.timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MapReader;
|
||||
|
||||
impl MapReader {
|
||||
/// Query the socket map for connection information using libbpf-rs
|
||||
pub fn lookup_connection(map: &libbpf_rs::Map, key: ConnKey) -> Result<Option<ProcessInfo>> {
|
||||
let key_bytes = key.as_bytes();
|
||||
|
||||
match map.lookup(&key_bytes, libbpf_rs::MapFlags::empty()) {
|
||||
Ok(Some(value_bytes)) => {
|
||||
if value_bytes.len() != 32 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid map value size: expected 32, got {}",
|
||||
value_bytes.len()
|
||||
));
|
||||
}
|
||||
|
||||
let mut info_bytes = [0u8; 32];
|
||||
info_bytes.copy_from_slice(&value_bytes);
|
||||
let conn_info: ConnInfo = unsafe { std::mem::transmute(info_bytes) };
|
||||
Ok(Some(conn_info.into()))
|
||||
}
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => {
|
||||
log::debug!("eBPF map lookup failed: {}", e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clean up stale entries from the map based on timestamp
|
||||
pub fn cleanup_stale_entries(map: &libbpf_rs::Map, stale_threshold_ns: u64) -> Result<u32> {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
let current_time_ns = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| anyhow::anyhow!("Time error: {}", e))?
|
||||
.as_nanos() as u64;
|
||||
|
||||
let mut cleanup_count = 0u32;
|
||||
let mut keys_to_delete = Vec::new();
|
||||
|
||||
// Try to iterate using MapKeyIter
|
||||
for key in map.keys() {
|
||||
// We have a key, check if its value is stale
|
||||
if let Ok(Some(value_bytes)) = map.lookup(&key, libbpf_rs::MapFlags::empty())
|
||||
&& value_bytes.len() >= 32
|
||||
{
|
||||
// Extract timestamp from last 8 bytes
|
||||
let timestamp_bytes = &value_bytes[24..32];
|
||||
let timestamp = u64::from_ne_bytes([
|
||||
timestamp_bytes[0],
|
||||
timestamp_bytes[1],
|
||||
timestamp_bytes[2],
|
||||
timestamp_bytes[3],
|
||||
timestamp_bytes[4],
|
||||
timestamp_bytes[5],
|
||||
timestamp_bytes[6],
|
||||
timestamp_bytes[7],
|
||||
]);
|
||||
|
||||
if current_time_ns.saturating_sub(timestamp) > stale_threshold_ns {
|
||||
// Entry is stale, mark for deletion
|
||||
keys_to_delete.push(key);
|
||||
log::debug!(
|
||||
"Found stale entry, timestamp: {}, current: {}, threshold: {}",
|
||||
timestamp,
|
||||
current_time_ns,
|
||||
stale_threshold_ns
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all stale entries
|
||||
for key in keys_to_delete {
|
||||
if let Err(e) = map.delete(&key) {
|
||||
log::debug!("Failed to delete stale entry: {}", e);
|
||||
} else {
|
||||
cleanup_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if cleanup_count > 0 {
|
||||
log::info!("eBPF cleanup: removed {} stale entries", cleanup_count);
|
||||
}
|
||||
|
||||
Ok(cleanup_count)
|
||||
}
|
||||
|
||||
/// Log map lookup details for debugging
|
||||
pub fn debug_lookup_miss(map: &libbpf_rs::Map, lookup_key: &ConnKey) -> Result<()> {
|
||||
log::info!("=== eBPF Map Lookup Miss Debug ===");
|
||||
|
||||
// Copy fields to avoid packed struct alignment issues
|
||||
let saddr = lookup_key.saddr[0];
|
||||
let daddr = lookup_key.daddr[0];
|
||||
let sport = lookup_key.sport;
|
||||
let dport = lookup_key.dport;
|
||||
let proto = lookup_key.proto;
|
||||
let family = lookup_key.family;
|
||||
|
||||
log::info!(
|
||||
"Looking for key: saddr={:08x} ({}.{}.{}.{}), daddr={:08x} ({}.{}.{}.{}), sport={}, dport={}, proto={}, family={}",
|
||||
saddr,
|
||||
saddr & 0xff,
|
||||
(saddr >> 8) & 0xff,
|
||||
(saddr >> 16) & 0xff,
|
||||
(saddr >> 24) & 0xff,
|
||||
daddr,
|
||||
daddr & 0xff,
|
||||
(daddr >> 8) & 0xff,
|
||||
(daddr >> 16) & 0xff,
|
||||
(daddr >> 24) & 0xff,
|
||||
sport,
|
||||
dport,
|
||||
proto,
|
||||
family
|
||||
);
|
||||
|
||||
log::info!("Key bytes: {:02x?}", lookup_key.as_bytes());
|
||||
|
||||
// Get map info
|
||||
let info = map.info();
|
||||
match info {
|
||||
Ok(map_info) => {
|
||||
log::info!(
|
||||
"Map type: {:?}, max_entries: {}, key_size: {}, value_size: {}",
|
||||
map_info.map_type(),
|
||||
map_info.info.max_entries,
|
||||
map_info.info.key_size,
|
||||
map_info.info.value_size
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("Failed to get map info: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("=== End Lookup Debug ===");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
19
src/network/platform/linux_ebpf/mod.rs
Normal file
19
src/network/platform/linux_ebpf/mod.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
//! Linux eBPF process tracking module
|
||||
//!
|
||||
//! This module provides enhanced process lookup using eBPF for TCP/UDP connections.
|
||||
//! It maintains compatibility with the existing procfs approach as a fallback.
|
||||
|
||||
pub mod loader;
|
||||
pub mod maps_libbpf;
|
||||
pub mod tracker_libbpf;
|
||||
|
||||
pub use tracker_libbpf::LibbpfSocketTracker as EbpfSocketTracker;
|
||||
|
||||
/// Process information from eBPF
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessInfo {
|
||||
pub pid: u32,
|
||||
pub uid: u32,
|
||||
pub comm: String,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
246
src/network/platform/linux_ebpf/programs/socket_tracker.bpf.c
Normal file
246
src/network/platform/linux_ebpf/programs/socket_tracker.bpf.c
Normal file
@@ -0,0 +1,246 @@
|
||||
// Socket tracker eBPF program
|
||||
// CO-RE (Compile Once - Run Everywhere) version using BTF
|
||||
|
||||
#include "vmlinux_min.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
|
||||
#define MAX_ENTRIES 32768
|
||||
#define TASK_COMM_LEN 16
|
||||
|
||||
// Network constants not included in vmlinux.h
|
||||
#define AF_INET 2 /* IPv4 */
|
||||
#define AF_INET6 10 /* IPv6 */
|
||||
#define IPPROTO_TCP 6 /* TCP */
|
||||
#define IPPROTO_UDP 17 /* UDP */
|
||||
|
||||
// Connection key for socket tracking (supports both IPv4 and IPv6)
|
||||
struct conn_key
|
||||
{
|
||||
__u32 saddr[4]; // IPv4 uses only saddr[0], IPv6 uses all 4
|
||||
__u32 daddr[4]; // IPv4 uses only daddr[0], IPv6 uses all 4
|
||||
__u16 sport;
|
||||
__u16 dport;
|
||||
__u8 proto; // IPPROTO_TCP or IPPROTO_UDP
|
||||
__u8 family; // AF_INET or AF_INET6
|
||||
} __attribute__((packed));
|
||||
|
||||
// Process information
|
||||
struct conn_info
|
||||
{
|
||||
__u32 pid;
|
||||
__u32 uid;
|
||||
char comm[TASK_COMM_LEN];
|
||||
__u64 timestamp;
|
||||
} __attribute__((packed));
|
||||
|
||||
// Socket tracking map
|
||||
struct
|
||||
{
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, struct conn_key);
|
||||
__type(value, struct conn_info);
|
||||
} socket_map SEC(".maps");
|
||||
|
||||
// Helper to populate process information
|
||||
static __always_inline void get_process_info(struct conn_info *info)
|
||||
{
|
||||
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
__u64 uid_gid = bpf_get_current_uid_gid();
|
||||
|
||||
info->pid = pid_tgid >> 32;
|
||||
info->uid = uid_gid >> 32;
|
||||
info->timestamp = bpf_ktime_get_ns();
|
||||
|
||||
bpf_get_current_comm(&info->comm, sizeof(info->comm));
|
||||
}
|
||||
|
||||
// TCP connect tracking - use tcp_connect for better address capture
|
||||
SEC("kprobe/tcp_connect")
|
||||
int trace_tcp_connect(struct pt_regs *ctx)
|
||||
{
|
||||
struct sock *sk = (struct sock *)PT_REGS_PARM1_CORE(ctx);
|
||||
if (!sk)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct conn_key key = {};
|
||||
struct conn_info info = {};
|
||||
|
||||
// Read socket information for IPv4 using CO-RE
|
||||
key.saddr[0] = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
|
||||
key.daddr[0] = BPF_CORE_READ(sk, __sk_common.skc_daddr);
|
||||
key.sport = BPF_CORE_READ(sk, __sk_common.skc_num);
|
||||
key.dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
|
||||
|
||||
key.dport = bpf_ntohs(key.dport);
|
||||
key.proto = IPPROTO_TCP;
|
||||
key.family = AF_INET;
|
||||
|
||||
get_process_info(&info);
|
||||
|
||||
int ret = bpf_map_update_elem(&socket_map, &key, &info, BPF_ANY);
|
||||
if (ret != 0)
|
||||
{
|
||||
bpf_printk("tcp_connect: map update failed ret=%d", ret);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TCP accept tracking
|
||||
SEC("kprobe/inet_csk_accept")
|
||||
int trace_tcp_accept(struct pt_regs *ctx)
|
||||
{
|
||||
struct sock *sk = (struct sock *)PT_REGS_PARM1_CORE(ctx);
|
||||
if (!sk)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct conn_key key = {};
|
||||
struct conn_info info = {};
|
||||
|
||||
key.saddr[0] = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
|
||||
key.daddr[0] = BPF_CORE_READ(sk, __sk_common.skc_daddr);
|
||||
key.sport = BPF_CORE_READ(sk, __sk_common.skc_num);
|
||||
key.dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
|
||||
|
||||
key.dport = bpf_ntohs(key.dport);
|
||||
key.proto = IPPROTO_TCP;
|
||||
key.family = AF_INET;
|
||||
|
||||
get_process_info(&info);
|
||||
|
||||
int ret = bpf_map_update_elem(&socket_map, &key, &info, BPF_ANY);
|
||||
if (ret != 0)
|
||||
{
|
||||
bpf_printk("inet_csk_accept: map update failed ret=%d", ret);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// UDP sendmsg tracking - extract destination from msghdr
|
||||
SEC("kprobe/udp_sendmsg")
|
||||
int trace_udp_sendmsg(struct pt_regs *ctx)
|
||||
{
|
||||
struct sock *sk = (struct sock *)PT_REGS_PARM1_CORE(ctx);
|
||||
struct msghdr *msg = (struct msghdr *)PT_REGS_PARM2_CORE(ctx);
|
||||
|
||||
if (!sk || !msg)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct conn_key key = {};
|
||||
struct conn_info info = {};
|
||||
|
||||
// Get source address from socket
|
||||
key.saddr[0] = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
|
||||
key.sport = BPF_CORE_READ(sk, __sk_common.skc_num);
|
||||
|
||||
// Try to get destination from msghdr->msg_name (sockaddr_in)
|
||||
struct sockaddr_in *dest_addr = NULL;
|
||||
bpf_probe_read_kernel(&dest_addr, sizeof(dest_addr), &msg->msg_name);
|
||||
|
||||
if (dest_addr)
|
||||
{
|
||||
bpf_probe_read_kernel(&key.daddr[0], sizeof(__u32), &dest_addr->sin_addr.s_addr);
|
||||
bpf_probe_read_kernel(&key.dport, sizeof(__u16), &dest_addr->sin_port);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to socket fields (might be zero for unconnected UDP)
|
||||
key.daddr[0] = BPF_CORE_READ(sk, __sk_common.skc_daddr);
|
||||
key.dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
|
||||
}
|
||||
|
||||
// Only skip if destination is zero (source might be unbound for UDP)
|
||||
if (key.daddr[0] == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
key.dport = bpf_ntohs(key.dport);
|
||||
key.proto = IPPROTO_UDP;
|
||||
key.family = AF_INET;
|
||||
|
||||
get_process_info(&info);
|
||||
|
||||
int ret = bpf_map_update_elem(&socket_map, &key, &info, BPF_ANY);
|
||||
if (ret != 0)
|
||||
{
|
||||
bpf_printk("udp_sendmsg: map update failed ret=%d", ret);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// IPv6 TCP connect tracking
|
||||
SEC("kprobe/tcp_v6_connect")
|
||||
int trace_tcp_v6_connect(struct pt_regs *ctx)
|
||||
{
|
||||
struct sock *sk = (struct sock *)PT_REGS_PARM1_CORE(ctx);
|
||||
if (!sk)
|
||||
return 0;
|
||||
|
||||
struct conn_key key = {};
|
||||
struct conn_info info = {};
|
||||
|
||||
// Read socket information for IPv6 using CO-RE
|
||||
// Use temporary variables to avoid packed member warnings
|
||||
struct in6_addr temp_saddr, temp_daddr;
|
||||
BPF_CORE_READ_INTO(&temp_saddr, sk, __sk_common.skc_v6_rcv_saddr);
|
||||
BPF_CORE_READ_INTO(&temp_daddr, sk, __sk_common.skc_v6_daddr);
|
||||
|
||||
// Copy to packed structure
|
||||
__builtin_memcpy(key.saddr, &temp_saddr, sizeof(temp_saddr));
|
||||
__builtin_memcpy(key.daddr, &temp_daddr, sizeof(temp_daddr));
|
||||
key.sport = BPF_CORE_READ(sk, __sk_common.skc_num);
|
||||
key.dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
|
||||
|
||||
key.dport = bpf_ntohs(key.dport);
|
||||
key.proto = IPPROTO_TCP;
|
||||
key.family = AF_INET6;
|
||||
|
||||
get_process_info(&info);
|
||||
|
||||
bpf_map_update_elem(&socket_map, &key, &info, BPF_ANY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// IPv6 UDP sendmsg tracking
|
||||
SEC("kprobe/udpv6_sendmsg")
|
||||
int trace_udp_v6_sendmsg(struct pt_regs *ctx)
|
||||
{
|
||||
struct sock *sk = (struct sock *)PT_REGS_PARM1_CORE(ctx);
|
||||
if (!sk)
|
||||
return 0;
|
||||
|
||||
struct conn_key key = {};
|
||||
struct conn_info info = {};
|
||||
|
||||
// Use temporary variables to avoid packed member warnings
|
||||
struct in6_addr temp_saddr, temp_daddr;
|
||||
BPF_CORE_READ_INTO(&temp_saddr, sk, __sk_common.skc_v6_rcv_saddr);
|
||||
BPF_CORE_READ_INTO(&temp_daddr, sk, __sk_common.skc_v6_daddr);
|
||||
|
||||
// Copy to packed structure
|
||||
__builtin_memcpy(key.saddr, &temp_saddr, sizeof(temp_saddr));
|
||||
__builtin_memcpy(key.daddr, &temp_daddr, sizeof(temp_daddr));
|
||||
key.sport = BPF_CORE_READ(sk, __sk_common.skc_num);
|
||||
key.dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
|
||||
|
||||
key.dport = bpf_ntohs(key.dport);
|
||||
key.proto = IPPROTO_UDP;
|
||||
key.family = AF_INET6;
|
||||
|
||||
get_process_info(&info);
|
||||
|
||||
bpf_map_update_elem(&socket_map, &key, &info, BPF_ANY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
243
src/network/platform/linux_ebpf/programs/vmlinux_min.h
Normal file
243
src/network/platform/linux_ebpf/programs/vmlinux_min.h
Normal file
@@ -0,0 +1,243 @@
|
||||
#ifndef __VMLINUX_MIN_H__
|
||||
#define __VMLINUX_MIN_H__
|
||||
|
||||
/*
|
||||
* Minimal vmlinux.h with only the kernel structures needed for our eBPF socket tracker.
|
||||
* This replaces the full vmlinux.h (3.4MB) with just the essential definitions.
|
||||
*
|
||||
* Generated for Linux kernel structures used in socket tracking.
|
||||
* See EBPF_BUILD.md for instructions on how to regenerate or customize.
|
||||
*/
|
||||
|
||||
/* Define this to trigger kernel field names in bpf_tracing.h */
|
||||
#define __VMLINUX_H__
|
||||
|
||||
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
|
||||
#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
|
||||
#endif
|
||||
|
||||
/* Basic kernel types */
|
||||
typedef unsigned char __u8;
|
||||
typedef unsigned short __u16;
|
||||
typedef unsigned int __u32;
|
||||
typedef unsigned long long __u64;
|
||||
typedef signed char __s8;
|
||||
typedef signed short __s16;
|
||||
typedef signed int __s32;
|
||||
typedef signed long long __s64;
|
||||
typedef __u16 __be16;
|
||||
typedef __u32 __be32;
|
||||
typedef __u64 __be64;
|
||||
typedef __u32 __wsum;
|
||||
typedef __u16 __kernel_sa_family_t;
|
||||
typedef _Bool bool;
|
||||
|
||||
/* Additional type definitions needed for kernel structures */
|
||||
typedef __u64 __addrpair;
|
||||
typedef __u32 __portpair;
|
||||
typedef struct { __s64 counter; } atomic64_t;
|
||||
typedef struct { __s32 counter; } atomic_t;
|
||||
|
||||
/* BPF map types and flags */
|
||||
enum {
|
||||
BPF_MAP_TYPE_HASH = 1,
|
||||
};
|
||||
|
||||
enum {
|
||||
BPF_ANY = 0,
|
||||
};
|
||||
|
||||
/* Network address structures */
|
||||
struct in_addr {
|
||||
__be32 s_addr;
|
||||
};
|
||||
|
||||
struct in6_addr {
|
||||
union {
|
||||
__u8 u6_addr8[16];
|
||||
__be16 u6_addr16[8];
|
||||
__be32 u6_addr32[4];
|
||||
} in6_u;
|
||||
};
|
||||
|
||||
/* Socket address structures */
|
||||
struct sockaddr_in {
|
||||
__kernel_sa_family_t sin_family;
|
||||
__be16 sin_port;
|
||||
struct in_addr sin_addr;
|
||||
unsigned char __pad[8];
|
||||
};
|
||||
|
||||
/* Forward declarations for complex types we don't need to fully define */
|
||||
struct proto;
|
||||
struct sk_buff_head;
|
||||
|
||||
/* Simple structures we need defined */
|
||||
struct hlist_node {
|
||||
struct hlist_node *next, **pprev;
|
||||
};
|
||||
|
||||
struct iov_iter {
|
||||
/* We don't access fields, just need size/layout for msghdr */
|
||||
void *__opaque[8]; /* Approximate size, CO-RE will handle differences */
|
||||
};
|
||||
|
||||
/* Minimal possible_net_t - we don't access its internals */
|
||||
typedef struct {
|
||||
void *net; /* We don't dereference this, just need the field present */
|
||||
} possible_net_t;
|
||||
|
||||
/* Socket common structure - core networking fields */
|
||||
struct sock_common {
|
||||
/* Address pair for IPv4 */
|
||||
union {
|
||||
__addrpair skc_addrpair; /* We don't use this directly */
|
||||
struct {
|
||||
__be32 skc_daddr; /* destination IPv4 address */
|
||||
__be32 skc_rcv_saddr; /* source IPv4 address */
|
||||
};
|
||||
};
|
||||
|
||||
/* Hash - we don't use this but it's part of the layout */
|
||||
union {
|
||||
unsigned int skc_hash;
|
||||
__u16 skc_u16hashes[2];
|
||||
};
|
||||
|
||||
/* Port pair */
|
||||
union {
|
||||
__portpair skc_portpair; /* We don't use this directly */
|
||||
struct {
|
||||
__be16 skc_dport; /* destination port */
|
||||
__u16 skc_num; /* source port */
|
||||
};
|
||||
};
|
||||
|
||||
/* Basic socket properties */
|
||||
short unsigned int skc_family;
|
||||
volatile unsigned char skc_state;
|
||||
unsigned char skc_reuse: 4;
|
||||
unsigned char skc_reuseport: 1;
|
||||
unsigned char skc_ipv6only: 1;
|
||||
unsigned char skc_net_refcnt: 1;
|
||||
int skc_bound_dev_if;
|
||||
|
||||
/* Hash table linkage - we don't use these but they're part of layout */
|
||||
union {
|
||||
struct hlist_node skc_bind_node;
|
||||
struct hlist_node skc_portaddr_node;
|
||||
};
|
||||
|
||||
/* Protocol and network namespace */
|
||||
struct proto *skc_prot;
|
||||
possible_net_t skc_net;
|
||||
|
||||
/* IPv6 addresses - these come after the above fields */
|
||||
struct in6_addr skc_v6_daddr;
|
||||
struct in6_addr skc_v6_rcv_saddr;
|
||||
|
||||
/* Socket cookie for identification */
|
||||
atomic64_t skc_cookie;
|
||||
|
||||
/* Additional fields exist but we don't need them for CO-RE access */
|
||||
};
|
||||
|
||||
/* Main socket structure - we only need the common part */
|
||||
struct sock {
|
||||
struct sock_common __sk_common;
|
||||
/*
|
||||
* Many more fields exist here, but we only access __sk_common
|
||||
* CO-RE will handle the field relocations regardless of what
|
||||
* other fields are present in different kernel versions
|
||||
*/
|
||||
};
|
||||
|
||||
/* Message header for sendmsg syscalls */
|
||||
struct msghdr {
|
||||
void *msg_name; /* Socket name (sockaddr_in* for UDP) */
|
||||
int msg_namelen; /* Length of socket name */
|
||||
int msg_inq; /* Bytes in receive queue */
|
||||
struct iov_iter msg_iter; /* Data payload iterator */
|
||||
|
||||
/* Control messages - we don't use these but they're part of layout */
|
||||
union {
|
||||
void *msg_control;
|
||||
void *msg_control_user;
|
||||
};
|
||||
bool msg_control_is_user: 1;
|
||||
bool msg_get_inq: 1;
|
||||
/* Additional fields may exist but we only need msg_name */
|
||||
};
|
||||
|
||||
/* FRED (Flexible Return and Event Delivery) support structures */
|
||||
struct fred_cs {
|
||||
__u64 cs: 16;
|
||||
__u64 sl: 2;
|
||||
__u64 wfe: 1;
|
||||
};
|
||||
|
||||
struct fred_ss {
|
||||
__u64 ss: 16;
|
||||
__u64 sti: 1;
|
||||
__u64 swevent: 1;
|
||||
__u64 nmi: 1;
|
||||
int: 13;
|
||||
__u64 vector: 8;
|
||||
short: 8;
|
||||
__u64 type: 4;
|
||||
char: 4;
|
||||
__u64 enclave: 1;
|
||||
};
|
||||
|
||||
/*
|
||||
* Architecture-specific pt_regs for kprobe context
|
||||
* x86_64 specific - for PT_REGS_PARM1/PT_REGS_PARM2 macros
|
||||
* Field names must match kernel exactly for CO-RE relocations
|
||||
*/
|
||||
struct pt_regs {
|
||||
/*
|
||||
* C ABI says these regs are callee-preserved. They aren't saved on kernel entry
|
||||
* unless syscall needs a complete, fully filled "struct pt_regs".
|
||||
*/
|
||||
long unsigned int r15;
|
||||
long unsigned int r14;
|
||||
long unsigned int r13;
|
||||
long unsigned int r12;
|
||||
long unsigned int bp; /* Must match kernel BTF field names */
|
||||
long unsigned int bx; /* Must match kernel BTF field names */
|
||||
/* These regs are callee-clobbered. Always saved on kernel entry. */
|
||||
long unsigned int r11;
|
||||
long unsigned int r10;
|
||||
long unsigned int r9;
|
||||
long unsigned int r8;
|
||||
long unsigned int ax; /* Must match kernel BTF field names */
|
||||
long unsigned int cx; /* Must match kernel BTF field names */
|
||||
long unsigned int dx; /* Must match kernel BTF field names */
|
||||
long unsigned int si; /* Must match kernel BTF field names */
|
||||
long unsigned int di; /* Must match kernel BTF field names */
|
||||
/*
|
||||
* On syscall entry, this is syscall#. On CPU exception, this is error code.
|
||||
* On hw interrupt, it's IRQ number:
|
||||
*/
|
||||
long unsigned int orig_ax;
|
||||
/* Return frame for iretq */
|
||||
long unsigned int ip; /* Must match kernel BTF field names */
|
||||
union {
|
||||
__u16 cs;
|
||||
__u64 csx;
|
||||
struct fred_cs fred_cs;
|
||||
};
|
||||
long unsigned int flags; /* Must match kernel BTF field names */
|
||||
long unsigned int sp; /* Must match kernel BTF field names */
|
||||
union {
|
||||
__u16 ss;
|
||||
__u64 ssx;
|
||||
struct fred_ss fred_ss;
|
||||
};
|
||||
};
|
||||
|
||||
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
|
||||
#pragma clang attribute pop
|
||||
#endif
|
||||
|
||||
#endif /* __VMLINUX_MIN_H__ */
|
||||
170
src/network/platform/linux_ebpf/tracker_libbpf.rs
Normal file
170
src/network/platform/linux_ebpf/tracker_libbpf.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
//! eBPF socket tracker implementation using libbpf-rs
|
||||
|
||||
use super::{
|
||||
ProcessInfo,
|
||||
loader::EbpfLoader,
|
||||
maps_libbpf::{ConnKey, MapReader},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
pub struct LibbpfSocketTracker {
|
||||
loader: EbpfLoader,
|
||||
}
|
||||
|
||||
unsafe impl Send for LibbpfSocketTracker {}
|
||||
unsafe impl Sync for LibbpfSocketTracker {}
|
||||
|
||||
impl LibbpfSocketTracker {
|
||||
/// Create a new eBPF socket tracker
|
||||
/// Returns None if eBPF cannot be loaded (insufficient privileges, etc.)
|
||||
pub fn new() -> Result<Option<Self>> {
|
||||
match EbpfLoader::try_load()? {
|
||||
Some(loader) => Ok(Some(Self { loader })),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up process information for a connection (IPv4)
|
||||
pub fn lookup_v4(
|
||||
&mut self,
|
||||
src_ip: Ipv4Addr,
|
||||
dst_ip: Ipv4Addr,
|
||||
src_port: u16,
|
||||
dst_port: u16,
|
||||
is_tcp: bool,
|
||||
) -> Option<ProcessInfo> {
|
||||
let socket_map = self.loader.socket_map();
|
||||
|
||||
// Try exact match first
|
||||
let key = ConnKey::new_v4(src_ip, dst_ip, src_port, dst_port, is_tcp);
|
||||
match MapReader::lookup_connection(socket_map, key) {
|
||||
Ok(Some(result)) => {
|
||||
return Some(result);
|
||||
}
|
||||
Ok(None) => {
|
||||
log::debug!("eBPF exact lookup miss, trying with zero source address");
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("eBPF IPv4 lookup failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Try with zero source address (common for eBPF UDP/TCP entries)
|
||||
let zero_src_key = ConnKey::new_v4(
|
||||
Ipv4Addr::new(0, 0, 0, 0),
|
||||
dst_ip,
|
||||
src_port,
|
||||
dst_port,
|
||||
is_tcp,
|
||||
);
|
||||
log::debug!(
|
||||
"eBPF zero-source key bytes: {:02x?}",
|
||||
zero_src_key.as_bytes()
|
||||
);
|
||||
match MapReader::lookup_connection(socket_map, zero_src_key) {
|
||||
Ok(Some(result)) => {
|
||||
log::info!(
|
||||
"🎉 eBPF lookup succeeded with zero source address! PID: {}, comm: {}",
|
||||
result.pid,
|
||||
result.comm
|
||||
);
|
||||
// Let cleanup handle entry deletion based on age
|
||||
Some(result)
|
||||
}
|
||||
Ok(None) => {
|
||||
// Debug both keys for comparison
|
||||
log::debug!("eBPF lookup missed with both exact and zero-source keys");
|
||||
if let Err(e) = MapReader::debug_lookup_miss(socket_map, &key) {
|
||||
log::debug!("Failed to debug lookup: {}", e);
|
||||
}
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("eBPF zero-source lookup failed: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up process information for a connection (IPv6)
|
||||
pub fn lookup_v6(
|
||||
&mut self,
|
||||
src_ip: Ipv6Addr,
|
||||
dst_ip: Ipv6Addr,
|
||||
src_port: u16,
|
||||
dst_port: u16,
|
||||
is_tcp: bool,
|
||||
) -> Option<ProcessInfo> {
|
||||
let key = ConnKey::new_v6(src_ip, dst_ip, src_port, dst_port, is_tcp);
|
||||
|
||||
let socket_map = self.loader.socket_map();
|
||||
match MapReader::lookup_connection(socket_map, key) {
|
||||
Ok(Some(result)) => {
|
||||
// Let cleanup handle entry deletion based on age
|
||||
Some(result)
|
||||
}
|
||||
Ok(None) => {
|
||||
// Debug map lookup miss to see what we're looking for
|
||||
if let Err(e) = MapReader::debug_lookup_miss(socket_map, &key) {
|
||||
log::debug!("Failed to debug lookup: {}", e);
|
||||
}
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("eBPF IPv6 lookup failed: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up process information for a connection (generic)
|
||||
pub fn lookup(
|
||||
&mut self,
|
||||
src_ip: IpAddr,
|
||||
dst_ip: IpAddr,
|
||||
src_port: u16,
|
||||
dst_port: u16,
|
||||
is_tcp: bool,
|
||||
) -> Option<ProcessInfo> {
|
||||
match (src_ip, dst_ip) {
|
||||
(IpAddr::V4(src), IpAddr::V4(dst)) => {
|
||||
self.lookup_v4(src, dst, src_port, dst_port, is_tcp)
|
||||
}
|
||||
(IpAddr::V6(src), IpAddr::V6(dst)) => {
|
||||
self.lookup_v6(src, dst, src_port, dst_port, is_tcp)
|
||||
}
|
||||
_ => {
|
||||
log::warn!("Mixed IPv4/IPv6 addresses not supported in eBPF lookup");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the tracker is healthy and operational
|
||||
pub fn is_healthy(&self) -> bool {
|
||||
// Simple health check - in a real implementation you might
|
||||
// check if programs are still attached, etc.
|
||||
true
|
||||
}
|
||||
|
||||
/// Clean up stale entries from the eBPF map
|
||||
/// Returns the number of entries cleaned up
|
||||
pub fn cleanup_stale_entries(&mut self, stale_threshold_secs: u64) -> u32 {
|
||||
let socket_map = self.loader.socket_map();
|
||||
let stale_threshold_ns = stale_threshold_secs * 1_000_000_000;
|
||||
|
||||
match MapReader::cleanup_stale_entries(socket_map, stale_threshold_ns) {
|
||||
Ok(count) => {
|
||||
if count > 0 {
|
||||
log::info!("eBPF map cleanup: removed {} stale entries", count);
|
||||
}
|
||||
count
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("eBPF map cleanup failed: {}", e);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
590
src/network/platform/linux_enhanced.rs
Normal file
590
src/network/platform/linux_enhanced.rs
Normal file
@@ -0,0 +1,590 @@
|
||||
//! Enhanced Linux process lookup combining eBPF and procfs approaches
|
||||
|
||||
use super::{ConnectionKey, ProcessLookup};
|
||||
|
||||
use super::linux::LinuxProcessLookup;
|
||||
use crate::network::types::{Connection, Protocol};
|
||||
use anyhow::Result;
|
||||
use log::{debug, info, warn};
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::RwLock;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[cfg(feature = "ebpf")]
|
||||
use super::linux_ebpf::EbpfSocketTracker;
|
||||
|
||||
// When eBPF is enabled, use the full enhanced implementation
|
||||
#[cfg(feature = "ebpf")]
|
||||
mod ebpf_enhanced {
|
||||
use super::*;
|
||||
|
||||
/// Enhanced process lookup that combines eBPF (fast path) with procfs (fallback)
|
||||
pub struct EnhancedLinuxProcessLookup {
|
||||
ebpf_tracker: RwLock<Option<Box<EbpfSocketTracker>>>,
|
||||
procfs_lookup: LinuxProcessLookup,
|
||||
unified_cache: RwLock<ProcessCache>,
|
||||
stats: RwLock<LookupStats>,
|
||||
cleanup_config: CleanupConfig,
|
||||
last_cleanup: RwLock<Instant>,
|
||||
}
|
||||
|
||||
pub struct ProcessCache {
|
||||
lookup: HashMap<ConnectionKey, (u32, String)>,
|
||||
last_refresh: Instant,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CleanupConfig {
|
||||
pub cleanup_interval_secs: u64,
|
||||
pub stale_threshold_secs: u64,
|
||||
}
|
||||
|
||||
impl Default for CleanupConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cleanup_interval_secs: 30,
|
||||
stale_threshold_secs: 60,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LookupStats {
|
||||
ebpf_hits: u64,
|
||||
procfs_hits: u64,
|
||||
cache_hits: u64,
|
||||
total_lookups: u64,
|
||||
ipv4_lookups: u64,
|
||||
ipv6_lookups: u64,
|
||||
tcp_lookups: u64,
|
||||
udp_lookups: u64,
|
||||
cache_entries: u64,
|
||||
failed_lookups: u64,
|
||||
ebpf_available: bool,
|
||||
}
|
||||
|
||||
impl EnhancedLinuxProcessLookup {
|
||||
pub fn new() -> Result<Self> {
|
||||
Self::new_with_config(CleanupConfig::default())
|
||||
}
|
||||
|
||||
pub fn new_with_config(cleanup_config: CleanupConfig) -> Result<Self> {
|
||||
let procfs_lookup = LinuxProcessLookup::new()?;
|
||||
|
||||
let ebpf_tracker = match EbpfSocketTracker::new() {
|
||||
Ok(tracker) => {
|
||||
if tracker.is_some() {
|
||||
info!("eBPF socket tracker initialized successfully");
|
||||
} else {
|
||||
info!("eBPF not available, using procfs only");
|
||||
}
|
||||
tracker.map(Box::new)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to initialize eBPF tracker: {}, falling back to procfs",
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
ebpf_tracker: RwLock::new(ebpf_tracker),
|
||||
procfs_lookup,
|
||||
unified_cache: RwLock::new(ProcessCache {
|
||||
lookup: HashMap::new(),
|
||||
last_refresh: Instant::now() - Duration::from_secs(3600),
|
||||
}),
|
||||
stats: RwLock::new(LookupStats::default()),
|
||||
cleanup_config,
|
||||
last_cleanup: RwLock::new(Instant::now() - Duration::from_secs(3600)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Try eBPF lookup first, fall back to procfs
|
||||
fn lookup_process_enhanced(&self, conn: &Connection) -> Option<(u32, String)> {
|
||||
// Try eBPF first for TCP/UDP connections
|
||||
if matches!(conn.protocol, Protocol::TCP | Protocol::UDP) {
|
||||
debug!(
|
||||
"Enhanced lookup: Trying eBPF for {}:{} -> {}:{} ({})",
|
||||
conn.local_addr.ip(),
|
||||
conn.local_addr.port(),
|
||||
conn.remote_addr.ip(),
|
||||
conn.remote_addr.port(),
|
||||
match conn.protocol {
|
||||
Protocol::TCP => "TCP",
|
||||
Protocol::UDP => "UDP",
|
||||
_ => "Unknown",
|
||||
}
|
||||
);
|
||||
|
||||
if let Some(result) = self.try_ebpf_lookup(conn) {
|
||||
let mut stats = self.stats.write().unwrap();
|
||||
stats.ebpf_hits += 1;
|
||||
debug!(
|
||||
"Enhanced lookup: eBPF hit for PID {} ({})",
|
||||
result.0, result.1
|
||||
);
|
||||
return Some(result);
|
||||
} else {
|
||||
debug!("Enhanced lookup: eBPF miss, falling back to procfs");
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to procfs approach
|
||||
if let Some(result) = self.procfs_lookup.get_process_for_connection(conn) {
|
||||
let mut stats = self.stats.write().unwrap();
|
||||
stats.procfs_hits += 1;
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn try_ebpf_lookup(&self, conn: &Connection) -> Option<(u32, String)> {
|
||||
let mut tracker_guard = self.ebpf_tracker.write().unwrap();
|
||||
let tracker = match tracker_guard.as_mut() {
|
||||
Some(t) => {
|
||||
debug!("eBPF lookup: Tracker available, performing lookup");
|
||||
t
|
||||
}
|
||||
None => {
|
||||
debug!("eBPF lookup: No tracker available");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let is_tcp = matches!(conn.protocol, Protocol::TCP);
|
||||
|
||||
match tracker.lookup(
|
||||
conn.local_addr.ip(),
|
||||
conn.remote_addr.ip(),
|
||||
conn.local_addr.port(),
|
||||
conn.remote_addr.port(),
|
||||
is_tcp,
|
||||
) {
|
||||
Some(process_info) => {
|
||||
debug!(
|
||||
"eBPF lookup successful for {}:{} -> {}:{} - PID: {}, UID: {}, Comm: {}, Age: {}ns",
|
||||
conn.local_addr.ip(),
|
||||
conn.local_addr.port(),
|
||||
conn.remote_addr.ip(),
|
||||
conn.remote_addr.port(),
|
||||
process_info.pid,
|
||||
process_info.uid,
|
||||
process_info.comm,
|
||||
process_info.timestamp
|
||||
);
|
||||
Some((process_info.pid, process_info.comm))
|
||||
}
|
||||
None => {
|
||||
debug!(
|
||||
"eBPF lookup missed for {}:{} -> {}:{}",
|
||||
conn.local_addr.ip(),
|
||||
conn.local_addr.port(),
|
||||
conn.remote_addr.ip(),
|
||||
conn.remote_addr.port()
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get diagnostic statistics about lookup performance
|
||||
#[allow(dead_code)]
|
||||
pub fn get_stats(&self) -> LookupStats {
|
||||
self.stats.read().unwrap().clone()
|
||||
}
|
||||
|
||||
/// Check if eBPF is available and functioning
|
||||
pub fn is_ebpf_available(&self) -> bool {
|
||||
self.ebpf_tracker
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map(|t| t.is_healthy())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Perform periodic cleanup of stale eBPF map entries
|
||||
fn maybe_cleanup_ebpf_map(&self) {
|
||||
let now = Instant::now();
|
||||
let mut last_cleanup = self.last_cleanup.write().unwrap();
|
||||
|
||||
if now.duration_since(*last_cleanup).as_secs()
|
||||
>= self.cleanup_config.cleanup_interval_secs
|
||||
{
|
||||
*last_cleanup = now;
|
||||
drop(last_cleanup);
|
||||
|
||||
// Perform cleanup
|
||||
if let Some(tracker) = self.ebpf_tracker.write().unwrap().as_mut() {
|
||||
let cleaned =
|
||||
tracker.cleanup_stale_entries(self.cleanup_config.stale_threshold_secs);
|
||||
if cleaned > 0 {
|
||||
debug!("eBPF map cleanup: removed {} stale entries", cleaned);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessLookup for EnhancedLinuxProcessLookup {
|
||||
fn get_process_for_connection(&self, conn: &Connection) -> Option<(u32, String)> {
|
||||
// Perform periodic cleanup of stale eBPF entries
|
||||
self.maybe_cleanup_ebpf_map();
|
||||
|
||||
let key = ConnectionKey::from_connection(conn);
|
||||
|
||||
// Update protocol statistics
|
||||
{
|
||||
let mut stats = self.stats.write().unwrap();
|
||||
stats.total_lookups += 1;
|
||||
|
||||
// Track IP version
|
||||
match conn.local_addr.ip() {
|
||||
IpAddr::V4(_) => stats.ipv4_lookups += 1,
|
||||
IpAddr::V6(_) => stats.ipv6_lookups += 1,
|
||||
}
|
||||
|
||||
// Track protocol type
|
||||
match conn.protocol {
|
||||
Protocol::TCP => stats.tcp_lookups += 1,
|
||||
Protocol::UDP => stats.udp_lookups += 1,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Update eBPF availability status
|
||||
stats.ebpf_available = self.is_ebpf_available();
|
||||
}
|
||||
|
||||
// Try cache first
|
||||
{
|
||||
let cache = self.unified_cache.read().unwrap();
|
||||
if cache.last_refresh.elapsed() < Duration::from_secs(2)
|
||||
&& let Some(process_info) = cache.lookup.get(&key)
|
||||
{
|
||||
let mut stats = self.stats.write().unwrap();
|
||||
stats.cache_hits += 1;
|
||||
return Some(process_info.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss or stale - do enhanced lookup
|
||||
if let Some(result) = self.lookup_process_enhanced(conn) {
|
||||
// Update cache with the result
|
||||
{
|
||||
let mut cache = self.unified_cache.write().unwrap();
|
||||
cache.lookup.insert(key, result.clone());
|
||||
|
||||
let mut stats = self.stats.write().unwrap();
|
||||
stats.cache_entries = cache.lookup.len() as u64;
|
||||
}
|
||||
Some(result)
|
||||
} else {
|
||||
// Track failed lookups
|
||||
let mut stats = self.stats.write().unwrap();
|
||||
stats.failed_lookups += 1;
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh(&self) -> Result<()> {
|
||||
// Refresh the procfs lookup
|
||||
self.procfs_lookup.refresh()?;
|
||||
|
||||
// Update our cache timestamp
|
||||
{
|
||||
let mut cache = self.unified_cache.write().unwrap();
|
||||
cache.last_refresh = Instant::now();
|
||||
// Optionally clear cache to force fresh lookups
|
||||
cache.lookup.clear();
|
||||
}
|
||||
|
||||
debug!("Enhanced process lookup refreshed");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for LookupStats {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
ebpf_hits: self.ebpf_hits,
|
||||
procfs_hits: self.procfs_hits,
|
||||
cache_hits: self.cache_hits,
|
||||
total_lookups: self.total_lookups,
|
||||
ipv4_lookups: self.ipv4_lookups,
|
||||
ipv6_lookups: self.ipv6_lookups,
|
||||
tcp_lookups: self.tcp_lookups,
|
||||
udp_lookups: self.udp_lookups,
|
||||
cache_entries: self.cache_entries,
|
||||
failed_lookups: self.failed_lookups,
|
||||
ebpf_available: self.ebpf_available,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LookupStats {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.total_lookups == 0 {
|
||||
write!(f, "No lookups performed yet")
|
||||
} else {
|
||||
let cache_hit_rate = (self.cache_hits as f64 / self.total_lookups as f64) * 100.0;
|
||||
let ebpf_rate = (self.ebpf_hits as f64 / self.total_lookups as f64) * 100.0;
|
||||
let procfs_rate = (self.procfs_hits as f64 / self.total_lookups as f64) * 100.0;
|
||||
let success_rate = ((self.total_lookups - self.failed_lookups) as f64
|
||||
/ self.total_lookups as f64)
|
||||
* 100.0;
|
||||
|
||||
writeln!(f, "Process Lookup Statistics:")?;
|
||||
writeln!(
|
||||
f,
|
||||
" Total lookups: {} (success: {:.1}%)",
|
||||
self.total_lookups, success_rate
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" Cache: {} hits ({:.1}%)",
|
||||
self.cache_hits, cache_hit_rate
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" eBPF: {} lookups ({:.1}%) | Available: {}",
|
||||
self.ebpf_hits, ebpf_rate, self.ebpf_available
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" procfs: {} lookups ({:.1}%)",
|
||||
self.procfs_hits, procfs_rate
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" Protocols - IPv4: {} | IPv6: {}",
|
||||
self.ipv4_lookups, self.ipv6_lookups
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
" Types - TCP: {} | UDP: {} | Cache entries: {}",
|
||||
self.tcp_lookups, self.udp_lookups, self.cache_entries
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When eBPF is disabled, use a simpler procfs-only implementation
|
||||
#[cfg(not(feature = "ebpf"))]
|
||||
mod procfs_only {
|
||||
use super::*;
|
||||
|
||||
/// Simplified process lookup using only procfs (no eBPF)
|
||||
pub struct EnhancedLinuxProcessLookup {
|
||||
procfs_lookup: LinuxProcessLookup,
|
||||
unified_cache: RwLock<ProcessCache>,
|
||||
stats: RwLock<LookupStats>,
|
||||
}
|
||||
|
||||
// Stub tracker for non-eBPF builds
|
||||
pub struct EbpfSocketTracker;
|
||||
|
||||
impl EbpfSocketTracker {
|
||||
pub fn new() -> anyhow::Result<Option<Self>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn cleanup_stale_entries(&mut self, _stale_threshold_secs: u64) -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn is_healthy(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProcessCache {
|
||||
lookup: HashMap<ConnectionKey, (u32, String)>,
|
||||
last_refresh: Instant,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LookupStats {
|
||||
procfs_hits: u64,
|
||||
cache_hits: u64,
|
||||
total_lookups: u64,
|
||||
ipv4_lookups: u64,
|
||||
ipv6_lookups: u64,
|
||||
tcp_lookups: u64,
|
||||
udp_lookups: u64,
|
||||
cache_entries: u64,
|
||||
failed_lookups: u64,
|
||||
ebpf_available: bool,
|
||||
}
|
||||
|
||||
impl EnhancedLinuxProcessLookup {
|
||||
pub fn new() -> Result<Self> {
|
||||
Self::new_with_config()
|
||||
}
|
||||
|
||||
pub fn new_with_config() -> Result<Self> {
|
||||
let procfs_lookup = LinuxProcessLookup::new()?;
|
||||
|
||||
Ok(Self {
|
||||
procfs_lookup,
|
||||
unified_cache: RwLock::new(ProcessCache {
|
||||
lookup: HashMap::new(),
|
||||
last_refresh: Instant::now() - Duration::from_secs(3600),
|
||||
}),
|
||||
stats: RwLock::new(LookupStats::default()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get diagnostic statistics about lookup performance
|
||||
#[allow(dead_code)]
|
||||
pub fn get_stats(&self) -> LookupStats {
|
||||
self.stats.read().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessLookup for EnhancedLinuxProcessLookup {
|
||||
fn get_process_for_connection(&self, conn: &Connection) -> Option<(u32, String)> {
|
||||
let key = ConnectionKey::from_connection(conn);
|
||||
|
||||
// Update protocol statistics
|
||||
{
|
||||
let mut stats = self.stats.write().unwrap();
|
||||
stats.total_lookups += 1;
|
||||
|
||||
// Track IP version
|
||||
match conn.local_addr.ip() {
|
||||
IpAddr::V4(_) => stats.ipv4_lookups += 1,
|
||||
IpAddr::V6(_) => stats.ipv6_lookups += 1,
|
||||
}
|
||||
|
||||
// Track protocol type
|
||||
match conn.protocol {
|
||||
Protocol::TCP => stats.tcp_lookups += 1,
|
||||
Protocol::UDP => stats.udp_lookups += 1,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// eBPF is never available in this build
|
||||
stats.ebpf_available = false;
|
||||
}
|
||||
|
||||
// Try cache first
|
||||
{
|
||||
let cache = self.unified_cache.read().unwrap();
|
||||
if cache.last_refresh.elapsed() < Duration::from_secs(2)
|
||||
&& let Some(process_info) = cache.lookup.get(&key)
|
||||
{
|
||||
let mut stats = self.stats.write().unwrap();
|
||||
stats.cache_hits += 1;
|
||||
return Some(process_info.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss or stale - use procfs lookup
|
||||
if let Some(result) = self.procfs_lookup.get_process_for_connection(conn) {
|
||||
// Update cache with the result
|
||||
{
|
||||
let mut cache = self.unified_cache.write().unwrap();
|
||||
cache.lookup.insert(key, result.clone());
|
||||
|
||||
let mut stats = self.stats.write().unwrap();
|
||||
stats.cache_entries = cache.lookup.len() as u64;
|
||||
stats.procfs_hits += 1;
|
||||
}
|
||||
Some(result)
|
||||
} else {
|
||||
// Track failed lookups
|
||||
let mut stats = self.stats.write().unwrap();
|
||||
stats.failed_lookups += 1;
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh(&self) -> Result<()> {
|
||||
// Refresh the procfs lookup
|
||||
self.procfs_lookup.refresh()?;
|
||||
|
||||
// Update our cache timestamp
|
||||
{
|
||||
let mut cache = self.unified_cache.write().unwrap();
|
||||
cache.last_refresh = Instant::now();
|
||||
// Optionally clear cache to force fresh lookups
|
||||
cache.lookup.clear();
|
||||
}
|
||||
|
||||
debug!("Enhanced process lookup refreshed");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for LookupStats {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
procfs_hits: self.procfs_hits,
|
||||
cache_hits: self.cache_hits,
|
||||
total_lookups: self.total_lookups,
|
||||
ipv4_lookups: self.ipv4_lookups,
|
||||
ipv6_lookups: self.ipv6_lookups,
|
||||
tcp_lookups: self.tcp_lookups,
|
||||
udp_lookups: self.udp_lookups,
|
||||
cache_entries: self.cache_entries,
|
||||
failed_lookups: self.failed_lookups,
|
||||
ebpf_available: self.ebpf_available,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LookupStats {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.total_lookups == 0 {
|
||||
write!(f, "No lookups performed yet")
|
||||
} else {
|
||||
let cache_hit_rate = (self.cache_hits as f64 / self.total_lookups as f64) * 100.0;
|
||||
let procfs_rate = (self.procfs_hits as f64 / self.total_lookups as f64) * 100.0;
|
||||
let success_rate = ((self.total_lookups - self.failed_lookups) as f64
|
||||
/ self.total_lookups as f64)
|
||||
* 100.0;
|
||||
|
||||
writeln!(f, "Process Lookup Statistics:")?;
|
||||
writeln!(
|
||||
f,
|
||||
" Total lookups: {} (success: {:.1}%)",
|
||||
self.total_lookups, success_rate
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" Cache: {} hits ({:.1}%)",
|
||||
self.cache_hits, cache_hit_rate
|
||||
)?;
|
||||
writeln!(f, " eBPF: Not available (feature disabled)")?;
|
||||
writeln!(
|
||||
f,
|
||||
" procfs: {} lookups ({:.1}%)",
|
||||
self.procfs_hits, procfs_rate
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" Protocols - IPv4: {} | IPv6: {}",
|
||||
self.ipv4_lookups, self.ipv6_lookups
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
" Types - TCP: {} | UDP: {} | Cache entries: {}",
|
||||
self.tcp_lookups, self.udp_lookups, self.cache_entries
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export the appropriate implementation based on feature flag
|
||||
#[cfg(feature = "ebpf")]
|
||||
pub use ebpf_enhanced::*;
|
||||
|
||||
#[cfg(not(feature = "ebpf"))]
|
||||
pub use procfs_only::*;
|
||||
@@ -6,6 +6,10 @@ use std::net::SocketAddr;
|
||||
// Platform-specific modules
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(all(target_os = "linux", feature = "ebpf"))]
|
||||
mod linux_ebpf;
|
||||
#[cfg(all(target_os = "linux", feature = "ebpf"))]
|
||||
mod linux_enhanced;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -14,6 +18,8 @@ mod windows;
|
||||
// Re-export the appropriate implementation
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use linux::LinuxProcessLookup;
|
||||
#[cfg(target_os = "linux")]
|
||||
// pub use linux_enhanced::EnhancedLinuxProcessLookup;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::MacOSProcessLookup;
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -51,12 +57,36 @@ pub fn create_process_lookup_with_pktap_status(
|
||||
_pktap_active: bool,
|
||||
) -> Result<Box<dyn ProcessLookup>> {
|
||||
#[cfg(target_os = "macos")]
|
||||
if _pktap_active {
|
||||
log::info!("Using no-op process lookup - PKTAP provides process metadata");
|
||||
return Ok(Box::new(NoOpProcessLookup));
|
||||
{
|
||||
use crate::network::platform::macos::MacOSProcessLookup;
|
||||
|
||||
if _pktap_active {
|
||||
log::info!("Using no-op process lookup - PKTAP provides process metadata");
|
||||
Ok(Box::new(NoOpProcessLookup))
|
||||
} else {
|
||||
Ok(Box::new(MacOSProcessLookup::new()?))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
#[cfg(feature = "ebpf")]
|
||||
{
|
||||
// Try enhanced lookup first (with eBPF if available), fall back to basic
|
||||
match linux_enhanced::EnhancedLinuxProcessLookup::new() {
|
||||
Ok(enhanced) => {
|
||||
log::info!("Using enhanced Linux process lookup (eBPF + procfs)");
|
||||
return Ok(Box::new(enhanced));
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"Enhanced lookup failed, falling back to basic procfs: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use basic procfs lookup (either as fallback or when eBPF is not enabled)
|
||||
Ok(Box::new(LinuxProcessLookup::new()?))
|
||||
}
|
||||
|
||||
@@ -65,17 +95,19 @@ pub fn create_process_lookup_with_pktap_status(
|
||||
Ok(Box::new(WindowsProcessLookup::new()?))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
Ok(Box::new(MacOSProcessLookup::new()?))
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
|
||||
{
|
||||
Err(anyhow::anyhow!("Unsupported platform"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a basic process lookup (procfs only on Linux) - for testing or fallback
|
||||
#[cfg(target_os = "linux")]
|
||||
#[allow(dead_code)]
|
||||
pub fn create_basic_process_lookup() -> Result<Box<dyn ProcessLookup>> {
|
||||
Ok(Box::new(LinuxProcessLookup::new()?))
|
||||
}
|
||||
|
||||
/// Connection identifier for lookups
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct ConnectionKey {
|
||||
|
||||
@@ -185,6 +185,12 @@ pub struct TlsInfo {
|
||||
pub cipher_suite: Option<u16>,
|
||||
}
|
||||
|
||||
impl Default for TlsInfo {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TlsInfo {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
||||
42
tests/integration_tests.rs
Normal file
42
tests/integration_tests.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
//! Integration tests for rustnet
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux_tests {
|
||||
use rustnet_monitor::network::platform::create_process_lookup_with_pktap_status;
|
||||
|
||||
#[test]
|
||||
fn test_process_lookup_creation() {
|
||||
// Test that we can create a process lookup without panicking
|
||||
let result = create_process_lookup_with_pktap_status(false);
|
||||
assert!(result.is_ok(), "Should be able to create process lookup");
|
||||
}
|
||||
|
||||
#[cfg(feature = "ebpf")]
|
||||
#[test]
|
||||
fn test_ebpf_enhanced_lookup() {
|
||||
// This test verifies that the enhanced lookup can be created
|
||||
// when eBPF feature is enabled
|
||||
let result = create_process_lookup_with_pktap_status(false);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Enhanced lookup should be created successfully"
|
||||
);
|
||||
|
||||
// Just verify we got a lookup instance that can be refreshed
|
||||
let lookup = result.unwrap();
|
||||
let refresh_result = lookup.refresh();
|
||||
assert!(refresh_result.is_ok(), "Refresh should work");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod other_platforms {
|
||||
use rustnet_monitor::network::platform::create_process_lookup_with_pktap_status;
|
||||
|
||||
#[test]
|
||||
fn test_other_platform_lookup() {
|
||||
// Test that other platforms can create process lookups
|
||||
let result = create_process_lookup_with_pktap_status(false);
|
||||
assert!(result.is_ok(), "Should work on other platforms too");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user