From 73eddfbd5bfc96f9acb28ac615ab1c86bc7afa7f Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sat, 7 Nov 2020 11:21:53 +0800 Subject: [PATCH] feat: add pcap --- __network/pcap/CHANGELOG.md | 29 + __network/pcap/Cargo.toml | 65 ++ __network/pcap/LICENSE-APACHE | 202 ++++ __network/pcap/LICENSE-MIT | 19 + __network/pcap/README.md | 76 ++ __network/pcap/build.rs | 122 +++ __network/pcap/examples/easylisten.rs | 15 + __network/pcap/examples/getdevices.rs | 18 + __network/pcap/examples/getstatistics.rs | 22 + __network/pcap/examples/listenlocalhost.rs | 16 + __network/pcap/examples/savefile.rs | 37 + __network/pcap/examples/streamlisten.rs | 47 + __network/pcap/src/lib.rs | 870 ++++++++++++++++++ __network/pcap/src/raw.rs | 221 +++++ __network/pcap/src/stream.rs | 68 ++ __network/pcap/src/unique.rs | 43 + .../pcap/tests/data/packet_snaplen_20.pcap | Bin 0 -> 60 bytes .../pcap/tests/data/packet_snaplen_65535.pcap | Bin 0 -> 138 bytes __network/pcap/tests/lib.rs | 290 ++++++ 19 files changed, 2160 insertions(+) create mode 100644 __network/pcap/CHANGELOG.md create mode 100644 __network/pcap/Cargo.toml create mode 100644 __network/pcap/LICENSE-APACHE create mode 100644 __network/pcap/LICENSE-MIT create mode 100644 __network/pcap/README.md create mode 100644 __network/pcap/build.rs create mode 100644 __network/pcap/examples/easylisten.rs create mode 100644 __network/pcap/examples/getdevices.rs create mode 100644 __network/pcap/examples/getstatistics.rs create mode 100644 __network/pcap/examples/listenlocalhost.rs create mode 100644 __network/pcap/examples/savefile.rs create mode 100644 __network/pcap/examples/streamlisten.rs create mode 100644 __network/pcap/src/lib.rs create mode 100644 __network/pcap/src/raw.rs create mode 100644 __network/pcap/src/stream.rs create mode 100644 __network/pcap/src/unique.rs create mode 100644 __network/pcap/tests/data/packet_snaplen_20.pcap create mode 100644 __network/pcap/tests/data/packet_snaplen_65535.pcap create mode 100644 __network/pcap/tests/lib.rs diff --git a/__network/pcap/CHANGELOG.md b/__network/pcap/CHANGELOG.md new file mode 100644 index 0000000..dd1d33e --- /dev/null +++ b/__network/pcap/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +## [Unreleased] + +### Added + +- Add `Derive(Clone)` to `Device` struct (#100). +- Build-time `libpcap` version detection. +- Add support for immediate mode. + +### Changed + +- Opt into Rust 2018. +- Now minimum supported rustc version is 1.40.0. +- Updated dependency from deprecated `tokio-core` to `tokio` 0.2. +- Updated dependency `futures` from version 0.1 to 0.3. +- Feature `tokio` renamed to `capture-stream` because Cargo does not allow + features and dependencies to have the same name. +- `PCAP_LIBDIR` renamed to `LIBPCAP_LIBDIR` to distinguish the `pcap` crate + from the `libpcap` library. + +### Removed + +- Feature flags `pcap-savefile-append`, `pcap-fopen-offline-precision` + (replaced by build-time `libpcap` version detection) + +## [0.7.0] - 2017-08-04 + +No Changelog entries for <= 0.7.0 diff --git a/__network/pcap/Cargo.toml b/__network/pcap/Cargo.toml new file mode 100644 index 0000000..101fcb0 --- /dev/null +++ b/__network/pcap/Cargo.toml @@ -0,0 +1,65 @@ +[package] + +name = "pcap" +version = "0.7.0" +authors = ["Sean Bowe "] +edition = "2018" +description = "A packet capture API around pcap/wpcap" +keywords = ["pcap", "packet", "sniffing"] +readme = "README.md" +homepage = "https://github.com/ebfull/pcap" +repository = "https://github.com/ebfull/pcap" +documentation = "https://docs.rs/pcap" +license = "MIT OR Apache-2.0" +build = "build.rs" + +[dependencies] +libc = "0.2" +clippy = { version = "0.0.*", optional = true } +mio = { version = "0.6", optional = true } +tokio = { version = "0.2", features = ["io-driver"], optional = true } +futures = { version = "0.3", optional = true } + +[dev-dependencies] +tempdir = "0.3" +tokio = { version = "0.2", features = ["rt-core"] } + +[build-dependencies] +libloading = "0.6" +regex = "1" + +[features] +# This feature enables access to the function Capture::stream. +# This is disabled by default, because it depends on a tokio and mio +capture-stream = ["mio", "tokio", "futures"] + +# A shortcut to enable all features. +full = ["capture-stream"] + +[lib] +name = "pcap" + +[[example]] +name = "listenlocalhost" +path = "examples/listenlocalhost.rs" + +[[example]] +name = "getdevices" +path = "examples/getdevices.rs" + +[[example]] +name = "easylisten" +path = "examples/easylisten.rs" + +[[example]] +name = "savefile" +path = "examples/savefile.rs" + +[[example]] +name = "getstatistics" +path = "examples/getstatistics.rs" + +[[example]] +name = "streamlisten" +path = "examples/streamlisten.rs" +required-features = ["capture-stream"] diff --git a/__network/pcap/LICENSE-APACHE b/__network/pcap/LICENSE-APACHE new file mode 100644 index 0000000..8f71f43 --- /dev/null +++ b/__network/pcap/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/__network/pcap/LICENSE-MIT b/__network/pcap/LICENSE-MIT new file mode 100644 index 0000000..62fc116 --- /dev/null +++ b/__network/pcap/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2015 The pcap Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/__network/pcap/README.md b/__network/pcap/README.md new file mode 100644 index 0000000..d2328b3 --- /dev/null +++ b/__network/pcap/README.md @@ -0,0 +1,76 @@ +# pcap [![Build status](https://api.travis-ci.org/ebfull/pcap.svg)](https://travis-ci.org/ebfull/pcap) [![Crates.io](https://img.shields.io/crates/v/pcap.svg)](https://crates.io/crates/pcap) [![Docs.rs](https://docs.rs/pcap/badge.svg)](https://docs.rs/pcap) # + +### [Documentation](https://docs.rs/pcap) + +This is a **Rust language** crate for accessing the packet sniffing capabilities of pcap (or wpcap on Windows). +If you need anything feel free to post an issue or submit a pull request! + +## Features: + +* List devices +* Open capture handle on a device or savefiles +* Get packets from the capture handle +* Filter packets using BPF programs +* List/set/get datalink link types +* Configure some parameters like promiscuity and buffer length +* Write packets to savefiles +* Inject packets into an interface + +See examples for usage. + +# Building + +As of 0.8.0 This crate uses Rust 2018 and requires a compiler version >= 1.40.0. + +## Windows + +Install [WinPcap](http://www.winpcap.org/install/default.htm). + +Download the WinPcap [Developer's Pack](https://www.winpcap.org/devel.htm). +Add the `/Lib` or `/Lib/x64` folder to your `LIB` environment variable. + +## Linux + +On Debian based Linux, install `libpcap-dev`. If not running as root, you need to set capabilities like so: ```sudo setcap cap_net_raw,cap_net_admin=eip path/to/bin``` + +## Mac OS X + +libpcap should be installed on Mac OS X by default. + +**Note:** A timeout of zero may cause ```pcap::Capture::next``` to hang and never return (because it waits for the timeout to expire before returning). This can be fixed by using a non-zero timeout (as the libpcap manual recommends) and calling ```pcap::Capture::next``` in a loop. + +## Library Location + +If `LIBPCAP_LIBDIR` environment variable is set when building the crate, it will be added to the linker search path - this allows linking against a specific `libpcap`. + +## Library Version + +The crate will automatically try to detect the installed `libpcap`/`wpcap` version by loading it during the build and calling `pcap_lib_version`. If for some reason this is not suitable, you can specify the desired library version by setting the environment variable `LIBPCAP_VER` to the desired version (e.g. `env LIBPCAP_VER=1.5.0`). The version number is used to determine which library calls to include in the compilation. + +## Optional Features + +#### `capture-stream` + +Use the `capture-stream` feature to enable support for streamed packet captures. +This feature is supported only on ubuntu and macosx. + +```toml +[dependencies] +pcap = { version = "0.7", features = ["capture-stream"] } +``` + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/__network/pcap/build.rs b/__network/pcap/build.rs new file mode 100644 index 0000000..1d9475a --- /dev/null +++ b/__network/pcap/build.rs @@ -0,0 +1,122 @@ +use std::env; +use std::ffi::CStr; +use std::os::raw::c_char; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct Version { + major: usize, + minor: usize, + micro: usize, +} + +impl Version { + fn new(major: usize, minor: usize, micro: usize) -> Version { + Version { + major, + minor, + micro, + } + } + + fn parse(s: &str) -> Result> { + let err = format!("invalid pcap lib version: {}", s); + + let re = regex::Regex::new(r"([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)")?; + let captures = re.captures(s).ok_or_else(|| err.clone())?; + + let major_str = captures.get(1).ok_or_else(|| err.clone())?.as_str(); + let minor_str = captures.get(2).ok_or_else(|| err.clone())?.as_str(); + let micro_str = captures.get(3).ok_or_else(|| err.clone())?.as_str(); + + Ok(Version::new( + major_str.parse::()?, + minor_str.parse::()?, + micro_str.parse::()?, + )) + } +} + +fn get_pcap_lib_version() -> Result> { + if let Ok(libver) = env::var("LIBPCAP_VER") { + return Version::parse(&libver); + } + + #[cfg(all(unix, not(target_os = "macos")))] + let libfile = "libpcap.so"; + #[cfg(target_os = "macos")] + let libfile = "libpcap.dylib"; + #[cfg(windows)] + let libfile = "wpcap.dll"; + + let lib = libloading::Library::new(libfile)?; + + type PcapLibVersion = unsafe extern "C" fn() -> *mut c_char; + let pcap_lib_version = unsafe { lib.get::(b"pcap_lib_version")? }; + + let c_buf: *const c_char = unsafe { pcap_lib_version() }; + let c_str: &CStr = unsafe { CStr::from_ptr(c_buf) }; + let v_str: &str = c_str.to_str()?; + + let err = format!("cannot infer pcap lib version from: {}", v_str); + + #[cfg(not(windows))] + { + let re = regex::Regex::new(r"libpcap version ([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)")?; + let captures = re.captures(v_str).ok_or_else(|| err.clone())?; + + let major_str = captures.get(1).ok_or_else(|| err.clone())?.as_str(); + let minor_str = captures.get(2).ok_or_else(|| err.clone())?.as_str(); + let micro_str = captures.get(3).ok_or_else(|| err.clone())?.as_str(); + + Ok(Version::new( + major_str.parse::()?, + minor_str.parse::()?, + micro_str.parse::()?, + )) + } + + #[cfg(windows)] + { + let re = regex::Regex::new(r"based on libpcap version ([[:digit:]]+)\.([[:digit:]]+)")?; + let captures = re.captures(v_str).ok_or(err.clone())?; + + let major_str = captures.get(1).ok_or(err.clone())?.as_str(); + let minor_str = captures.get(2).ok_or(err.clone())?.as_str(); + + Ok(Version::new( + major_str.parse::()?, + minor_str.parse::()?, + 0, + )) + } +} + +fn emit_cfg_flags(version: Version) { + assert!( + version >= Version::new(1, 0, 0), + "required pcap lib version: >=1.0.0" + ); + let api_vers: Vec = vec![ + Version::new(1, 2, 1), + Version::new(1, 5, 0), + Version::new(1, 7, 2), + Version::new(1, 9, 0), + Version::new(1, 9, 1), + ]; + + for v in api_vers.iter().filter(|&v| v <= &version) { + println!("cargo:rustc-cfg=libpcap_{}_{}_{}", v.major, v.minor, v.micro); + } +} + +fn main() { + println!("cargo:rerun-if-env-changed=LIBPCAP_LIBDIR"); + println!("cargo:rerun-if-env-changed=LIBPCAP_VER"); + + if let Ok(libdir) = env::var("LIBPCAP_LIBDIR") { + println!("cargo:rustc-link-search=native={}", libdir); + } + + let version = get_pcap_lib_version().unwrap(); + emit_cfg_flags(version); +} diff --git a/__network/pcap/examples/easylisten.rs b/__network/pcap/examples/easylisten.rs new file mode 100644 index 0000000..af18def --- /dev/null +++ b/__network/pcap/examples/easylisten.rs @@ -0,0 +1,15 @@ +fn main() { + // get the default Device + let device = pcap::Device::lookup().unwrap(); + println!("Using device {}", device.name); + + // Setup Capture + let mut cap = pcap::Capture::from_device(device) + .unwrap() + .immediate_mode(true) + .open() + .unwrap(); + + // get a packet and print its bytes + println!("{:?}", cap.next()); +} diff --git a/__network/pcap/examples/getdevices.rs b/__network/pcap/examples/getdevices.rs new file mode 100644 index 0000000..f5ce72b --- /dev/null +++ b/__network/pcap/examples/getdevices.rs @@ -0,0 +1,18 @@ +fn main() { + // list all of the devices pcap tells us are available + for device in pcap::Device::list().unwrap() { + println!("Found device! {:?}", device); + + // now you can create a Capture with this Device if you want. + let mut cap = pcap::Capture::from_device(device) + .unwrap() + .immediate_mode(true) + .open() + .unwrap(); + + // get a packet from this capture + let packet = cap.next(); + + println!("got a packet! {:?}", packet); + } +} diff --git a/__network/pcap/examples/getstatistics.rs b/__network/pcap/examples/getstatistics.rs new file mode 100644 index 0000000..175bf1d --- /dev/null +++ b/__network/pcap/examples/getstatistics.rs @@ -0,0 +1,22 @@ +fn main() { + // get the default Device + let device = pcap::Device::lookup().unwrap(); + println!("Using device {}", device.name); + + // Setup Capture + let mut cap = pcap::Capture::from_device(device) + .unwrap() + .immediate_mode(true) + .open() + .unwrap(); + + // get 10 packets + for _ in 0..10 { + cap.next().ok(); + } + let stats = cap.stats().unwrap(); + println!( + "Received: {}, dropped: {}, if_dropped: {}", + stats.received, stats.dropped, stats.if_dropped + ); +} diff --git a/__network/pcap/examples/listenlocalhost.rs b/__network/pcap/examples/listenlocalhost.rs new file mode 100644 index 0000000..9a9d2b5 --- /dev/null +++ b/__network/pcap/examples/listenlocalhost.rs @@ -0,0 +1,16 @@ +fn main() { + // listen on the device named "any", which is only available on Linux. This is only for + // demonstration purposes. + let mut cap = pcap::Capture::from_device("any") + .unwrap() + .immediate_mode(true) + .open() + .unwrap(); + + // filter out all packets that don't have 127.0.0.1 as a source or destination. + cap.filter("host 127.0.0.1").unwrap(); + + while let Ok(packet) = cap.next() { + println!("got packet! {:?}", packet); + } +} diff --git a/__network/pcap/examples/savefile.rs b/__network/pcap/examples/savefile.rs new file mode 100644 index 0000000..7441d6e --- /dev/null +++ b/__network/pcap/examples/savefile.rs @@ -0,0 +1,37 @@ +use pcap::*; + +fn main() { + { + // open capture from default device + let device = Device::lookup().unwrap(); + println!("Using device {}", device.name); + + // Setup Capture + let mut cap = Capture::from_device(device) + .unwrap() + .immediate_mode(true) + .open() + .unwrap(); + + // open savefile using the capture + let mut savefile = cap.savefile("test.pcap").unwrap(); + + // get a packet from the interface + let p = cap.next().unwrap(); + + // print the packet out + println!("packet received on network: {:?}", p); + + // write the packet to the savefile + savefile.write(&p); + } + + // open a new capture from the test.pcap file we wrote to above + let mut cap = Capture::from_file("test.pcap").unwrap(); + + // get a packet + let p = cap.next().unwrap(); + + // print that packet out -- it should be the same as the one we printed above + println!("packet obtained from file: {:?}", p); +} diff --git a/__network/pcap/examples/streamlisten.rs b/__network/pcap/examples/streamlisten.rs new file mode 100644 index 0000000..0813c06 --- /dev/null +++ b/__network/pcap/examples/streamlisten.rs @@ -0,0 +1,47 @@ +use futures::StreamExt; +use pcap::stream::{PacketCodec, PacketStream}; +use pcap::{Active, Capture, Device, Error, Packet}; + +pub struct SimpleDumpCodec; + +impl PacketCodec for SimpleDumpCodec { + type Type = String; + + fn decode<'p>(&mut self, packet: Packet<'p>) -> Result { + Ok(format!("{:?}", packet)) + } +} + +fn new_stream() -> Result, Error> { + // get the default Device + let device = Device::lookup()?; + println!("Using device {}", device.name); + + let cap = Capture::from_device(device)? + .immediate_mode(true) + .open()? + .setnonblock()?; + cap.stream(SimpleDumpCodec {}) +} + +fn main() { + let mut rt = tokio::runtime::Builder::new() + .enable_io() + .basic_scheduler() + .build() + .unwrap(); + + let stream = rt.enter(|| match new_stream() { + Ok(stream) => stream, + Err(e) => { + println!("{:?}", e); + std::process::exit(1); + } + }); + + let fut = stream.for_each(move |s| { + println!("{:?}", s); + futures::future::ready(()) + }); + rt.block_on(fut); +} diff --git a/__network/pcap/src/lib.rs b/__network/pcap/src/lib.rs new file mode 100644 index 0000000..9beb26c --- /dev/null +++ b/__network/pcap/src/lib.rs @@ -0,0 +1,870 @@ +//! pcap is a packet capture library available on Linux, Windows and Mac. This +//! crate supports creating and configuring capture contexts, sniffing packets, +//! sending packets to interfaces, listing devices, and recording packet captures +//! to pcap-format dump files. +//! +//! # Capturing packets +//! The easiest way to open an active capture handle and begin sniffing is to +//! use `.open()` on a `Device`. You can obtain the "default" device using +//! `Device::lookup()`, or you can obtain the device(s) you need via `Device::list()`. +//! +//! ```ignore +//! use pcap::Device; +//! +//! fn main() { +//! let mut cap = Device::lookup().unwrap().open().unwrap(); +//! +//! while let Ok(packet) = cap.next() { +//! println!("received packet! {:?}", packet); +//! } +//! } +//! ``` +//! +//! `Capture`'s `.next()` will produce a `Packet` which can be dereferenced to access the +//! `&[u8]` packet contents. +//! +//! # Custom configuration +//! +//! You may want to configure the `timeout`, `snaplen` or other parameters for the capture +//! handle. In this case, use `Capture::from_device()` to obtain a `Capture`, and +//! proceed to configure the capture handle. When you're finished, run `.open()` on it to +//! turn it into a `Capture`. +//! +//! ```ignore +//! use pcap::{Device,Capture}; +//! +//! fn main() { +//! let main_device = Device::lookup().unwrap(); +//! let mut cap = Capture::from_device(main_device).unwrap() +//! .promisc(true) +//! .snaplen(5000) +//! .open().unwrap(); +//! +//! while let Ok(packet) = cap.next() { +//! println!("received packet! {:?}", packet); +//! } +//! } +//! ``` + +use unique::Unique; + +use std::borrow::Borrow; +use std::marker::PhantomData; +use std::ptr; +use std::ffi::{self, CString, CStr}; +use std::path::Path; +use std::slice; +use std::ops::Deref; +use std::mem; +use std::fmt; +#[cfg(feature = "capture-stream")] +use std::io; +#[cfg(not(windows))] +use std::os::unix::io::{RawFd, AsRawFd}; + +use self::Error::*; + +mod raw; +mod unique; +#[cfg(feature = "capture-stream")] +pub mod stream; + +/// An error received from pcap +#[derive(Debug, PartialEq)] +pub enum Error { + MalformedError(std::str::Utf8Error), + InvalidString, + PcapError(String), + InvalidLinktype, + TimeoutExpired, + NoMorePackets, + NonNonBlock, + InsufficientMemory, + InvalidInputString, + IoError(std::io::ErrorKind), + #[cfg(not(windows))] + InvalidRawFd, +} + +impl Error { + fn new(ptr: *const libc::c_char) -> Error { + match cstr_to_string(ptr) { + Err(e) => e as Error, + Ok(string) => PcapError(string.unwrap_or_default()), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + MalformedError(ref e) => write!(f, "libpcap returned invalid UTF-8: {}", e), + InvalidString => write!(f, "libpcap returned a null string"), + PcapError(ref e) => write!(f, "libpcap error: {}", e), + InvalidLinktype => write!(f, "invalid or unknown linktype"), + TimeoutExpired => write!(f, "timeout expired while reading from a live capture"), + NonNonBlock => write!(f, "must be in non-blocking mode to function"), + NoMorePackets => write!(f, "no more packets to read from the file"), + InsufficientMemory => write!(f, "insufficient memory"), + InvalidInputString => write!(f, "invalid input string (internal null)"), + IoError(ref e) => write!(f, "io error occurred: {:?}", e), + #[cfg(not(windows))] + InvalidRawFd => write!(f, "invalid raw file descriptor provided"), + } + } +} + +impl std::error::Error for Error { + fn description(&self) -> &str { + match *self { + MalformedError(..) => "libpcap returned invalid UTF-8", + PcapError(..) => "libpcap FFI error", + InvalidString => "libpcap returned a null string", + InvalidLinktype => "invalid or unknown linktype", + TimeoutExpired => "timeout expired while reading from a live capture", + NonNonBlock => "must be in non-blocking mode to function", + NoMorePackets => "no more packets to read from the file", + InsufficientMemory => "insufficient memory", + InvalidInputString => "invalid input string (internal null)", + IoError(..) => "io error occurred", + #[cfg(not(windows))] + InvalidRawFd => "invalid raw file descriptor provided", + } + } + + fn cause(&self) -> Option<&dyn std::error::Error> { + match *self { + MalformedError(ref e) => Some(e), + _ => None, + } + } +} + +impl From for Error { + fn from(_: ffi::NulError) -> Error { + InvalidInputString + } +} + +impl From for Error { + fn from(obj: std::str::Utf8Error) -> Error { + MalformedError(obj) + } +} + +impl From for Error { + fn from(obj: std::io::Error) -> Error { + IoError(obj.kind()) + } +} + +impl From for Error { + fn from(obj: std::io::ErrorKind) -> Error { + IoError(obj) + } +} + +#[derive(Debug, Clone)] +/// A network device name and (potentially) pcap's description of it. +pub struct Device { + pub name: String, + pub desc: Option, +} + +impl Device { + fn new(name: String, desc: Option) -> Device { + Device { name, desc } + } + + /// Opens a `Capture` on this device. + pub fn open(self) -> Result, Error> { + Capture::from_device(self)?.open() + } + + /// Returns the default Device suitable for captures according to pcap_lookupdev, + /// or an error from pcap. + pub fn lookup() -> Result { + with_errbuf(|err| unsafe { + cstr_to_string(raw::pcap_lookupdev(err)) + ? + .map(|name| Device::new(name, None)) + .ok_or_else(|| Error::new(err)) + }) + } + + /// Returns a vector of `Device`s known by pcap via pcap_findalldevs. + pub fn list() -> Result, Error> { + with_errbuf(|err| unsafe { + let mut dev_buf: *mut raw::pcap_if_t = ptr::null_mut(); + if raw::pcap_findalldevs(&mut dev_buf, err) != 0 { + return Err(Error::new(err)); + } + let result = (|| { + let mut devices = vec![]; + let mut cur = dev_buf; + while !cur.is_null() { + let dev = &*cur; + devices.push(Device::new(cstr_to_string(dev.name)?.ok_or(InvalidString)?, + cstr_to_string(dev.description)?)); + cur = dev.next; + } + Ok(devices) + })(); + raw::pcap_freealldevs(dev_buf); + result + }) + } +} + +impl<'a> Into for &'a str { + fn into(self) -> Device { + Device::new(self.into(), None) + } +} + +/// This is a datalink link type. +/// +/// As an example, `Linktype(1)` is ethernet. A full list of linktypes is available +/// [here](http://www.tcpdump.org/linktypes.html). +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Linktype(pub i32); + +impl Linktype { + /// Gets the name of the link type, such as EN10MB + pub fn get_name(&self) -> Result { + cstr_to_string(unsafe { raw::pcap_datalink_val_to_name(self.0) }) + ? + .ok_or(InvalidLinktype) + } + + /// Gets the description of a link type. + pub fn get_description(&self) -> Result { + cstr_to_string(unsafe { raw::pcap_datalink_val_to_description(self.0) }) + ? + .ok_or(InvalidLinktype) + } +} + +/// Represents a packet returned from pcap. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Packet<'a> { + pub header: &'a PacketHeader, + pub data: &'a [u8], +} + +impl<'a> Packet<'a> { + #[doc(hidden)] + pub fn new(header: &'a PacketHeader, data: &'a [u8]) -> Packet<'a> { + Packet { header, data } + } +} + +impl<'b> Deref for Packet<'b> { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + self.data + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +/// Represents a packet header provided by pcap, including the timeval, caplen and len. +pub struct PacketHeader { + pub ts: libc::timeval, + pub caplen: u32, + pub len: u32, +} + +impl fmt::Debug for PacketHeader { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, + "PacketHeader {{ ts: {}.{:06}, caplen: {}, len: {} }}", + self.ts.tv_sec, + self.ts.tv_usec, + self.caplen, + self.len) + } +} + +impl PartialEq for PacketHeader { + fn eq(&self, rhs: &PacketHeader) -> bool { + self.ts.tv_sec == rhs.ts.tv_sec && self.ts.tv_usec == rhs.ts.tv_usec && + self.caplen == rhs.caplen && self.len == rhs.len + } +} + +impl Eq for PacketHeader {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Stat { + pub received: u32, + pub dropped: u32, + pub if_dropped: u32, +} + +impl Stat { + fn new(received: u32, dropped: u32, if_dropped: u32) -> Stat { + Stat { received, dropped, if_dropped } + } +} + +#[repr(u32)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Precision { + Micro = 0, + Nano = 1, +} + +/// Phantom type representing an inactive capture handle. +pub enum Inactive {} + +/// Phantom type representing an active capture handle. +pub enum Active {} + +/// Phantom type representing an offline capture handle, from a pcap dump file. +/// Implements `Activated` because it behaves nearly the same as a live handle. +pub enum Offline {} + +/// Phantom type representing a dead capture handle. This can be use to create +/// new save files that are not generated from an active capture. +/// Implements `Activated` because it behaves nearly the same as a live handle. +pub enum Dead {} + +pub unsafe trait Activated: State {} + +unsafe impl Activated for Active {} + +unsafe impl Activated for Offline {} + +unsafe impl Activated for Dead {} + +/// `Capture`s can be in different states at different times, and in these states they +/// may or may not have particular capabilities. This trait is implemented by phantom +/// types which allows us to punt these invariants to the type system to avoid runtime +/// errors. +pub unsafe trait State {} + +unsafe impl State for Inactive {} + +unsafe impl State for Active {} + +unsafe impl State for Offline {} + +unsafe impl State for Dead {} + +/// This is a pcap capture handle which is an abstraction over the `pcap_t` provided by pcap. +/// There are many ways to instantiate and interact with a pcap handle, so phantom types are +/// used to express these behaviors. +/// +/// **`Capture`** is created via `Capture::from_device()`. This handle is inactive, +/// so you cannot (yet) obtain packets from it. However, you can configure things like the +/// buffer size, snaplen, timeout, and promiscuity before you activate it. +/// +/// **`Capture`** is created by calling `.open()` on a `Capture`. This +/// activates the capture handle, allowing you to get packets with `.next()` or apply filters +/// with `.filter()`. +/// +/// **`Capture`** is created via `Capture::from_file()`. This allows you to read a +/// pcap format dump file as if you were opening an interface -- very useful for testing or +/// analysis. +/// +/// **`Capture`** is created via `Capture::dead()`. This allows you to create a pcap +/// format dump file without needing an active capture. +/// +/// # Example: +/// +/// ```ignore +/// let cap = Capture::from_device(Device::lookup().unwrap()) // open the "default" interface +/// .unwrap() // assume the device exists and we are authorized to open it +/// .open() // activate the handle +/// .unwrap(); // assume activation worked +/// +/// while let Ok(packet) = cap.next() { +/// println!("received packet! {:?}", packet); +/// } +/// ``` +pub struct Capture { + nonblock: bool, + handle: Unique, + _marker: PhantomData, +} + +impl Capture { + fn new(handle: *mut raw::pcap_t) -> Capture { + unsafe { + Capture { + nonblock: false, + handle: Unique::new(handle), + _marker: PhantomData, + } + } + } + + fn new_raw(path: Option<&str>, func: F) -> Result, Error> + where F: FnOnce(*const libc::c_char, *mut libc::c_char) -> *mut raw::pcap_t + { + with_errbuf(|err| { + let handle = match path { + None => func(ptr::null(), err), + Some(path) => { + let path = CString::new(path)?; + func(path.as_ptr(), err) + } + }; + unsafe { handle.as_mut() }.map(|h| Capture::new(h)).ok_or_else(|| Error::new(err)) + }) + } + + /// Set the minumum amount of data received by the kernel in a single call. + /// + /// Note that this value is set to 0 when the capture is set to immediate mode. You should not + /// call `min_to_copy` on captures in immediate mode if you want them to stay in immediate mode. + #[cfg(windows)] + pub fn min_to_copy(self, to: i32) -> Capture { + unsafe { raw::pcap_setmintocopy(*self.handle, to as _); } + self + } + + #[inline] + fn check_err(&self, success: bool) -> Result<(), Error> { + if success { + Ok(()) + } else { + Err(Error::new(unsafe { raw::pcap_geterr(*self.handle) })) + } + } +} + +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 { raw::pcap_open_offline(path, err) }) + } + + /// Opens an offline capture handle from a pcap dump file, given a path. + /// Takes an additional precision argument specifying the time stamp precision desired. + #[cfg(libpcap_1_5_0)] + pub fn from_file_with_precision>(path: P, precision: Precision) -> Result, Error> { + Capture::new_raw(path.as_ref().to_str(), |path, err| unsafe { + raw::pcap_open_offline_with_tstamp_precision(path, precision as _, err) + }) + } + + /// Opens an offline capture handle from a pcap dump file, given a file descriptor. + #[cfg(not(windows))] + pub fn from_raw_fd(fd: RawFd) -> Result, Error> { + 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 additional precision argument specifying the time stamp precision desired. + #[cfg(all(not(windows), libpcap_1_5_0))] + pub fn from_raw_fd_with_precision(fd: RawFd, precision: Precision) -> Result, Error> { + 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) + })) + } +} + +#[repr(i32)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum TimestampType { + Host = 0, + HostLowPrec = 1, + HostHighPrec = 2, + Adapter = 3, + AdapterUnsynced = 4, +} + +#[deprecated(note = "Renamed to TimestampType")] +pub type TstampType = TimestampType; + +#[repr(u32)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Direction { + InOut = raw::PCAP_D_INOUT, + In = raw::PCAP_D_IN, + Out = raw::PCAP_D_OUT, +} + +impl Capture { + /// Opens a capture handle for a device. You can pass a `Device` or an `&str` device + /// name here. The handle is inactive, but can be activated via `.open()`. + /// + /// # Example + /// ``` + /// use pcap::*; + /// + /// // Usage 1: Capture from a single owned device + /// let dev: Device = pcap::Device::lookup().unwrap(); + /// let cap1 = Capture::from_device(dev); + /// + /// // Usage 2: Capture from an element of device list. + /// let list: Vec = pcap::Device::list().unwrap(); + /// let cap2 = Capture::from_device(list[0].clone()); + /// + /// // Usage 3: Capture from `&str` device name + /// let cap3 = Capture::from_device("eth0"); + /// ``` + pub fn from_device>(device: D) -> Result, Error> { + let device: Device = device.into(); + Capture::new_raw(Some(&device.name), + |name, err| unsafe { raw::pcap_create(name, err) }) + } + + /// Activates an inactive capture created from `Capture::from_device()` or returns + /// an error. + pub fn open(self) -> Result, Error> { + unsafe { + self.check_err(raw::pcap_activate(*self.handle) == 0)?; + Ok(mem::transmute(self)) + } + } + + /// Set the read timeout for the Capture. By default, this is 0, so it will block + /// indefinitely. + pub fn timeout(self, ms: i32) -> Capture { + unsafe { raw::pcap_set_timeout(*self.handle, ms) }; + self + } + + /// Set the time stamp type to be used by a capture device. + #[cfg(libpcap_1_2_1)] + pub fn tstamp_type(self, tstamp_type: TimestampType) -> Capture { + unsafe { raw::pcap_set_tstamp_type(*self.handle, tstamp_type as _) }; + self + } + + /// Set promiscuous mode on or off. By default, this is off. + pub fn promisc(self, to: bool) -> Capture { + unsafe { raw::pcap_set_promisc(*self.handle, to as _) }; + self + } + + /// Set immediate mode on or off. By default, this is off. + /// + /// Note that in WinPcap immediate mode is set by passing a 0 argument to `min_to_copy`. + /// Immediate mode will be unset if `min_to_copy` is later called with a non-zero argument. + /// Immediate mode is unset by resetting `min_to_copy` to the WinPcap default possibly changing + /// a previously set value. When using `min_to_copy`, it is best to avoid `immediate_mode`. + #[cfg(any(libpcap_1_5_0, windows))] + pub fn immediate_mode(self, to: bool) -> Capture { + // Prior to 1.5.0 when `pcap_set_immediate_mode` was introduced, the necessary steps to set + // immediate mode were more complicated, depended on the OS, and in some configurations had + // to be set on an active capture. See + // https://www.tcpdump.org/manpages/pcap_set_immediate_mode.3pcap.html. Since we do not + // expect pre-1.5.0 version on unix systems in the wild, we simply ignore those cases. + #[cfg(libpcap_1_5_0)] + unsafe { raw::pcap_set_immediate_mode(*self.handle, to as _) }; + + // In WinPcap we use `pcap_setmintocopy` as it does not have `pcap_set_immediate_mode`. + #[cfg(all(windows, not(libpcap_1_5_0)))] + unsafe { raw::pcap_setmintocopy(*self.handle, if to { 0 } else { raw::WINPCAP_MINTOCOPY_DEFAULT }) }; + + self + } + + /// Set rfmon mode on or off. The default is maintained by pcap. + #[cfg(not(windows))] + pub fn rfmon(self, to: bool) -> Capture { + unsafe { raw::pcap_set_rfmon(*self.handle, to as _) }; + self + } + + /// Set the buffer size for incoming packet data. + /// + /// The default is 1000000. This should always be larger than the snaplen. + pub fn buffer_size(self, to: i32) -> Capture { + unsafe { raw::pcap_set_buffer_size(*self.handle, to) }; + self + } + + /// Set the time stamp precision returned in captures. + #[cfg(libpcap_1_5_0)] + pub fn precision(self, precision: Precision) -> Capture { + unsafe { raw::pcap_set_tstamp_precision(*self.handle, precision as _) }; + self + } + + /// Set the snaplen size (the maximum length of a packet captured into the buffer). + /// Useful if you only want certain headers, but not the entire packet. + /// + /// The default is 65535. + pub fn snaplen(self, to: i32) -> Capture { + unsafe { raw::pcap_set_snaplen(*self.handle, to) }; + self + } +} + +///# Activated captures include `Capture` and `Capture`. +impl Capture { + /// List the datalink types that this captured device supports. + pub fn list_datalinks(&self) -> Result, Error> { + unsafe { + let mut links: *mut i32 = ptr::null_mut(); + let num = raw::pcap_list_datalinks(*self.handle, &mut links); + let mut vec = vec![]; + if num > 0 { + vec.extend(slice::from_raw_parts(links, num as _).iter().cloned().map(Linktype)) + } + raw::pcap_free_datalinks(links); + self.check_err(num > 0).and(Ok(vec)) + } + } + + /// Set the datalink type for the current capture handle. + pub fn set_datalink(&mut self, linktype: Linktype) -> Result<(), Error> { + self.check_err(unsafe { raw::pcap_set_datalink(*self.handle, linktype.0) == 0 }) + } + + /// Get the current datalink type for this capture handle. + pub fn get_datalink(&self) -> Linktype { + unsafe { Linktype(raw::pcap_datalink(*self.handle)) } + } + + /// 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 handle = unsafe { raw::pcap_dump_open(*self.handle, name.as_ptr()) }; + self.check_err(!handle.is_null()).map(|_| Savefile::new(handle)) + } + + /// Create a `Savefile` context for recording captured packets using this `Capture`'s + /// configurations. The output is written to a raw file descriptor which is opened + /// in `"w"` mode. + #[cfg(not(windows))] + pub fn savefile_raw_fd(&self, fd: RawFd) -> Result { + open_raw_fd(fd, b'w') + .and_then(|file| { + let handle = unsafe { raw::pcap_dump_fopen(*self.handle, file) }; + self.check_err(!handle.is_null()).map(|_| Savefile::new(handle)) + }) + } + + /// Reopen a `Savefile` context for recording captured packets using this `Capture`'s + /// configurations. This is similar to `savefile()` but does not create the file if it + /// does not exist and, if it does already exist, and is a pcap file with the same + /// byte order as the host opening the file, and has the same time stamp precision, + /// link-layer header type, and snapshot length as p, it will write new packets + /// 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 handle = unsafe { raw::pcap_dump_open_append(*self.handle, name.as_ptr()) }; + self.check_err(!handle.is_null()).map(|_| Savefile::new(handle)) + } + + /// Set the direction of the capture + pub fn direction(&self, direction: Direction) -> Result<(), Error> { + self.check_err(unsafe { raw::pcap_setdirection(*self.handle, direction as u32 as _) == 0 }) + } + + /// Blocks until a packet is returned from the capture handle or an error occurs. + /// + /// pcap captures packets and places them into a buffer which this function reads + /// from. This buffer has a finite length, so if the buffer fills completely new + /// packets will be discarded temporarily. This means that in realtime situations, + /// you probably want to minimize the time between calls of this next() method. + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> Result { + unsafe { + let mut header: *mut raw::pcap_pkthdr = ptr::null_mut(); + let mut packet: *const libc::c_uchar = ptr::null(); + let retcode = raw::pcap_next_ex(*self.handle, &mut header, &mut packet); + self.check_err(retcode != -1)?; // -1 => an error occured while reading the packet + match retcode { + i if i >= 1 => { + // packet was read without issue + Ok(Packet::new(&*(&*header as *const raw::pcap_pkthdr as *const PacketHeader), + slice::from_raw_parts(packet, (*header).caplen as _))) + } + 0 => { + // packets are being read from a live capture and the + // timeout expired + Err(TimeoutExpired) + } + -2 => { + // packets are being read from a "savefile" and there are no + // more packets to read + Err(NoMorePackets) + } + _ => { + // libpcap only defines codes >=1, 0, -1, and -2 + unreachable!() + } + } + } + } + + #[cfg(feature = "capture-stream")] + fn next_noblock<'a>(&'a mut self, cx: &mut core::task::Context, fd: &mut tokio::io::PollEvented) -> Result, Error> { + if let futures::task::Poll::Pending = fd.poll_read_ready(cx, mio::Ready::readable()) { + Err(IoError(io::ErrorKind::WouldBlock)) + } else { + match self.next() { + Ok(p) => Ok(p), + Err(TimeoutExpired) => { + fd.clear_read_ready(cx, mio::Ready::readable())?; + Err(IoError(io::ErrorKind::WouldBlock)) + } + Err(e) => Err(e) + } + } + } + + #[cfg(feature = "capture-stream")] + pub fn stream(self, codec: C) -> Result, Error> { + if !self.nonblock { + return Err(NonNonBlock); + } + unsafe { + let fd = raw::pcap_get_selectable_fd(*self.handle); + stream::PacketStream::new(self, fd, codec) + } + } + + /// Adds a filter to the capture using the given BPF program string. Internally + /// this is compiled using `pcap_compile()`. + /// + /// See http://biot.com/capstats/bpf.html for more information about this syntax. + pub fn filter(&mut self, program: &str) -> Result<(), Error> { + let program = CString::new(program)?; + unsafe { + let mut bpf_program: raw::bpf_program = mem::zeroed(); + let ret = raw::pcap_compile(*self.handle, &mut bpf_program, program.as_ptr(), 0, 0); + self.check_err(ret != -1)?; + let ret = raw::pcap_setfilter(*self.handle, &mut bpf_program); + raw::pcap_freecode(&mut bpf_program); + self.check_err(ret != -1) + } + } + + pub fn stats(&mut self) -> Result { + unsafe { + let mut stats: raw::pcap_stat = mem::zeroed(); + self.check_err(raw::pcap_stats(*self.handle, &mut stats) != -1) + .map(|_| Stat::new(stats.ps_recv, stats.ps_drop, stats.ps_ifdrop)) + } + } +} + +impl Capture { + /// Sends a packet over this capture handle's interface. + pub fn sendpacket>(&mut self, buf: B) -> Result<(), Error> { + let buf = buf.borrow(); + self.check_err(unsafe { + raw::pcap_sendpacket(*self.handle, buf.as_ptr() as _, buf.len() as _) == 0 + }) + } + + pub fn setnonblock(mut self) -> Result, Error> { + with_errbuf(|err| unsafe { + if raw::pcap_setnonblock(*self.handle, 1, err) != 0 { + return Err(Error::new(err)); + } + self.nonblock = true; + Ok(self) + }) + } +} + +impl Capture { + /// Creates a "fake" capture handle for the given link type. + pub fn dead(linktype: Linktype) -> Result, Error> { + unsafe { raw::pcap_open_dead(linktype.0, 65535).as_mut() } + .map(|h| Capture::new(h)) + .ok_or(InsufficientMemory) + } +} + +#[cfg(not(windows))] +impl AsRawFd for Capture { + fn as_raw_fd(&self) -> RawFd { + unsafe { + let fd = raw::pcap_fileno(*self.handle); + + match fd { + -1 => { + panic!("Unable to get file descriptor for live capture"); + } + fd => fd, + } + } + } +} + +impl Drop for Capture { + fn drop(&mut self) { + unsafe { raw::pcap_close(*self.handle) } + } +} + +impl From> for Capture { + fn from(cap: Capture) -> Capture { + unsafe { mem::transmute(cap) } + } +} + +/// Abstraction for writing pcap savefiles, which can be read afterwards via `Capture::from_file()`. +pub struct Savefile { + handle: Unique, +} + +impl Savefile { + pub fn write(&mut self, packet: &Packet) { + unsafe { + raw::pcap_dump(*self.handle as _, + &*(packet.header as *const PacketHeader as *const raw::pcap_pkthdr), + packet.data.as_ptr()); + } + } +} + +impl Savefile { + fn new(handle: *mut raw::pcap_dumper_t) -> Savefile { + unsafe { Savefile { handle: Unique::new(handle) } } + } +} + +impl Drop for Savefile { + fn drop(&mut self) { + unsafe { raw::pcap_dump_close(*self.handle) } + } +} + +#[cfg(not(windows))] +pub fn open_raw_fd(fd: RawFd, mode: u8) -> Result<*mut libc::FILE, Error> { + let mode = vec![mode, 0]; + unsafe { libc::fdopen(fd, mode.as_ptr() as _).as_mut() }.map(|f| f as _).ok_or(InvalidRawFd) +} + +#[inline] +fn cstr_to_string(ptr: *const libc::c_char) -> Result, Error> { + let string = if ptr.is_null() { + None + } else { + Some(unsafe { CStr::from_ptr(ptr as _) }.to_str()?.to_owned()) + }; + Ok(string) +} + +#[inline] +fn with_errbuf(func: F) -> Result +where F: FnOnce(*mut libc::c_char) -> Result +{ + let mut errbuf = [0i8; 256]; + func(errbuf.as_mut_ptr() as _) +} + +#[test] +fn test_struct_size() { + use std::mem::size_of; + assert_eq!(size_of::(), size_of::()); +} diff --git a/__network/pcap/src/raw.rs b/__network/pcap/src/raw.rs new file mode 100644 index 0000000..5447a55 --- /dev/null +++ b/__network/pcap/src/raw.rs @@ -0,0 +1,221 @@ +#![allow(dead_code)] +#![allow(non_camel_case_types)] + +use libc::{c_int, c_uint, c_char, c_uchar, c_ushort, sockaddr, timeval, FILE}; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct bpf_program { + pub bf_len: c_uint, + pub bf_insns: *mut bpf_insn, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct bpf_insn { + pub code: c_ushort, + pub jt: c_uchar, + pub jf: c_uchar, + pub k: c_uint, +} + +pub enum pcap_t { } + +pub enum pcap_dumper_t { } + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct pcap_file_header { + pub magic: c_uint, + pub version_major: c_ushort, + pub version_minor: c_ushort, + pub thiszone: c_int, + pub sigfigs: c_uint, + pub snaplen: c_uint, + pub linktype: c_uint, +} + +pub type pcap_direction_t = c_uint; + +pub const PCAP_D_INOUT: pcap_direction_t = 0; +pub const PCAP_D_IN: pcap_direction_t = 1; +pub const PCAP_D_OUT: pcap_direction_t = 2; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct pcap_pkthdr { + pub ts: timeval, + pub caplen: c_uint, + pub len: c_uint, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct pcap_stat { + pub ps_recv: c_uint, + pub ps_drop: c_uint, + pub ps_ifdrop: c_uint, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct pcap_if_t { + pub next: *mut pcap_if_t, + pub name: *mut c_char, + pub description: *mut c_char, + pub addresses: *mut pcap_addr_t, + pub flags: c_uint, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct pcap_addr_t { + pub next: *mut pcap_addr_t, + pub addr: *mut sockaddr, + pub netmask: *mut sockaddr, + pub broadaddr: *mut sockaddr, + pub dstaddr: *mut sockaddr, +} + +pub type pcap_handler = Option ()>; + +extern "C" { + 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; + pub fn pcap_create(arg1: *const c_char, arg2: *mut c_char) -> *mut pcap_t; + pub fn pcap_set_snaplen(arg1: *mut pcap_t, arg2: c_int) -> c_int; + pub fn pcap_set_promisc(arg1: *mut pcap_t, arg2: c_int) -> c_int; + // pub fn pcap_can_set_rfmon(arg1: *mut pcap_t) -> c_int; + pub fn pcap_set_timeout(arg1: *mut pcap_t, arg2: c_int) -> c_int; + pub fn pcap_set_buffer_size(arg1: *mut pcap_t, arg2: c_int) -> c_int; + pub fn pcap_activate(arg1: *mut pcap_t) -> c_int; + // pub fn pcap_open_live(arg1: *const c_char, arg2: c_int, arg3: c_int, arg4: c_int, + // arg5: *mut c_char) -> *mut pcap_t; + pub fn pcap_open_dead(arg1: c_int, arg2: c_int) -> *mut pcap_t; + pub fn pcap_open_offline(arg1: *const c_char, arg2: *mut c_char) -> *mut pcap_t; + pub fn pcap_fopen_offline(arg1: *mut FILE, arg2: *mut c_char) -> *mut pcap_t; + pub fn pcap_close(arg1: *mut pcap_t); + // pub fn pcap_loop(arg1: *mut pcap_t, arg2: c_int, + // arg3: pcap_handler, arg4: *mut c_uchar) -> c_int; + // pub fn pcap_dispatch(arg1: *mut pcap_t, arg2: c_int, arg3: pcap_handler, + // arg4: *mut c_uchar)-> c_int; + // pub fn pcap_next(arg1: *mut pcap_t, arg2: *mut pcap_pkthdr) -> *const c_uchar; + pub fn pcap_next_ex(arg1: *mut pcap_t, arg2: *mut *mut pcap_pkthdr, + arg3: *mut *const c_uchar) -> c_int; + // pub fn pcap_breakloop(arg1: *mut pcap_t); + pub fn pcap_stats(arg1: *mut pcap_t, arg2: *mut pcap_stat) -> c_int; + pub fn pcap_setfilter(arg1: *mut pcap_t, arg2: *mut bpf_program) -> c_int; + pub fn pcap_setdirection(arg1: *mut pcap_t, arg2: pcap_direction_t) -> c_int; + // pub fn pcap_getnonblock(arg1: *mut pcap_t, arg2: *mut c_char) -> c_int; + pub fn pcap_setnonblock(arg1: *mut pcap_t, arg2: c_int, arg3: *mut c_char) -> c_int; + pub fn pcap_sendpacket(arg1: *mut pcap_t, arg2: *const c_uchar, arg3: c_int) -> c_int; + // pub fn pcap_statustostr(arg1: c_int) -> *const c_char; + // pub fn pcap_strerror(arg1: c_int) -> *const c_char; + pub fn pcap_geterr(arg1: *mut pcap_t) -> *mut c_char; + // pub fn pcap_perror(arg1: *mut pcap_t, arg2: *mut c_char); + pub fn pcap_compile(arg1: *mut pcap_t, arg2: *mut bpf_program, arg3: *const c_char, + arg4: c_int, arg5: c_uint) -> c_int; + // pub fn pcap_compile_nopcap(arg1: c_int, arg2: c_int, arg3: *mut bpf_program, + // arg4: *const c_char, arg5: c_int, arg6: c_uint) -> c_int; + pub fn pcap_freecode(arg1: *mut bpf_program); + // pub fn pcap_offline_filter(arg1: *const bpf_program, arg2: *const pcap_pkthdr, + // arg3: *const c_uchar) -> c_int; + pub fn pcap_datalink(arg1: *mut pcap_t) -> c_int; + // pub fn pcap_datalink_ext(arg1: *mut pcap_t) -> c_int; + pub fn pcap_list_datalinks(arg1: *mut pcap_t, arg2: *mut *mut c_int) -> c_int; + pub fn pcap_set_datalink(arg1: *mut pcap_t, arg2: c_int) -> c_int; + pub fn pcap_free_datalinks(arg1: *mut c_int); + // 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_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; + // 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_fopen(arg1: *mut pcap_t, fp: *mut FILE) -> *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); + pub fn pcap_dump(arg1: *mut c_uchar, arg2: *const pcap_pkthdr, arg3: *const c_uchar); + pub fn pcap_findalldevs(arg1: *mut *mut pcap_if_t, arg2: *mut c_char) -> c_int; + pub fn pcap_freealldevs(arg1: *mut pcap_if_t); + // pub fn pcap_lib_version() -> *const c_char; + // pub fn bpf_image(arg1: *const bpf_insn, arg2: c_int) -> *mut c_char; + // pub fn bpf_dump(arg1: *const bpf_program, arg2: c_int); + pub fn pcap_get_selectable_fd(arg1: *mut pcap_t) -> c_int; +} + +#[cfg(libpcap_1_2_1)] +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_set_tstamp_type(arg1: *mut pcap_t, arg2: c_int) -> c_int; + // pub fn pcap_tstamp_type_name_to_val(arg1: *const c_char) -> c_int; + // pub fn pcap_tstamp_type_val_to_description(arg1: c_int) -> *const c_char; + // pub fn pcap_tstamp_type_val_to_name(arg1: c_int) -> *const c_char; +} + +#[cfg(libpcap_1_5_0)] +extern "C" { + pub fn pcap_fopen_offline_with_tstamp_precision(arg1: *mut FILE, arg2: c_uint, + arg3: *mut c_char) -> *mut pcap_t; + // pub fn pcap_get_tstamp_precision(arg1: *mut pcap_t) -> c_int; + pub fn pcap_open_dead_with_tstamp_precision(arg1: c_int, arg2: c_int, + arg3: c_uint) -> *mut pcap_t; + pub fn pcap_open_offline_with_tstamp_precision(arg1: *const c_char, arg2: c_uint, + arg3: *mut c_char) -> *mut pcap_t; + pub fn pcap_set_immediate_mode(arg1: *mut pcap_t, arg2: c_int) -> c_int; + pub fn pcap_set_tstamp_precision(arg1: *mut pcap_t, arg2: c_int) -> c_int; +} + +#[cfg(libpcap_1_7_2)] +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" { + // pcap_bufsize + // pcap_createsrcstr + // pcap_dump_ftell64 + // pcap_findalldevs_ex + // pcap_get_required_select_timeout + // pcap_open + // pcap_parsesrcstr + // pcap_remoteact_accept + // pcap_remoteact_cleanup + // pcap_remoteact_close + // pcap_remoteact_list + // pcap_set_protocol_linux + // pcap_setsampling +} + +#[cfg(libpcap_1_9_1)] +extern "C" { + // pcap_datalink_val_to_description_or_dlt +} + +#[cfg(windows)] +#[link(name = "wpcap")] +pub const WINPCAP_MINTOCOPY_DEFAULT: c_int = 16000; + +#[cfg(windows)] +#[link(name = "wpcap")] +extern "C" { + pub fn pcap_setmintocopy(arg1: *mut pcap_t, arg2: c_int) -> c_int; +} + +#[cfg(not(windows))] +#[link(name = "pcap")] +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; +} diff --git a/__network/pcap/src/stream.rs b/__network/pcap/src/stream.rs new file mode 100644 index 0000000..316476c --- /dev/null +++ b/__network/pcap/src/stream.rs @@ -0,0 +1,68 @@ +use mio::{Ready, Poll, PollOpt, Token}; +use mio::event::Evented; +use mio::unix::EventedFd; +use std::io; +use std::marker::Unpin; +#[cfg(not(windows))] +use std::os::unix::io::RawFd; +use std::pin::Pin; +use super::Activated; +use super::Packet; +use super::Error; +use super::State; +use super::Capture; + +pub struct SelectableFd { + fd: RawFd +} + +impl Evented for SelectableFd { + fn register(&self, poll: &Poll, token: Token, interest: Ready, opts: PollOpt) + -> io::Result<()> + { + EventedFd(&self.fd).register(poll, token, interest, opts) + } + + fn reregister(&self, poll: &Poll, token: Token, interest: Ready, opts: PollOpt) + -> io::Result<()> + { + EventedFd(&self.fd).reregister(poll, token, interest, opts) + } + + fn deregister(&self, poll: &Poll) -> io::Result<()> { + EventedFd(&self.fd).deregister(poll) + } +} + +pub trait PacketCodec { + type Type; + fn decode<'a>(&mut self, packet: Packet<'a>) -> Result; +} + +pub struct PacketStream { + cap: Capture, + fd: tokio::io::PollEvented, + codec: C, +} + +impl PacketStream { + pub fn new(cap: Capture, fd: RawFd, codec: C) -> Result, Error> { + Ok(PacketStream { cap, fd: tokio::io::PollEvented::new(SelectableFd { fd })?, codec }) + } +} + +impl<'a, T: Activated + ? Sized + Unpin, C: PacketCodec + Unpin> futures::Stream for PacketStream { + type Item = Result; + fn poll_next(self: Pin<&mut Self>, cx: &mut core::task::Context) -> futures::task::Poll> { + let stream = Pin::into_inner(self); + let p = match stream.cap.next_noblock(cx, &mut stream.fd) { + Ok(t) => t, + Err(Error::IoError(ref e)) if *e == ::std::io::ErrorKind::WouldBlock => { + return futures::task::Poll::Pending; + } + Err(e) => return futures::task::Poll::Ready(Some(Err(e))), + }; + let frame_result = stream.codec.decode(p); + futures::task::Poll::Ready(Some(frame_result)) + } +} diff --git a/__network/pcap/src/unique.rs b/__network/pcap/src/unique.rs new file mode 100644 index 0000000..38ffd1e --- /dev/null +++ b/__network/pcap/src/unique.rs @@ -0,0 +1,43 @@ +#![allow(dead_code)] + +use std::fmt; +use std::marker::PhantomData; +use std::ops::Deref; + +pub struct Unique { + pointer: *const T, + _marker: PhantomData, +} + +unsafe impl Send for Unique {} +unsafe impl Sync for Unique {} + +impl Unique { + pub unsafe fn new(ptr: *mut T) -> Unique { + Unique { + pointer: ptr, + _marker: PhantomData, + } + } + pub unsafe fn get(&self) -> &T { + &*self.pointer + } + pub unsafe fn get_mut(&mut self) -> &mut T { + &mut ***self + } +} + +impl Deref for Unique { + type Target = *mut T; + + #[inline] + fn deref(&self) -> &*mut T { + unsafe { &*(&self.pointer as *const *const T as *const *mut T) } + } +} + +impl fmt::Pointer for Unique { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Pointer::fmt(&self.pointer, f) + } +} diff --git a/__network/pcap/tests/data/packet_snaplen_20.pcap b/__network/pcap/tests/data/packet_snaplen_20.pcap new file mode 100644 index 0000000000000000000000000000000000000000..aee84dbd06d1f59085ed1d11a41df778e4caf3fd GIT binary patch literal 60 zcmca|c+)~A1{MYw5CJk6fw=bC@<8Jt76uSM35c1b1V5axPB8WsJ{rNn;L5-dqV^sD DSKd9_+B&*=`UZwZ#wMm_<^ag;9jyQW literal 0 HcmV?d00001 diff --git a/__network/pcap/tests/lib.rs b/__network/pcap/tests/lib.rs new file mode 100644 index 0000000..7b38068 --- /dev/null +++ b/__network/pcap/tests/lib.rs @@ -0,0 +1,290 @@ +#[cfg(not(windows))] +use std::io; +use std::ops::Add; +use std::path::Path; +use tempdir::TempDir; + +use pcap::{Active, Activated, Offline, Capture, Packet, PacketHeader, Linktype}; +#[cfg(not(windows))] +use pcap::{Precision, Error}; + +#[cfg(not(windows))] +#[allow(non_camel_case_types)] +type time_t = libc::time_t; +#[cfg(windows)] +#[allow(non_camel_case_types)] +type time_t = libc::c_long; + +#[cfg(not(windows))] +#[allow(non_camel_case_types)] +type suseconds_t = libc::suseconds_t; +#[cfg(windows)] +#[allow(non_camel_case_types)] +type suseconds_t = libc::c_long; + +#[test] +fn read_packet_with_full_data() { + let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); + assert_eq!(capture.next().unwrap().len(), 98); +} + +#[test] +fn read_packet_with_truncated_data() { + let mut capture = capture_from_test_file("packet_snaplen_20.pcap"); + assert_eq!(capture.next().unwrap().len(), 20); +} + +fn capture_from_test_file(file_name: &str) -> Capture { + let path = Path::new("tests/data/").join(file_name); + Capture::from_file(path).unwrap() +} + +#[test] +fn unify_activated() { + #![allow(dead_code)] + fn test1() -> Capture { + loop {} + } + + fn test2() -> Capture { + loop {} + } + + fn maybe(a: bool) -> Capture { + if a { test1().into() } else { test2().into() } + } + + fn also_maybe(a: &mut Capture) { + a.filter("whatever filter string, this won't be run anyway").unwrap(); + } +} + +#[derive(Clone)] +pub struct Packets { + headers: Vec, + data: Vec>, +} + +impl Packets { + pub fn new() -> Packets { + Packets { + headers: vec![], + data: vec![], + } + } + + pub fn push(&mut self, + tv_sec: time_t, + tv_usec: suseconds_t, + caplen: u32, + len: u32, + data: &[u8]) { + self.headers.push(PacketHeader { + ts: libc::timeval { tv_sec, tv_usec }, + caplen, + len, + }); + self.data.push(data.to_vec()); + } + + pub fn foreach(&self, mut f: F) { + for (header, data) in self.headers.iter().zip(self.data.iter()) { + let packet = Packet::new(header, &data); + f(&packet); + } + } + + pub fn verify(&self, cap: &mut Capture) { + for (header, data) in self.headers.iter().zip(self.data.iter()) { + assert_eq!(cap.next().unwrap(), Packet::new(header, &data)); + } + assert!(cap.next().is_err()); + } +} + +impl<'a> Add for &'a Packets { + type Output = Packets; + + fn add(self, rhs: &'a Packets) -> Packets { + let mut packets = self.clone(); + packets.headers.extend(rhs.headers.iter()); + packets.data.extend(rhs.data.iter().cloned()); + packets + } +} + +#[test] +fn capture_dead_savefile() { + let mut packets = Packets::new(); + packets.push(1460408319, 1234, 1, 1, &[1]); + packets.push(1460408320, 4321, 1, 1, &[2]); + + let dir = TempDir::new("pcap").unwrap(); + let tmpfile = dir.path().join("test.pcap"); + + let cap = Capture::dead(Linktype(1)).unwrap(); + let mut save = cap.savefile(&tmpfile).unwrap(); + packets.foreach(|p| save.write(p)); + drop(save); + + let mut cap = Capture::from_file(&tmpfile).unwrap(); + packets.verify(&mut cap); +} + +#[test] +#[cfg(libpcap_1_7_2)] +fn capture_dead_savefile_append() { + let mut packets1 = Packets::new(); + packets1.push(1460408319, 1234, 1, 1, &[1]); + packets1.push(1460408320, 4321, 1, 1, &[2]); + let mut packets2 = Packets::new(); + packets2.push(1460408321, 2345, 1, 1, &[3]); + packets2.push(1460408322, 5432, 1, 1, &[4]); + let packets = &packets1 + &packets2; + + let dir = TempDir::new("pcap").unwrap(); + let tmpfile = dir.path().join("test.pcap"); + + let cap = Capture::dead(Linktype(1)).unwrap(); + let mut save = cap.savefile(&tmpfile).unwrap(); + packets1.foreach(|p| save.write(p)); + drop(save); + + let cap = Capture::dead(Linktype(1)).unwrap(); + let mut save = cap.savefile_append(&tmpfile).unwrap(); + packets2.foreach(|p| save.write(p)); + drop(save); + + let mut cap = Capture::from_file(&tmpfile).unwrap(); + packets.verify(&mut cap); +} + +#[test] +#[cfg(not(windows))] +fn test_raw_fd_api() { + use std::fs::File; + use std::thread; + use std::io::prelude::*; + #[cfg(not(windows))] + use std::os::unix::io::{RawFd, FromRawFd}; + + // Create a total of more than 64K data (> max pipe buf size) + const N_PACKETS: usize = 64; + let data: Vec = (0..191).cycle().take(N_PACKETS * 1024).collect(); + let mut packets = Packets::new(); + for i in 0..N_PACKETS { + packets.push(1460408319 + i as time_t, + 1000 + i as suseconds_t, + 1024, + 1024, + &data[i * 1024..(i + 1) * 1024]); + } + + let dir = TempDir::new("pcap").unwrap(); + let tmpfile = dir.path().join("test.pcap"); + + // Write all packets to test.pcap savefile + let cap = Capture::dead(Linktype(1)).unwrap(); + let mut save = cap.savefile(&tmpfile).unwrap(); + packets.foreach(|p| save.write(p)); + drop(save); + + assert_eq!(Capture::from_raw_fd(-999).err().unwrap(), + Error::InvalidRawFd); + #[cfg(libpcap_1_5_0)] + { + assert_eq!(Capture::from_raw_fd_with_precision(-999, Precision::Micro).err().unwrap(), + Error::InvalidRawFd); + } + assert_eq!(cap.savefile_raw_fd(-999).err().unwrap(), + Error::InvalidRawFd); + + // Create an unnamed pipe + let mut pipe = [0 as libc::c_int; 2]; + assert_eq!(unsafe { libc::pipe(pipe.as_mut_ptr()) }, 0); + let (fd_in, fd_out) = (pipe[0], pipe[1]); + + let filename = dir.path().join("test2.pcap"); + let packets_c = packets.clone(); + let pipe_thread = thread::spawn(move || { + // Write all packets to the pipe + let cap = Capture::dead(Linktype(1)).unwrap(); + let mut save = cap.savefile_raw_fd(fd_out).unwrap(); + packets_c.foreach(|p| save.write(p)); + // fd_out will be closed by savefile destructor + }); + + // Save the pcap from pipe in a separate thread. + // Hypothetically, we could do any sort of processing here, + // like encoding to a gzip stream. + let mut file_in = unsafe { File::from_raw_fd(fd_in) }; + let mut file_out = File::create(&filename).unwrap(); + io::copy(&mut file_in, &mut file_out).unwrap(); + + // Verify that the contents match + let filename = dir.path().join("test2.pcap"); + let (mut v1, mut v2) = (vec![], vec![]); + File::open(&tmpfile).unwrap().read_to_end(&mut v1).unwrap(); + File::open(&filename).unwrap().read_to_end(&mut v2).unwrap(); + assert_eq!(v1, v2); + + // Join thread. + pipe_thread.join().unwrap(); + + #[cfg(libpcap_1_5_0)] + fn from_raw_fd_with_precision(fd: RawFd, precision: Precision) -> Capture { + Capture::from_raw_fd_with_precision(fd, precision).unwrap() + } + + #[cfg(not(libpcap_1_5_0))] + fn from_raw_fd_with_precision(fd: RawFd, _: Precision) -> Capture { + Capture::from_raw_fd(fd).unwrap() + } + + for with_tstamp in &[false, true] { + // Create an unnamed pipe + let mut pipe = [0 as libc::c_int; 2]; + assert_eq!(unsafe { libc::pipe(pipe.as_mut_ptr()) }, 0); + let (fd_in, fd_out) = (pipe[0], pipe[1]); + + let filename = tmpfile.clone(); + let pipe_thread = thread::spawn(move || { + // Cat the pcap into the pipe in a separate thread. + // Hypothetically, we could do any sort of processing here, + // like decoding from a gzip stream. + let mut file_in = File::open(&filename).unwrap(); + let mut file_out = unsafe { File::from_raw_fd(fd_out) }; + io::copy(&mut file_in, &mut file_out).unwrap(); + }); + + // Open the capture with pipe's file descriptor + let mut cap = if *with_tstamp { + from_raw_fd_with_precision(fd_in, Precision::Micro) + } else { + Capture::from_raw_fd(fd_in).unwrap() + }; + + // Verify that packets match + packets.verify(&mut cap); + + // Join thread. + pipe_thread.join().unwrap(); + } +} + +#[test] +fn test_linktype() { + let capture = capture_from_test_file("packet_snaplen_65535.pcap"); + let linktype = capture.get_datalink(); + + assert!(linktype.get_name().is_ok()); + assert_eq!(linktype.get_name().unwrap(), String::from("EN10MB")); + assert!(linktype.get_description().is_ok()); +} + +#[test] +fn test_error() { + let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); + // Trying to get stats from offline capture should error. + assert!(capture.stats().err().is_some()); +}