From ceca73f70467a04cdef7242ecea0d392d17d4d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=84=CA=9C=CA=80=C9=AAs=E1=B4=9B=E1=B4=8F=E1=B4=98?= =?UTF-8?q?=CA=9C=E1=B4=87=CA=80=20=E1=B4=8D=E1=B4=87=C9=B4=C9=A2?= Date: Thu, 20 Aug 2026 10:21:15 -0400 Subject: [PATCH 1/8] Update dependencies as far as MSRV 1.85 allows --- .github/workflows/00-linux.yml | 2 +- .github/workflows/00-macos.yml | 2 +- .github/workflows/00-windows.yml | 2 +- CHANGELOG.md | 18 + Cargo.toml | 16 +- README.md | 2 +- msrv.lock | 623 +++++++++---------------------- src/capture/activated/mod.rs | 28 +- src/capture/mod.rs | 4 +- src/device.rs | 9 +- src/stream/mod.rs | 10 + src/stream/windows.rs | 51 ++- 12 files changed, 281 insertions(+), 486 deletions(-) diff --git a/.github/workflows/00-linux.yml b/.github/workflows/00-linux.yml index c091b684..193c4d4c 100644 --- a/.github/workflows/00-linux.yml +++ b/.github/workflows/00-linux.yml @@ -19,7 +19,7 @@ jobs: uses: './.github/workflows/01-build-and-test-unix.yml' with: os: 'ubuntu-latest' - toolchain: '1.64' + toolchain: '1.85' msrv: true linux-coverage-stable: diff --git a/.github/workflows/00-macos.yml b/.github/workflows/00-macos.yml index a7e0bea9..23d7f19c 100644 --- a/.github/workflows/00-macos.yml +++ b/.github/workflows/00-macos.yml @@ -19,7 +19,7 @@ jobs: uses: './.github/workflows/01-build-and-test-unix.yml' with: os: 'macos-latest' - toolchain: '1.64' + toolchain: '1.85' msrv: true macos-coverage-stable: diff --git a/.github/workflows/00-windows.yml b/.github/workflows/00-windows.yml index af8927d4..d65efd64 100644 --- a/.github/workflows/00-windows.yml +++ b/.github/workflows/00-windows.yml @@ -22,7 +22,7 @@ jobs: uses: './.github/workflows/01-build-and-test-windows.yml' with: os: 'windows-latest' - toolchain: '1.64' + toolchain: '1.85' msrv: true secrets: NPCAP_OEM_PASSWORD: ${{ secrets.NPCAP_OEM_PASSWORD }} diff --git a/CHANGELOG.md b/CHANGELOG.md index f491f715..ca2e1759 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ ## [Unreleased] +### Changed + +- MSRV is now `1.85`. +- `bitflags` updated from 1.3 to 2.13. `IfFlags` is generated by `bitflags`, so it changes along + with it; in particular its `Debug` output is now `IfFlags(LOOPBACK | UP)` instead of + `LOOPBACK | UP`. +- `errno` updated from 0.2 to 0.3. `Error::ErrnoError` carries an `errno::Errno`, so crates that + construct or match on it have to move to 0.3 as well. +- `windows-sys` updated from 0.36 to 0.61. `HANDLE` is a raw pointer there rather than an `isize`, + which changes the signature of `Capture::get_event` on Windows. A raw pointer is not `Send`, so + a type of your own that stores the returned `HANDLE` no longer derives `Send` and can no longer + be moved to another thread without a wrapper of its own. `PacketStream` is unaffected. + +### Removed + +- `IfFlags::from_bits_unchecked`, which `bitflags` 1 generated. `bitflags` 2 provides + `IfFlags::from_bits_retain` instead. + ## [2.5.0] - 2026-08-15 ### Added diff --git a/Cargo.toml b/Cargo.toml index 2447bd6c..c216b735 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "pcap" -version = "2.5.0" +version = "3.0.0" authors = ["Sean Bowe ", "Wojciech Kozlowski ", "Hideki Sekine"] edition = "2021" -rust-version = "1.64" +rust-version = "1.85" description = "A packet capture API around pcap/wpcap" keywords = ["pcap", "packet", "sniffing"] readme = "README.md" @@ -14,25 +14,25 @@ license = "MIT OR Apache-2.0" build = "build.rs" [dependencies] -bitflags = "1.3" +bitflags = "2.13" libc = "0.2" -errno = "0.2" +errno = "0.3" tokio = { version = "1.0", features = ["net", "rt", "macros", "rt-multi-thread"], optional = true } futures = { version = "0.3", optional = true } gat-std = { version = "0.1.1", optional = true } [target.'cfg(target_os = "windows")'.dependencies] -windows-sys = { version = "0.36.1", features = ["Win32_Foundation", "Win32_Networking_WinSock"] } +windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Networking_WinSock"] } [dev-dependencies] -etherparse = "0.13.0" +etherparse = "0.21.0" once_cell = "1.14.0" -mockall = "0.11.4" +mockall = "0.15.0" tempfile = "3.10" [target.'cfg(target_os = "windows")'.dev-dependencies] eui48 = { version = "1.1", default-features = false } -windows-sys = { version = "0.36.1", features = ["Win32_System_Threading"] } +windows-sys = { version = "0.61", features = ["Win32_System_Threading"] } [target.'cfg(not(target_os = "windows"))'.dev-dependencies] tun-tap = "0.1.3" diff --git a/README.md b/README.md index 75280fc2..771dbfad 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Use the `lending-iter` feature to enable the lending packet iterator. See `lendi ## Minimum Supported Rust Version (MSRV) -This crate uses Rust 2021 and requires a compiler version >= 1.64. +This crate uses Rust 2021 and requires a compiler version >= 1.85. The feature `capture-stream` depends on `tokio = "1.0"`. Therefore, when `capture-stream` is enabled, this crate requires a compiler version new enough to compile the `tokio` crate. diff --git a/msrv.lock b/msrv.lock index fde2c1e7..2148d849 100644 --- a/msrv.lock +++ b/msrv.lock @@ -1,57 +1,33 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] [[package]] -name = "arrayvec" -version = "0.7.4" +name = "anstyle" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] -name = "autocfg" -version = "1.2.0" +name = "arrayvec" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] -name = "backtrace" -version = "0.3.71" +name = "autocfg" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" -dependencies = [ - "addr2line", - "cc", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "bitflags" @@ -61,9 +37,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "byteorder" @@ -83,9 +59,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.95" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] [[package]] name = "cfg-if" @@ -95,9 +75,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cloudabi" @@ -156,60 +136,27 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "difflib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" - [[package]] name = "downcast" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" -[[package]] -name = "either" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" - [[package]] name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "errno" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "cc", "libc", + "windows-sys", ] [[package]] name = "etherparse" -version = "0.13.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "827292ea592108849932ad8e30218f8b1f21c0dfd0696698a18b5d0aed62d990" +checksum = "17304d06addb3283cdc4bd528e42dd95e73c8ee2d6492ffce415e93660885449" dependencies = [ "arrayvec", ] @@ -221,23 +168,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "887418ac5e8d57c2e66e04bdc2fe15f9a5407be20b54a82c86bd0e368b709701" dependencies = [ "regex", - "rustc-serialize", ] [[package]] name = "fastrand" -version = "2.0.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] -name = "float-cmp" -version = "0.9.0" +name = "find-msvc-tools" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" -dependencies = [ - "num-traits", -] +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "fnv" @@ -247,9 +190,12 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fragile" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" +dependencies = [ + "futures-core", +] [[package]] name = "fuchsia-zircon" @@ -275,9 +221,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -290,9 +236,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -300,15 +246,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -317,38 +263,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 3.0.3", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -358,7 +304,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -383,16 +328,21 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.28.1" +name = "getrandom" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if 1.0.4", + "libc", + "r-efi", +] [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "iovec" @@ -403,15 +353,6 @@ dependencies = [ "libc", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "kernel32-sys" version = "0.2.2" @@ -424,31 +365,31 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ - "cfg-if 1.0.0", - "windows-targets 0.52.5", + "cfg-if 1.0.4", + "windows-link", ] [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "lock_api" @@ -461,9 +402,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "maybe-uninit" @@ -473,9 +414,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" -version = "2.7.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memoffset" @@ -486,15 +427,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "miniz_oxide" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" -dependencies = [ - "adler", -] - [[package]] name = "mio" version = "0.6.23" @@ -516,13 +448,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -550,14 +482,13 @@ dependencies = [ [[package]] name = "mockall" -version = "0.11.4" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" +checksum = "1a6ceddfe3ce334925e96bf420fdb2dcee5bed6c632a168ece622676dadeaf8a" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", "downcast", "fragile", - "lazy_static", "mockall_derive", "predicates", "predicates-tree", @@ -565,14 +496,14 @@ dependencies = [ [[package]] name = "mockall_derive" -version = "0.11.4" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" +checksum = "9cfe16fbe8a314aeec0b861ac24e60b1e123e97634bab045475b9d6a18416fd8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.119", ] [[package]] @@ -586,45 +517,21 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "normalize-line-endings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" - -[[package]] -name = "num-traits" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" -dependencies = [ - "autocfg", -] - [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", ] -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "parking_lot" @@ -654,13 +561,13 @@ dependencies = [ [[package]] name = "pcap" -version = "2.5.0" +version = "3.0.0" dependencies = [ - "bitflags 1.3.2", - "errno 0.2.8", + "bitflags 2.13.1", + "errno", "etherparse", "eui48", - "futures 0.3.30", + "futures 0.3.34", "gat-std", "libc", "libloading", @@ -669,54 +576,44 @@ dependencies = [ "pkg-config", "regex", "tempfile", - "tokio 1.37.0", + "tokio 1.53.1", "tun-tap", - "windows-sys 0.36.1", + "windows-sys", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "predicates" -version = "2.1.5" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" dependencies = [ - "difflib", - "float-cmp", - "itertools", - "normalize-line-endings", + "anstyle", "predicates-core", - "regex", ] [[package]] name = "predicates-core" -version = "1.0.5" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" [[package]] name = "predicates-tree" -version = "1.0.7" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" dependencies = [ "predicates-core", "termtree", @@ -724,22 +621,28 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "redox_syscall" version = "0.1.57" @@ -748,9 +651,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "regex" -version = "1.9.6" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -760,9 +663,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.9" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -771,21 +674,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc-serialize" -version = "0.3.25" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rustc_version" @@ -798,15 +689,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.33" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3cc72858054fcff6d7dea32df2aeaee6a7c24227366d7ea429aada2f26b16ad" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.5.0", - "errno 0.3.8", + "bitflags 2.13.1", + "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -836,14 +727,17 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -856,12 +750,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -877,9 +771,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.60" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -888,21 +793,22 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "cfg-if 1.0.0", "fastrand", + "getrandom", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] name = "termtree" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "tokio" @@ -930,18 +836,16 @@ dependencies = [ [[package]] name = "tokio" -version = "1.37.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ - "backtrace", "libc", - "mio 0.8.11", - "num_cpus", + "mio 1.2.2", "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1018,13 +922,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 3.0.3", ] [[package]] @@ -1147,15 +1051,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "winapi" @@ -1192,187 +1096,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.5", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" -dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" - [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/src/capture/activated/mod.rs b/src/capture/activated/mod.rs index ab8411d1..a0441532 100644 --- a/src/capture/activated/mod.rs +++ b/src/capture/activated/mod.rs @@ -1177,7 +1177,6 @@ mod tests { } #[test] - #[should_panic = "panic in callback"] fn panic_in_pcap_loop() { let _m = RAWMTX.lock(); @@ -1209,9 +1208,16 @@ mod tests { .withf_st(move |arg1| *arg1 == pcap) .return_once_st(move |_| {}); - capture - .for_each(None, |_| panic!("panic in callback")) - .unwrap(); + // Catch the unwind here instead of letting it leave the test. mockall skips its + // checkpoint while a thread is panicking, so the expectations above would stay + // registered for whichever test runs next to drop on the wrong thread. + let panic = catch_unwind(AssertUnwindSafe(|| { + capture + .for_each(None, |_| panic!("panic in callback")) + .unwrap() + })) + .unwrap_err(); + assert_eq!(*panic.downcast_ref::<&str>().unwrap(), "panic in callback"); } #[test] @@ -1309,7 +1315,6 @@ mod tests { } #[test] - #[should_panic = "panic in callback"] fn panic_in_pcap_dispatch() { let _m = RAWMTX.lock(); @@ -1341,9 +1346,16 @@ mod tests { .withf_st(move |arg1| *arg1 == pcap) .return_once_st(move |_| {}); - capture - .dispatch(None, |_| panic!("panic in callback")) - .unwrap(); + // Catch the unwind here instead of letting it leave the test. mockall skips its + // checkpoint while a thread is panicking, so the expectations above would stay + // registered for whichever test runs next to drop on the wrong thread. + let panic = catch_unwind(AssertUnwindSafe(|| { + capture + .dispatch(None, |_| panic!("panic in callback")) + .unwrap() + })) + .unwrap_err(); + assert_eq!(*panic.downcast_ref::<&str>().unwrap(), "panic in callback"); } #[test] diff --git a/src/capture/mod.rs b/src/capture/mod.rs index 330dd3d7..4362d818 100644 --- a/src/capture/mod.rs +++ b/src/capture/mod.rs @@ -299,10 +299,10 @@ mod tests { let ctx = raw::pcap_getevent_context(); ctx.expect() .withf_st(move |arg1| *arg1 == pcap) - .return_once(|_| 5); + .return_once(|_| 5 as HANDLE); let handle = unsafe { capture.get_event() }; - assert_eq!(handle, 5); + assert_eq!(handle, 5 as HANDLE); } #[test] diff --git a/src/device.rs b/src/device.rs index d9b6cab5..f1653ed9 100644 --- a/src/device.rs +++ b/src/device.rs @@ -12,6 +12,7 @@ use crate::{ bitflags! { /// Network device flags. + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct IfFlags: u32 { /// Set if the device is a loopback interface const LOOPBACK = raw::PCAP_IF_LOOPBACK; @@ -266,7 +267,7 @@ impl Address { return None; } - match (*ptr).sa_family as u32 { + match (*ptr).sa_family { WinSock::AF_INET => { let ptr: *const WinSock::SOCKADDR_IN = std::mem::transmute(ptr); let addr: [u8; 4] = ((*ptr).sin_addr.S_un.S_addr).to_ne_bytes(); @@ -371,8 +372,7 @@ mod tests { impl InetAddressV4 for WinSock::SOCKADDR_IN { fn new() -> Self { let mut addr: Self = unsafe { std::mem::zeroed() }; - // The cast is only necessary due to a bug in windows_sys@v0.36.1 - addr.sin_family = WinSock::AF_INET as u16; + addr.sin_family = WinSock::AF_INET; addr } @@ -417,8 +417,7 @@ mod tests { impl InetAddressV6 for WinSock::SOCKADDR_IN6 { fn new() -> Self { let mut addr: Self = unsafe { std::mem::zeroed() }; - // The cast is only necessary due to a bug in windows_sys@v0.36.1 - addr.sin6_family = WinSock::AF_INET6 as u16; + addr.sin6_family = WinSock::AF_INET6; unsafe { addr.sin6_addr.u.Byte[0] = 0xFE; addr.sin6_addr.u.Byte[1] = 0x80; diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 2488baac..969c698c 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -37,6 +37,8 @@ mod tests { raw::testmod::{as_pcap_t, RAWMTX}, }; + use super::PacketStream; + #[test] fn test_stream_error() { let _m = RAWMTX.lock(); @@ -51,4 +53,12 @@ mod tests { let result = capture.stream(Codec); assert!(result.is_err()); } + + // On Windows the stream drives an event HANDLE, which is a raw pointer and so not Send on + // its own. Callers hand the stream to an executor, so make sure it stays Send. + #[test] + fn test_stream_is_send() { + fn assert_send() {} + assert_send::>(); + } } diff --git a/src/stream/windows.rs b/src/stream/windows.rs index 11656c8f..bc1e9070 100644 --- a/src/stream/windows.rs +++ b/src/stream/windows.rs @@ -25,7 +25,7 @@ pub struct PacketStream { impl PacketStream { pub(crate) fn new(capture: Capture, codec: C) -> Result { Ok(Self { - event_handle: EventHandle::new(&capture), + event_handle: EventHandle::new(), capture, codec, }) @@ -49,7 +49,7 @@ impl futures::Stream for PacketStream Ok(codec.decode(p)), @@ -64,15 +64,32 @@ impl futures::Stream for PacketStream HANDLE { + self.0 + } +} + enum EventHandleState { /// We haven't started waiting for an event yet. Init, @@ -83,27 +100,29 @@ enum EventHandleState { } impl EventHandle { - pub fn new(capture: &Capture) -> Self { + pub fn new() -> Self { Self { - handle: unsafe { - // SAFETY: PacketStream stores the handle before the capture, - // so the handle will be dropped before the capture. - capture.get_event() - }, state: EventHandleState::Init, } } - pub fn poll_ready(&mut self, cx: &mut task::Context) -> Poll<()> { + pub fn poll_ready( + &mut self, + cx: &mut task::Context, + capture: &Capture, + ) -> Poll<()> { loop { match self.state { EventHandleState::Init => { - let handle = self.handle; + // SAFETY: the event belongs to the capture's pcap context, + // which is borrowed for this call, so the handle is live + // when it is handed to the blocking task. + let handle = SendHandle(unsafe { capture.get_event() }); self.state = EventHandleState::Polling(tokio::task::spawn_blocking(move || { const INFINITE: u32 = !0; unsafe { - WaitForSingleObject(handle, INFINITE); + WaitForSingleObject(handle.get(), INFINITE); } })); } From d7cf35bfc22a7c8c471759b2d339071ff2c0bd38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=84=CA=9C=CA=80=C9=AAs=E1=B4=9B=E1=B4=8F=E1=B4=98?= =?UTF-8?q?=CA=9C=E1=B4=87=CA=80=20=E1=B4=8D=E1=B4=87=C9=B4=C9=A2?= Date: Thu, 20 Aug 2026 11:13:32 -0400 Subject: [PATCH 2/8] Bump the Rust edition to 2024 --- CHANGELOG.md | 1 + Cargo.toml | 2 +- README.md | 2 +- build.rs | 15 ++---- examples/winevt.rs | 4 +- src/capture/activated/active.rs | 5 +- src/capture/activated/dead.rs | 5 +- src/capture/activated/iterator.rs | 8 +-- src/capture/activated/mod.rs | 29 +++++------ src/capture/activated/offline.rs | 14 ++--- src/capture/inactive.rs | 5 +- src/capture/mod.rs | 12 ++--- src/capture/selectable.rs | 7 +-- src/device.rs | 85 +++++++++++++++++-------------- src/lib.rs | 6 +-- src/linktype.rs | 2 +- src/raw.rs | 22 ++++---- src/sendqueue/windows.rs | 3 +- src/stream/mod.rs | 6 +-- src/stream/unix.rs | 4 +- src/stream/windows.rs | 4 +- tests/capture/activated/mod.rs | 2 +- tests/lib.rs | 2 +- tests/tap_tests.rs | 4 +- 24 files changed, 126 insertions(+), 123 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca2e1759..8f5cb309 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changed +- Rust Edition is now `2024`. - MSRV is now `1.85`. - `bitflags` updated from 1.3 to 2.13. `IfFlags` is generated by `bitflags`, so it changes along with it; in particular its `Debug` output is now `IfFlags(LOOPBACK | UP)` instead of diff --git a/Cargo.toml b/Cargo.toml index c216b735..6492fa16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pcap" version = "3.0.0" authors = ["Sean Bowe ", "Wojciech Kozlowski ", "Hideki Sekine"] -edition = "2021" +edition = "2024" rust-version = "1.85" description = "A packet capture API around pcap/wpcap" keywords = ["pcap", "packet", "sniffing"] diff --git a/README.md b/README.md index 771dbfad..20209eef 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Use the `lending-iter` feature to enable the lending packet iterator. See `lendi ## Minimum Supported Rust Version (MSRV) -This crate uses Rust 2021 and requires a compiler version >= 1.85. +This crate uses Rust 2024 and requires a compiler version >= 1.85. The feature `capture-stream` depends on `tokio = "1.0"`. Therefore, when `capture-stream` is enabled, this crate requires a compiler version new enough to compile the `tokio` crate. diff --git a/build.rs b/build.rs index 46745d7f..73dd27bc 100644 --- a/build.rs +++ b/build.rs @@ -101,16 +101,11 @@ fn get_libpcap_version(libdirpath: Option) -> Result { @@ -60,7 +61,7 @@ mod tests { capture::testmod::test_capture, raw::{ mock_ffi::*, - testmod::{as_pcap_t, geterr_expect, RAWMTX}, + testmod::{RAWMTX, as_pcap_t, geterr_expect}, }, }; diff --git a/src/capture/activated/dead.rs b/src/capture/activated/dead.rs index 5efbbf06..c65db1e6 100644 --- a/src/capture/activated/dead.rs +++ b/src/capture/activated/dead.rs @@ -1,9 +1,10 @@ use std::ptr::NonNull; use crate::{ + Error, capture::{Capture, Dead}, linktype::Linktype, - raw, Error, + raw, }; #[cfg(libpcap_1_5_0)] @@ -38,7 +39,7 @@ mod tests { #[cfg(libpcap_1_5_0)] use mockall::predicate; - use crate::raw::testmod::{as_pcap_t, RAWMTX}; + use crate::raw::testmod::{RAWMTX, as_pcap_t}; use super::*; diff --git a/src/capture/activated/iterator.rs b/src/capture/activated/iterator.rs index f7a5080f..d0d975d1 100644 --- a/src/capture/activated/iterator.rs +++ b/src/capture/activated/iterator.rs @@ -1,7 +1,7 @@ use crate::{ + Error, capture::{Activated, Capture}, codec::PacketCodec, - Error, }; /// Implement an Iterator of Packet @@ -76,14 +76,14 @@ mod lending_iter { mod tests { use crate::{ capture::{ - activated::testmod::{next_ex_expect, PACKET}, - testmod::test_capture, Active, Offline, + activated::testmod::{PACKET, next_ex_expect}, + testmod::test_capture, }, codec::testmod::Codec, raw::{ self, - testmod::{as_pcap_t, geterr_expect, RAWMTX}, + testmod::{RAWMTX, as_pcap_t, geterr_expect}, }, }; diff --git a/src/capture/activated/mod.rs b/src/capture/activated/mod.rs index a0441532..2f0c4476 100644 --- a/src/capture/activated/mod.rs +++ b/src/capture/activated/mod.rs @@ -8,7 +8,7 @@ use std::{ convert::TryInto, ffi::CString, fmt, mem, - panic::{catch_unwind, resume_unwind, AssertUnwindSafe}, + panic::{AssertUnwindSafe, catch_unwind, resume_unwind}, path::Path, ptr::{self, NonNull}, slice, @@ -19,11 +19,12 @@ use std::{ use std::os::unix::io::RawFd; use crate::{ + Error, capture::{Activated, Capture, PcapHandle}, codec::PacketCodec, linktype::Linktype, packet::{Packet, PacketHeader}, - raw, Error, + raw, }; use iterator::PacketIter; @@ -115,11 +116,10 @@ impl Capture { /// Unsafe, because the returned Savefile assumes it is the sole owner of the file descriptor. #[cfg(not(windows))] pub unsafe fn savefile_raw_fd(&self, fd: RawFd) -> Result { - open_raw_fd(fd, b'w').and_then(|file| { - let handle_opt = NonNull::::new(raw::pcap_dump_fopen( - self.handle.as_ptr(), - file, - )); + unsafe { open_raw_fd(fd, b'w') }.and_then(|file| { + let handle_opt = NonNull::::new(unsafe { + raw::pcap_dump_fopen(self.handle.as_ptr(), file) + }); let handle = self .check_err(handle_opt.is_some()) .map(|_| handle_opt.unwrap())?; @@ -539,8 +539,7 @@ unsafe impl Send for BpfProgram {} /// Unsafe, because the returned FILE assumes it is the sole owner of the file descriptor. pub unsafe fn open_raw_fd(fd: RawFd, mode: u8) -> Result<*mut libc::FILE, Error> { let mode = [mode, 0]; - libc::fdopen(fd, mode.as_ptr() as _) - .as_mut() + unsafe { libc::fdopen(fd, mode.as_ptr() as _).as_mut() } .map(|f| f as _) .ok_or(Error::InvalidRawFd) } @@ -601,11 +600,11 @@ mod testmod { mod tests { use crate::{ capture::{ - activated::testmod::{next_ex_expect, PACKET}, - testmod::test_capture, Active, Capture, Offline, + activated::testmod::{PACKET, next_ex_expect}, + testmod::test_capture, }, - raw::testmod::{as_pcap_dumper_t, as_pcap_t, geterr_expect, RAWMTX}, + raw::testmod::{RAWMTX, as_pcap_dumper_t, as_pcap_t, geterr_expect}, }; use super::*; @@ -718,11 +717,7 @@ mod tests { } fn maybe(a: bool) -> Capture { - if a { - test1().into() - } else { - test2().into() - } + if a { test1().into() } else { test2().into() } } fn also_maybe(a: &mut Capture) { diff --git a/src/capture/activated/offline.rs b/src/capture/activated/offline.rs index 950d3894..9f786690 100644 --- a/src/capture/activated/offline.rs +++ b/src/capture/activated/offline.rs @@ -4,8 +4,9 @@ use std::path::Path; use std::os::unix::io::RawFd; use crate::{ + Error, capture::{Capture, Offline}, - raw, Error, + raw, }; #[cfg(libpcap_1_5_0)] @@ -41,8 +42,9 @@ impl Capture { /// Unsafe, because the returned Capture assumes it is the sole owner of the file descriptor. #[cfg(not(windows))] pub unsafe fn from_raw_fd(fd: RawFd) -> Result, Error> { - open_raw_fd(fd, b'r') - .and_then(|file| Capture::new_raw(None, |_, err| raw::pcap_fopen_offline(file, err))) + unsafe { open_raw_fd(fd, b'r') }.and_then(|file| { + Capture::new_raw(None, |_, err| unsafe { raw::pcap_fopen_offline(file, err) }) + }) } /// Opens an offline capture handle from a pcap dump file, given a file descriptor. Takes an @@ -56,8 +58,8 @@ impl Capture { fd: RawFd, precision: Precision, ) -> Result, Error> { - open_raw_fd(fd, b'r').and_then(|file| { - Capture::new_raw(None, |_, err| { + unsafe { open_raw_fd(fd, b'r') }.and_then(|file| { + Capture::new_raw(None, |_, err| unsafe { raw::pcap_fopen_offline_with_tstamp_precision(file, precision as _, err) }) }) @@ -86,7 +88,7 @@ mod tests { use crate::{ capture::testmod::test_capture, - raw::testmod::{as_pcap_t, RAWMTX}, + raw::testmod::{RAWMTX, as_pcap_t}, }; use super::*; diff --git a/src/capture/inactive.rs b/src/capture/inactive.rs index ab6bcd72..ee6ef636 100644 --- a/src/capture/inactive.rs +++ b/src/capture/inactive.rs @@ -1,9 +1,10 @@ use std::mem; use crate::{ + Error, capture::{Active, Capture, Inactive}, device::Device, - raw, Error, + raw, }; #[cfg(libpcap_1_5_0)] @@ -185,7 +186,7 @@ pub enum TimestampType { mod tests { use crate::{ capture::testmod::test_capture, - raw::testmod::{as_pcap_t, geterr_expect, RAWMTX}, + raw::testmod::{RAWMTX, as_pcap_t, geterr_expect}, }; use super::*; diff --git a/src/capture/mod.rs b/src/capture/mod.rs index 4362d818..a0f69b82 100644 --- a/src/capture/mod.rs +++ b/src/capture/mod.rs @@ -14,7 +14,7 @@ use std::{ #[cfg(windows)] use windows_sys::Win32::Foundation::HANDLE; -use crate::{raw, Error}; +use crate::{Error, raw}; /// Phantom type representing an inactive capture handle. pub enum Inactive {} @@ -184,15 +184,11 @@ impl Capture { /// a kernel object owned by the `Capture`'s pcap context. #[cfg(windows)] pub unsafe fn get_event(&self) -> HANDLE { - raw::pcap_getevent(self.handle.as_ptr()) + unsafe { raw::pcap_getevent(self.handle.as_ptr()) } } fn check_err(&self, success: bool) -> Result<(), Error> { - if success { - Ok(()) - } else { - Err(self.get_err()) - } + if success { Ok(()) } else { Err(self.get_err()) } } fn get_err(&self) -> Error { @@ -247,7 +243,7 @@ pub mod testmod { mod tests { use crate::{ capture::testmod::test_capture, - raw::testmod::{as_pcap_t, RAWMTX}, + raw::testmod::{RAWMTX, as_pcap_t}, }; use super::*; diff --git a/src/capture/selectable.rs b/src/capture/selectable.rs index 55b0fe9d..f350d316 100644 --- a/src/capture/selectable.rs +++ b/src/capture/selectable.rs @@ -1,8 +1,9 @@ use std::os::unix::io::{AsRawFd, RawFd}; use crate::{ + Error, capture::{Activated, Capture, State}, - raw, Error, + raw, }; /// Newtype [`Capture`] wrapper that exposes `pcap_get_selectable_fd()`. @@ -34,8 +35,8 @@ impl AsRawFd for SelectableCapture { #[cfg(test)] mod tests { use crate::{ - capture::{testmod::test_capture, Active}, - raw::testmod::{as_pcap_t, RAWMTX}, + capture::{Active, testmod::test_capture}, + raw::testmod::{RAWMTX, as_pcap_t}, }; use super::*; diff --git a/src/device.rs b/src/device.rs index f1653ed9..65d914cd 100644 --- a/src/device.rs +++ b/src/device.rs @@ -6,8 +6,9 @@ use bitflags::bitflags; use windows_sys::Win32::Networking::WinSock; use crate::{ + Error, capture::{Active, Capture}, - cstr_to_string, raw, Error, + cstr_to_string, raw, }; bitflags! { @@ -176,13 +177,13 @@ impl Device { { let all_devs = Error::with_errbuf(|err| { let mut all_devs: *mut raw::pcap_if_t = ptr::null_mut(); - if raw::pcap_findalldevs(&mut all_devs, err) != 0 { - return Err(Error::new(err)); + if unsafe { raw::pcap_findalldevs(&mut all_devs, err) } != 0 { + return Err(unsafe { Error::new(err) }); } Ok(all_devs) })?; let result = func(all_devs); - raw::pcap_freealldevs(all_devs); + unsafe { raw::pcap_freealldevs(all_devs) }; result } } @@ -223,21 +224,23 @@ impl Address { unsafe fn new_vec(mut ptr: *const raw::pcap_addr_t) -> Vec
{ let mut vec = Vec::new(); while !ptr.is_null() { - if let Some(addr) = Address::new(ptr) { + if let Some(addr) = unsafe { Address::new(ptr) } { vec.push(addr); } - ptr = (*ptr).next; + ptr = unsafe { (*ptr).next }; } vec } unsafe fn new(ptr: *const raw::pcap_addr_t) -> Option
{ - Self::convert_sockaddr((*ptr).addr).map(|addr| Address { - addr, - netmask: Self::convert_sockaddr((*ptr).netmask), - broadcast_addr: Self::convert_sockaddr((*ptr).broadaddr), - dst_addr: Self::convert_sockaddr((*ptr).dstaddr), - }) + unsafe { + Self::convert_sockaddr((*ptr).addr).map(|addr| Address { + addr, + netmask: Self::convert_sockaddr((*ptr).netmask), + broadcast_addr: Self::convert_sockaddr((*ptr).broadaddr), + dst_addr: Self::convert_sockaddr((*ptr).dstaddr), + }) + } } #[cfg(not(windows))] @@ -246,18 +249,20 @@ impl Address { return None; } - match (*ptr).sa_family as i32 { - libc::AF_INET => { - let ptr: *const libc::sockaddr_in = std::mem::transmute(ptr); - Some(IpAddr::V4(u32::from_be((*ptr).sin_addr.s_addr).into())) - } + unsafe { + match (*ptr).sa_family as i32 { + libc::AF_INET => { + let ptr: *const libc::sockaddr_in = std::mem::transmute(ptr); + Some(IpAddr::V4(u32::from_be((*ptr).sin_addr.s_addr).into())) + } - libc::AF_INET6 => { - let ptr: *const libc::sockaddr_in6 = std::mem::transmute(ptr); - Some(IpAddr::V6((*ptr).sin6_addr.s6_addr.into())) - } + libc::AF_INET6 => { + let ptr: *const libc::sockaddr_in6 = std::mem::transmute(ptr); + Some(IpAddr::V6((*ptr).sin6_addr.s6_addr.into())) + } - _ => None, + _ => None, + } } } @@ -267,19 +272,21 @@ impl Address { return None; } - match (*ptr).sa_family { - WinSock::AF_INET => { - let ptr: *const WinSock::SOCKADDR_IN = std::mem::transmute(ptr); - let addr: [u8; 4] = ((*ptr).sin_addr.S_un.S_addr).to_ne_bytes(); - Some(IpAddr::from(addr)) - } - WinSock::AF_INET6 => { - let ptr: *const WinSock::SOCKADDR_IN6 = std::mem::transmute(ptr); - let addr = (*ptr).sin6_addr.u.Byte; - Some(IpAddr::from(addr)) - } + unsafe { + match (*ptr).sa_family { + WinSock::AF_INET => { + let ptr: *const WinSock::SOCKADDR_IN = std::mem::transmute(ptr); + let addr: [u8; 4] = ((*ptr).sin_addr.S_un.S_addr).to_ne_bytes(); + Some(IpAddr::from(addr)) + } + WinSock::AF_INET6 => { + let ptr: *const WinSock::SOCKADDR_IN6 = std::mem::transmute(ptr); + let addr = (*ptr).sin6_addr.u.Byte; + Some(IpAddr::from(addr)) + } - _ => None, + _ => None, + } } } } @@ -288,7 +295,7 @@ impl Address { mod tests { use std::ffi::CString; - use crate::raw::testmod::{as_pcap_t, RAWMTX}; + use crate::raw::testmod::{RAWMTX, as_pcap_t}; use super::*; @@ -307,8 +314,8 @@ mod tests { impl Sockaddr { fn as_mut_ptr(&mut self) -> *mut libc::sockaddr { match self { - Sockaddr::SockaddrIn(ref mut sin) => sin as *mut _ as _, - Sockaddr::SockaddrIn6(ref mut sin6) => sin6 as *mut _ as _, + Sockaddr::SockaddrIn(sin) => sin as *mut _ as _, + Sockaddr::SockaddrIn6(sin6) => sin6 as *mut _ as _, } } @@ -318,8 +325,8 @@ mod tests { let family = family as libc::sa_family_t; match self { - Sockaddr::SockaddrIn(ref mut sin) => sin.sin_family = family, - Sockaddr::SockaddrIn6(ref mut sin6) => sin6.sin6_family = family, + Sockaddr::SockaddrIn(sin) => sin.sin_family = family, + Sockaddr::SockaddrIn6(sin6) => sin6.sin6_family = family, } } } diff --git a/src/lib.rs b/src/lib.rs index f562cd2f..d99fc976 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ mod packet; pub use capture::activated::open_raw_fd; pub use capture::{ activated::{ - iterator::PacketIter, BpfInstruction, BpfProgram, BreakLoop, Direction, Savefile, Stat, + BpfInstruction, BpfProgram, BreakLoop, Direction, Savefile, Stat, iterator::PacketIter, }, inactive::TimestampType, {Activated, Active, Capture, Dead, Inactive, Offline, Precision, State}, @@ -135,7 +135,7 @@ pub enum Error { impl Error { unsafe fn new(ptr: *const libc::c_char) -> Error { - match cstr_to_string(ptr) { + match unsafe { cstr_to_string(ptr) } { Err(e) => e as Error, Ok(string) => PcapError(string.unwrap_or_default()), } @@ -154,7 +154,7 @@ unsafe fn cstr_to_string(ptr: *const libc::c_char) -> Result, Err let string = if ptr.is_null() { None } else { - Some(CStr::from_ptr(ptr as _).to_str()?.to_owned()) + Some(unsafe { CStr::from_ptr(ptr as _) }.to_str()?.to_owned()) }; Ok(string) } diff --git a/src/linktype.rs b/src/linktype.rs index 26044e89..bf200296 100644 --- a/src/linktype.rs +++ b/src/linktype.rs @@ -1,6 +1,6 @@ use std::ffi::CString; -use crate::{cstr_to_string, raw, Error}; +use crate::{Error, cstr_to_string, raw}; /// This is a datalink link type. /// diff --git a/src/raw.rs b/src/raw.rs index 926562d4..c8371e19 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -109,7 +109,7 @@ pub type pcap_handler = pub mod ffi { use super::*; - extern "C" { + unsafe extern "C" { // [OBSOLETE] pub fn pcap_lookupdev(arg1: *mut c_char) -> *mut c_char; // pub fn pcap_lookupnet(arg1: *const c_char, arg2: *mut c_uint, arg3: *mut c_uint, // arg4: *mut c_char) -> c_int; @@ -197,7 +197,7 @@ pub mod ffi { } #[cfg(libpcap_1_2_1)] - extern "C" { + unsafe extern "C" { // pub fn pcap_free_tstamp_types(arg1: *mut c_int) -> (); // pub fn pcap_list_tstamp_types(arg1: *mut pcap_t, arg2: *mut *mut c_int) -> c_int; // pub fn pcap_tstamp_type_name_to_val(arg1: *const c_char) -> c_int; @@ -207,7 +207,7 @@ pub mod ffi { } #[cfg(libpcap_1_5_0)] - extern "C" { + unsafe extern "C" { // pub fn pcap_get_tstamp_precision(arg1: *mut pcap_t) -> c_int; pub fn pcap_open_dead_with_tstamp_precision( arg1: c_int, @@ -224,12 +224,12 @@ pub mod ffi { } #[cfg(libpcap_1_7_2)] - extern "C" { + unsafe extern "C" { pub fn pcap_dump_open_append(arg1: *mut pcap_t, arg2: *const c_char) -> *mut pcap_dumper_t; } #[cfg(libpcap_1_9_0)] - extern "C" { + unsafe extern "C" { // pcap_bufsize // pcap_createsrcstr // pcap_dump_ftell64 @@ -246,12 +246,12 @@ pub mod ffi { } #[cfg(libpcap_1_9_1)] - extern "C" { + unsafe extern "C" { // pcap_datalink_val_to_description_or_dlt } #[cfg(libpcap_1_10_0)] - extern "C" { + unsafe extern "C" { // pcap_init // pcap_remoteact_accept_ex } @@ -265,7 +265,7 @@ pub mod ffi_unix { use super::*; #[link(name = "pcap")] - extern "C" { + unsafe extern "C" { // pub fn pcap_inject(arg1: *mut pcap_t, arg2: *const c_void, arg3: size_t) -> c_int; pub fn pcap_set_rfmon(arg1: *mut pcap_t, arg2: c_int) -> c_int; pub fn pcap_get_selectable_fd(arg1: *mut pcap_t) -> c_int; @@ -278,7 +278,7 @@ pub mod ffi_unix { #[cfg(libpcap_1_5_0)] #[link(name = "pcap")] - extern "C" { + unsafe extern "C" { pub fn pcap_fopen_offline_with_tstamp_precision( arg1: *mut FILE, arg2: c_uint, @@ -293,7 +293,7 @@ pub mod ffi_macos { use super::*; #[cfg(libpcap_1_5_3)] - extern "C" { + unsafe extern "C" { pub fn pcap_set_want_pktap(arg1: *mut pcap_t, arg2: c_int) -> c_int; } } @@ -308,7 +308,7 @@ pub mod ffi_windows { pub const WINPCAP_MINTOCOPY_DEFAULT: c_int = 16000; #[link(name = "wpcap")] - extern "C" { + unsafe extern "C" { pub fn pcap_setmintocopy(arg1: *mut pcap_t, arg2: c_int) -> c_int; pub fn pcap_getevent(p: *mut pcap_t) -> HANDLE; pub fn pcap_sendqueue_alloc(memsize: c_uint) -> *mut pcap_send_queue; diff --git a/src/sendqueue/windows.rs b/src/sendqueue/windows.rs index 4708c783..5d59895e 100644 --- a/src/sendqueue/windows.rs +++ b/src/sendqueue/windows.rs @@ -10,8 +10,9 @@ use std::io::IoSlice; use std::ptr::NonNull; use crate::{ + Error, capture::{Active, Capture}, - raw, Error, + raw, }; /// Representation of a batch of packets that can be transferred in a single call using diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 969c698c..e57ea646 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -9,9 +9,9 @@ pub mod windows; pub use windows::PacketStream; use crate::{ + Error, capture::{Activated, Capture}, codec::PacketCodec, - Error, }; impl Capture { @@ -32,9 +32,9 @@ impl Capture { #[cfg(test)] mod tests { use crate::{ - capture::{testmod::test_capture, Active}, + capture::{Active, testmod::test_capture}, codec::testmod::Codec, - raw::testmod::{as_pcap_t, RAWMTX}, + raw::testmod::{RAWMTX, as_pcap_t}, }; use super::PacketStream; diff --git a/src/stream/unix.rs b/src/stream/unix.rs index 538ae9de..3bd30942 100644 --- a/src/stream/unix.rs +++ b/src/stream/unix.rs @@ -10,9 +10,9 @@ use futures::ready; use tokio::io::unix::AsyncFd; use crate::{ - capture::{selectable::SelectableCapture, Activated, Capture}, - codec::PacketCodec, Error, + capture::{Activated, Capture, selectable::SelectableCapture}, + codec::PacketCodec, }; /// Implement Stream for async use of pcap diff --git a/src/stream/windows.rs b/src/stream/windows.rs index bc1e9070..d025898e 100644 --- a/src/stream/windows.rs +++ b/src/stream/windows.rs @@ -5,14 +5,14 @@ use std::marker::Unpin; use std::pin::Pin; use std::task::{self, Poll}; -use futures::{ready, FutureExt}; +use futures::{FutureExt, ready}; use tokio::task::JoinHandle; use windows_sys::Win32::{Foundation::HANDLE, System::Threading::WaitForSingleObject}; use crate::{ + Error, capture::{Activated, Capture}, codec::PacketCodec, - Error, }; /// Implement Stream for async use of pcap diff --git a/tests/capture/activated/mod.rs b/tests/capture/activated/mod.rs index 1de66490..380ef996 100644 --- a/tests/capture/activated/mod.rs +++ b/tests/capture/activated/mod.rs @@ -4,7 +4,7 @@ use tempfile::TempDir; use pcap::{Capture, Linktype}; -use crate::{capture_from_test_file, Packets}; +use crate::{Packets, capture_from_test_file}; #[test] fn read_packet_with_full_data() { diff --git a/tests/lib.rs b/tests/lib.rs index a475beb8..14051d27 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -175,7 +175,7 @@ fn test_raw_fd_api() { #[cfg(libpcap_1_5_0)] unsafe fn from_raw_fd_with_precision(fd: RawFd, precision: Precision) -> Capture { - Capture::from_raw_fd_with_precision(fd, precision).unwrap() + unsafe { Capture::from_raw_fd_with_precision(fd, precision).unwrap() } } #[cfg(not(libpcap_1_5_0))] diff --git a/tests/tap_tests.rs b/tests/tap_tests.rs index 5f031fcb..991fc034 100644 --- a/tests/tap_tests.rs +++ b/tests/tap_tests.rs @@ -101,7 +101,9 @@ mod tests { if let Err(e) = iface_result { if e.kind() == std::io::ErrorKind::PermissionDenied { println!("Permission denied - needs tp be run as root/sudo!"); - panic!("Failed to bind the tap interface: PermissionDenied - please run with root/sudo!"); + panic!( + "Failed to bind the tap interface: PermissionDenied - please run with root/sudo!" + ); } // common error is to not run these tests as root; provide a nicer message panic!("Failed to bind the tap interface: {e:#?}"); From a4d500efa8c4b0ec8d58b2c976d9f800688084c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=84=CA=9C=CA=80=C9=AAs=E1=B4=9B=E1=B4=8F=E1=B4=98?= =?UTF-8?q?=CA=9C=E1=B4=87=CA=80=20=E1=B4=8D=E1=B4=87=C9=B4=C9=A2?= Date: Mon, 17 Aug 2026 01:54:26 -0400 Subject: [PATCH 3/8] Add a binding for pcap_dump_ftell64 --- CHANGELOG.md | 5 +++++ src/capture/activated/mod.rs | 32 ++++++++++++++++++++++++++++++++ src/raw.rs | 2 +- tests/capture/activated/mod.rs | 27 +++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f5cb309..082fd087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +### Added + +- Binding for `pcap_dump_ftell64` added. It can be accessed via the `offset` call on `Savefile` + and reports how many bytes have been written to the file so far. Requires libpcap 1.9.0. + ### Changed - Rust Edition is now `2024`. diff --git a/src/capture/activated/mod.rs b/src/capture/activated/mod.rs index 2f0c4476..c1351b9c 100644 --- a/src/capture/activated/mod.rs +++ b/src/capture/activated/mod.rs @@ -470,6 +470,18 @@ impl Savefile { Ok(()) } + + /// Get the current offset of the savefile, that is the number of bytes written so far, + /// including any that are still buffered + #[cfg(libpcap_1_9_0)] + pub fn offset(&self) -> Result { + let offset = unsafe { raw::pcap_dump_ftell64(self.handle.as_ptr()) }; + if offset < 0 { + return Err(Error::ErrnoError(errno::errno())); + } + + Ok(offset as u64) + } } impl From> for Savefile { @@ -888,6 +900,26 @@ mod tests { let result = savefile.flush(); assert!(result.is_err()); + + #[cfg(libpcap_1_9_0)] + { + let ctx = raw::pcap_dump_ftell64_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap_dumper) + .return_once(|_| 6144); + + let result = savefile.offset(); + assert_eq!(result.unwrap(), 6144); + + let ctx = raw::pcap_dump_ftell64_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap_dumper) + .return_once(|_| -1); + + let result = savefile.offset(); + assert!(result.is_err()); + } } #[test] diff --git a/src/raw.rs b/src/raw.rs index c8371e19..753b4a1d 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -232,7 +232,7 @@ pub mod ffi { unsafe extern "C" { // pcap_bufsize // pcap_createsrcstr - // pcap_dump_ftell64 + pub fn pcap_dump_ftell64(arg1: *mut pcap_dumper_t) -> i64; // pcap_findalldevs_ex // pcap_get_required_select_timeout // pcap_open diff --git a/tests/capture/activated/mod.rs b/tests/capture/activated/mod.rs index 380ef996..d4ed637b 100644 --- a/tests/capture/activated/mod.rs +++ b/tests/capture/activated/mod.rs @@ -64,6 +64,33 @@ fn capture_dead_savefile_append() { packets.verify(&mut cap); } +#[test] +#[cfg(libpcap_1_9_0)] +fn capture_dead_savefile_offset() { + let mut packets = Packets::new(); + packets.push(1460408319, 1234, 1, 1, &[1]); + packets.push(1460408320, 4321, 1, 1, &[2]); + + let dir = TempDir::new().unwrap(); + let tmpfile = dir.path().join("test.pcap"); + + let cap = Capture::dead(Linktype(1)).unwrap(); + let mut save = cap.savefile(&tmpfile).unwrap(); + + // The file header has been written, the packets have not. + let header_only = save.offset().unwrap(); + assert!(header_only > 0); + + packets.foreach(|p| save.write(p)); + let with_packets = save.offset().unwrap(); + assert!(with_packets > header_only); + + drop(save); + + let written = std::fs::metadata(&tmpfile).unwrap().len(); + assert_eq!(with_packets, written); +} + #[test] fn test_linktype() { let capture = capture_from_test_file("packet_snaplen_65535.pcap"); From 5d9f7177b57fd279730801a1603f95f30d54b569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=84=CA=9C=CA=80=C9=AAs=E1=B4=9B=E1=B4=8F=E1=B4=98?= =?UTF-8?q?=CA=9C=E1=B4=87=CA=80=20=E1=B4=8D=E1=B4=87=C9=B4=C9=A2?= Date: Fri, 21 Aug 2026 10:06:45 -0400 Subject: [PATCH 4/8] Add a binding for pcap_init --- CHANGELOG.md | 2 ++ src/lib.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/raw.rs | 5 ++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 082fd087..ac5a6a74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - Binding for `pcap_dump_ftell64` added. It can be accessed via the `offset` call on `Savefile` and reports how many bytes have been written to the file so far. Requires libpcap 1.9.0. +- Binding for `pcap_init` added. It can be accessed via the `init` call and selects the character + encoding libpcap uses for strings. Requires libpcap 1.10.0. ### Changed diff --git a/src/lib.rs b/src/lib.rs index d99fc976..c71b7af1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -241,11 +241,41 @@ pub const fn packet_header_size() -> usize { std::mem::size_of::() } +#[cfg(libpcap_1_10_0)] +#[repr(u32)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +/// The character encoding libpcap uses for strings. Use with `init`. +pub enum CharEncoding { + /// Strings are in the local character encoding. On UN*X that is taken to be UTF-8, on Windows + /// it is the local ANSI code page. This is the default. + Local = raw::PCAP_CHAR_ENC_LOCAL, + /// Strings are in UTF-8. + Utf8 = raw::PCAP_CHAR_ENC_UTF_8, +} + +/// Initialize the library, choosing the character encoding it uses for the strings it is given +/// and the strings it returns. +/// +/// This is optional, but it has to come before any other libpcap call, and a second call asking +/// for a different encoding fails. Without it strings are in the local character encoding. +#[cfg(libpcap_1_10_0)] +pub fn init(encoding: CharEncoding) -> Result<(), Error> { + Error::with_errbuf(|err| { + if unsafe { raw::pcap_init(encoding as _, err) } != 0 { + return Err(unsafe { Error::new(err) }); + } + Ok(()) + }) +} + #[cfg(test)] mod tests { use std::error::Error as StdError; use std::{ffi::CString, io}; + #[cfg(libpcap_1_10_0)] + use crate::raw::testmod::RAWMTX; + use super::*; #[test] @@ -301,4 +331,27 @@ mod tests { std::mem::size_of::() ); } + + #[test] + #[cfg(libpcap_1_10_0)] + fn test_init() { + let _m = RAWMTX.lock(); + + let ctx = raw::pcap_init_context(); + ctx.expect() + .withf_st(|arg1, _| *arg1 == raw::PCAP_CHAR_ENC_UTF_8) + .return_once(|_, _| 0); + + let result = init(CharEncoding::Utf8); + assert!(result.is_ok()); + + let ctx = raw::pcap_init_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(|arg1, _| *arg1 == raw::PCAP_CHAR_ENC_LOCAL) + .return_once(|_, _| -1); + + let result = init(CharEncoding::Local); + assert!(result.is_err()); + } } diff --git a/src/raw.rs b/src/raw.rs index 753b4a1d..81fc7241 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -17,6 +17,9 @@ pub const PCAP_IF_CONNECTION_STATUS_CONNECTED: u32 = 0x00000010; pub const PCAP_IF_CONNECTION_STATUS_DISCONNECTED: u32 = 0x00000020; pub const PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE: u32 = 0x00000030; +pub const PCAP_CHAR_ENC_LOCAL: u32 = 0x00000000; +pub const PCAP_CHAR_ENC_UTF_8: u32 = 0x00000001; + #[repr(C)] #[derive(Copy, Clone)] pub struct bpf_program { @@ -252,7 +255,7 @@ pub mod ffi { #[cfg(libpcap_1_10_0)] unsafe extern "C" { - // pcap_init + pub fn pcap_init(arg1: c_uint, arg2: *mut c_char) -> c_int; // pcap_remoteact_accept_ex } } From c93d9b6218daa273649d1adfe93d80e969956902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=84=CA=9C=CA=80=C9=AAs=E1=B4=9B=E1=B4=8F=E1=B4=98?= =?UTF-8?q?=CA=9C=E1=B4=87=CA=80=20=E1=B4=8D=E1=B4=87=C9=B4=C9=A2?= Date: Fri, 21 Aug 2026 11:07:49 -0400 Subject: [PATCH 5/8] Stop putting file paths through Path::to_str --- CHANGELOG.md | 9 ++++++ src/capture/activated/mod.rs | 6 ++-- src/capture/activated/offline.rs | 8 +++-- src/capture/inactive.rs | 4 ++- src/capture/mod.rs | 7 ++-- src/lib.rs | 43 ++++++++++++++++++++++++- tests/capture/activated/mod.rs | 27 ++++++++++++++++ tests/charenc.rs | 55 ++++++++++++++++++++++++++++++++ 8 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 tests/charenc.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ac5a6a74..71456f7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ `LOOPBACK | UP`. - `errno` updated from 0.2 to 0.3. `Error::ErrnoError` carries an `errno::Errno`, so crates that construct or match on it have to move to 0.3 as well. +- `Error` has a new `InvalidPath` variant on Windows, which exhaustive matches have to cover. - `windows-sys` updated from 0.36 to 0.61. `HANDLE` is a raw pointer there rather than an `isize`, which changes the signature of `Capture::get_event` on Windows. A raw pointer is not `Send`, so a type of your own that stores the returned `HANDLE` no longer derives `Send` and can no longer @@ -28,6 +29,14 @@ - `IfFlags::from_bits_unchecked`, which `bitflags` 1 generated. `bitflags` 2 provides `IfFlags::from_bits_retain` instead. +### Fixed + +- `Capture::from_file`, `Capture::from_file_with_precision`, `Capture::savefile` and + `Capture::savefile_append` no longer convert the path with `Path::to_str`. On UN*X the path is + handed to libpcap as bytes, so file names that are not valid UTF-8 now work. On Windows such a + path returns the new `Error::InvalidPath`, where `savefile` used to panic and `from_file` used + to report that a null pointer had been supplied as the file name. + ## [2.5.0] - 2026-08-15 ### Added diff --git a/src/capture/activated/mod.rs b/src/capture/activated/mod.rs index c1351b9c..236cdb8f 100644 --- a/src/capture/activated/mod.rs +++ b/src/capture/activated/mod.rs @@ -24,7 +24,7 @@ use crate::{ codec::PacketCodec, linktype::Linktype, packet::{Packet, PacketHeader}, - raw, + path_to_cstring, raw, }; use iterator::PacketIter; @@ -97,7 +97,7 @@ impl Capture { /// Create a `Savefile` context for recording captured packets using this `Capture`'s /// configurations. pub fn savefile>(&self, path: P) -> Result { - let name = CString::new(path.as_ref().to_str().unwrap())?; + let name = path_to_cstring(path.as_ref())?; let handle_opt = NonNull::::new(unsafe { raw::pcap_dump_open(self.handle.as_ptr(), name.as_ptr()) }); @@ -135,7 +135,7 @@ impl Capture { /// at the end of the file. #[cfg(libpcap_1_7_2)] pub fn savefile_append>(&self, path: P) -> Result { - let name = CString::new(path.as_ref().to_str().unwrap())?; + let name = path_to_cstring(path.as_ref())?; let handle_opt = NonNull::::new(unsafe { raw::pcap_dump_open_append(self.handle.as_ptr(), name.as_ptr()) }); diff --git a/src/capture/activated/offline.rs b/src/capture/activated/offline.rs index 9f786690..1a0aeec6 100644 --- a/src/capture/activated/offline.rs +++ b/src/capture/activated/offline.rs @@ -6,7 +6,7 @@ use std::os::unix::io::RawFd; use crate::{ Error, capture::{Capture, Offline}, - raw, + path_to_cstring, raw, }; #[cfg(libpcap_1_5_0)] @@ -18,7 +18,8 @@ use crate::capture::activated::open_raw_fd; impl Capture { /// Opens an offline capture handle from a pcap dump file, given a path. pub fn from_file>(path: P) -> Result, Error> { - Capture::new_raw(path.as_ref().to_str(), |path, err| unsafe { + let path = path_to_cstring(path.as_ref())?; + Capture::new_raw(Some(path), |path, err| unsafe { raw::pcap_open_offline(path, err) }) } @@ -30,7 +31,8 @@ impl Capture { path: P, precision: Precision, ) -> Result, Error> { - Capture::new_raw(path.as_ref().to_str(), |path, err| unsafe { + let path = path_to_cstring(path.as_ref())?; + Capture::new_raw(Some(path), |path, err| unsafe { raw::pcap_open_offline_with_tstamp_precision(path, precision as _, err) }) } diff --git a/src/capture/inactive.rs b/src/capture/inactive.rs index ee6ef636..8a3148db 100644 --- a/src/capture/inactive.rs +++ b/src/capture/inactive.rs @@ -1,3 +1,4 @@ +use std::ffi::CString; use std::mem; use crate::{ @@ -33,7 +34,8 @@ impl Capture { /// ``` pub fn from_device>(device: D) -> Result, Error> { let device: Device = device.into(); - Capture::new_raw(Some(&device.name), |name, err| unsafe { + let name = CString::new(device.name)?; + Capture::new_raw(Some(name), |name, err| unsafe { raw::pcap_create(name, err) }) } diff --git a/src/capture/mod.rs b/src/capture/mod.rs index a0f69b82..1f5997ea 100644 --- a/src/capture/mod.rs +++ b/src/capture/mod.rs @@ -132,17 +132,14 @@ impl From> for Capture { } impl Capture { - fn new_raw(path: Option<&str>, func: F) -> Result, Error> + fn new_raw(path: Option, func: F) -> Result, Error> where F: FnOnce(*const libc::c_char, *mut libc::c_char) -> *mut raw::pcap_t, { Error::with_errbuf(|err| { let handle = match path { None => func(ptr::null(), err), - Some(path) => { - let path = CString::new(path)?; - func(path.as_ptr(), err) - } + Some(path) => func(path.as_ptr(), err), }; Ok(Capture::from( NonNull::::new(handle).ok_or_else(|| unsafe { Error::new(err) })?, diff --git a/src/lib.rs b/src/lib.rs index c71b7af1..add05d5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,8 +60,9 @@ #![cfg_attr(docsrs, feature(doc_cfg))] -use std::ffi::{self, CStr}; +use std::ffi::{self, CStr, CString}; use std::fmt; +use std::path::Path; use self::Error::*; @@ -127,6 +128,9 @@ pub enum Error { #[cfg(not(windows))] /// An invalid raw file descriptor was provided InvalidRawFd, + #[cfg(windows)] + /// A path that libpcap cannot be given because it is not valid UTF-8 + InvalidPath, /// Errno error ErrnoError(errno::Errno), /// Buffer size overflows capacity @@ -159,6 +163,25 @@ unsafe fn cstr_to_string(ptr: *const libc::c_char) -> Result, Err Ok(string) } +fn path_to_cstring(path: &Path) -> Result { + #[cfg(not(windows))] + let bytes = { + use std::os::unix::ffi::OsStrExt; + path.as_os_str().as_bytes() + }; + // libpcap has no entry points taking wide strings. It reads the path in the local code page, + // or in UTF-8 once pcap_init has been asked for that, so give it the UTF-8 form. A path that + // is not valid UTF-8 holds an unpaired surrogate, which has no form libpcap would accept. + #[cfg(windows)] + let bytes = path + .as_os_str() + .to_str() + .ok_or(Error::InvalidPath)? + .as_bytes(); + + Ok(CString::new(bytes)?) +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { @@ -174,6 +197,8 @@ impl fmt::Display for Error { IoError(ref e) => write!(f, "io error occurred: {e:?}"), #[cfg(not(windows))] InvalidRawFd => write!(f, "invalid raw file descriptor provided"), + #[cfg(windows)] + InvalidPath => write!(f, "invalid path (not valid UTF-8)"), ErrnoError(ref e) => write!(f, "libpcap os errno: {e}"), BufferOverflow => write!(f, "buffer size too large"), } @@ -196,6 +221,8 @@ impl std::error::Error for Error { IoError(..) => "io error occurred", #[cfg(not(windows))] InvalidRawFd => "invalid raw file descriptor provided", + #[cfg(windows)] + InvalidPath => "invalid path (not valid UTF-8)", ErrnoError(..) => "internal error, providing errno", BufferOverflow => "buffer size too large", } @@ -311,6 +338,8 @@ mod tests { errors.push(io::Error::new(io::ErrorKind::Interrupted, "error").into()); #[cfg(not(windows))] errors.push(Error::InvalidRawFd); + #[cfg(windows)] + errors.push(Error::InvalidPath); errors.push(Error::ErrnoError(errno::Errno(125))); errors.push(Error::BufferOverflow); @@ -332,6 +361,18 @@ mod tests { ); } + #[test] + #[cfg(windows)] + fn test_path_to_cstring_not_utf8() { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + + // An unpaired surrogate, which a Windows path may hold and UTF-8 cannot express. + let name = OsString::from_wide(&[0xd800]); + let result = path_to_cstring(Path::new(&name)); + assert_eq!(result.unwrap_err(), Error::InvalidPath); + } + #[test] #[cfg(libpcap_1_10_0)] fn test_init() { diff --git a/tests/capture/activated/mod.rs b/tests/capture/activated/mod.rs index d4ed637b..7325a500 100644 --- a/tests/capture/activated/mod.rs +++ b/tests/capture/activated/mod.rs @@ -36,6 +36,33 @@ fn capture_dead_savefile() { packets.verify(&mut cap); } +// APFS validates file names as UTF-8 and rejects the rest with EILSEQ, so a name that is not +// valid UTF-8 only round trips where a file name is an opaque byte string. +#[test] +#[cfg(not(any(windows, target_os = "macos")))] +fn capture_dead_savefile_non_utf8_name() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + let mut packets = Packets::new(); + packets.push(1460408319, 1234, 1, 1, &[1]); + packets.push(1460408320, 4321, 1, 1, &[2]); + + let dir = TempDir::new().unwrap(); + // A UN*X path is a byte string, it does not have to be valid UTF-8. + let tmpfile = dir.path().join(OsStr::from_bytes(b"\xe9capture.pcap")); + + let cap = Capture::dead(Linktype(1)).unwrap(); + let mut save = cap.savefile(&tmpfile).unwrap(); + packets.foreach(|p| save.write(p)); + drop(save); + + assert!(tmpfile.exists()); + + let mut cap = Capture::from_file(&tmpfile).unwrap(); + packets.verify(&mut cap); +} + #[test] #[cfg(libpcap_1_7_2)] fn capture_dead_savefile_append() { diff --git a/tests/charenc.rs b/tests/charenc.rs new file mode 100644 index 00000000..bc082dbd --- /dev/null +++ b/tests/charenc.rs @@ -0,0 +1,55 @@ +//! Capture file paths that are not plain ASCII. +//! +//! On Windows libpcap reads the path in the local code page unless pcap_init has been asked for +//! UTF-8, and that choice is made once for the whole process and cannot be taken back. These +//! tests therefore need a test binary to themselves. +#![cfg(any(not(windows), libpcap_1_10_0))] + +use std::fs; + +use tempfile::TempDir; + +use pcap::{Capture, Linktype}; + +/// "capture", in four scripts. libpcap converts the path with one call that does not care which +/// script it is looking at, so these cover the encoding widths rather than the languages. +const NAMES: &[&str] = &[ + "захват", // Russian, two-byte + "捕获", // Chinese, three-byte + "कैप्चर", // Hindi, three-byte with combining marks + "𝄞capture🎯", // Astral plane, four-byte +]; + +#[cfg(not(windows))] +fn use_utf8_paths() {} + +#[cfg(windows)] +fn use_utf8_paths() { + pcap::init(pcap::CharEncoding::Utf8).unwrap(); +} + +#[test] +fn savefile_round_trip_non_ascii_paths() { + use_utf8_paths(); + + let dir = TempDir::new().unwrap(); + + for name in NAMES { + // The name is used for a directory as well as for the file, since libpcap converts the + // whole path and a directory component has to work just as a file name does. + let subdir = dir.path().join(name); + fs::create_dir(&subdir).unwrap(); + let tmpfile = subdir.join(format!("{name}.pcap")); + + let cap = Capture::dead(Linktype(1)).unwrap(); + let save = cap.savefile(&tmpfile).unwrap(); + drop(save); + + assert!( + tmpfile.exists(), + "{name} was not written where it was asked for" + ); + + Capture::from_file(&tmpfile).unwrap(); + } +} From 1cbd81841542b397088c078f4b64cd6544cc9dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=84=CA=9C=CA=80=C9=AAs=E1=B4=9B=E1=B4=8F=E1=B4=98?= =?UTF-8?q?=CA=9C=E1=B4=87=CA=80=20=E1=B4=8D=E1=B4=87=C9=B4=C9=A2?= Date: Sat, 22 Aug 2026 12:06:51 -0400 Subject: [PATCH 6/8] Add a binding for pcap_dump_ftell --- CHANGELOG.md | 7 ++-- src/capture/activated/mod.rs | 61 ++++++++++++++++++++++++---------- src/raw.rs | 4 +-- tests/capture/activated/mod.rs | 1 - 4 files changed, 51 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71456f7e..b67ecb73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,11 @@ ### Added -- Binding for `pcap_dump_ftell64` added. It can be accessed via the `offset` call on `Savefile` - and reports how many bytes have been written to the file so far. Requires libpcap 1.9.0. +- Bindings for `pcap_dump_ftell64` and `pcap_dump_ftell` added. They can be accessed via the + `offset` call on `Savefile`, which reports how many bytes have been written to the file so far. + `pcap_dump_ftell64` is used where it is available, that is from libpcap 1.9.0 on; before that + the offset comes back as a `long` and the call fails once the file has grown past 2 GB on the + platforms where that is a 32-bit type. - Binding for `pcap_init` added. It can be accessed via the `init` call and selects the character encoding libpcap uses for strings. Requires libpcap 1.10.0. diff --git a/src/capture/activated/mod.rs b/src/capture/activated/mod.rs index 236cdb8f..4c18babf 100644 --- a/src/capture/activated/mod.rs +++ b/src/capture/activated/mod.rs @@ -473,9 +473,16 @@ impl Savefile { /// Get the current offset of the savefile, that is the number of bytes written so far, /// including any that are still buffered - #[cfg(libpcap_1_9_0)] pub fn offset(&self) -> Result { + // Prior to 1.9.0 when `pcap_dump_ftell64` was introduced, the offset was only reported as + // a `long`. Where that is a 32-bit type, as it is on Windows, the call fails once the + // savefile has grown past 2 GB. + #[cfg(libpcap_1_9_0)] let offset = unsafe { raw::pcap_dump_ftell64(self.handle.as_ptr()) }; + + #[cfg(not(libpcap_1_9_0))] + let offset = unsafe { raw::pcap_dump_ftell(self.handle.as_ptr()) }; + if offset < 0 { return Err(Error::ErrnoError(errno::errno())); } @@ -861,6 +868,36 @@ mod tests { assert!(result.is_err()); } + #[cfg(libpcap_1_9_0)] + struct DumpFtellExpect(raw::__pcap_dump_ftell64::Context); + + #[cfg(not(libpcap_1_9_0))] + struct DumpFtellExpect(raw::__pcap_dump_ftell::Context); + + fn dump_ftell_expect(pcap_dumper: *mut raw::pcap_dumper_t, offset: i64) -> DumpFtellExpect { + // Lock must be acquired by caller. + assert!(RAWMTX.try_lock().is_err()); + + #[cfg(libpcap_1_9_0)] + { + let ctx = raw::pcap_dump_ftell64_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap_dumper) + .return_once(move |_| offset); + DumpFtellExpect(ctx) + } + #[cfg(not(libpcap_1_9_0))] + { + let ctx = raw::pcap_dump_ftell_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap_dumper) + .return_once(move |_| offset as _); + DumpFtellExpect(ctx) + } + } + #[test] fn test_savefile_ops() { let _m = RAWMTX.lock(); @@ -901,25 +938,15 @@ mod tests { let result = savefile.flush(); assert!(result.is_err()); - #[cfg(libpcap_1_9_0)] - { - let ctx = raw::pcap_dump_ftell64_context(); - ctx.expect() - .withf_st(move |arg1| *arg1 == pcap_dumper) - .return_once(|_| 6144); + let _ctx = dump_ftell_expect(pcap_dumper, 6144); - let result = savefile.offset(); - assert_eq!(result.unwrap(), 6144); + let result = savefile.offset(); + assert_eq!(result.unwrap(), 6144); - let ctx = raw::pcap_dump_ftell64_context(); - ctx.checkpoint(); - ctx.expect() - .withf_st(move |arg1| *arg1 == pcap_dumper) - .return_once(|_| -1); + let _ctx = dump_ftell_expect(pcap_dumper, -1); - let result = savefile.offset(); - assert!(result.is_err()); - } + let result = savefile.offset(); + assert!(result.is_err()); } #[test] diff --git a/src/raw.rs b/src/raw.rs index 81fc7241..bc37d781 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -2,7 +2,7 @@ #![allow(dead_code)] #![allow(non_camel_case_types)] -use libc::{c_char, c_int, c_uchar, c_uint, c_ushort, sockaddr, timeval}; +use libc::{c_char, c_int, c_long, c_uchar, c_uint, c_ushort, sockaddr, timeval}; #[cfg(test)] use mockall::automock; @@ -188,7 +188,7 @@ pub mod ffi { pub fn pcap_fileno(arg1: *mut pcap_t) -> c_int; pub fn pcap_dump_open(arg1: *mut pcap_t, arg2: *const c_char) -> *mut pcap_dumper_t; // pub fn pcap_dump_file(arg1: *mut pcap_dumper_t) -> *mut FILE; - // pub fn pcap_dump_ftell(arg1: *mut pcap_dumper_t) -> c_long; + pub fn pcap_dump_ftell(arg1: *mut pcap_dumper_t) -> c_long; pub fn pcap_dump_flush(arg1: *mut pcap_dumper_t) -> c_int; pub fn pcap_dump_close(arg1: *mut pcap_dumper_t); pub fn pcap_dump(arg1: *mut c_uchar, arg2: *const pcap_pkthdr, arg3: *const c_uchar); diff --git a/tests/capture/activated/mod.rs b/tests/capture/activated/mod.rs index 7325a500..47575a9f 100644 --- a/tests/capture/activated/mod.rs +++ b/tests/capture/activated/mod.rs @@ -92,7 +92,6 @@ fn capture_dead_savefile_append() { } #[test] -#[cfg(libpcap_1_9_0)] fn capture_dead_savefile_offset() { let mut packets = Packets::new(); packets.push(1460408319, 1234, 1, 1, &[1]); From 9d26248ee03c61cecfebac7b3ccf8830fcbbef69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=84=CA=9C=CA=80=C9=AAs=E1=B4=9B=E1=B4=8F=E1=B4=98?= =?UTF-8?q?=CA=9C=E1=B4=87=CA=80=20=E1=B4=8D=E1=B4=87=C9=B4=C9=A2?= Date: Sun, 23 Aug 2026 12:06:56 -0400 Subject: [PATCH 7/8] Add a binding for pcap_dump_file --- CHANGELOG.md | 3 +++ src/capture/activated/mod.rs | 30 ++++++++++++++++++++++++++++++ src/raw.rs | 5 ++++- tests/capture/activated/mod.rs | 23 +++++++++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b67ecb73..1f4f9b74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ `pcap_dump_ftell64` is used where it is available, that is from libpcap 1.9.0 on; before that the offset comes back as a `long` and the call fails once the file has grown past 2 GB on the platforms where that is a 32-bit type. +- Binding for `pcap_dump_file` added. It can be accessed via the `file` call on `Savefile` and + returns the `FILE *` the savefile is being written to. Not available on Windows, where wpcap + may be linked against a different C runtime than its caller. - Binding for `pcap_init` added. It can be accessed via the `init` call and selects the character encoding libpcap uses for strings. Requires libpcap 1.10.0. diff --git a/src/capture/activated/mod.rs b/src/capture/activated/mod.rs index 4c18babf..cb377552 100644 --- a/src/capture/activated/mod.rs +++ b/src/capture/activated/mod.rs @@ -18,6 +18,9 @@ use std::{ #[cfg(not(windows))] use std::os::unix::io::RawFd; +#[cfg(not(windows))] +use libc::FILE; + use crate::{ Error, capture::{Activated, Capture, PcapHandle}, @@ -489,6 +492,20 @@ impl Savefile { Ok(offset as u64) } + + /// Get the `FILE *` the savefile is being written to + /// + /// This is not available on Windows, where wpcap may be linked against a different C runtime + /// than its caller and the `FILE *` would belong to the wrong one. + /// + /// # Safety + /// + /// The caller must ensure that the `Savefile` outlives the returned `FILE *` since it is + /// closed when the `Savefile` is dropped. + #[cfg(not(windows))] + pub unsafe fn file(&self) -> *mut FILE { + unsafe { raw::pcap_dump_file(self.handle.as_ptr()) } + } } impl From> for Savefile { @@ -947,6 +964,19 @@ mod tests { let result = savefile.offset(); assert!(result.is_err()); + + #[cfg(not(windows))] + { + let mut dummy: isize = 999; + let file = &mut dummy as *mut isize as *mut FILE; + + let ctx = raw::pcap_dump_file_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap_dumper) + .return_once_st(move |_| file); + + assert_eq!(unsafe { savefile.file() }, file); + } } #[test] diff --git a/src/raw.rs b/src/raw.rs index bc37d781..e0ed75b5 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -187,7 +187,6 @@ pub mod ffi { // pub fn pcap_file(arg1: *mut pcap_t) -> *mut FILE; pub fn pcap_fileno(arg1: *mut pcap_t) -> c_int; pub fn pcap_dump_open(arg1: *mut pcap_t, arg2: *const c_char) -> *mut pcap_dumper_t; - // pub fn pcap_dump_file(arg1: *mut pcap_dumper_t) -> *mut FILE; pub fn pcap_dump_ftell(arg1: *mut pcap_dumper_t) -> c_long; pub fn pcap_dump_flush(arg1: *mut pcap_dumper_t) -> c_int; pub fn pcap_dump_close(arg1: *mut pcap_dumper_t); @@ -277,6 +276,10 @@ pub mod ffi_unix { // the OS handle out of the FILE * and call pcap_hopen_offline()/pcap_dump_hopen(). pub fn pcap_fopen_offline(arg1: *mut FILE, arg2: *mut c_char) -> *mut pcap_t; pub fn pcap_dump_fopen(arg1: *mut pcap_t, fp: *mut FILE) -> *mut pcap_dumper_t; + // wpcap does export this one, but the FILE * it hands back belongs to the C runtime + // wpcap was linked against, which is not necessarily the caller's, so it is no more + // usable on Windows than the entry points above. + pub fn pcap_dump_file(arg1: *mut pcap_dumper_t) -> *mut FILE; } #[cfg(libpcap_1_5_0)] diff --git a/tests/capture/activated/mod.rs b/tests/capture/activated/mod.rs index 47575a9f..8226f6a3 100644 --- a/tests/capture/activated/mod.rs +++ b/tests/capture/activated/mod.rs @@ -117,6 +117,29 @@ fn capture_dead_savefile_offset() { assert_eq!(with_packets, written); } +#[test] +#[cfg(not(windows))] +fn capture_dead_savefile_file() { + let dir = TempDir::new().unwrap(); + let tmpfile = dir.path().join("test.pcap"); + + let cap = Capture::dead(Linktype(1)).unwrap(); + let mut save = cap.savefile(&tmpfile).unwrap(); + save.flush().unwrap(); + + // SAFETY: the FILE * is not used past the end of this test, where `save` is still alive. + let fd = unsafe { libc::fileno(save.file()) }; + assert!(fd >= 0); + + // The stream the header was just flushed to is the one the savefile was opened on. + let mut stat = std::mem::MaybeUninit::::uninit(); + assert_eq!(unsafe { libc::fstat(fd, stat.as_mut_ptr()) }, 0); + assert_eq!( + unsafe { stat.assume_init() }.st_size as u64, + save.offset().unwrap() + ); +} + #[test] fn test_linktype() { let capture = capture_from_test_file("packet_snaplen_65535.pcap"); From 5acfe9f940a4052463ea410bc69309722079fc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=84=CA=9C=CA=80=C9=AAs=E1=B4=9B=E1=B4=8F=E1=B4=98?= =?UTF-8?q?=CA=9C=E1=B4=87=CA=80=20=E1=B4=8D=E1=B4=87=C9=B4=C9=A2?= Date: Sun, 23 Aug 2026 12:53:54 -0400 Subject: [PATCH 8/8] Add a binding for pcap_snapshot --- CHANGELOG.md | 4 ++++ src/capture/activated/mod.rs | 27 +++++++++++++++++++++++++++ src/raw.rs | 2 +- tests/capture/activated/mod.rs | 18 ++++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f4f9b74..c8dae413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ - Binding for `pcap_dump_file` added. It can be accessed via the `file` call on `Savefile` and returns the `FILE *` the savefile is being written to. Not available on Windows, where wpcap may be linked against a different C runtime than its caller. +- Binding for `pcap_snapshot` added. It can be accessed via the `snaplen` call on activated + captures and reports the snapshot length in effect, which the `snaplen` call on + `Capture` could previously only set. For a `Capture` this reports the length + the savefile was recorded with. - Binding for `pcap_init` added. It can be accessed via the `init` call and selects the character encoding libpcap uses for strings. Requires libpcap 1.10.0. diff --git a/src/capture/activated/mod.rs b/src/capture/activated/mod.rs index cb377552..3fa7c447 100644 --- a/src/capture/activated/mod.rs +++ b/src/capture/activated/mod.rs @@ -97,6 +97,15 @@ impl Capture { unsafe { Linktype(raw::pcap_datalink(self.handle.as_ptr())) } } + /// Get the snapshot length, that is the maximum number of bytes captured from each packet. + /// + /// For a `Capture` this is the length the savefile was recorded with, except that a + /// header claiming zero, or a length too large for an `i32`, is replaced with the largest + /// length the link-layer type can produce. + pub fn snaplen(&self) -> i32 { + unsafe { raw::pcap_snapshot(self.handle.as_ptr()) } + } + /// Create a `Savefile` context for recording captured packets using this `Capture`'s /// configurations. pub fn savefile>(&self, path: P) -> Result { @@ -741,6 +750,24 @@ mod tests { assert_eq!(linktype, Linktype::ETHERNET); } + #[test] + fn test_snaplen() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture: Capture = test_capture.capture.into(); + + let ctx = raw::pcap_snapshot_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap) + .return_once(|_| 65535); + + assert_eq!(capture.snaplen(), 65535); + } + #[test] fn unify_activated() { #![allow(dead_code)] diff --git a/src/raw.rs b/src/raw.rs index e0ed75b5..29829f81 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -180,7 +180,7 @@ pub mod ffi { pub fn pcap_datalink_name_to_val(arg1: *const c_char) -> c_int; pub fn pcap_datalink_val_to_name(arg1: c_int) -> *const c_char; pub fn pcap_datalink_val_to_description(arg1: c_int) -> *const c_char; - // pub fn pcap_snapshot(arg1: *mut pcap_t) -> c_int; + pub fn pcap_snapshot(arg1: *mut pcap_t) -> c_int; // pub fn pcap_is_swapped(arg1: *mut pcap_t) -> c_int; pub fn pcap_major_version(arg1: *mut pcap_t) -> c_int; pub fn pcap_minor_version(arg1: *mut pcap_t) -> c_int; diff --git a/tests/capture/activated/mod.rs b/tests/capture/activated/mod.rs index 8226f6a3..8fe1872b 100644 --- a/tests/capture/activated/mod.rs +++ b/tests/capture/activated/mod.rs @@ -140,6 +140,24 @@ fn capture_dead_savefile_file() { ); } +#[test] +fn capture_offline_snaplen() { + for (file_name, snaplen) in [ + ("packet_snaplen_20.pcap", 20), + ("packet_snaplen_65535.pcap", 65535), + ] { + let capture = capture_from_test_file(file_name); + assert_eq!(capture.snaplen(), snaplen, "{file_name}"); + } +} + +#[test] +fn capture_dead_snaplen() { + // `Capture::dead` opens the handle with a snaplen of 65535. + let cap = Capture::dead(Linktype(1)).unwrap(); + assert_eq!(cap.snaplen(), 65535); +} + #[test] fn test_linktype() { let capture = capture_from_test_file("packet_snaplen_65535.pcap");