├── .github ├── dependabot.yml └── workflows │ ├── rust.yml │ └── security-audit.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets ├── err-buffertoosmall.pcapng ├── err-eof.bin ├── modified-format.pcap ├── ntp.pcap ├── test001-be.pcapng ├── test001-le.pcapng ├── test010-le.pcapng ├── test016-be.pcapng ├── test016-le.pcapng ├── test017-be.pcapng ├── test017-le.pcapng └── wireshark_samples-test.pcapng ├── benches ├── pcap.rs ├── pcapng.rs └── profile_pcapng.rs ├── examples └── pcap-info.rs ├── src ├── blocks.rs ├── capture.rs ├── data │ ├── exported_pdu.rs │ ├── mod.rs │ └── pcap_nflog.rs ├── endianness.rs ├── error.rs ├── lib.rs ├── linktype.rs ├── pcap.rs ├── pcap │ ├── capture.rs │ ├── frame.rs │ ├── header.rs │ └── reader.rs ├── pcapng.rs ├── pcapng │ ├── block.rs │ ├── capture.rs │ ├── custom.rs │ ├── decryption_secrets.rs │ ├── enhanced_packet.rs │ ├── header.rs │ ├── interface_description.rs │ ├── interface_statistics.rs │ ├── name_resolution.rs │ ├── option.rs │ ├── process_information.rs │ ├── reader.rs │ ├── section.rs │ ├── section_header.rs │ ├── simple_packet.rs │ ├── systemd_journal_export.rs │ ├── time.rs │ └── unknown.rs ├── serialize.rs ├── traits.rs └── utils.rs └── tests ├── pcap.rs ├── pcapng.rs └── reader.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | groups: 9 | crates-io: 10 | patterns: 11 | - "*" 12 | - package-ecosystem: github-actions 13 | directory: "/" 14 | schedule: 15 | interval: weekly 16 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | 3 | on: 4 | push: 5 | pull_request: 6 | merge_group: 7 | schedule: 8 | - cron: '0 18 * * *' 9 | 10 | jobs: 11 | check: 12 | name: Check 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | rust: 17 | - stable 18 | - 1.63.0 19 | - nightly 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: dtolnay/rust-toolchain@master 23 | with: 24 | toolchain: ${{ matrix.rust }} 25 | - name: Cargo update 26 | run: cargo update 27 | - run: RUSTFLAGS="-D warnings" cargo check 28 | 29 | test: 30 | name: Test Suite 31 | runs-on: ubuntu-latest 32 | strategy: 33 | matrix: 34 | features: 35 | - --no-default-features 36 | - --features=default 37 | - --all-features 38 | steps: 39 | - uses: actions/checkout@v4 40 | - name: Install stable toolchain 41 | uses: dtolnay/rust-toolchain@stable 42 | - run: cargo test ${{ matrix.features }} 43 | 44 | fmt: 45 | name: Rustfmt 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v4 49 | - uses: dtolnay/rust-toolchain@stable 50 | with: 51 | components: rustfmt 52 | - run: cargo fmt --all -- --check 53 | 54 | clippy: 55 | name: Clippy 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/checkout@v4 59 | - uses: dtolnay/rust-toolchain@nightly 60 | with: 61 | components: clippy 62 | - run: cargo clippy -- -D warnings 63 | - run: cargo clippy --all-features -- -D warnings 64 | - run: cargo clippy --no-default-features -- -D warnings 65 | 66 | doc: 67 | name: Build documentation 68 | runs-on: ubuntu-latest 69 | env: 70 | RUSTDOCFLAGS: --cfg docsrs 71 | steps: 72 | - uses: actions/checkout@v4 73 | - uses: dtolnay/rust-toolchain@nightly 74 | - run: cargo doc --workspace --no-deps --all-features 75 | 76 | semver: 77 | name: Check semver compatibility 78 | runs-on: ubuntu-latest 79 | steps: 80 | - name: Checkout sources 81 | uses: actions/checkout@v4 82 | - name: Check semver 83 | uses: obi1kenobi/cargo-semver-checks-action@v2 84 | 85 | check-external-types: 86 | name: Validate external types appearing in public API 87 | runs-on: ubuntu-latest 88 | steps: 89 | - name: Checkout sources 90 | uses: actions/checkout@v4 91 | - name: Install rust toolchain 92 | uses: dtolnay/rust-toolchain@master 93 | with: 94 | toolchain: nightly-2024-05-01 95 | # ^ sync with https://github.com/awslabs/cargo-check-external-types/blob/main/rust-toolchain.toml 96 | - run: cargo install cargo-check-external-types 97 | - run: cargo check-external-types 98 | -------------------------------------------------------------------------------- /.github/workflows/security-audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: "0 8 * * *" 5 | push: 6 | paths: 7 | - "**/Cargo.*" 8 | jobs: 9 | security_audit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: generate Cargo.lock 14 | run: cargo generate-lockfile 15 | - uses: rustsec/audit-check@v2 16 | with: 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | /target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | /.idea 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [Unreleased][unreleased] 4 | 5 | ### Added 6 | 7 | ### Changed/Fixed 8 | 9 | ## 0.16.0 10 | 11 | ### Added 12 | 13 | - Add SLL2 support (#31) 14 | - Add support for PCAP "modified" format (#35) 15 | 16 | ### Changed/Fixed 17 | 18 | - code refactor: split source files, reorganize crate (#36) 19 | Note: no change in the public API 20 | - CI refactor (#33) 21 | 22 | ### Thanks 23 | 24 | - Daniel McCarney, @gcsbt, David Ibbitson 25 | 26 | ## 0.15.0 27 | 28 | ### Upgrade notes and breaking changes 29 | 30 | The PcapNGOption type now uses a `Cow<[u8]>` instead of `&[u8]`. To access raw value, 31 | use `option.value()` or `&option.value` instead of `option.value`. 32 | This allow changing the options when serializing blocks. 33 | 34 | The new error types may require to match more variants when handling errors. 35 | 36 | The `PcapError::Incomplete` variant now contains a value: the number of missing 37 | bytes, or 0 if unknown. 38 | 39 | The `InterfaceDescriptionBlock::if_tsoffset` field is now a i64 (signed). 40 | 41 | ### Changed/Fixed 42 | 43 | - Set MSRV to 1.53.0 44 | - Fix if_tsoffset to be a signed value (#21) 45 | - Add support for Apple-specific process information block (#27) 46 | - Added const for LINUX_SLL2 (#31) 47 | - PcapNGOption: 48 | - Allow PcapNGOption to own data (using a Cow<[u8]>) 49 | - Serialize: use provided if_tsresol and if_tsoffset (#24) 50 | - Add helper methods to allow extracting value from PcapNGOption 51 | - Error handling: 52 | - Add PcapError::UnexpectedEof error type (#28) 53 | - Reader: report how many additional bytes are needed when returning `Incomplete` 54 | - Add new error kind BufferTooSmall, and raise error if buffer capacity is too small (#29) 55 | - Return Eof if reader is exhausted AND bytes have been consumed (#30) 56 | 57 | ### Thanks 58 | 59 | - iczero, Julien Gamba, Bobby Richter 60 | 61 | ## 0.14.1 62 | 63 | ### Changed/Fixed 64 | 65 | - Fix serialization of some options (#23, #25, #26) 66 | - Fix broken internal links (#19) 67 | - Fix returned value for Incomplete to be number of missing bytes, not total (#22) 68 | - NG Reader: fix documentation and example to correctly refill buffer (#18, #20) 69 | 70 | ### Thanks 71 | 72 | - vrbhartiya, Clint White, Jade Lovelace 73 | 74 | ## 0.14.0 75 | 76 | ### Changed/Fixed 77 | 78 | - Properly handle errors when decoding timestamp resolution (#16) 79 | - Remove deprecated pcapng parsing functions 80 | 81 | ## 0.13.3 82 | 83 | ### Changed/Fixed 84 | 85 | - Fix computation of resolution for pcapng, when using power of two (#16) 86 | 87 | ## 0.13.2 88 | 89 | ### Changed/Fixed 90 | 91 | - Add explicit configuration flags for rustdoc (#17) 92 | 93 | ## 0.13.1 94 | 95 | ### Changed/Fixed 96 | 97 | - PcapReaderIterator: add method to test if reader is exhausted 98 | This also fix a case where reader returned Incomplete because it could 99 | not distinguish EOF and a request of 0 bytes. 100 | 101 | ## 0.13.0 102 | 103 | ### Changed/Fixed 104 | 105 | - Upgrade to nom 7 106 | - Set MSRV to 1.46 107 | 108 | ## 0.12.0 109 | 110 | ### Added 111 | 112 | - Add method to get total number of consumed bytes for PcapReaderIterator 113 | 114 | ### Changed/Fixed 115 | 116 | - Report error instead of EOF when a block is incomplete and no more data is available 117 | - Add input to error type (helps diagnosing errors) 118 | `PcapError` now usually has a lifetime 119 | 120 | ## 0.11.1 121 | 122 | ### Changed/Fixed 123 | 124 | - Fix potential integer overflows when aligning data in EPB and BSD 125 | Overflow was harmless, length arguments is tested after aligning anyway 126 | - pcapng: use streaming parsers, return incomplete instead of eof (#13) 127 | 128 | ## 0.11.0 129 | 130 | ### Added 131 | 132 | - Add trait PcapNGPacketBlock 133 | 134 | ### Changed/Fixed 135 | 136 | - Rewrite Pcap and PcapNG block parsing functions 137 | - the 'block_type' field is always read as little-endian 138 | This is used to get the endianness for the encoding of the block. 139 | - new parsing functions are faster 140 | - Use consistent names for big-endian/little-endian versions: 141 | - 'parse_block' is deprecated and replaced by 'parse_block_le' 142 | - 'parse_enhancedpacketblock' is deprecated and replaced by 'parse_enhancedpacketblock_le' 143 | - same for all parsing functions with '_le' 144 | - 'parse_sectionheader' is replaced by 'parse_sectionheaderblock' 145 | - Functions that parse a specific block type (for ex. EPB) return the matching type 146 | (EnhancedPacketBlock) with encapsulating it in a Block 147 | - Improve documentation and examples 148 | 149 | ## 0.10.1 150 | 151 | ### Changed/Fixed 152 | 153 | - Re-export nom so crate users do not need to import it 154 | - Convert doc links to short form when possible 155 | 156 | ## 0.10.0 157 | 158 | ### Changed/Fixed 159 | 160 | - Upgrade to nom 6 161 | 162 | ## 0.9.4 163 | 164 | ### Added 165 | 166 | - Add trait PartialEq to PcapError 167 | 168 | ### Changed/Fixed 169 | 170 | - Improve error handling in create_reader (if input is empty or incomplete) 171 | - Set MSRV to 1.44 172 | - Update consume() documentation, it does not refill buffer (Closes #9) 173 | 174 | ## 0.9.3 175 | 176 | ### Added 177 | 178 | - Add support for nanosecond-resolution legacy PCAP files 179 | 180 | ## 0.9.2 181 | 182 | ### Added 183 | 184 | - Add Error trait to PcapError 185 | - Edition 2018: remove all "extern crate" statements 186 | - QA: warn if using unstable features, forbid unsafe code 187 | 188 | ## 0.9.1 189 | 190 | ### Added 191 | 192 | - Check magic constants when reading DSB and SJE blocks 193 | - Add `ToVec` implementation for SJE, DCB and Block 194 | 195 | ## 0.9.0 196 | 197 | ### Added 198 | 199 | - Add `Default` trait to `Linktype` 200 | - Add `NameRecordType` constants 201 | - pcap-ng: add method to get the (normalized) magic of blocks 202 | - Add support for Decryption Secrets Block (DSB) 203 | - Add support for Systemd Journal Export Block (SJE) 204 | 205 | ### Changed/Fixed 206 | 207 | - Improve documentation (remove relative links, broken outside docs.rs) 208 | 209 | ## 0.8.4 210 | 211 | - Add method to access data of the PcapReaderIterator buffer 212 | - Fix all clippy warnings 213 | 214 | ## 0.8.3 215 | 216 | - Avoid integer overflow in `parse_name_record` edge case 217 | 218 | ## 0.8.2 219 | 220 | - Remove byteorder crate, use functions from std 221 | - Return Eof if refill failed (avoid infinite loop) 222 | 223 | ## 0.8.1 224 | 225 | - Upgrade to cookie-factory 0.3.0 226 | 227 | ## 0.8.0 228 | 229 | - Add basic support for serialization (little-endian only) 230 | - Add basic support for Wireshark exported PDUs 231 | - Add traits Clone and Debug to PacketData 232 | - Move data parsing functions to a subdirectory 233 | 234 | ## 0.7.1 235 | 236 | - Fix wrong EOF detection 237 | - Fix handling of incomplete reads (in example) 238 | 239 | ## 0.7.0 240 | 241 | - Upgrade to nom 5 242 | - Breaking API changes, mainly for error types 243 | 244 | ## 0.6.1 245 | 246 | - Make LegacyPcapBlock a regular structure with parser, and add serialization 247 | 248 | ## 0.6.0 249 | 250 | - Complete rewrite of the crate (breaks API) 251 | - Add streaming parser iterators 252 | - Replace Packet with Blocks 253 | - Allows handling of non-data blocks 254 | - Handles correctly timestamps and resolution 255 | - Remove incorrect or deprecated code 256 | - Better parsing of all variants (BE/LE, block types, etc.) 257 | - Better (and panic-free) functions to extract block contents 258 | - Set edition to 2018 259 | - Better documentation 260 | 261 | ## 0.5.1 262 | 263 | - Fix computation of timestamp for high-resolution pcap-ng 264 | 265 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pcap-parser" 3 | version = "0.16.0" 4 | description = "Parser for the PCAP/PCAPNG format" 5 | license = "MIT/Apache-2.0" 6 | keywords = ["pcap","pcapng","parser","nom"] 7 | homepage = "https://github.com/rusticata/pcap-parser" 8 | repository = "https://github.com/rusticata/pcap-parser.git" 9 | authors = ["Pierre Chifflier "] 10 | 11 | readme = "README.md" 12 | categories = ["network-programming", "parser-implementations"] 13 | edition = "2018" 14 | rust-version = "1.63" 15 | 16 | include = [ 17 | "CHANGELOG.md", 18 | "LICENSE-*", 19 | "README.md", 20 | ".gitignore", 21 | ".travis.yml", 22 | "Cargo.toml", 23 | "assets/*.pcap", 24 | "assets/*.pcapng", 25 | "examples/*.rs", 26 | "src/*.rs", 27 | "src/data/*.rs", 28 | "src/pcap/*.rs", 29 | "src/pcapng/*.rs", 30 | "tests/*.rs" 31 | ] 32 | 33 | [features] 34 | default = [] 35 | 36 | # include parsers for data (depending on linktype) 37 | data = [] 38 | # add support for writing blocks 39 | serialize = ["cookie-factory"] 40 | 41 | [package.metadata.docs.rs] 42 | features = [ "data", "serialize" ] 43 | all-features = true 44 | rustdoc-args = ["--cfg", "docsrs"] 45 | 46 | [dependencies] 47 | circular = "0.3" 48 | cookie-factory = { version="0.3.0", optional=true } 49 | nom = "7.0" 50 | rusticata-macros = "4.0" 51 | 52 | [dev-dependencies] 53 | criterion = { version="0.5", features=["html_reports"] } 54 | hex-literal = "0.4" 55 | pprof = { version="0.13", features=["criterion","flamegraph"] } 56 | 57 | [[bench]] 58 | name = "pcap" 59 | harness = false 60 | 61 | [[bench]] 62 | name = "pcapng" 63 | harness = false 64 | 65 | [[bench]] 66 | name = "profile_pcapng" 67 | harness = false 68 | 69 | [package.metadata.cargo_check_external_types] 70 | allowed_external_types = [ 71 | "nom", 72 | "nom::*", 73 | "circular::Buffer", 74 | ] 75 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Pierre Chifflier 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT) 4 | [![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) 5 | [![Crates.io Version](https://img.shields.io/crates/v/pcap-parser.svg)](https://crates.io/crates/pcap-parser) 6 | [![docs.rs](https://docs.rs/pcap-parser/badge.svg)](https://docs.rs/pcap-parser) 7 | [![Github CI](https://github.com/rusticata/pcap-parser/workflows/Continuous%20integration/badge.svg)](https://github.com/rusticata/pcap-parser/actions) 8 | [![Minimum rustc version](https://img.shields.io/badge/rustc-1.63.0+-lightgray.svg)](#rust-version-requirements) 9 | 10 | # PCAP and PCAPNG parsers 11 | 12 | This crate contains several parsers for PCAP and PCAPNG files. 13 | 14 | Compared to other similar projects, it is designed to offer a complete support of the many 15 | possible formats (legacy pcap, pcapng, little or big-endian, etc.) and features (pcapng files 16 | with multiple sections, interfaces, and endianness) while using only safe code and without 17 | copying data (zero-copy). 18 | 19 | The code is available on [Github](https://github.com/rusticata/pcap-parser) 20 | and is part of the [Rusticata](https://github.com/rusticata) project. 21 | 22 | # The pcap format(s) 23 | 24 | The [PCAP] format (files usually ending with `.pcap` extension) is rather 25 | trivial. The [PCAPNG] format (usually `.pcapng` extension) is much more complex: it 26 | can be composed of multiple sections, each with multiple interfaces, having 27 | different capture lengths, time precision and even endianness! 28 | 29 | These formats are more containers than data formats: packets contain data, 30 | formatted according to its interface linktype. There are *many* possible 31 | linktypes, defined in the [linktypes registry]. Support for parsing some of 32 | them is provided using the `data` feature (disabled by default). 33 | 34 | This crate provides an abstraction over these different formats. 35 | 36 | [PCAP]: https://wiki.wireshark.org/Development/LibpcapFileFormat 37 | [PCAPNG]: https://pcapng.github.io/pcapng/ 38 | [linktypes registry]: https://www.tcpdump.org/linktypes.html 39 | 40 | # Parsing a file 41 | 42 | `pcap-parser` provides several ways of parsing pcap data. Choosing the right 43 | one is mostly driven by resources: if the input file is small, the 44 | `parse_pcap` and `parse_pcapng` functions can be used directly. 45 | 46 | Fine-grained functions are also available, to parse specifically some block 47 | types for example. They are listed in the `pcap` and `pcapng` modules. 48 | 49 | If the input is larger and cannot fit into memory, then streaming parsers 50 | are available. They work by iterating on blocks, and so do not require to map 51 | the entire input. They cannot seek to a specific block, however. 52 | 53 | *Note: due to PCAPNG limitations, it is not possible to directly seek in 54 | a file to get a packet and handle it: the caller has to iterate though the 55 | file and store (at least) the interface descriptions for the current 56 | section, in order of appearance.* 57 | 58 | ## Example: streaming parsers 59 | 60 | The following code shows how to parse a file in the pcap-ng format, using a 61 | [`PcapNGReader`] streaming parser. 62 | This reader provides a convenient abstraction over the file format, and takes 63 | care of the endianness. 64 | 65 | ```rust 66 | use pcap_parser::*; 67 | use pcap_parser::traits::PcapReaderIterator; 68 | use std::fs::File; 69 | 70 | let file = File::open(path).unwrap(); 71 | let mut num_blocks = 0; 72 | let mut reader = PcapNGReader::new(65536, file).expect("PcapNGReader"); 73 | loop { 74 | match reader.next() { 75 | Ok((offset, _block)) => { 76 | println!("got new block"); 77 | num_blocks += 1; 78 | reader.consume(offset); 79 | }, 80 | Err(PcapError::Eof) => break, 81 | Err(PcapError::Incomplete(_)) => { 82 | reader.refill().unwrap(); 83 | }, 84 | Err(e) => panic!("error while reading: {:?}", e), 85 | } 86 | } 87 | println!("num_blocks: {}", num_blocks); 88 | ``` 89 | See [`PcapNGReader`] for a complete example, 90 | including handling of linktype and accessing packet data. 91 | 92 | See also the [`pcapng`] module for more details about the new capture file format. 93 | 94 | For legacy pcap files, use similar code with the 95 | [`LegacyPcapReader`] streaming parser. 96 | 97 | See [pcap-analyzer](https://github.com/rusticata/pcap-analyzer), in particular the 98 | [libpcap-tools](https://github.com/rusticata/pcap-analyzer/tree/master/libpcap-tools) and 99 | [pcap-info](https://github.com/rusticata/pcap-analyzer/tree/master/pcap-info) modules 100 | for more examples. 101 | 102 | ## Example: generic streaming parsing 103 | 104 | To create a pcap reader for input in either PCAP or PCAPNG format, use the 105 | [`create_reader`] function. 106 | 107 | # Serialization 108 | 109 | Support for serialization (*i.e.* generating binary data) is available by 110 | enabling the `serialize` feature. 111 | Most structures gain the `to_vec()` method (provided by the `ToVec` trait). 112 | 113 | Note: support is still experimental, though working. API may change in the 114 | future. 115 | 116 | 117 | ## Changes 118 | 119 | See `CHANGELOG.md`. 120 | 121 | ## License 122 | 123 | Licensed under either of 124 | 125 | * Apache License, Version 2.0 126 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 127 | * MIT license 128 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 129 | 130 | at your option. 131 | 132 | ## Contribution 133 | 134 | Unless you explicitly state otherwise, any contribution intentionally submitted 135 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 136 | dual licensed as above, without any additional terms or conditions. 137 | 138 | -------------------------------------------------------------------------------- /assets/err-buffertoosmall.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/err-buffertoosmall.pcapng -------------------------------------------------------------------------------- /assets/err-eof.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/err-eof.bin -------------------------------------------------------------------------------- /assets/modified-format.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/modified-format.pcap -------------------------------------------------------------------------------- /assets/ntp.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/ntp.pcap -------------------------------------------------------------------------------- /assets/test001-be.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/test001-be.pcapng -------------------------------------------------------------------------------- /assets/test001-le.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/test001-le.pcapng -------------------------------------------------------------------------------- /assets/test010-le.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/test010-le.pcapng -------------------------------------------------------------------------------- /assets/test016-be.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/test016-be.pcapng -------------------------------------------------------------------------------- /assets/test016-le.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/test016-le.pcapng -------------------------------------------------------------------------------- /assets/test017-be.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/test017-be.pcapng -------------------------------------------------------------------------------- /assets/test017-le.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/test017-le.pcapng -------------------------------------------------------------------------------- /assets/wireshark_samples-test.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/pcap-parser/c2fd14bdec4b7a74e86a2c8ed3e6556d86a9e826/assets/wireshark_samples-test.pcapng -------------------------------------------------------------------------------- /benches/pcap.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use pcap_parser::parse_pcap; 3 | use std::fs; 4 | 5 | fn bench_parse_pcap(c: &mut Criterion) { 6 | let bytes = fs::read("assets/ntp.pcap").unwrap(); 7 | c.bench_function("parse_pcap ntp", |b| b.iter(|| parse_pcap(&bytes))); 8 | } 9 | 10 | criterion_group!(benches, bench_parse_pcap); 11 | criterion_main!(benches); 12 | -------------------------------------------------------------------------------- /benches/pcapng.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; 2 | use pcap_parser::{parse_pcapng, traits::PcapReaderIterator, PcapError, PcapNGReader}; 3 | use std::fs; 4 | 5 | fn bench_parse_pcapng(c: &mut Criterion) { 6 | let bytes = fs::read("assets/wireshark_samples-test.pcapng").unwrap(); 7 | c.bench_function("parse_pcapng wireshark_samples-test", |b| { 8 | b.iter(|| parse_pcapng(&bytes)) 9 | }); 10 | } 11 | 12 | fn do_reader_pcapng(bytes: &[u8], buffer_size: usize) { 13 | let mut num_blocks = 0; 14 | let mut reader = PcapNGReader::new(buffer_size, bytes).expect("could not create reader"); 15 | loop { 16 | match reader.next() { 17 | Ok((offset, _block)) => { 18 | num_blocks += 1; 19 | reader.consume_noshift(offset); 20 | } 21 | Err(PcapError::Eof) => break, 22 | Err(PcapError::Incomplete(_)) => { 23 | reader.refill().unwrap(); 24 | } 25 | Err(e) => panic!("unexpected error {:?}", e), 26 | } 27 | } 28 | assert_eq!(num_blocks, 5902); 29 | } 30 | 31 | fn bench_reader_pcapng(c: &mut Criterion) { 32 | let bytes = fs::read("assets/wireshark_samples-test.pcapng").unwrap(); 33 | c.bench_function("reader_pcapng wireshark_samples-test", |b| { 34 | b.iter(|| do_reader_pcapng(&bytes, 65536)) 35 | }); 36 | } 37 | 38 | fn bench_reader_pcapng_buffer_size(c: &mut Criterion) { 39 | let bytes = fs::read("assets/wireshark_samples-test.pcapng").unwrap(); 40 | let mut group = c.benchmark_group("reader_pcapng buffer_size"); 41 | const KB16: usize = 16384; 42 | for buffer_size in [KB16, KB16 * 2, KB16 * 4, KB16 * 8, KB16 * 16].iter() { 43 | group.throughput(Throughput::Bytes(*buffer_size as u64)); 44 | group.bench_with_input( 45 | BenchmarkId::from_parameter(buffer_size), 46 | buffer_size, 47 | |b, &size| b.iter(|| do_reader_pcapng(&bytes, size)), 48 | ); 49 | } 50 | } 51 | 52 | criterion_group!( 53 | benches, 54 | bench_parse_pcapng, 55 | bench_reader_pcapng, 56 | bench_reader_pcapng_buffer_size 57 | ); 58 | criterion_main!(benches); 59 | -------------------------------------------------------------------------------- /benches/profile_pcapng.rs: -------------------------------------------------------------------------------- 1 | // Run: 2 | // cargo bench --bench profile_pcapng -- --profile-time=5 3 | // 4 | // 5 | // Use one the following to display the results: 6 | // ~/go/bin/pprof -svg ./target/criterion/profile_reader_pcapng\ test001-le/profile/profile.pb 7 | // + use firefox to open file `profile001.svg` 8 | // Or 9 | // ~/go/bin/pprof -http "0.0.0.0:8081" ./target/criterion/profile_reader_pcapng\ wireshark_samples-test/profile/profile.pb 10 | // + connect to 127.0.0.1:8081 11 | 12 | use criterion::{criterion_group, criterion_main, Criterion}; 13 | use pcap_parser::{traits::PcapReaderIterator, PcapError, PcapNGReader}; 14 | use pprof::criterion::{Output, PProfProfiler}; 15 | use std::fs; 16 | 17 | fn do_reader_pcapng(bytes: &[u8]) { 18 | let mut num_blocks = 0; 19 | let mut reader = PcapNGReader::new(65536, bytes).expect("could not create reader"); 20 | loop { 21 | match reader.next() { 22 | Ok((offset, _block)) => { 23 | num_blocks += 1; 24 | reader.consume_noshift(offset); 25 | } 26 | Err(PcapError::Eof) => break, 27 | Err(PcapError::Incomplete(_)) => { 28 | reader.refill().unwrap(); 29 | } 30 | Err(e) => panic!("unexpected error {:?}", e), 31 | } 32 | } 33 | assert_eq!(num_blocks, 5902); 34 | } 35 | 36 | fn profile_reader_pcapng(c: &mut Criterion) { 37 | let bytes = fs::read("assets/wireshark_samples-test.pcapng").unwrap(); 38 | c.bench_function("profile_reader_pcapng wireshark_samples-test", |b| { 39 | b.iter(|| do_reader_pcapng(&bytes)) 40 | }); 41 | } 42 | 43 | fn profiled() -> Criterion { 44 | Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))) 45 | //Criterion::default().with_profiler(PProfProfiler::new(100, Output::Protobuf)) 46 | } 47 | 48 | criterion_group! { 49 | name = benches; 50 | config = profiled(); 51 | targets = profile_reader_pcapng 52 | } 53 | criterion_main!(benches); 54 | -------------------------------------------------------------------------------- /examples/pcap-info.rs: -------------------------------------------------------------------------------- 1 | use pcap_parser::*; 2 | use std::env; 3 | use std::error::Error; 4 | use std::fs::File; 5 | 6 | fn main() { 7 | for arg in env::args().skip(1) { 8 | print_pcap_info(&arg).unwrap(); 9 | } 10 | } 11 | 12 | fn print_pcap_info(arg: &str) -> Result<(), Box> { 13 | println!("Name: {}", arg); 14 | 15 | let file = File::open(arg)?; 16 | let file_size = file.metadata()?.len(); 17 | println!("\tfile size: {}", file_size); 18 | 19 | let mut reader = create_reader(10 * 1024, file)?; 20 | 21 | // Note that we do not call `consume()` here, so the next call to `.next()` 22 | // will return the same block 23 | let first_block = reader.next(); 24 | 25 | match first_block { 26 | Ok((_, PcapBlockOwned::LegacyHeader(header))) => { 27 | println!("\tformat: legacy Pcap file"); 28 | println!( 29 | "\tversion: {}.{}", 30 | header.version_major, header.version_minor 31 | ); 32 | println!("\tData Link Type: {}", header.network); 33 | } 34 | Ok((_, PcapBlockOwned::NG(block))) => { 35 | println!("\tformat: Pcap-NG file"); 36 | // first block should be a SectionHeader 37 | if !matches!(block, Block::SectionHeader(_)) { 38 | return Err("pcapng: first block is not a section header".into()); 39 | } 40 | } 41 | _ => return Err("Unexpected first block, or wrong file format".into()), 42 | } 43 | 44 | // count blocks in file 45 | let mut num_blocks = 0; 46 | 47 | loop { 48 | match reader.next() { 49 | Ok((offset, block)) => { 50 | print_block_info(&block); 51 | num_blocks += 1; 52 | reader.consume_noshift(offset); 53 | } 54 | Err(PcapError::Eof) => break, 55 | Err(PcapError::Incomplete(_)) => { 56 | reader.refill().unwrap(); 57 | } 58 | Err(e) => panic!("Unexpected error: {}", e), 59 | } 60 | } 61 | 62 | println!("\tnum_blocks: {}", num_blocks); 63 | 64 | Ok(()) 65 | } 66 | 67 | fn print_block_info(block: &PcapBlockOwned) { 68 | if let PcapBlockOwned::NG(b) = block { 69 | print_block_info_ng(b) 70 | } 71 | } 72 | 73 | fn print_block_info_ng(block: &Block) { 74 | match block { 75 | Block::SectionHeader(shb) => { 76 | println!("\t\tNew Section"); 77 | println!("\t\t\tVersion: {}.{}", shb.major_version, shb.minor_version); 78 | if let Some(option) = shb.shb_hardware() { 79 | println!("\t\t\tshb_hardware: {}", option.unwrap_or("")); 80 | } 81 | if let Some(option) = shb.shb_os() { 82 | println!("\t\t\tshb_os: {}", option.unwrap_or("")); 83 | } 84 | if let Some(option) = shb.shb_userappl() { 85 | println!("\t\t\tshb_userappl: {}", option.unwrap_or("")); 86 | } 87 | } 88 | Block::InterfaceDescription(idb) => { 89 | println!("\t\tNew interface"); 90 | if let Some(option) = idb.if_name() { 91 | println!("\t\t\tif_name: {}", option.unwrap_or("")); 92 | } 93 | if let Some(option) = idb.if_description() { 94 | println!("\t\t\tif_description: {}", option.unwrap_or("")); 95 | } 96 | if let Some(option) = idb.if_os() { 97 | println!("\t\t\tif_os: {}", option.unwrap_or("")); 98 | } 99 | if let Some(option) = idb.if_tsresol() { 100 | println!("\t\t\tif_tsresol: {}", option.unwrap_or(0)); 101 | } 102 | if let Some(option) = idb.if_filter() { 103 | println!("\t\t\tif_filter: {}", option.unwrap_or("")); 104 | } 105 | if let Some(option) = idb.if_tsoffset() { 106 | println!("\t\t\tif_tsoffset: {}", option.unwrap_or(0)); 107 | } 108 | } 109 | Block::InterfaceStatistics(isb) => { 110 | println!("\t\tStatistics:"); 111 | if let Some(option) = isb.isb_starttime() { 112 | let (ts_high, ts_low) = option.unwrap_or((0, 0)); 113 | println!("\t\t\tisb_starttime: 0x{:x}:0x{:x}", ts_high, ts_low); 114 | // to decode, this require the ts_offset and resolution from the matching IDB block 115 | // for ex: 116 | // let resolution = build_ts_resolution(6).unwrap(); 117 | // let ts = build_ts(ts_high, ts_low, 0, resolution); 118 | // println!("\t\t\t\t{}.{}", ts.0, ts.1); 119 | } 120 | if let Some(option) = isb.isb_ifrecv() { 121 | println!("\t\t\tisb_ifrecv: {}", option.unwrap_or(0)); 122 | } 123 | if let Some(option) = isb.isb_ifdrop() { 124 | println!("\t\t\tisb_ifdrop: {}", option.unwrap_or(0)); 125 | } 126 | if let Some(option) = isb.isb_filteraccept() { 127 | println!("\t\t\tisb_filteraccept: {}", option.unwrap_or(0)); 128 | } 129 | if let Some(option) = isb.isb_osdrop() { 130 | println!("\t\t\tisb_osdrop: {}", option.unwrap_or(0)); 131 | } 132 | if let Some(option) = isb.isb_usrdeliv() { 133 | println!("\t\t\tisb_usrdeliv: {}", option.unwrap_or(0)); 134 | } 135 | } 136 | _ => (), 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/blocks.rs: -------------------------------------------------------------------------------- 1 | use crate::pcap::{LegacyPcapBlock, PcapHeader}; 2 | use crate::pcapng::Block; 3 | 4 | /// A block from a Pcap or PcapNG file 5 | pub enum PcapBlockOwned<'a> { 6 | Legacy(LegacyPcapBlock<'a>), 7 | LegacyHeader(PcapHeader), 8 | NG(Block<'a>), 9 | } 10 | 11 | /// A block from a Pcap or PcapNG file 12 | pub enum PcapBlock<'a> { 13 | Legacy(&'a LegacyPcapBlock<'a>), 14 | LegacyHeader(&'a PcapHeader), 15 | NG(&'a Block<'a>), 16 | } 17 | 18 | impl<'a> From> for PcapBlockOwned<'a> { 19 | fn from(b: LegacyPcapBlock<'a>) -> PcapBlockOwned<'a> { 20 | PcapBlockOwned::Legacy(b) 21 | } 22 | } 23 | 24 | impl<'a> From for PcapBlockOwned<'a> { 25 | fn from(b: PcapHeader) -> PcapBlockOwned<'a> { 26 | PcapBlockOwned::LegacyHeader(b) 27 | } 28 | } 29 | 30 | impl<'a> From> for PcapBlockOwned<'a> { 31 | fn from(b: Block<'a>) -> PcapBlockOwned<'a> { 32 | PcapBlockOwned::NG(b) 33 | } 34 | } 35 | 36 | impl<'a> From<&'a LegacyPcapBlock<'a>> for PcapBlock<'a> { 37 | fn from(b: &'a LegacyPcapBlock) -> PcapBlock<'a> { 38 | PcapBlock::Legacy(b) 39 | } 40 | } 41 | 42 | impl<'a> From<&'a PcapHeader> for PcapBlock<'a> { 43 | fn from(b: &'a PcapHeader) -> PcapBlock<'a> { 44 | PcapBlock::LegacyHeader(b) 45 | } 46 | } 47 | 48 | impl<'a> From<&'a Block<'a>> for PcapBlock<'a> { 49 | fn from(b: &'a Block) -> PcapBlock<'a> { 50 | PcapBlock::NG(b) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/capture.rs: -------------------------------------------------------------------------------- 1 | use crate::blocks::PcapBlock; 2 | use crate::error::PcapError; 3 | use crate::linktype::Linktype; 4 | use crate::pcap::parse_pcap_header; 5 | use crate::pcapng::parse_sectionheaderblock; 6 | use crate::traits::PcapReaderIterator; 7 | use crate::{LegacyPcapReader, PcapNGReader}; 8 | use circular::Buffer; 9 | use nom::Needed; 10 | use std::io::Read; 11 | 12 | /// Generic interface for PCAP or PCAPNG file access 13 | pub trait Capture { 14 | fn get_datalink(&self) -> Linktype; 15 | 16 | fn get_snaplen(&self) -> u32; 17 | 18 | fn iter<'a>(&'a self) -> Box> + 'a>; 19 | } 20 | 21 | /// Get a generic `PcapReaderIterator`, given a `Read` input. The input is probed for pcap-ng first, 22 | /// then pcap. 23 | /// 24 | /// ```rust 25 | /// # use pcap_parser::*; 26 | /// # use std::fs::File; 27 | /// # 28 | /// # let path = "assets/ntp.pcap"; 29 | /// let file = File::open(path).expect("File open failed"); 30 | /// let mut reader = create_reader(65536, file).expect("LegacyPcapReader"); 31 | /// let _ = reader.next(); 32 | /// ``` 33 | pub fn create_reader<'b, R>( 34 | capacity: usize, 35 | mut reader: R, 36 | ) -> Result, PcapError<&'static [u8]>> 37 | where 38 | R: Read + 'b, 39 | { 40 | let mut buffer = Buffer::with_capacity(capacity); 41 | let sz = reader.read(buffer.space()).or(Err(PcapError::ReadError))?; 42 | if sz == 0 { 43 | return Err(PcapError::Eof); 44 | } 45 | buffer.fill(sz); 46 | // just check that first block is a valid one 47 | if parse_sectionheaderblock(buffer.data()).is_ok() { 48 | return PcapNGReader::from_buffer(buffer, reader) 49 | .map(|r| Box::new(r) as Box); 50 | } 51 | match parse_pcap_header(buffer.data()) { 52 | Ok(_) => LegacyPcapReader::from_buffer(buffer, reader) 53 | .map(|r| Box::new(r) as Box), 54 | Err(nom::Err::Incomplete(Needed::Size(n))) => Err(PcapError::Incomplete(n.into())), 55 | Err(nom::Err::Incomplete(Needed::Unknown)) => Err(PcapError::Incomplete(0)), 56 | _ => Err(PcapError::HeaderNotRecognized), 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/data/exported_pdu.rs: -------------------------------------------------------------------------------- 1 | use crate::data::PacketData; 2 | use nom::bytes::streaming::{tag, take}; 3 | use nom::multi::many_till; 4 | use nom::number::streaming::be_u16; 5 | use nom::IResult; 6 | use std::convert::TryFrom; 7 | 8 | /* values from epan/exported_pdu.h */ 9 | 10 | pub const EXP_PDU_TAG_PROTO_NAME: u16 = 12; 11 | pub const EXP_PDU_TAG_DISSECTOR_TABLE_NAME: u16 = 14; 12 | 13 | pub const EXP_PDU_TAG_DISSECTOR_TABLE_NAME_NUM_VAL: u16 = 32; 14 | 15 | #[derive(Debug)] 16 | pub struct ExportedTlv<'a> { 17 | pub t: u16, 18 | pub l: u16, 19 | pub v: &'a [u8], 20 | } 21 | 22 | pub fn parse_exported_tlv(i: &[u8]) -> IResult<&[u8], ExportedTlv> { 23 | let (i, t) = be_u16(i)?; 24 | let (i, l) = be_u16(i)?; 25 | let (i, v) = take(l)(i)?; 26 | Ok((i, ExportedTlv { t, l, v })) 27 | } 28 | 29 | pub fn parse_many_exported_tlv(i: &[u8]) -> IResult<&[u8], Vec> { 30 | many_till(parse_exported_tlv, tag(b"\x00\x00\x00\x00"))(i).map(|(rem, (v, _))| (rem, v)) 31 | } 32 | 33 | /// Get packet data for WIRESHARK_UPPER_PDU (252) 34 | /// 35 | /// Upper-layer protocol saves from Wireshark 36 | pub fn get_packetdata_wireshark_upper_pdu(i: &[u8], caplen: usize) -> Option { 37 | if i.len() < caplen || caplen == 0 { 38 | None 39 | } else { 40 | match parse_many_exported_tlv(i) { 41 | Ok((rem, v)) => { 42 | // get protocol name (or return None) 43 | let proto_name = v 44 | .iter() 45 | .find(|tlv| tlv.t == EXP_PDU_TAG_DISSECTOR_TABLE_NAME) 46 | .map(|tlv| tlv.v)?; 47 | // get protocol value (or return None) 48 | let ip_proto = v 49 | .iter() 50 | .find(|tlv| tlv.t == EXP_PDU_TAG_DISSECTOR_TABLE_NAME_NUM_VAL && tlv.l >= 4) 51 | .map(|tlv| { 52 | let int_bytes = <[u8; 4]>::try_from(tlv.v).expect("Convert bytes to u32"); 53 | u32::from_be_bytes(int_bytes) 54 | })?; 55 | match proto_name { 56 | b"ip.proto" => Some(PacketData::L4(ip_proto as u8, rem)), 57 | _ => { 58 | // XXX unknown protocol name 59 | None 60 | } 61 | } 62 | } 63 | _ => None, 64 | } 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | pub mod tests { 70 | use super::get_packetdata_wireshark_upper_pdu; 71 | use crate::data::PacketData; 72 | use hex_literal::hex; 73 | pub const UPPER_PDU: &[u8] = &hex!( 74 | " 75 | 00 0e 00 08 69 70 2e 70 72 6f 74 6f 00 20 00 04 76 | 00 00 00 11 00 00 00 00 00 58 20 20 20 ff ff 20 77 | 63 20 68 20 a0 20 7f 20 8a 20 20 20 20 20 20 ff 78 | ff ff ff ff 20 00 00 00" 79 | ); 80 | #[test] 81 | fn test_wireshark_exported_pdu() { 82 | match get_packetdata_wireshark_upper_pdu(UPPER_PDU, UPPER_PDU.len()) { 83 | Some(PacketData::L4(proto, data)) => { 84 | assert_eq!(proto, 17); 85 | assert_eq!(data.len(), 32); 86 | } 87 | None => panic!("get_packetdata_wireshark_upper_pdu could not decode exported PDU"), 88 | _ => panic!("unexpected result type from get_packetdata_wireshark_upper_pdu"), 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/data/mod.rs: -------------------------------------------------------------------------------- 1 | //! Helper functions to access block contents (depending in linktype) 2 | //! 3 | //! ## Example 4 | //! 5 | //! ```rust 6 | //! use pcap_parser::data::{get_packetdata, PacketData}; 7 | //! use pcap_parser::pcapng::EnhancedPacketBlock; 8 | //! use pcap_parser::Linktype; 9 | //! 10 | //! fn parse_block_content<'a>( 11 | //! epb: &'a EnhancedPacketBlock<'_>, 12 | //! linktype: Linktype 13 | //! ) -> Option<()> { 14 | //! let packet_data = get_packetdata(epb.data, linktype, epb.caplen as usize)?; 15 | //! match packet_data { 16 | //! PacketData::L3(_, _data) => { 17 | //! // ... 18 | //! }, 19 | //! _ => println!("Unsupported link type"), 20 | //! } 21 | //! Some(()) 22 | //! } 23 | //! ``` 24 | 25 | mod exported_pdu; 26 | mod pcap_nflog; 27 | 28 | pub use crate::data::exported_pdu::*; 29 | pub use crate::data::pcap_nflog::*; 30 | use crate::linktype::Linktype; 31 | use crate::read_u32_e; 32 | use nom::number::complete::be_u32; 33 | use nom::number::streaming::{be_u16, be_u64, be_u8}; 34 | use nom::IResult; 35 | 36 | pub const ETHERTYPE_IPV4: u16 = 0x0800; 37 | pub const ETHERTYPE_IPV6: u16 = 0x86dd; 38 | 39 | /// Contents of a pcap/pcap-ng block. This can be network data, USB, etc. 40 | #[derive(Clone, Debug)] 41 | pub enum PacketData<'a> { 42 | L2(&'a [u8]), 43 | L3(u16, &'a [u8]), 44 | L4(u8, &'a [u8]), 45 | 46 | Unsupported(&'a [u8]), 47 | } 48 | 49 | /// Get packet data for LINKTYPE_NULL (0) 50 | /// 51 | /// BSD loopback encapsulation; the link layer header is a 4-byte field, in host byte order, 52 | /// containing a value of 2 for IPv4 packets, a value of either 24, 28, or 30 for IPv6 packets, a 53 | /// value of 7 for OSI packets, or a value of 23 for IPX packets. All of the IPv6 values correspond 54 | /// to IPv6 packets; code reading files should check for all of them. 55 | /// 56 | /// Note that ``host byte order'' is the byte order of the machine on which the packets are 57 | /// captured; if a live capture is being done, ``host byte order'' is the byte order of the machine 58 | /// capturing the packets, but if a ``savefile'' is being read, the byte order is not necessarily 59 | /// that of the machine reading the capture file. 60 | pub fn get_packetdata_null(i: &[u8], caplen: usize) -> Option { 61 | // debug!("data.len: {}, caplen: {}", packet.data.len(), packet.header.caplen); 62 | if i.len() < caplen || caplen < 4 { 63 | None 64 | } else { 65 | let vers = read_u32_e!(i, false); 66 | let ethertype = match vers { 67 | 2 => ETHERTYPE_IPV4, 68 | 24 | 28 | 30 => ETHERTYPE_IPV6, 69 | _ => 0, 70 | }; 71 | Some(PacketData::L3(ethertype, &i[4..caplen])) 72 | } 73 | } 74 | 75 | /// Get packet data for LINKTYPE_ETHERNET (1) 76 | /// 77 | /// IEEE 802.3 Ethernet (10Mb, 100Mb, 1000Mb, and up); the 10MB in the DLT_ name is historical. 78 | pub fn get_packetdata_ethernet(i: &[u8], caplen: usize) -> Option { 79 | if i.len() < caplen || caplen == 0 { 80 | None 81 | } else { 82 | Some(PacketData::L2(&i[..caplen])) 83 | } 84 | } 85 | 86 | /// Get packet data for LINKTYPE_RAW (101) 87 | /// 88 | /// Raw IP; the packet begins with an IPv4 or IPv6 header, with the "version" field of the header 89 | /// indicating whether it's an IPv4 or IPv6 header. 90 | pub fn get_packetdata_raw(i: &[u8], caplen: usize) -> Option { 91 | if i.len() < caplen || caplen == 0 { 92 | None 93 | } else { 94 | let vers = i[0] >> 4; 95 | let ethertype = match vers { 96 | 4 => ETHERTYPE_IPV4, 97 | 6 => ETHERTYPE_IPV6, 98 | _ => 0, 99 | }; 100 | Some(PacketData::L3(ethertype, &i[..caplen])) 101 | } 102 | } 103 | 104 | /// Get packet data for LINKTYPE_LINUX_SLL (113) 105 | /// 106 | /// See 107 | pub fn get_packetdata_linux_sll(i: &[u8], caplen: usize) -> Option { 108 | if i.len() < caplen || caplen == 0 { 109 | None 110 | } else { 111 | match parse_sll_header(i) { 112 | Err(_) => None, 113 | Ok((rem, sll)) => { 114 | match sll.arphrd_type { 115 | 778 /* ARPHRD_IPGRE */ => Some(PacketData::L4(47, rem)), 116 | 803 /* ARPHRD_IEEE80211_RADIOTAP */ | 117 | 824 /* ARPHRD_NETLINK */ => None, 118 | _ => Some(PacketData::L3(sll.proto, rem)), 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | struct SLLHeader { 126 | _packet_type: u16, 127 | arphrd_type: u16, 128 | _ll_addr_len: u16, 129 | _ll_addr: u64, 130 | proto: u16, 131 | } 132 | 133 | fn parse_sll_header(i: &[u8]) -> IResult<&[u8], SLLHeader> { 134 | let (i, _packet_type) = be_u16(i)?; 135 | let (i, arphrd_type) = be_u16(i)?; 136 | let (i, _ll_addr_len) = be_u16(i)?; 137 | let (i, _ll_addr) = be_u64(i)?; 138 | let (i, proto) = be_u16(i)?; 139 | let header = SLLHeader { 140 | _packet_type, 141 | arphrd_type, 142 | _ll_addr_len, 143 | _ll_addr, 144 | proto, 145 | }; 146 | Ok((i, header)) 147 | } 148 | 149 | /// Get packet data for LINKTYPE_LINUX_SLL2 (276) 150 | /// 151 | /// See 152 | pub fn get_packetdata_linux_sll2(i: &[u8], caplen: usize) -> Option { 153 | if i.len() < caplen || caplen == 0 { 154 | None 155 | } else { 156 | match parse_sll2_header(i) { 157 | Err(_) => None, 158 | Ok((rem, sll)) => { 159 | match sll.arphrd_type { 160 | 778 /* ARPHRD_IPGRE */ => Some(PacketData::L4(47, rem)), 161 | 803 /* ARPHRD_IEEE80211_RADIOTAP */ | 162 | 824 /* ARPHRD_NETLINK */ => None, 163 | _ => Some(PacketData::L3(sll.protocol_type, rem)), 164 | } 165 | } 166 | } 167 | } 168 | } 169 | 170 | struct SLL2Header { 171 | protocol_type: u16, 172 | _reserved: u16, 173 | _interface_index: u32, 174 | arphrd_type: u16, 175 | _packet_type: u8, 176 | _ll_addr_len: u8, 177 | _ll_addr: u64, 178 | } 179 | 180 | fn parse_sll2_header(i: &[u8]) -> IResult<&[u8], SLL2Header> { 181 | let (i, protocol_type) = be_u16(i)?; 182 | let (i, _reserved) = be_u16(i)?; 183 | let (i, _interface_index) = be_u32(i)?; 184 | let (i, arphrd_type) = be_u16(i)?; 185 | let (i, _packet_type) = be_u8(i)?; 186 | let (i, _ll_addr_len) = be_u8(i)?; 187 | let (i, _ll_addr) = be_u64(i)?; 188 | let header = SLL2Header { 189 | protocol_type, 190 | _reserved, 191 | _interface_index, 192 | arphrd_type, 193 | _packet_type, 194 | _ll_addr_len, 195 | _ll_addr, 196 | }; 197 | Ok((i, header)) 198 | } 199 | 200 | /// Get packet data for LINKTYPE_IPV4 (228) 201 | /// 202 | /// Raw IPv4; the packet begins with an IPv4 header. 203 | pub fn get_packetdata_ipv4(i: &[u8], _caplen: usize) -> Option { 204 | Some(PacketData::L3(ETHERTYPE_IPV4, i)) 205 | } 206 | 207 | /// Get packet data for LINKTYPE_IPV6 (229) 208 | /// 209 | /// Raw IPv4; the packet begins with an IPv6 header. 210 | pub fn get_packetdata_ipv6(i: &[u8], _caplen: usize) -> Option { 211 | Some(PacketData::L3(ETHERTYPE_IPV6, i)) 212 | } 213 | 214 | /// Get packet data, depending on linktype. 215 | /// 216 | /// Returns packet data, or None if data could not be extracted (for ex, inner parsing error). 217 | /// If linktype is not supported, `PacketData::Unsupported` is used. 218 | pub fn get_packetdata(i: &[u8], linktype: Linktype, caplen: usize) -> Option { 219 | match linktype { 220 | Linktype::NULL => get_packetdata_null(i, caplen), 221 | Linktype::ETHERNET => get_packetdata_ethernet(i, caplen), 222 | Linktype::RAW => get_packetdata_raw(i, caplen), 223 | Linktype::LINUX_SLL => get_packetdata_linux_sll(i, caplen), 224 | Linktype::LINUX_SLL2 => get_packetdata_linux_sll2(i, caplen), 225 | Linktype::IPV4 => get_packetdata_ipv4(i, caplen), 226 | Linktype::IPV6 => get_packetdata_ipv6(i, caplen), 227 | Linktype::NFLOG => get_packetdata_nflog(i, caplen), 228 | Linktype::WIRESHARK_UPPER_PDU => get_packetdata_wireshark_upper_pdu(i, caplen), 229 | _ => Some(PacketData::Unsupported(i)), 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/data/pcap_nflog.rs: -------------------------------------------------------------------------------- 1 | //! NFLOG link layer encapsulation for PCAP 2 | //! 3 | //! Helper module to access content of data stored using NFLOG (239) 4 | //! data link type. 5 | //! 6 | //! See for details. 7 | 8 | use crate::data::{PacketData, ETHERTYPE_IPV4, ETHERTYPE_IPV6}; 9 | use nom::bytes::streaming::take; 10 | use nom::combinator::{complete, cond, verify}; 11 | use nom::multi::many0; 12 | use nom::number::streaming::{be_u16, le_u16, le_u8}; 13 | use nom::IResult; 14 | 15 | // Defined in linux/netfilter/nfnetlink_log.h 16 | #[derive(Copy, Clone)] 17 | #[repr(u16)] 18 | pub enum NfAttrType { 19 | /// packet header structure: hardware protocol (2 bytes), nf hook (1 byte), padding (1 byte) 20 | PacketHdr = 1, 21 | /// packet mark value from the skbuff for the packet 22 | Mark = 2, 23 | /// packet time stamp structure: seconds (8 bytes), microseconds (8 bytes) 24 | Timestamp = 3, 25 | /// 32-bit ifindex of the device on which the packet was received, which could be a bridge group 26 | IfIndexInDev = 4, 27 | /// 32-bit ifindex of the device on which the packet was sent, which could be a bridge group 28 | IfIndexOutDev = 5, 29 | /// 32-bit ifindex of the physical device on which the packet was received, which is not a bridge group 30 | IfIndexPhysInDev = 6, 31 | /// 32-bit ifindex of the physical device on which the packet was sent, which is not a bridge group 32 | IfIndexPhysOutDev = 7, 33 | /// hardware address structure: 34 | /// address length (2 bytes), padding (1 byte), address (8 bytes) 35 | HwAddr = 8, 36 | /// packet payload following the link-layer header 37 | Payload = 9, 38 | /// null-terminated text string 39 | Prefix = 10, 40 | /// 32-bit ifindex of the device on which the packet was received, which could be a bridge group 41 | Uid = 11, 42 | /// 32-bit sequence number for packets provided by this nflog device 43 | Seq = 12, 44 | /// 32-bit sequence number for packets provided by all nflog devices 45 | SeqGlobal = 13, 46 | /// 32-bit group ID that owned the socket on which the packet was sent or received 47 | Gid = 14, 48 | /// 32-bit Linux ARPHRD_ value for the device associated with the skbuff for the packet 49 | HwType = 15, 50 | /// MAC-layer header for the skbuff for the packet 51 | HwHeader = 16, 52 | /// length of the MAC-layer header 53 | HwLen = 17, 54 | /// conntrack header (nfnetlink_conntrack.h) 55 | Ct = 18, 56 | /// enum ip_conntrack_info 57 | CtInfo = 19, 58 | } 59 | 60 | #[derive(Debug)] 61 | pub struct NflogTlv<'a> { 62 | /// Length of data (including 4 bytes for length and types) 63 | pub l: u16, 64 | /// Type of data (see `NfAttrType`) 65 | pub t: u16, 66 | /// Data 67 | pub v: &'a [u8], 68 | } 69 | 70 | pub fn parse_nflog_tlv(i: &[u8]) -> IResult<&[u8], NflogTlv> { 71 | let (i, l) = verify(le_u16, |&n| n >= 4)(i)?; 72 | let (i, t) = le_u16(i)?; 73 | let (i, v) = take(l - 4)(i)?; 74 | let (i, _padding) = cond(l % 4 != 0, take(4 - (l % 4)))(i)?; 75 | Ok((i, NflogTlv { l, t, v })) 76 | } 77 | 78 | #[derive(Debug)] 79 | pub struct NflogHdr { 80 | /// Address family 81 | pub af: u8, 82 | /// Version (currently: 0) 83 | pub vers: u8, 84 | /// Resource ID: nflog group for the packet 85 | pub res_id: u16, 86 | } 87 | 88 | #[derive(Debug)] 89 | pub struct NflogPacket<'a> { 90 | /// The nflog packet header 91 | pub header: NflogHdr, 92 | /// The objects (Type-Length-Value) 93 | pub data: Vec>, 94 | } 95 | 96 | pub fn parse_nflog_header(i: &[u8]) -> IResult<&[u8], NflogHdr> { 97 | let (i, af) = le_u8(i)?; 98 | let (i, vers) = le_u8(i)?; 99 | let (i, res_id) = be_u16(i)?; 100 | Ok((i, NflogHdr { af, vers, res_id })) 101 | } 102 | 103 | impl NflogPacket<'_> { 104 | pub fn get(&self, attr: NfAttrType) -> Option<&NflogTlv> { 105 | self.data.iter().find(|v| v.t == attr as u16) 106 | } 107 | 108 | pub fn get_payload(&self) -> Option<&[u8]> { 109 | self.get(NfAttrType::Payload).map(|tlv| tlv.v) 110 | } 111 | } 112 | 113 | pub fn parse_nflog(i: &[u8]) -> IResult<&[u8], NflogPacket> { 114 | let (i, header) = parse_nflog_header(i)?; 115 | let (i, data) = many0(complete(parse_nflog_tlv))(i)?; 116 | Ok((i, NflogPacket { header, data })) 117 | } 118 | 119 | /// Get packet data for LINKTYPE_NFLOG (239) 120 | /// 121 | /// Parse nflog data, and extract only packet payload 122 | /// 123 | /// See 124 | pub fn get_packetdata_nflog(i: &[u8], _caplen: usize) -> Option { 125 | match parse_nflog(i) { 126 | Ok((_, res)) => { 127 | let ethertype = match res.header.af { 128 | 2 => ETHERTYPE_IPV4, 129 | 10 => ETHERTYPE_IPV6, 130 | _ => 0, 131 | }; 132 | res.data 133 | .into_iter() 134 | .find(|v| v.t == NfAttrType::Payload as u16) 135 | .map(|tlv| PacketData::L3(ethertype, tlv.v)) 136 | } 137 | _ => None, 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/endianness.rs: -------------------------------------------------------------------------------- 1 | use nom::error::ParseError; 2 | use nom::number::streaming::{be_u16, be_u32, le_u16, le_u32}; 3 | use nom::IResult; 4 | 5 | pub(crate) struct PcapBE; 6 | pub(crate) struct PcapLE; 7 | 8 | pub(crate) trait PcapEndianness { 9 | fn native_u32(n: u32) -> u32; 10 | 11 | fn parse_u16<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], u16, E>; 12 | fn parse_u32<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], u32, E>; 13 | 14 | fn u32_from_bytes(i: [u8; 4]) -> u32; 15 | } 16 | 17 | impl PcapEndianness for PcapBE { 18 | #[inline] 19 | fn native_u32(n: u32) -> u32 { 20 | u32::from_be(n) 21 | } 22 | 23 | #[inline] 24 | fn parse_u16<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], u16, E> { 25 | be_u16(i) 26 | } 27 | 28 | #[inline] 29 | fn parse_u32<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], u32, E> { 30 | be_u32(i) 31 | } 32 | 33 | #[inline] 34 | fn u32_from_bytes(i: [u8; 4]) -> u32 { 35 | u32::from_be_bytes(i) 36 | } 37 | } 38 | 39 | impl PcapEndianness for PcapLE { 40 | #[inline] 41 | fn native_u32(n: u32) -> u32 { 42 | u32::from_le(n) 43 | } 44 | 45 | #[inline] 46 | fn parse_u16<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], u16, E> { 47 | le_u16(i) 48 | } 49 | 50 | #[inline] 51 | fn parse_u32<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], u32, E> { 52 | le_u32(i) 53 | } 54 | 55 | #[inline] 56 | fn u32_from_bytes(i: [u8; 4]) -> u32 { 57 | u32::from_le_bytes(i) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use nom::error::{ErrorKind, ParseError}; 2 | use std::fmt; 3 | 4 | /// The error type which is returned when reading a pcap file 5 | #[derive(Debug, PartialEq)] 6 | pub enum PcapError { 7 | /// No more data available 8 | Eof, 9 | /// Buffer capacity is too small, and some full frame cannot be stored 10 | BufferTooSmall, 11 | /// Expected more data but got EOF 12 | UnexpectedEof, 13 | /// An error happened during a `read` operation 14 | ReadError, 15 | /// Last block is incomplete, and no more data available 16 | Incomplete(usize), 17 | 18 | /// File could not be recognized as Pcap nor Pcap-NG 19 | HeaderNotRecognized, 20 | 21 | /// An error encountered during parsing 22 | NomError(I, ErrorKind), 23 | /// An error encountered during parsing (owned version) 24 | OwnedNomError(Vec, ErrorKind), 25 | } 26 | 27 | impl PcapError { 28 | /// Creates a `PcapError` from input and error kind. 29 | pub fn from_data(input: I, errorkind: ErrorKind) -> Self { 30 | Self::NomError(input, errorkind) 31 | } 32 | } 33 | 34 | impl PcapError 35 | where 36 | I: AsRef<[u8]> + Sized, 37 | { 38 | /// Creates an owned `PcapError` object from borrowed data, cloning object. 39 | /// Owned object has `'static` lifetime. 40 | pub fn to_owned_vec(&self) -> PcapError<&'static [u8]> { 41 | match self { 42 | PcapError::Eof => PcapError::Eof, 43 | PcapError::BufferTooSmall => PcapError::BufferTooSmall, 44 | PcapError::UnexpectedEof => PcapError::UnexpectedEof, 45 | PcapError::ReadError => PcapError::ReadError, 46 | PcapError::Incomplete(n) => PcapError::Incomplete(*n), 47 | PcapError::HeaderNotRecognized => PcapError::HeaderNotRecognized, 48 | PcapError::NomError(i, errorkind) => { 49 | PcapError::OwnedNomError(i.as_ref().to_vec(), *errorkind) 50 | } 51 | PcapError::OwnedNomError(v, e) => PcapError::OwnedNomError(v.clone(), *e), 52 | } 53 | } 54 | } 55 | 56 | impl ParseError for PcapError { 57 | fn from_error_kind(input: I, kind: ErrorKind) -> Self { 58 | PcapError::NomError(input, kind) 59 | } 60 | fn append(input: I, kind: ErrorKind, _other: Self) -> Self { 61 | PcapError::NomError(input, kind) 62 | } 63 | } 64 | 65 | impl fmt::Display for PcapError 66 | where 67 | I: fmt::Debug, 68 | { 69 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 70 | match self { 71 | PcapError::Eof => write!(f, "End of file"), 72 | PcapError::BufferTooSmall => write!(f, "Buffer is too small"), 73 | PcapError::UnexpectedEof => write!(f, "Unexpected end of file"), 74 | PcapError::ReadError => write!(f, "Read error"), 75 | PcapError::Incomplete(n) => write!(f, "Incomplete read: {}", n), 76 | PcapError::HeaderNotRecognized => write!(f, "Header not recognized as PCAP or PCAPNG"), 77 | PcapError::NomError(i, e) => write!(f, "Internal parser error {:?}, input {:?}", e, i), 78 | PcapError::OwnedNomError(i, e) => { 79 | write!(f, "Internal parser error {:?}, input {:?}", e, &i) 80 | } 81 | } 82 | } 83 | } 84 | 85 | impl std::error::Error for PcapError where I: fmt::Debug {} 86 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT) 2 | //! [![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) 3 | //! [![Crates.io Version](https://img.shields.io/crates/v/pcap-parser.svg)](https://crates.io/crates/pcap-parser) 4 | //! [![docs.rs](https://docs.rs/pcap-parser/badge.svg)](https://docs.rs/pcap-parser) 5 | //! [![Github CI](https://github.com/rusticata/pcap-parser/workflows/Continuous%20integration/badge.svg)](https://github.com/rusticata/pcap-parser/actions) 6 | //! [![Minimum rustc version](https://img.shields.io/badge/rustc-1.63.0+-lightgray.svg)](#rust-version-requirements) 7 | //! 8 | //! # PCAP and PCAPNG parsers 9 | //! 10 | //! This crate contains several parsers for PCAP and PCAPNG files. 11 | //! 12 | //! Compared to other similar projects, it is designed to offer a complete support of the many 13 | //! possible formats (legacy pcap, pcapng, little or big-endian, etc.) and features (pcapng files 14 | //! with multiple sections, interfaces, and endianness) while using only safe code and without 15 | //! copying data (zero-copy). 16 | //! 17 | //! The code is available on [Github](https://github.com/rusticata/pcap-parser) 18 | //! and is part of the [Rusticata](https://github.com/rusticata) project. 19 | //! 20 | //! # The pcap format(s) 21 | //! 22 | //! The [PCAP] format (files usually ending with `.pcap` extension) is rather 23 | //! trivial. The [PCAPNG] format (usually `.pcapng` extension) is much more complex: it 24 | //! can be composed of multiple sections, each with multiple interfaces, having 25 | //! different capture lengths, time precision and even endianness! 26 | //! 27 | //! These formats are more containers than data formats: packets contain data, 28 | //! formatted according to its interface linktype. There are *many* possible 29 | //! linktypes, defined in the [linktypes registry]. Support for parsing some of 30 | //! them is provided using the `data` feature (disabled by default). 31 | //! 32 | //! This crate provides an abstraction over these different formats. 33 | //! 34 | //! [PCAP]: https://wiki.wireshark.org/Development/LibpcapFileFormat 35 | //! [PCAPNG]: https://pcapng.github.io/pcapng/ 36 | //! [linktypes registry]: https://www.tcpdump.org/linktypes.html 37 | //! 38 | //! # Parsing a file 39 | //! 40 | //! `pcap-parser` provides several ways of parsing pcap data. Choosing the right 41 | //! one is mostly driven by resources: if the input file is small, the 42 | //! `parse_pcap` and `parse_pcapng` functions can be used directly. 43 | //! 44 | //! Fine-grained functions are also available, to parse specifically some block 45 | //! types for example. They are listed in the `pcap` and `pcapng` modules. 46 | //! 47 | //! If the input is larger and cannot fit into memory, then streaming parsers 48 | //! are available. They work by iterating on blocks, and so do not require to map 49 | //! the entire input. They cannot seek to a specific block, however. 50 | //! 51 | //! *Note: due to PCAPNG limitations, it is not possible to directly seek in 52 | //! a file to get a packet and handle it: the caller has to iterate though the 53 | //! file and store (at least) the interface descriptions for the current 54 | //! section, in order of appearance.* 55 | //! 56 | //! ## Example: streaming parsers 57 | //! 58 | //! The following code shows how to parse a file in the pcap-ng format, using a 59 | //! [`PcapNGReader`] streaming parser. 60 | //! This reader provides a convenient abstraction over the file format, and takes 61 | //! care of the endianness. 62 | //! 63 | //! ```rust 64 | //! use pcap_parser::*; 65 | //! use pcap_parser::traits::PcapReaderIterator; 66 | //! use std::fs::File; 67 | //! 68 | //! # let path = "assets/test001-le.pcapng"; 69 | //! let file = File::open(path).unwrap(); 70 | //! let mut num_blocks = 0; 71 | //! let mut reader = PcapNGReader::new(65536, file).expect("PcapNGReader"); 72 | //! loop { 73 | //! match reader.next() { 74 | //! Ok((offset, _block)) => { 75 | //! println!("got new block"); 76 | //! num_blocks += 1; 77 | //! reader.consume(offset); 78 | //! }, 79 | //! Err(PcapError::Eof) => break, 80 | //! Err(PcapError::Incomplete(_)) => { 81 | //! reader.refill().unwrap(); 82 | //! }, 83 | //! Err(e) => panic!("error while reading: {:?}", e), 84 | //! } 85 | //! } 86 | //! println!("num_blocks: {}", num_blocks); 87 | //! ``` 88 | //! See [`PcapNGReader`] for a complete example, 89 | //! including handling of linktype and accessing packet data. 90 | //! 91 | //! See also the [`pcapng`] module for more details about the new capture file format. 92 | //! 93 | //! For legacy pcap files, use similar code with the 94 | //! [`LegacyPcapReader`] streaming parser. 95 | //! 96 | //! See [pcap-analyzer](https://github.com/rusticata/pcap-analyzer), in particular the 97 | //! [libpcap-tools](https://github.com/rusticata/pcap-analyzer/tree/master/libpcap-tools) and 98 | //! [pcap-info](https://github.com/rusticata/pcap-analyzer/tree/master/pcap-info) modules 99 | //! for more examples. 100 | //! 101 | //! ## Example: generic streaming parsing 102 | //! 103 | //! To create a pcap reader for input in either PCAP or PCAPNG format, use the 104 | //! [`create_reader`] function. 105 | //! 106 | //! # Serialization 107 | //! 108 | //! Support for serialization (*i.e.* generating binary data) is available by 109 | //! enabling the `serialize` feature. 110 | //! Most structures gain the `to_vec()` method (provided by the `ToVec` trait). 111 | //! 112 | //! Note: support is still experimental, though working. API may change in the 113 | //! future. 114 | 115 | #![deny(/*missing_docs,*/ 116 | unstable_features, 117 | unused_import_braces, unused_qualifications)] 118 | #![deny(unsafe_code)] 119 | #![allow(clippy::upper_case_acronyms)] 120 | // pragmas for doc 121 | #![deny(rustdoc::broken_intra_doc_links)] 122 | #![cfg_attr(docsrs, feature(doc_cfg))] 123 | #![doc(test( 124 | no_crate_inject, 125 | attr(deny(warnings/*, rust_2018_idioms*/), allow(dead_code, unused_variables)) 126 | ))] 127 | 128 | mod utils; 129 | pub use utils::{Data, MutableData}; 130 | 131 | mod blocks; 132 | mod capture; 133 | mod endianness; 134 | mod error; 135 | mod linktype; 136 | pub use blocks::*; 137 | pub use capture::*; 138 | pub use error::*; 139 | pub use linktype::*; 140 | 141 | pub mod pcap; 142 | pub mod pcapng; 143 | pub use pcap::*; 144 | pub use pcapng::*; 145 | 146 | pub mod traits; 147 | 148 | #[cfg(feature = "serialize")] 149 | #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] 150 | mod serialize; 151 | #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] 152 | #[cfg(feature = "serialize")] 153 | pub use serialize::ToVec; 154 | 155 | #[cfg(feature = "data")] 156 | #[cfg_attr(docsrs, doc(cfg(feature = "data")))] 157 | pub mod data; 158 | 159 | // re-exports 160 | pub use nom; 161 | -------------------------------------------------------------------------------- /src/linktype.rs: -------------------------------------------------------------------------------- 1 | use rusticata_macros::newtype_enum; 2 | 3 | /// Data link type 4 | /// 5 | /// The link-layer header type specifies the type of headers at the beginning 6 | /// of the packet. 7 | /// 8 | /// See 9 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 10 | pub struct Linktype(pub i32); 11 | 12 | newtype_enum! { 13 | impl display Linktype { 14 | NULL = 0, 15 | ETHERNET = 1, 16 | 17 | FDDI = 10, 18 | 19 | RAW = 101, 20 | 21 | LOOP = 108, 22 | LINUX_SLL = 113, 23 | LINUX_SLL2 = 276, 24 | 25 | // Raw IPv4; the packet begins with an IPv4 header. 26 | IPV4 = 228, 27 | // Raw IPv6; the packet begins with an IPv6 header. 28 | IPV6 = 229, 29 | 30 | // Linux netlink NETLINK NFLOG socket log messages. 31 | // Use the [`pcap_nflog`] module to access content. 32 | NFLOG = 239, 33 | 34 | // Upper-layer protocol saves from Wireshark 35 | WIRESHARK_UPPER_PDU = 252, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/pcap.rs: -------------------------------------------------------------------------------- 1 | //! PCAP file format 2 | //! 3 | //! See for details. 4 | //! 5 | //! There are 2 main ways of parsing a PCAP file. The first method is to use 6 | //! [`parse_pcap`]. This method requires to load the entire 7 | //! file to memory, and thus may not be good for large files. 8 | //! 9 | //! The [`PcapCapture`] implements the 10 | //! [`Capture`](crate::Capture) trait to provide generic methods. However, 11 | //! this trait also reads the entire file. 12 | //! 13 | //! The second method is to first parse the PCAP header 14 | //! using [`parse_pcap_header`], then 15 | //! loop over [`parse_pcap_frame`] to get the data. 16 | //! This can be used in a streaming parser. 17 | 18 | mod capture; 19 | mod frame; 20 | mod header; 21 | mod reader; 22 | 23 | pub use capture::*; 24 | pub use frame::*; 25 | pub use header::*; 26 | pub use reader::*; 27 | 28 | #[cfg(test)] 29 | pub mod tests { 30 | use crate::pcap::{parse_pcap_frame, parse_pcap_header}; 31 | use crate::traits::tests::FRAME_PCAP; 32 | use hex_literal::hex; 33 | // ntp.pcap header 34 | pub const PCAP_HDR: &[u8] = &hex!( 35 | " 36 | D4 C3 B2 A1 02 00 04 00 00 00 00 00 00 00 00 00 37 | 00 00 04 00 01 00 00 00" 38 | ); 39 | 40 | // pcap header with nanosecond-precision timestamping 41 | pub const PCAP_HDR_NSEC: &[u8] = &hex!( 42 | " 43 | 4D 3C B2 A1 02 00 04 00 00 00 00 00 00 00 00 00 44 | 00 00 04 00 01 00 00 00" 45 | ); 46 | #[test] 47 | fn test_parse_pcap_header() { 48 | let (rem, hdr) = parse_pcap_header(PCAP_HDR).expect("header parsing failed"); 49 | assert!(rem.is_empty()); 50 | assert_eq!(hdr.magic_number, 0xa1b2_c3d4); 51 | assert_eq!(hdr.version_major, 2); 52 | assert_eq!(hdr.version_minor, 4); 53 | assert_eq!(hdr.snaplen, 262_144); 54 | assert!(!hdr.is_nanosecond_precision()); 55 | } 56 | #[test] 57 | fn test_parse_nanosecond_precision_pcap_header() { 58 | let (rem, hdr) = parse_pcap_header(PCAP_HDR_NSEC).expect("header parsing failed"); 59 | assert!(rem.is_empty()); 60 | assert_eq!(hdr.magic_number, 0xa1b2_3c4d); 61 | assert_eq!(hdr.version_major, 2); 62 | assert_eq!(hdr.version_minor, 4); 63 | assert_eq!(hdr.snaplen, 262_144); 64 | assert!(hdr.is_nanosecond_precision()); 65 | } 66 | #[test] 67 | fn test_parse_pcap_frame() { 68 | let (rem, pkt) = parse_pcap_frame(FRAME_PCAP).expect("packet parsing failed"); 69 | assert!(rem.is_empty()); 70 | assert_eq!(pkt.origlen, 74); 71 | assert_eq!(pkt.ts_usec, 562_913); 72 | assert_eq!(pkt.ts_sec, 1_515_933_236); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/pcap/capture.rs: -------------------------------------------------------------------------------- 1 | use crate::blocks::{PcapBlock, PcapBlockOwned}; 2 | use crate::capture::Capture; 3 | use crate::error::PcapError; 4 | use crate::linktype::Linktype; 5 | use crate::pcap::{parse_pcap_frame, parse_pcap_header, LegacyPcapBlock, PcapHeader}; 6 | use nom::combinator::complete; 7 | use nom::multi::many0; 8 | use nom::{IResult, Needed}; 9 | use std::fmt; 10 | 11 | /// Parsing iterator over legacy pcap data (requires data to be loaded into memory) 12 | /// 13 | /// ```rust 14 | /// use pcap_parser::*; 15 | /// use std::fs::File; 16 | /// use std::io::Read; 17 | /// 18 | /// # let path = "assets/ntp.pcap"; 19 | /// let mut file = File::open(path).unwrap(); 20 | /// let mut buffer = Vec::new(); 21 | /// file.read_to_end(&mut buffer).unwrap(); 22 | /// let mut num_blocks = 0; 23 | /// match LegacyPcapSlice::from_slice(&buffer) { 24 | /// Ok(iter) => { 25 | /// println!("Format: PCAP"); 26 | /// for _block in iter { 27 | /// num_blocks += 1; 28 | /// } 29 | /// return; 30 | /// }, 31 | /// _ => () 32 | /// } 33 | /// ``` 34 | pub struct LegacyPcapSlice<'a> { 35 | pub header: PcapHeader, 36 | // remaining (unparsed) data 37 | rem: &'a [u8], 38 | } 39 | 40 | impl LegacyPcapSlice<'_> { 41 | pub fn from_slice(i: &[u8]) -> Result>> { 42 | let (rem, header) = parse_pcap_header(i)?; 43 | Ok(LegacyPcapSlice { header, rem }) 44 | } 45 | } 46 | 47 | /// Iterator for LegacyPcapSlice. Returns a result so parsing errors are not 48 | /// silently ignored 49 | impl<'a> Iterator for LegacyPcapSlice<'a> { 50 | type Item = Result, nom::Err>>; 51 | 52 | fn next(&mut self) -> Option { 53 | if self.rem.is_empty() { 54 | return None; 55 | } 56 | let r = parse_pcap_frame(self.rem).map(|(rem, b)| { 57 | self.rem = rem; 58 | PcapBlockOwned::from(b) 59 | }); 60 | Some(r) 61 | } 62 | } 63 | 64 | /// Generic interface for PCAP file access 65 | pub struct PcapCapture<'a> { 66 | pub header: PcapHeader, 67 | 68 | pub blocks: Vec>, 69 | } 70 | 71 | impl PcapCapture<'_> { 72 | pub fn from_file(i: &[u8]) -> Result> { 73 | match parse_pcap(i) { 74 | Ok((_, pcap)) => Ok(pcap), 75 | Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(e), 76 | Err(nom::Err::Incomplete(Needed::Size(n))) => Err(PcapError::Incomplete(n.into())), 77 | Err(nom::Err::Incomplete(Needed::Unknown)) => Err(PcapError::Incomplete(0)), 78 | } 79 | } 80 | } 81 | 82 | impl fmt::Debug for PcapCapture<'_> { 83 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 84 | writeln!(f, "PcapCapture:") 85 | } 86 | } 87 | 88 | /// Iterator over `PcapCapture` 89 | pub struct LegacyPcapIterator<'a> { 90 | cap: &'a PcapCapture<'a>, 91 | idx: usize, 92 | } 93 | 94 | impl<'a> Iterator for LegacyPcapIterator<'a> { 95 | type Item = PcapBlock<'a>; 96 | 97 | fn next(&mut self) -> Option> { 98 | self.cap.blocks.get(self.idx).map(|b| { 99 | self.idx += 1; 100 | PcapBlock::from(b) 101 | }) 102 | } 103 | } 104 | 105 | impl Capture for PcapCapture<'_> { 106 | fn get_datalink(&self) -> Linktype { 107 | self.header.network 108 | } 109 | 110 | fn get_snaplen(&self) -> u32 { 111 | self.header.snaplen 112 | } 113 | 114 | fn iter<'b>(&'b self) -> Box> + 'b> { 115 | Box::new(LegacyPcapIterator { cap: self, idx: 0 }) 116 | } 117 | } 118 | 119 | /// Parse the entire file 120 | /// 121 | /// Note: this requires the file to be fully loaded to memory. 122 | pub fn parse_pcap(i: &[u8]) -> IResult<&[u8], PcapCapture, PcapError<&[u8]>> { 123 | let (i, header) = parse_pcap_header(i)?; 124 | let (i, blocks) = many0(complete(parse_pcap_frame))(i)?; 125 | Ok((i, PcapCapture { header, blocks })) 126 | } 127 | -------------------------------------------------------------------------------- /src/pcap/frame.rs: -------------------------------------------------------------------------------- 1 | use nom::bytes::streaming::take; 2 | use nom::IResult; 3 | 4 | use crate::utils::array_ref4; 5 | use crate::PcapError; 6 | 7 | /// Container for network data in legacy Pcap files 8 | #[derive(Debug)] 9 | pub struct LegacyPcapBlock<'a> { 10 | pub ts_sec: u32, 11 | pub ts_usec: u32, 12 | pub caplen: u32, 13 | pub origlen: u32, 14 | pub data: &'a [u8], 15 | } 16 | 17 | /// Read a PCAP record header and data 18 | /// 19 | /// Each PCAP record starts with a small header, and is followed by packet data. 20 | /// The packet data format depends on the LinkType. 21 | pub fn parse_pcap_frame(i: &[u8]) -> IResult<&[u8], LegacyPcapBlock, PcapError<&[u8]>> { 22 | if i.len() < 16 { 23 | return Err(nom::Err::Incomplete(nom::Needed::new(16 - i.len()))); 24 | } 25 | let ts_sec = u32::from_le_bytes(*array_ref4(i, 0)); 26 | let ts_usec = u32::from_le_bytes(*array_ref4(i, 4)); 27 | let caplen = u32::from_le_bytes(*array_ref4(i, 8)); 28 | let origlen = u32::from_le_bytes(*array_ref4(i, 12)); 29 | let (i, data) = take(caplen as usize)(&i[16..])?; 30 | let block = LegacyPcapBlock { 31 | ts_sec, 32 | ts_usec, 33 | caplen, 34 | origlen, 35 | data, 36 | }; 37 | Ok((i, block)) 38 | } 39 | 40 | /// Read a PCAP record header and data (big-endian) 41 | /// 42 | /// Each PCAP record starts with a small header, and is followed by packet data. 43 | /// The packet data format depends on the LinkType. 44 | pub fn parse_pcap_frame_be(i: &[u8]) -> IResult<&[u8], LegacyPcapBlock, PcapError<&[u8]>> { 45 | if i.len() < 16 { 46 | return Err(nom::Err::Incomplete(nom::Needed::new(16 - i.len()))); 47 | } 48 | let ts_sec = u32::from_be_bytes(*array_ref4(i, 0)); 49 | let ts_usec = u32::from_be_bytes(*array_ref4(i, 4)); 50 | let caplen = u32::from_be_bytes(*array_ref4(i, 8)); 51 | let origlen = u32::from_be_bytes(*array_ref4(i, 12)); 52 | let (i, data) = take(caplen as usize)(&i[16..])?; 53 | let block = LegacyPcapBlock { 54 | ts_sec, 55 | ts_usec, 56 | caplen, 57 | origlen, 58 | data, 59 | }; 60 | Ok((i, block)) 61 | } 62 | 63 | /// Read a PCAP record header and data ("modified" pcap format) 64 | /// 65 | /// Each PCAP record starts with a small header, and is followed by packet data. 66 | /// The packet data format depends on the LinkType. 67 | pub fn parse_pcap_frame_modified(i: &[u8]) -> IResult<&[u8], LegacyPcapBlock, PcapError<&[u8]>> { 68 | if i.len() < 24 { 69 | return Err(nom::Err::Incomplete(nom::Needed::new(24 - i.len()))); 70 | } 71 | let ts_sec = u32::from_le_bytes(*array_ref4(i, 0)); 72 | let ts_usec = u32::from_le_bytes(*array_ref4(i, 4)); 73 | let caplen = u32::from_le_bytes(*array_ref4(i, 8)); 74 | let origlen = u32::from_le_bytes(*array_ref4(i, 12)); 75 | let (i, data) = take(caplen as usize)(&i[24..])?; 76 | let block = LegacyPcapBlock { 77 | ts_sec, 78 | ts_usec, 79 | caplen, 80 | origlen, 81 | data, 82 | }; 83 | Ok((i, block)) 84 | } 85 | -------------------------------------------------------------------------------- /src/pcap/header.rs: -------------------------------------------------------------------------------- 1 | use nom::number::streaming::{be_i32, be_u16, be_u32, le_i32, le_u16, le_u32}; 2 | use nom::IResult; 3 | 4 | use crate::linktype::Linktype; 5 | use crate::PcapError; 6 | 7 | /// PCAP global header 8 | #[derive(Clone, Debug)] 9 | pub struct PcapHeader { 10 | /// File format and byte ordering. If equal to `0xa1b2c3d4` or `0xa1b23c4d` then the rest of 11 | /// the file uses native byte ordering. If `0xd4c3b2a1` or `0x4d3cb2a1` (swapped), then all 12 | /// following fields will have to be swapped too. 13 | pub magic_number: u32, 14 | /// Version major number (currently 2) 15 | pub version_major: u16, 16 | /// Version minor number (currently 4) 17 | pub version_minor: u16, 18 | /// The correction time in seconds between GMT (UTC) and the local timezone of the following packet header timestamps 19 | pub thiszone: i32, 20 | /// In theory, the accuracy of time stamps in the capture; in practice, all tools set it to 0 21 | pub sigfigs: u32, 22 | /// max len of captured packets, in octets 23 | pub snaplen: u32, 24 | /// Data link type 25 | pub network: Linktype, 26 | } 27 | 28 | impl PcapHeader { 29 | pub fn new() -> PcapHeader { 30 | PcapHeader { 31 | magic_number: 0xa1b2_c3d4, // native order 32 | version_major: 2, 33 | version_minor: 4, 34 | thiszone: 0, 35 | sigfigs: 0, 36 | snaplen: 0, 37 | network: Linktype(1), // default: LINKTYPE_ETHERNET 38 | } 39 | } 40 | 41 | pub const fn size(&self) -> usize { 42 | 24 43 | } 44 | 45 | pub fn is_bigendian(&self) -> bool { 46 | (self.magic_number & 0xFFFF) == 0xb2a1 // works for both nanosecond and microsecond resolution timestamps 47 | } 48 | 49 | pub fn is_modified_format(&self) -> bool { 50 | self.magic_number == 0xa1b2_cd34 51 | } 52 | 53 | pub fn is_nanosecond_precision(&self) -> bool { 54 | self.magic_number == 0xa1b2_3c4d || self.magic_number == 0x4d3c_b2a1 55 | } 56 | } 57 | 58 | impl Default for PcapHeader { 59 | fn default() -> Self { 60 | PcapHeader::new() 61 | } 62 | } 63 | 64 | /// Read the PCAP global header 65 | /// 66 | /// The global header contains the PCAP description and options 67 | pub fn parse_pcap_header(i: &[u8]) -> IResult<&[u8], PcapHeader, PcapError<&[u8]>> { 68 | let (i, magic_number) = le_u32(i)?; 69 | match magic_number { 70 | 0xa1b2_c3d4 | 0xa1b2_3c4d | 0xa1b2_cd34 => { 71 | let (i, version_major) = le_u16(i)?; 72 | let (i, version_minor) = le_u16(i)?; 73 | let (i, thiszone) = le_i32(i)?; 74 | let (i, sigfigs) = le_u32(i)?; 75 | let (i, snaplen) = le_u32(i)?; 76 | let (i, network) = le_i32(i)?; 77 | let header = PcapHeader { 78 | magic_number, 79 | version_major, 80 | version_minor, 81 | thiszone, 82 | sigfigs, 83 | snaplen, 84 | network: Linktype(network), 85 | }; 86 | Ok((i, header)) 87 | } 88 | 0xd4c3_b2a1 | 0x4d3c_b2a1 => { 89 | let (i, version_major) = be_u16(i)?; 90 | let (i, version_minor) = be_u16(i)?; 91 | let (i, thiszone) = be_i32(i)?; 92 | let (i, sigfigs) = be_u32(i)?; 93 | let (i, snaplen) = be_u32(i)?; 94 | let (i, network) = be_i32(i)?; 95 | let header = PcapHeader { 96 | magic_number, 97 | version_major, 98 | version_minor, 99 | thiszone, 100 | sigfigs, 101 | snaplen, 102 | network: Linktype(network), 103 | }; 104 | Ok((i, header)) 105 | } 106 | _ => Err(nom::Err::Error(PcapError::HeaderNotRecognized)), 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/pcap/reader.rs: -------------------------------------------------------------------------------- 1 | use crate::blocks::PcapBlockOwned; 2 | use crate::error::PcapError; 3 | use crate::pcap::{ 4 | parse_pcap_frame, parse_pcap_frame_be, parse_pcap_frame_modified, parse_pcap_header, 5 | LegacyPcapBlock, PcapHeader, 6 | }; 7 | use crate::traits::PcapReaderIterator; 8 | use circular::Buffer; 9 | use nom::{IResult, Needed, Offset}; 10 | use std::io::Read; 11 | 12 | /// Parsing iterator over legacy pcap data (streaming version) 13 | /// 14 | /// ## Pcap Reader 15 | /// 16 | /// This reader is a streaming parser based on a circular buffer, which means memory 17 | /// usage is constant, and that it can be used to parse huge files or infinite streams. 18 | /// It creates an abstraction over any input providing the `Read` trait, and takes care 19 | /// of managing the circular buffer to provide an iterator-like interface. 20 | /// 21 | /// The first call to `next` will return the file header. Some information of this header must 22 | /// be stored (for ex. the data link type) to be able to parse following block contents. 23 | /// Following calls to `next` will always return legacy data blocks. 24 | /// 25 | /// The size of the circular buffer has to be big enough for at least one complete block. Using a 26 | /// larger value (at least 65k) is advised to avoid frequent reads and buffer shifts. 27 | /// 28 | /// **There are precautions to take when reading multiple blocks before consuming data. See 29 | /// [`PcapReaderIterator`] for details.** 30 | /// 31 | /// ## Example 32 | /// 33 | /// ```rust 34 | /// use pcap_parser::*; 35 | /// use pcap_parser::traits::PcapReaderIterator; 36 | /// use std::fs::File; 37 | /// 38 | /// # let path = "assets/ntp.pcap"; 39 | /// let file = File::open(path).unwrap(); 40 | /// let mut num_blocks = 0; 41 | /// let mut reader = LegacyPcapReader::new(65536, file).expect("LegacyPcapReader"); 42 | /// loop { 43 | /// match reader.next() { 44 | /// Ok((offset, block)) => { 45 | /// println!("got new block"); 46 | /// num_blocks += 1; 47 | /// match block { 48 | /// PcapBlockOwned::LegacyHeader(_hdr) => { 49 | /// // save hdr.network (linktype) 50 | /// }, 51 | /// PcapBlockOwned::Legacy(_b) => { 52 | /// // use linktype to parse b.data() 53 | /// }, 54 | /// PcapBlockOwned::NG(_) => unreachable!(), 55 | /// } 56 | /// reader.consume(offset); 57 | /// }, 58 | /// Err(PcapError::Eof) => break, 59 | /// Err(PcapError::Incomplete(_)) => { 60 | /// reader.refill().unwrap(); 61 | /// }, 62 | /// Err(e) => panic!("error while reading: {:?}", e), 63 | /// } 64 | /// } 65 | /// println!("num_blocks: {}", num_blocks); 66 | /// ``` 67 | pub struct LegacyPcapReader 68 | where 69 | R: Read, 70 | { 71 | header: PcapHeader, 72 | reader: R, 73 | buffer: Buffer, 74 | consumed: usize, 75 | header_sent: bool, 76 | reader_exhausted: bool, 77 | parse: LegacyParseFn, 78 | } 79 | 80 | type LegacyParseFn = fn(&[u8]) -> IResult<&[u8], LegacyPcapBlock, PcapError<&[u8]>>; 81 | 82 | impl LegacyPcapReader 83 | where 84 | R: Read, 85 | { 86 | /// Creates a new `LegacyPcapReader` with the provided buffer capacity. 87 | pub fn new( 88 | capacity: usize, 89 | reader: R, 90 | ) -> Result, PcapError<&'static [u8]>> { 91 | let buffer = Buffer::with_capacity(capacity); 92 | Self::from_buffer(buffer, reader) 93 | } 94 | /// Creates a new `LegacyPcapReader` using the provided `Buffer`. 95 | pub fn from_buffer( 96 | mut buffer: Buffer, 97 | mut reader: R, 98 | ) -> Result, PcapError<&'static [u8]>> { 99 | let sz = reader.read(buffer.space()).or(Err(PcapError::ReadError))?; 100 | buffer.fill(sz); 101 | let (_rem, header) = match parse_pcap_header(buffer.data()) { 102 | Ok((r, h)) => Ok((r, h)), 103 | Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(e.to_owned_vec()), 104 | Err(nom::Err::Incomplete(Needed::Size(n))) => Err(PcapError::Incomplete(n.into())), 105 | Err(nom::Err::Incomplete(Needed::Unknown)) => Err(PcapError::Incomplete(0)), 106 | }?; 107 | let parse = if !header.is_modified_format() { 108 | if header.is_bigendian() { 109 | parse_pcap_frame_be 110 | } else { 111 | parse_pcap_frame 112 | } 113 | } else { 114 | parse_pcap_frame_modified 115 | }; 116 | // do not consume 117 | Ok(LegacyPcapReader { 118 | header, 119 | reader, 120 | buffer, 121 | consumed: 0, 122 | header_sent: false, 123 | reader_exhausted: false, 124 | parse, 125 | }) 126 | } 127 | } 128 | 129 | impl PcapReaderIterator for LegacyPcapReader 130 | where 131 | R: Read, 132 | { 133 | fn next(&mut self) -> Result<(usize, PcapBlockOwned), PcapError<&'_ [u8]>> { 134 | if !self.header_sent { 135 | self.header_sent = true; 136 | return Ok(( 137 | self.header.size(), 138 | PcapBlockOwned::from(self.header.clone()), 139 | )); 140 | } 141 | // Return EOF if 142 | // 1) all bytes have been read 143 | // 2) no more data is available 144 | if self.buffer.available_data() == 0 145 | && (self.buffer.position() == 0 && self.reader_exhausted) 146 | { 147 | return Err(PcapError::Eof); 148 | } 149 | let data = self.buffer.data(); 150 | match (self.parse)(data) { 151 | Ok((rem, b)) => { 152 | let offset = data.offset(rem); 153 | Ok((offset, PcapBlockOwned::from(b))) 154 | } 155 | Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(e), 156 | Err(nom::Err::Incomplete(n)) => { 157 | if self.reader_exhausted { 158 | // expected more bytes but reader is EOF, truncated pcap? 159 | Err(PcapError::UnexpectedEof) 160 | } else { 161 | match n { 162 | Needed::Size(n) => { 163 | if self.buffer.available_data() + usize::from(n) 164 | >= self.buffer.capacity() 165 | { 166 | Err(PcapError::BufferTooSmall) 167 | } else { 168 | Err(PcapError::Incomplete(n.into())) 169 | } 170 | } 171 | Needed::Unknown => Err(PcapError::Incomplete(0)), 172 | } 173 | } 174 | } 175 | } 176 | } 177 | fn consume(&mut self, offset: usize) { 178 | self.consumed += offset; 179 | self.buffer.consume(offset); 180 | } 181 | fn consume_noshift(&mut self, offset: usize) { 182 | self.consumed += offset; 183 | self.buffer.consume_noshift(offset); 184 | } 185 | fn consumed(&self) -> usize { 186 | self.consumed 187 | } 188 | fn refill(&mut self) -> Result<(), PcapError<&[u8]>> { 189 | self.buffer.shift(); 190 | let space = self.buffer.space(); 191 | // check if available space is empty, so we can distinguish 192 | // a read() returning 0 because of EOF or because we requested 0 193 | if space.is_empty() { 194 | return Ok(()); 195 | } 196 | let sz = self.reader.read(space).or(Err(PcapError::ReadError))?; 197 | self.reader_exhausted = sz == 0; 198 | self.buffer.fill(sz); 199 | Ok(()) 200 | } 201 | fn position(&self) -> usize { 202 | self.buffer.position() 203 | } 204 | fn grow(&mut self, new_size: usize) -> bool { 205 | self.buffer.grow(new_size) 206 | } 207 | fn data(&self) -> &[u8] { 208 | self.buffer.data() 209 | } 210 | fn reader_exhausted(&self) -> bool { 211 | self.reader_exhausted 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/pcapng.rs: -------------------------------------------------------------------------------- 1 | //! PCAPNG file format 2 | //! 3 | //! See for details. 4 | //! 5 | //! There are several ways of parsing a PCAPNG file. The first method is to use 6 | //! [`parse_pcapng`]. This method requires to load the entire 7 | //! file to memory, and thus may not be good for large files. 8 | //! 9 | //! The second method is to create a [`PcapNGCapture`] object, 10 | //! which implements the [`Capture`](crate::Capture) trait to provide generic methods. 11 | //! However, this method also reads the entire file. 12 | //! 13 | //! The third (and prefered) method is to use a [`PcapNGReader`] 14 | //! object. 15 | //! 16 | //! The last method is to manually read the blocks using 17 | //! [`parse_sectionheaderblock`], 18 | //! [`parse_block_le`] and/or 19 | //! [`parse_block_be`]. 20 | //! 21 | //! ## File format and parsing 22 | //! 23 | //! A capture file is organized in blocks. Blocks are organized in sections, each section 24 | //! starting with a Section Header Block (SHB), and followed by blocks (interface description, 25 | //! statistics, packets, etc.). 26 | //! A file is usually composed of one section, but can contain multiple sections. When a SHB is 27 | //! encountered, this means a new section starts (and all information about previous section has to 28 | //! be flushed, like interfaces). 29 | //! 30 | //! ## Endianness 31 | //! 32 | //! The endianness of a block is indicated by the Section Header Block that started the section 33 | //! containing this block. Since a file can contain several sections, a single file can contain 34 | //! both endianness variants. 35 | 36 | // helpers and common modules 37 | mod block; 38 | mod capture; 39 | mod header; 40 | mod option; 41 | mod reader; 42 | mod section; 43 | mod time; 44 | 45 | pub use block::*; 46 | pub use capture::*; 47 | pub use header::*; 48 | pub use option::*; 49 | pub use reader::*; 50 | pub use section::*; 51 | pub use time::*; 52 | 53 | /// Blocks 54 | mod custom; 55 | mod decryption_secrets; 56 | mod enhanced_packet; 57 | mod interface_description; 58 | mod interface_statistics; 59 | mod name_resolution; 60 | mod process_information; 61 | mod section_header; 62 | mod simple_packet; 63 | mod systemd_journal_export; 64 | mod unknown; 65 | 66 | pub use custom::*; 67 | pub use decryption_secrets::*; 68 | pub use enhanced_packet::*; 69 | pub use interface_description::*; 70 | pub use interface_statistics::*; 71 | pub use name_resolution::*; 72 | pub use process_information::*; 73 | pub use section_header::*; 74 | pub use simple_packet::*; 75 | pub use systemd_journal_export::*; 76 | pub use unknown::*; 77 | 78 | /// Section Header Block magic 79 | pub const SHB_MAGIC: u32 = 0x0A0D_0D0A; 80 | /// Interface Description Block magic 81 | pub const IDB_MAGIC: u32 = 0x0000_0001; 82 | /// Simple Packet Block magic 83 | pub const SPB_MAGIC: u32 = 0x0000_0003; 84 | /// Name Resolution Block magic 85 | pub const NRB_MAGIC: u32 = 0x0000_0004; 86 | /// Interface Statistic Block magic 87 | pub const ISB_MAGIC: u32 = 0x0000_0005; 88 | /// Enhanced Packet Block magic 89 | pub const EPB_MAGIC: u32 = 0x0000_0006; 90 | 91 | /// Systemd Journal Export Block magic 92 | pub const SJE_MAGIC: u32 = 0x0000_0009; 93 | 94 | /// Decryption Secrets Block magic 95 | pub const DSB_MAGIC: u32 = 0x0000_000A; 96 | 97 | /// Custom Block magic 98 | pub const CB_MAGIC: u32 = 0x0000_0BAD; 99 | 100 | /// Do-not-copy Custom Block magic 101 | pub const DCB_MAGIC: u32 = 0x4000_0BAD; 102 | 103 | /// Byte Order magic 104 | pub const BOM_MAGIC: u32 = 0x1A2B_3C4D; 105 | 106 | /// Process Information Block magic 107 | /// (Apple addition, non standardized) 108 | pub const PIB_MAGIC: u32 = 0x8000_0001; 109 | -------------------------------------------------------------------------------- /src/pcapng/block.rs: -------------------------------------------------------------------------------- 1 | use nom::bytes::streaming::take; 2 | use nom::combinator::map; 3 | use nom::error::*; 4 | use nom::number::streaming::{be_u32, le_u32}; 5 | use nom::{Err, IResult}; 6 | 7 | use crate::endianness::PcapEndianness; 8 | use crate::PcapError; 9 | 10 | use super::*; 11 | 12 | /// A block from a PcapNG file 13 | #[derive(Debug)] 14 | pub enum Block<'a> { 15 | SectionHeader(SectionHeaderBlock<'a>), 16 | InterfaceDescription(InterfaceDescriptionBlock<'a>), 17 | EnhancedPacket(EnhancedPacketBlock<'a>), 18 | SimplePacket(SimplePacketBlock<'a>), 19 | NameResolution(NameResolutionBlock<'a>), 20 | InterfaceStatistics(InterfaceStatisticsBlock<'a>), 21 | SystemdJournalExport(SystemdJournalExportBlock<'a>), 22 | DecryptionSecrets(DecryptionSecretsBlock<'a>), 23 | ProcessInformation(ProcessInformationBlock<'a>), 24 | Custom(CustomBlock<'a>), 25 | Unknown(UnknownBlock<'a>), 26 | } 27 | 28 | impl Block<'_> { 29 | /// Returns true if blocks contains a network packet 30 | pub fn is_data_block(&self) -> bool { 31 | matches!(self, &Block::EnhancedPacket(_) | &Block::SimplePacket(_)) 32 | } 33 | 34 | /// Return the normalized magic number of the block 35 | pub fn magic(&self) -> u32 { 36 | match self { 37 | Block::SectionHeader(_) => SHB_MAGIC, 38 | Block::InterfaceDescription(_) => IDB_MAGIC, 39 | Block::EnhancedPacket(_) => EPB_MAGIC, 40 | Block::SimplePacket(_) => SPB_MAGIC, 41 | Block::NameResolution(_) => NRB_MAGIC, 42 | Block::InterfaceStatistics(_) => ISB_MAGIC, 43 | Block::SystemdJournalExport(_) => SJE_MAGIC, 44 | Block::DecryptionSecrets(_) => DSB_MAGIC, 45 | Block::ProcessInformation(_) => PIB_MAGIC, 46 | Block::Custom(cb) => cb.block_type, 47 | Block::Unknown(ub) => ub.block_type, 48 | } 49 | } 50 | } 51 | 52 | /// Parse any block, as little-endian 53 | /// 54 | /// To find which endianess to use, read the section header 55 | /// using `parse_sectionheaderblock` 56 | pub fn parse_block_le(i: &[u8]) -> IResult<&[u8], Block, PcapError<&[u8]>> { 57 | match le_u32(i) { 58 | Ok((_, id)) => match id { 59 | SHB_MAGIC => map(parse_sectionheaderblock, Block::SectionHeader)(i), 60 | IDB_MAGIC => map( 61 | parse_interfacedescriptionblock_le, 62 | Block::InterfaceDescription, 63 | )(i), 64 | SPB_MAGIC => map(parse_simplepacketblock_le, Block::SimplePacket)(i), 65 | EPB_MAGIC => map(parse_enhancedpacketblock_le, Block::EnhancedPacket)(i), 66 | NRB_MAGIC => map(parse_nameresolutionblock_le, Block::NameResolution)(i), 67 | ISB_MAGIC => map( 68 | parse_interfacestatisticsblock_le, 69 | Block::InterfaceStatistics, 70 | )(i), 71 | SJE_MAGIC => map( 72 | parse_systemdjournalexportblock_le, 73 | Block::SystemdJournalExport, 74 | )(i), 75 | DSB_MAGIC => map(parse_decryptionsecretsblock_le, Block::DecryptionSecrets)(i), 76 | CB_MAGIC => map(parse_customblock_le, Block::Custom)(i), 77 | DCB_MAGIC => map(parse_dcb_le, Block::Custom)(i), 78 | PIB_MAGIC => map(parse_processinformationblock_le, Block::ProcessInformation)(i), 79 | _ => map(parse_unknownblock_le, Block::Unknown)(i), 80 | }, 81 | Err(e) => Err(e), 82 | } 83 | } 84 | 85 | /// Parse any block, as big-endian 86 | /// 87 | /// To find which endianess to use, read the section header 88 | /// using `parse_sectionheaderblock` 89 | pub fn parse_block_be(i: &[u8]) -> IResult<&[u8], Block, PcapError<&[u8]>> { 90 | match be_u32(i) { 91 | Ok((_, id)) => match id { 92 | SHB_MAGIC => map(parse_sectionheaderblock, Block::SectionHeader)(i), 93 | IDB_MAGIC => map( 94 | parse_interfacedescriptionblock_be, 95 | Block::InterfaceDescription, 96 | )(i), 97 | SPB_MAGIC => map(parse_simplepacketblock_be, Block::SimplePacket)(i), 98 | EPB_MAGIC => map(parse_enhancedpacketblock_be, Block::EnhancedPacket)(i), 99 | NRB_MAGIC => map(parse_nameresolutionblock_be, Block::NameResolution)(i), 100 | ISB_MAGIC => map( 101 | parse_interfacestatisticsblock_be, 102 | Block::InterfaceStatistics, 103 | )(i), 104 | SJE_MAGIC => map( 105 | parse_systemdjournalexportblock_be, 106 | Block::SystemdJournalExport, 107 | )(i), 108 | DSB_MAGIC => map(parse_decryptionsecretsblock_be, Block::DecryptionSecrets)(i), 109 | CB_MAGIC => map(parse_customblock_be, Block::Custom)(i), 110 | DCB_MAGIC => map(parse_dcb_be, Block::Custom)(i), 111 | PIB_MAGIC => map(parse_processinformationblock_be, Block::ProcessInformation)(i), 112 | _ => map(parse_unknownblock_be, Block::Unknown)(i), 113 | }, 114 | Err(e) => Err(e), 115 | } 116 | } 117 | 118 | pub(crate) trait PcapNGBlockParser<'a, En: PcapEndianness, O: 'a> { 119 | /// Minimum header size, in bytes 120 | const HDR_SZ: usize; 121 | /// Little-endian magic number for this block type 122 | const MAGIC: u32; 123 | 124 | // caller function must have tested header type(magic) and length 125 | fn inner_parse>( 126 | block_type: u32, 127 | block_len1: u32, 128 | i: &'a [u8], 129 | block_len2: u32, 130 | ) -> IResult<&'a [u8], O, E>; 131 | } 132 | 133 | /// Create a block parser function, given the parameters (block object and endianness) 134 | pub(crate) fn ng_block_parser<'a, P, En, O, E>() -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], O, E> 135 | where 136 | P: PcapNGBlockParser<'a, En, O>, 137 | En: PcapEndianness, 138 | O: 'a, 139 | E: ParseError<&'a [u8]>, 140 | { 141 | move |i: &[u8]| { 142 | // read generic block layout 143 | // 144 | if i.len() < P::HDR_SZ { 145 | return Err(Err::Incomplete(nom::Needed::new(P::HDR_SZ - i.len()))); 146 | } 147 | let (i, block_type) = le_u32(i)?; 148 | let (i, block_len1) = En::parse_u32(i)?; 149 | if block_len1 < P::HDR_SZ as u32 { 150 | return Err(Err::Error(E::from_error_kind(i, ErrorKind::Verify))); 151 | } 152 | if P::MAGIC != 0 && En::native_u32(block_type) != P::MAGIC { 153 | return Err(Err::Error(E::from_error_kind(i, ErrorKind::Verify))); 154 | } 155 | // 12 is block_type (4) + block_len1 (4) + block_len2 (4) 156 | let (i, block_content) = take(block_len1 - 12)(i)?; 157 | let (i, block_len2) = En::parse_u32(i)?; 158 | // call block content parsing function 159 | let (_, b) = P::inner_parse(block_type, block_len1, block_content, block_len2)?; 160 | // return the remaining bytes from the container, not content 161 | Ok((i, b)) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/pcapng/capture.rs: -------------------------------------------------------------------------------- 1 | use crate::blocks::{PcapBlock, PcapBlockOwned}; 2 | use crate::error::PcapError; 3 | use crate::pcapng::*; 4 | use nom::combinator::{complete, map}; 5 | use nom::multi::many1; 6 | use nom::{IResult, Needed}; 7 | use std::fmt; 8 | 9 | #[derive(Default)] 10 | pub(crate) struct CurrentSectionInfo { 11 | pub(crate) big_endian: bool, 12 | } 13 | 14 | /// Parsing iterator over pcap-ng data (requires data to be loaded into memory) 15 | /// 16 | /// ```rust 17 | /// use pcap_parser::*; 18 | /// use std::fs::File; 19 | /// use std::io::Read; 20 | /// 21 | /// # let path = "assets/test001-le.pcapng"; 22 | /// let mut file = File::open(path).unwrap(); 23 | /// let mut buffer = Vec::new(); 24 | /// file.read_to_end(&mut buffer).unwrap(); 25 | /// let mut num_blocks = 0; 26 | /// let capture = PcapNGSlice::from_slice(&buffer).expect("parse file"); 27 | /// for _block in capture { 28 | /// num_blocks += 1; 29 | /// } 30 | pub struct PcapNGSlice<'a> { 31 | info: CurrentSectionInfo, 32 | // remaining (unparsed) data 33 | rem: &'a [u8], 34 | } 35 | 36 | impl PcapNGSlice<'_> { 37 | pub fn from_slice(i: &[u8]) -> Result>> { 38 | // just check that first block is a valid one 39 | let (_rem, _shb) = parse_sectionheaderblock(i)?; 40 | let info = CurrentSectionInfo::default(); 41 | let rem = i; 42 | Ok(PcapNGSlice { info, rem }) 43 | } 44 | } 45 | 46 | /// Iterator for PcapNGSlice. Returns a result so parsing errors are not 47 | /// silently ignored 48 | impl<'a> Iterator for PcapNGSlice<'a> { 49 | type Item = Result, nom::Err>>; 50 | 51 | fn next(&mut self) -> Option { 52 | if self.rem.is_empty() { 53 | return None; 54 | } 55 | let parse = if self.info.big_endian { 56 | parse_block_be 57 | } else { 58 | parse_block_le 59 | }; 60 | let r = parse(self.rem).map(|(rem, b)| { 61 | self.rem = rem; 62 | if let Block::SectionHeader(ref shb) = b { 63 | self.info.big_endian = shb.big_endian(); 64 | } 65 | PcapBlockOwned::from(b) 66 | }); 67 | Some(r) 68 | } 69 | } 70 | 71 | /// Generic interface for PCAPNG file access 72 | pub struct PcapNGCapture<'a> { 73 | pub sections: Vec>, 74 | } 75 | 76 | impl fmt::Debug for PcapNGCapture<'_> { 77 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 78 | writeln!(f, "PcapNGCapture:") 79 | } 80 | } 81 | 82 | /// Iterator over `PcapNGCapture` 83 | pub struct PcapNGCaptureIterator<'a> { 84 | cap: &'a PcapNGCapture<'a>, 85 | idx: usize, 86 | } 87 | 88 | impl<'a> Iterator for PcapNGCaptureIterator<'a> { 89 | type Item = PcapBlock<'a>; 90 | 91 | fn next(&mut self) -> Option> { 92 | if self.cap.sections.len() != 1 { 93 | // XXX only one section supported 94 | unimplemented!(); 95 | } 96 | self.cap.sections[0].blocks.get(self.idx).map(|b| { 97 | self.idx += 1; 98 | PcapBlock::from(b) 99 | }) 100 | } 101 | } 102 | 103 | impl<'a> PcapNGCapture<'a> { 104 | pub fn from_file(i: &[u8]) -> Result> { 105 | // XXX change return type to just an IResult 106 | match parse_pcapng(i) { 107 | Ok((_, pcap)) => Ok(pcap), 108 | Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(e), 109 | Err(nom::Err::Incomplete(Needed::Size(n))) => Err(PcapError::Incomplete(n.into())), 110 | Err(nom::Err::Incomplete(Needed::Unknown)) => Err(PcapError::Incomplete(0)), 111 | } 112 | } 113 | 114 | pub fn iter(&'a self) -> PcapNGCaptureIterator<'a> { 115 | PcapNGCaptureIterator { cap: self, idx: 0 } 116 | } 117 | } 118 | 119 | /// Parse the entire file 120 | /// 121 | /// Note: this requires the file to be fully loaded to memory. 122 | pub fn parse_pcapng(i: &[u8]) -> IResult<&[u8], PcapNGCapture, PcapError<&[u8]>> { 123 | map(many1(complete(parse_section)), |sections| PcapNGCapture { 124 | sections, 125 | })(i) 126 | } 127 | -------------------------------------------------------------------------------- /src/pcapng/custom.rs: -------------------------------------------------------------------------------- 1 | use nom::error::{ErrorKind, ParseError}; 2 | use nom::{Err, IResult}; 3 | 4 | use crate::endianness::{PcapBE, PcapEndianness, PcapLE}; 5 | use crate::{PcapError, CB_MAGIC, DCB_MAGIC}; 6 | 7 | use super::*; 8 | 9 | #[derive(Debug)] 10 | pub struct CustomBlock<'a> { 11 | pub block_type: u32, 12 | pub block_len1: u32, 13 | // Private Enterprise Number (PEN) 14 | pub pen: u32, 15 | pub data: &'a [u8], 16 | // pub options: &'a [u8], 17 | pub block_len2: u32, 18 | } 19 | 20 | impl<'a, En: PcapEndianness> PcapNGBlockParser<'a, En, CustomBlock<'a>> for CustomBlock<'a> { 21 | const HDR_SZ: usize = 16; 22 | const MAGIC: u32 = CB_MAGIC; 23 | 24 | fn inner_parse>( 25 | block_type: u32, 26 | block_len1: u32, 27 | i: &'a [u8], 28 | block_len2: u32, 29 | ) -> IResult<&'a [u8], CustomBlock<'a>, E> { 30 | // caller function already tested header type(magic) and length 31 | // read end of header 32 | let (i, pen) = En::parse_u32(i)?; 33 | // there is no way to differentiate custom data and options, 34 | // since length of data is not provided 35 | let data = i; 36 | if block_len2 != block_len1 { 37 | return Err(Err::Error(E::from_error_kind(i, ErrorKind::Verify))); 38 | } 39 | let block = CustomBlock { 40 | block_type, 41 | block_len1, 42 | pen, 43 | data, 44 | block_len2, 45 | }; 46 | Ok((i, block)) 47 | } 48 | } 49 | 50 | struct DCBParser; 51 | impl<'a, En: PcapEndianness> PcapNGBlockParser<'a, En, CustomBlock<'a>> for DCBParser { 52 | const HDR_SZ: usize = 16; 53 | const MAGIC: u32 = DCB_MAGIC; 54 | 55 | fn inner_parse>( 56 | block_type: u32, 57 | block_len1: u32, 58 | i: &'a [u8], 59 | block_len2: u32, 60 | ) -> IResult<&'a [u8], CustomBlock<'a>, E> { 61 | >>::inner_parse::( 62 | block_type, block_len1, i, block_len2, 63 | ) 64 | } 65 | } 66 | 67 | impl CustomBlock<'_> { 68 | pub fn do_not_copy(&self) -> bool { 69 | self.block_type == DCB_MAGIC || self.block_type == DCB_MAGIC.swap_bytes() 70 | } 71 | } 72 | 73 | /// Parse a Custom Block (little-endian) 74 | #[inline] 75 | pub fn parse_customblock_le(i: &[u8]) -> IResult<&[u8], CustomBlock, PcapError<&[u8]>> { 76 | ng_block_parser::()(i) 77 | } 78 | 79 | /// Parse a Custom Block (big-endian) 80 | #[inline] 81 | pub fn parse_customblock_be(i: &[u8]) -> IResult<&[u8], CustomBlock, PcapError<&[u8]>> { 82 | ng_block_parser::()(i) 83 | } 84 | 85 | /// Parse a Do-not-copy Custom Block (little-endian) 86 | #[inline] 87 | pub fn parse_dcb_le(i: &[u8]) -> IResult<&[u8], CustomBlock, PcapError<&[u8]>> { 88 | ng_block_parser::()(i) 89 | } 90 | 91 | /// Parse a Do-not-copy Custom Block (big-endian) 92 | #[inline] 93 | pub fn parse_dcb_be(i: &[u8]) -> IResult<&[u8], CustomBlock, PcapError<&[u8]>> { 94 | ng_block_parser::()(i) 95 | } 96 | -------------------------------------------------------------------------------- /src/pcapng/decryption_secrets.rs: -------------------------------------------------------------------------------- 1 | use nom::bytes::streaming::take; 2 | use nom::error::{ErrorKind, ParseError}; 3 | use nom::{Err, IResult}; 4 | use rusticata_macros::{align32, newtype_enum}; 5 | 6 | use crate::endianness::{PcapBE, PcapEndianness, PcapLE}; 7 | use crate::{opt_parse_options, PcapError, PcapNGOption, DSB_MAGIC}; 8 | 9 | use super::*; 10 | 11 | #[derive(Clone, Copy, Eq, PartialEq)] 12 | pub struct SecretsType(pub u32); 13 | 14 | newtype_enum! { 15 | impl debug SecretsType { 16 | TlsKeyLog = 0x544c_534b, // TLSK 17 | WireguardKeyLog = 0x5747_4b4c, 18 | } 19 | } 20 | 21 | #[derive(Debug)] 22 | pub struct DecryptionSecretsBlock<'a> { 23 | pub block_type: u32, 24 | pub block_len1: u32, 25 | pub secrets_type: SecretsType, 26 | pub secrets_len: u32, 27 | pub data: &'a [u8], 28 | pub options: Vec>, 29 | pub block_len2: u32, 30 | } 31 | 32 | impl<'a, En: PcapEndianness> PcapNGBlockParser<'a, En, DecryptionSecretsBlock<'a>> 33 | for DecryptionSecretsBlock<'a> 34 | { 35 | const HDR_SZ: usize = 20; 36 | const MAGIC: u32 = DSB_MAGIC; 37 | 38 | fn inner_parse>( 39 | block_type: u32, 40 | block_len1: u32, 41 | i: &'a [u8], 42 | block_len2: u32, 43 | ) -> IResult<&'a [u8], DecryptionSecretsBlock<'a>, E> { 44 | // caller function already tested header type(magic) and length 45 | // read end of header 46 | let (i, secrets_type) = En::parse_u32(i)?; 47 | let (i, secrets_len) = En::parse_u32(i)?; 48 | // read packet data 49 | // align32 can overflow 50 | if secrets_len >= u32::MAX - 4 { 51 | return Err(Err::Error(E::from_error_kind(i, ErrorKind::Verify))); 52 | } 53 | let padded_length = align32!(secrets_len); 54 | let (i, data) = take(padded_length)(i)?; 55 | // read options 56 | let current_offset = (20 + padded_length) as usize; 57 | let (i, options) = opt_parse_options::(i, block_len1 as usize, current_offset)?; 58 | if block_len2 != block_len1 { 59 | return Err(Err::Error(E::from_error_kind(i, ErrorKind::Verify))); 60 | } 61 | let block = DecryptionSecretsBlock { 62 | block_type, 63 | block_len1, 64 | secrets_type: SecretsType(secrets_type), 65 | secrets_len, 66 | data, 67 | options, 68 | block_len2, 69 | }; 70 | Ok((i, block)) 71 | } 72 | } 73 | 74 | /// Parse a DecryptionSecrets Block (little-endian) 75 | #[inline] 76 | pub fn parse_decryptionsecretsblock_le( 77 | i: &[u8], 78 | ) -> IResult<&[u8], DecryptionSecretsBlock, PcapError<&[u8]>> { 79 | ng_block_parser::()(i) 80 | } 81 | 82 | /// Parse a DecryptionSecrets Block (big-endian) 83 | #[inline] 84 | pub fn parse_decryptionsecretsblock_be( 85 | i: &[u8], 86 | ) -> IResult<&[u8], DecryptionSecretsBlock, PcapError<&[u8]>> { 87 | ng_block_parser::()(i) 88 | } 89 | -------------------------------------------------------------------------------- /src/pcapng/enhanced_packet.rs: -------------------------------------------------------------------------------- 1 | use nom::bytes::streaming::take; 2 | use nom::error::{ErrorKind, ParseError}; 3 | use nom::{Err, IResult}; 4 | use rusticata_macros::align32; 5 | 6 | use crate::endianness::{PcapBE, PcapEndianness, PcapLE}; 7 | use crate::traits::PcapNGPacketBlock; 8 | use crate::utils::array_ref4; 9 | use crate::{build_ts, build_ts_f64, opt_parse_options, PcapError, PcapNGOption, EPB_MAGIC}; 10 | 11 | use super::*; 12 | 13 | /// An Enhanced Packet Block (EPB) is the standard container for storing 14 | /// the packets coming from the network. 15 | /// 16 | /// This struct is a thin abstraction layer, and stores the raw block data. 17 | /// For ex the `data` field is stored with the padding. 18 | /// It implements the `PcapNGPacketBlock` trait, which provides helper functions. 19 | /// 20 | /// ## Examples 21 | /// 22 | /// ```rust 23 | /// use pcap_parser::pcapng::parse_enhancedpacketblock_le; 24 | /// use pcap_parser::traits::PcapNGPacketBlock; 25 | /// 26 | /// # let input_data = include_bytes!("../../assets/test001-le.pcapng"); 27 | /// # let pcap_data = &input_data[148..=495]; 28 | /// let (i, epb) = parse_enhancedpacketblock_le(pcap_data).unwrap(); 29 | /// let packet_data = epb.packet_data(); 30 | /// if packet_data.len() < epb.orig_len() as usize { 31 | /// // packet was truncated 32 | /// } else { 33 | /// // we have a full packet 34 | /// } 35 | /// ``` 36 | #[derive(Debug)] 37 | pub struct EnhancedPacketBlock<'a> { 38 | // Block type, read as little-endian. 39 | // If block value is the reverse the the expected magic, this means block is encoded as big-endian 40 | pub block_type: u32, 41 | pub block_len1: u32, 42 | pub if_id: u32, 43 | pub ts_high: u32, 44 | pub ts_low: u32, 45 | /// Captured packet length 46 | pub caplen: u32, 47 | /// Original packet length 48 | pub origlen: u32, 49 | /// Raw data from packet (with padding) 50 | pub data: &'a [u8], 51 | pub options: Vec>, 52 | pub block_len2: u32, 53 | } 54 | 55 | impl EnhancedPacketBlock<'_> { 56 | /// Decode the packet timestamp 57 | /// 58 | /// To decode the timestamp, the raw values if_tsresol and if_tsoffset are required. 59 | /// These values are stored as options in the [`InterfaceDescriptionBlock`] 60 | /// matching the interface ID. 61 | /// 62 | /// Return the timestamp seconds and fractional part (in resolution units) 63 | #[inline] 64 | pub fn decode_ts(&self, ts_offset: u64, resolution: u64) -> (u32, u32) { 65 | build_ts(self.ts_high, self.ts_low, ts_offset, resolution) 66 | } 67 | 68 | /// Decode the packet timestamp as `f64` 69 | /// 70 | /// To decode the timestamp, the resolution and offset are required. 71 | /// These values are stored as options in the [`InterfaceDescriptionBlock`] 72 | /// matching the interface ID. 73 | #[inline] 74 | pub fn decode_ts_f64(&self, ts_offset: u64, resolution: u64) -> f64 { 75 | build_ts_f64(self.ts_high, self.ts_low, ts_offset, resolution) 76 | } 77 | } 78 | 79 | impl PcapNGPacketBlock for EnhancedPacketBlock<'_> { 80 | fn big_endian(&self) -> bool { 81 | self.block_type != EPB_MAGIC 82 | } 83 | fn truncated(&self) -> bool { 84 | self.origlen != self.caplen 85 | } 86 | fn orig_len(&self) -> u32 { 87 | self.origlen 88 | } 89 | fn raw_packet_data(&self) -> &[u8] { 90 | self.data 91 | } 92 | fn packet_data(&self) -> &[u8] { 93 | let caplen = self.caplen as usize; 94 | if caplen < self.data.len() { 95 | &self.data[..caplen] 96 | } else { 97 | self.data 98 | } 99 | } 100 | } 101 | 102 | impl<'a, En: PcapEndianness> PcapNGBlockParser<'a, En, EnhancedPacketBlock<'a>> 103 | for EnhancedPacketBlock<'a> 104 | { 105 | const HDR_SZ: usize = 32; 106 | const MAGIC: u32 = EPB_MAGIC; 107 | 108 | fn inner_parse>( 109 | block_type: u32, 110 | block_len1: u32, 111 | i: &'a [u8], 112 | block_len2: u32, 113 | ) -> IResult<&'a [u8], EnhancedPacketBlock<'a>, E> { 114 | // caller function already tested header type(magic) and length 115 | // read end of header 116 | let (b_hdr, packet_data) = i.split_at(20); 117 | let if_id = En::u32_from_bytes(*array_ref4(b_hdr, 0)); 118 | let ts_high = En::u32_from_bytes(*array_ref4(b_hdr, 4)); 119 | let ts_low = En::u32_from_bytes(*array_ref4(b_hdr, 8)); 120 | let caplen = En::u32_from_bytes(*array_ref4(b_hdr, 12)); 121 | let origlen = En::u32_from_bytes(*array_ref4(b_hdr, 16)); 122 | // read packet data 123 | // align32 can overflow 124 | if caplen >= u32::MAX - 4 { 125 | return Err(Err::Error(E::from_error_kind(i, ErrorKind::Verify))); 126 | } 127 | let padded_length = align32!(caplen); 128 | let (i, data) = take(padded_length)(packet_data)?; 129 | // read options 130 | let current_offset = (32 + padded_length) as usize; 131 | let (i, options) = opt_parse_options::(i, block_len1 as usize, current_offset)?; 132 | if block_len2 != block_len1 { 133 | return Err(Err::Error(E::from_error_kind(i, ErrorKind::Verify))); 134 | } 135 | let block = EnhancedPacketBlock { 136 | block_type, 137 | block_len1, 138 | if_id, 139 | ts_high, 140 | ts_low, 141 | caplen, 142 | origlen, 143 | data, 144 | options, 145 | block_len2, 146 | }; 147 | Ok((i, block)) 148 | } 149 | } 150 | 151 | /// Parse an Enhanced Packet Block (little-endian) 152 | pub fn parse_enhancedpacketblock_le( 153 | i: &[u8], 154 | ) -> IResult<&[u8], EnhancedPacketBlock, PcapError<&[u8]>> { 155 | ng_block_parser::()(i) 156 | } 157 | 158 | /// Parse an Enhanced Packet Block (big-endian) 159 | pub fn parse_enhancedpacketblock_be( 160 | i: &[u8], 161 | ) -> IResult<&[u8], EnhancedPacketBlock, PcapError<&[u8]>> { 162 | ng_block_parser::()(i) 163 | } 164 | -------------------------------------------------------------------------------- /src/pcapng/header.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct PcapNGHeader { 3 | pub magic_number: u32, 4 | pub version_major: u16, 5 | pub version_minor: u16, 6 | pub thiszone: i32, 7 | pub sigfigs: u32, 8 | /// max len of captured packets, in octets 9 | pub snaplen: u32, 10 | /// Data link type 11 | pub network: u32, 12 | } 13 | -------------------------------------------------------------------------------- /src/pcapng/interface_description.rs: -------------------------------------------------------------------------------- 1 | use std::net::{Ipv4Addr, Ipv6Addr}; 2 | 3 | use nom::error::{ErrorKind, ParseError}; 4 | use nom::{Err, IResult}; 5 | 6 | use crate::endianness::{PcapBE, PcapEndianness, PcapLE}; 7 | use crate::{opt_parse_options, Linktype, PcapError, PcapNGOption, IDB_MAGIC}; 8 | 9 | use super::*; 10 | 11 | /// An Interface Description Block (IDB) is the container for information 12 | /// describing an interface on which packet data is captured. 13 | #[derive(Debug)] 14 | pub struct InterfaceDescriptionBlock<'a> { 15 | pub block_type: u32, 16 | pub block_len1: u32, 17 | pub linktype: Linktype, 18 | pub reserved: u16, 19 | pub snaplen: u32, 20 | pub options: Vec>, 21 | pub block_len2: u32, 22 | pub if_tsresol: u8, 23 | pub if_tsoffset: i64, 24 | } 25 | 26 | impl InterfaceDescriptionBlock<'_> { 27 | /// Decode the interface time resolution, in units per second 28 | /// 29 | /// Return the resolution, or `None` if the resolution is invalid (for ex. greater than `2^64`) 30 | #[inline] 31 | pub fn ts_resolution(&self) -> Option { 32 | build_ts_resolution(self.if_tsresol) 33 | } 34 | 35 | /// Return the interface timestamp offset 36 | #[inline] 37 | pub fn ts_offset(&self) -> i64 { 38 | self.if_tsoffset 39 | } 40 | 41 | /// Return the `if_name` option value, if present 42 | /// 43 | /// If the option is present multiple times, the first value is returned. 44 | /// 45 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 46 | /// or `Some(Err(_))` if value is present but invalid 47 | pub fn if_name(&self) -> Option> { 48 | options_get_as_str(&self.options, OptionCode::IfName) 49 | } 50 | 51 | /// Return the `if_description` option value, if present 52 | /// 53 | /// If the option is present multiple times, the first value is returned. 54 | /// 55 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 56 | /// or `Some(Err(_))` if value is present but invalid 57 | pub fn if_description(&self) -> Option> { 58 | options_get_as_str(&self.options, OptionCode::IfDescription) 59 | } 60 | 61 | /// Return the `if_os` option value, if present 62 | /// 63 | /// If the option is present multiple times, the first value is returned. 64 | /// 65 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 66 | /// or `Some(Err(_))` if value is present but invalid 67 | pub fn if_os(&self) -> Option> { 68 | options_get_as_str(&self.options, OptionCode::IfOs) 69 | } 70 | 71 | /// Return the `if_ipv4addr` option values, if present 72 | /// 73 | /// This option can be multi-valued. 74 | /// 75 | /// Returns `None` if option is not present, `Some(Ok(Vec))` if the value is present and valid, 76 | /// or `Some(Err(_))` if value is present but invalid 77 | /// 78 | /// Each item of the `Vec` is a pair `(IPv4Addr, IPv4Mask)` 79 | pub fn if_ipv4addr(&self) -> Option, PcapNGOptionError>> { 80 | let res = self.options.iter().try_fold(Vec::new(), |mut acc, opt| { 81 | if opt.code == OptionCode::IfIpv4Addr { 82 | let b = opt.as_bytes()?; 83 | if b.len() != 8 { 84 | return Err(PcapNGOptionError::InvalidLength); 85 | } 86 | let addr = Ipv4Addr::new(b[0], b[1], b[2], b[3]); 87 | let mask = Ipv4Addr::new(b[4], b[5], b[6], b[7]); 88 | acc.push((addr, mask)); 89 | Ok(acc) 90 | } else { 91 | Ok(acc) 92 | } 93 | }); 94 | if res.as_ref().map_or(false, |v| v.is_empty()) { 95 | None 96 | } else { 97 | Some(res) 98 | } 99 | } 100 | 101 | /// Return the `if_ipv6addr` option values, if present 102 | /// 103 | /// This option can be multi-valued. 104 | /// 105 | /// Returns `None` if option is not present, `Some(Ok(Vec))` if the value is present and valid, 106 | /// or `Some(Err(_))` if value is present but invalid 107 | /// 108 | /// Each item of the `Vec` is a pair `(IPv6Addr, PrefixLen)` 109 | pub fn if_ipv6addr(&self) -> Option, PcapNGOptionError>> { 110 | let res = self.options.iter().try_fold(Vec::new(), |mut acc, opt| { 111 | if opt.code == OptionCode::IfIpv4Addr { 112 | let b = opt.as_bytes()?; 113 | if b.len() != 17 { 114 | return Err(PcapNGOptionError::InvalidLength); 115 | } 116 | let mut array_u16 = [0u16; 8]; 117 | for i in 0..8 { 118 | array_u16[i] = ((b[2 * i] as u16) << 8) + b[2 * i + 1] as u16; 119 | } 120 | let addr = Ipv6Addr::new( 121 | array_u16[0], 122 | array_u16[1], 123 | array_u16[2], 124 | array_u16[3], 125 | array_u16[4], 126 | array_u16[5], 127 | array_u16[6], 128 | array_u16[7], 129 | ); 130 | let mask = b[16]; 131 | acc.push((addr, mask)); 132 | Ok(acc) 133 | } else { 134 | Ok(acc) 135 | } 136 | }); 137 | if res.as_ref().map_or(false, |v| v.is_empty()) { 138 | None 139 | } else { 140 | Some(res) 141 | } 142 | } 143 | 144 | /// Return the `if_macaddr` option value, if present 145 | /// 146 | /// If the option is present multiple times, the first value is returned. 147 | /// 148 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 149 | /// or `Some(Err(_))` if value is present but invalid 150 | pub fn if_macaddr(&self) -> Option> { 151 | options_get_as_bytes(&self.options, OptionCode::IfMacAddr) 152 | } 153 | 154 | /// Return the `if_euiaddr` option value, if present 155 | /// 156 | /// If the option is present multiple times, the first value is returned. 157 | /// 158 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 159 | /// or `Some(Err(_))` if value is present but invalid 160 | pub fn if_euiaddr(&self) -> Option> { 161 | options_get_as_bytes(&self.options, OptionCode::IfEuiAddr) 162 | } 163 | 164 | /// Return the `if_speed` option value, if present 165 | /// 166 | /// If the option is present multiple times, the first value is returned. 167 | /// 168 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 169 | /// or `Some(Err(_))` if value is present but invalid 170 | pub fn if_speed(&self) -> Option> { 171 | options_get_as_u64_le(&self.options, OptionCode::IfSpeed) 172 | } 173 | 174 | /// Return the `if_tsresol` option value, if present 175 | /// 176 | /// If the option is present multiple times, the first value is returned. 177 | /// 178 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 179 | /// or `Some(Err(_))` if value is present but invalid 180 | pub fn if_tsresol(&self) -> Option> { 181 | options_get_as_u8(&self.options, OptionCode::IfTsresol) 182 | } 183 | 184 | /// Return the `if_filter` option value, if present 185 | /// 186 | /// If the option is present multiple times, the first value is returned. 187 | /// 188 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 189 | /// or `Some(Err(_))` if value is present but invalid 190 | pub fn if_filter(&self) -> Option> { 191 | options_get_as_str(&self.options, OptionCode::IfFilter) 192 | } 193 | 194 | /// Return the `if_tsoffset` option value, if present 195 | /// 196 | /// If the option is present multiple times, the first value is returned. 197 | /// 198 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 199 | /// or `Some(Err(_))` if value is present but invalid 200 | pub fn if_tsoffset(&self) -> Option> { 201 | options_get_as_i64_le(&self.options, OptionCode::IfTsoffset) 202 | } 203 | } 204 | 205 | impl<'a, En: PcapEndianness> PcapNGBlockParser<'a, En, InterfaceDescriptionBlock<'a>> 206 | for InterfaceDescriptionBlock<'a> 207 | { 208 | const HDR_SZ: usize = 20; 209 | const MAGIC: u32 = IDB_MAGIC; 210 | 211 | fn inner_parse>( 212 | block_type: u32, 213 | block_len1: u32, 214 | i: &'a [u8], 215 | block_len2: u32, 216 | ) -> IResult<&'a [u8], InterfaceDescriptionBlock<'a>, E> { 217 | // caller function already tested header type(magic) and length 218 | // read end of header 219 | let (i, linktype) = En::parse_u16(i)?; 220 | let (i, reserved) = En::parse_u16(i)?; 221 | let (i, snaplen) = En::parse_u32(i)?; 222 | // read options 223 | let (i, options) = opt_parse_options::(i, block_len1 as usize, 20)?; 224 | if block_len2 != block_len1 { 225 | return Err(Err::Error(E::from_error_kind(i, ErrorKind::Verify))); 226 | } 227 | let (if_tsresol, if_tsoffset) = if_extract_tsoffset_and_tsresol(&options); 228 | let block = InterfaceDescriptionBlock { 229 | block_type, 230 | block_len1, 231 | linktype: Linktype(linktype as i32), 232 | reserved, 233 | snaplen, 234 | options, 235 | block_len2, 236 | if_tsresol, 237 | if_tsoffset, 238 | }; 239 | Ok((i, block)) 240 | } 241 | } 242 | 243 | /// Parse an Interface Packet Block (little-endian) 244 | pub fn parse_interfacedescriptionblock_le( 245 | i: &[u8], 246 | ) -> IResult<&[u8], InterfaceDescriptionBlock, PcapError<&[u8]>> { 247 | ng_block_parser::()(i) 248 | } 249 | 250 | /// Parse an Interface Packet Block (big-endian) 251 | pub fn parse_interfacedescriptionblock_be( 252 | i: &[u8], 253 | ) -> IResult<&[u8], InterfaceDescriptionBlock, PcapError<&[u8]>> { 254 | ng_block_parser::()(i) 255 | } 256 | -------------------------------------------------------------------------------- /src/pcapng/interface_statistics.rs: -------------------------------------------------------------------------------- 1 | use nom::error::{ErrorKind, ParseError}; 2 | use nom::{Err, IResult}; 3 | 4 | use crate::endianness::{PcapBE, PcapEndianness, PcapLE}; 5 | use crate::{opt_parse_options, PcapError, PcapNGOption, ISB_MAGIC}; 6 | 7 | use super::*; 8 | 9 | #[derive(Debug)] 10 | pub struct InterfaceStatisticsBlock<'a> { 11 | pub block_type: u32, 12 | pub block_len1: u32, 13 | pub if_id: u32, 14 | pub ts_high: u32, 15 | pub ts_low: u32, 16 | pub options: Vec>, 17 | pub block_len2: u32, 18 | } 19 | 20 | impl InterfaceStatisticsBlock<'_> { 21 | /// Return the `isb_starttime` option value, if present 22 | /// 23 | /// The returned value is `(ts_high,ts_low)`. To convert to a full timestamp, 24 | /// use the [build_ts] function with the `ts_offset` and `resolution` values from 25 | /// the `InterfaceDescriptionBlock` matching `self.if_id`. 26 | /// 27 | /// If the option is present multiple times, the first value is returned. 28 | /// 29 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 30 | /// or `Some(Err(_))` if value is present but invalid 31 | pub fn isb_starttime(&self) -> Option> { 32 | options_get_as_ts(&self.options, OptionCode::IsbStartTime) 33 | } 34 | 35 | /// Return the `isb_endtime` option value, if present 36 | /// 37 | /// The returned value is `(ts_high,ts_low)`. To convert to a full timestamp, 38 | /// use the [build_ts] function with the `ts_offset` and `resolution` values from 39 | /// the `InterfaceDescriptionBlock` matching `self.if_id`. 40 | /// 41 | /// If the option is present multiple times, the first value is returned. 42 | /// 43 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 44 | /// or `Some(Err(_))` if value is present but invalid 45 | pub fn isb_endtime(&self) -> Option> { 46 | options_get_as_ts(&self.options, OptionCode::IsbEndTime) 47 | } 48 | 49 | /// Return the `isb_ifrecv` option value, if present 50 | /// 51 | /// If the option is present multiple times, the first value is returned. 52 | /// 53 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 54 | /// or `Some(Err(_))` if value is present but invalid 55 | pub fn isb_ifrecv(&self) -> Option> { 56 | options_get_as_u64_le(&self.options, OptionCode::IsbIfRecv) 57 | } 58 | 59 | /// Return the `isb_ifdrop` option value, if present 60 | /// 61 | /// If the option is present multiple times, the first value is returned. 62 | /// 63 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 64 | /// or `Some(Err(_))` if value is present but invalid 65 | pub fn isb_ifdrop(&self) -> Option> { 66 | options_get_as_u64_le(&self.options, OptionCode::IsbIfDrop) 67 | } 68 | 69 | /// Return the `isb_filteraccept` option value, if present 70 | /// 71 | /// If the option is present multiple times, the first value is returned. 72 | /// 73 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 74 | /// or `Some(Err(_))` if value is present but invalid 75 | pub fn isb_filteraccept(&self) -> Option> { 76 | options_get_as_u64_le(&self.options, OptionCode::IsbFilterAccept) 77 | } 78 | 79 | /// Return the `isb_osdrop` option value, if present 80 | /// 81 | /// If the option is present multiple times, the first value is returned. 82 | /// 83 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 84 | /// or `Some(Err(_))` if value is present but invalid 85 | pub fn isb_osdrop(&self) -> Option> { 86 | options_get_as_u64_le(&self.options, OptionCode::IsbOsDrop) 87 | } 88 | 89 | /// Return the `isb_usrdeliv` option value, if present 90 | /// 91 | /// If the option is present multiple times, the first value is returned. 92 | /// 93 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 94 | /// or `Some(Err(_))` if value is present but invalid 95 | pub fn isb_usrdeliv(&self) -> Option> { 96 | options_get_as_u64_le(&self.options, OptionCode::IsbUsrDeliv) 97 | } 98 | } 99 | 100 | impl<'a, En: PcapEndianness> PcapNGBlockParser<'a, En, InterfaceStatisticsBlock<'a>> 101 | for InterfaceStatisticsBlock<'a> 102 | { 103 | const HDR_SZ: usize = 24; 104 | const MAGIC: u32 = ISB_MAGIC; 105 | 106 | fn inner_parse>( 107 | block_type: u32, 108 | block_len1: u32, 109 | i: &'a [u8], 110 | block_len2: u32, 111 | ) -> IResult<&'a [u8], InterfaceStatisticsBlock<'a>, E> { 112 | // caller function already tested header type(magic) and length 113 | // read end of header 114 | let (i, if_id) = En::parse_u32(i)?; 115 | let (i, ts_high) = En::parse_u32(i)?; 116 | let (i, ts_low) = En::parse_u32(i)?; 117 | // caller function already tested header type(magic) and length 118 | // read options 119 | let (i, options) = opt_parse_options::(i, block_len1 as usize, 24)?; 120 | if block_len2 != block_len1 { 121 | return Err(Err::Error(E::from_error_kind(i, ErrorKind::Verify))); 122 | } 123 | let block = InterfaceStatisticsBlock { 124 | block_type, 125 | block_len1, 126 | if_id, 127 | ts_high, 128 | ts_low, 129 | options, 130 | block_len2, 131 | }; 132 | Ok((i, block)) 133 | } 134 | } 135 | 136 | /// Parse an InterfaceStatistics Block (little-endian) 137 | #[inline] 138 | pub fn parse_interfacestatisticsblock_le( 139 | i: &[u8], 140 | ) -> IResult<&[u8], InterfaceStatisticsBlock, PcapError<&[u8]>> { 141 | ng_block_parser::()(i) 142 | } 143 | 144 | /// Parse an InterfaceStatistics Block (big-endian) 145 | #[inline] 146 | pub fn parse_interfacestatisticsblock_be( 147 | i: &[u8], 148 | ) -> IResult<&[u8], InterfaceStatisticsBlock, PcapError<&[u8]>> { 149 | ng_block_parser::()(i) 150 | } 151 | -------------------------------------------------------------------------------- /src/pcapng/name_resolution.rs: -------------------------------------------------------------------------------- 1 | use nom::bytes::streaming::{tag, take}; 2 | use nom::combinator::map; 3 | use nom::error::{ErrorKind, ParseError}; 4 | use nom::multi::many_till; 5 | use nom::{Err, IResult}; 6 | use rusticata_macros::{align32, newtype_enum}; 7 | 8 | use crate::endianness::{PcapBE, PcapEndianness, PcapLE}; 9 | use crate::{opt_parse_options, PcapError, PcapNGOption, NRB_MAGIC}; 10 | 11 | use super::*; 12 | 13 | #[derive(Clone, Copy, Eq, PartialEq)] 14 | pub struct NameRecordType(pub u16); 15 | 16 | newtype_enum! { 17 | impl debug NameRecordType { 18 | End = 0, 19 | Ipv4 = 1, 20 | Ipv6 = 2 21 | } 22 | } 23 | 24 | #[derive(Debug)] 25 | pub struct NameRecord<'a> { 26 | pub record_type: NameRecordType, 27 | pub record_value: &'a [u8], 28 | } 29 | 30 | impl NameRecord<'_> { 31 | pub const END: NameRecord<'static> = NameRecord { 32 | record_type: NameRecordType::End, 33 | record_value: &[], 34 | }; 35 | } 36 | 37 | #[derive(Debug)] 38 | pub struct NameResolutionBlock<'a> { 39 | pub block_type: u32, 40 | pub block_len1: u32, 41 | pub nr: Vec>, 42 | pub options: Vec>, 43 | pub block_len2: u32, 44 | } 45 | 46 | impl<'a, En: PcapEndianness> PcapNGBlockParser<'a, En, NameResolutionBlock<'a>> 47 | for NameResolutionBlock<'a> 48 | { 49 | const HDR_SZ: usize = 12; 50 | const MAGIC: u32 = NRB_MAGIC; 51 | 52 | fn inner_parse>( 53 | block_type: u32, 54 | block_len1: u32, 55 | i: &'a [u8], 56 | block_len2: u32, 57 | ) -> IResult<&'a [u8], NameResolutionBlock<'a>, E> { 58 | let start_i = i; 59 | // caller function already tested header type(magic) and length 60 | // read records 61 | let (i, nr) = parse_name_record_list::(i)?; 62 | // read options 63 | let current_offset = 12 + (i.as_ptr() as usize) - (start_i.as_ptr() as usize); 64 | let (i, options) = opt_parse_options::(i, block_len1 as usize, current_offset)?; 65 | if block_len2 != block_len1 { 66 | return Err(Err::Error(E::from_error_kind(i, ErrorKind::Verify))); 67 | } 68 | let block = NameResolutionBlock { 69 | block_type, 70 | block_len1, 71 | nr, 72 | options, 73 | block_len2, 74 | }; 75 | Ok((i, block)) 76 | } 77 | } 78 | 79 | fn parse_name_record<'a, En: PcapEndianness, E: ParseError<&'a [u8]>>( 80 | i: &'a [u8], 81 | ) -> IResult<&'a [u8], NameRecord<'a>, E> { 82 | let (i, record_type) = En::parse_u16(i)?; 83 | let (i, record_len) = En::parse_u16(i)?; 84 | let aligned_len = align32!(record_len as u32); 85 | let (i, record_value) = take(aligned_len)(i)?; 86 | let name_record = NameRecord { 87 | record_type: NameRecordType(record_type), 88 | record_value, 89 | }; 90 | Ok((i, name_record)) 91 | } 92 | 93 | fn parse_name_record_list<'a, En: PcapEndianness, E: ParseError<&'a [u8]>>( 94 | i: &'a [u8], 95 | ) -> IResult<&'a [u8], Vec>, E> { 96 | map( 97 | many_till(parse_name_record::, tag(b"\x00\x00\x00\x00")), 98 | |(mut v, _)| { 99 | v.push(NameRecord::END); 100 | v 101 | }, 102 | )(i) 103 | } 104 | 105 | /// Parse a Name Resolution Block (little-endian) 106 | #[inline] 107 | pub fn parse_nameresolutionblock_le( 108 | i: &[u8], 109 | ) -> IResult<&[u8], NameResolutionBlock, PcapError<&[u8]>> { 110 | ng_block_parser::()(i) 111 | } 112 | 113 | /// Parse a Name Resolution Block (big-endian) 114 | #[inline] 115 | pub fn parse_nameresolutionblock_be( 116 | i: &[u8], 117 | ) -> IResult<&[u8], NameResolutionBlock, PcapError<&[u8]>> { 118 | ng_block_parser::()(i) 119 | } 120 | -------------------------------------------------------------------------------- /src/pcapng/option.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::convert::TryFrom; 3 | use std::fmt; 4 | 5 | use nom::combinator::{complete, map_parser}; 6 | use nom::multi::many0; 7 | use nom::IResult; 8 | use nom::{bytes::streaming::take, error::ParseError}; 9 | use rusticata_macros::{align32, newtype_enum}; 10 | 11 | use crate::endianness::{PcapBE, PcapEndianness, PcapLE}; 12 | 13 | #[derive(Clone, Copy, Eq, PartialEq)] 14 | pub struct OptionCode(pub u16); 15 | 16 | newtype_enum! { 17 | impl debug OptionCode { 18 | EndOfOpt = 0, 19 | Comment = 1, 20 | ShbHardware = 2, 21 | IfName = 2, 22 | IsbStartTime = 2, 23 | ShbOs = 3, 24 | IfDescription = 3, 25 | IsbEndTime = 3, 26 | ShbUserAppl = 4, 27 | IfIpv4Addr = 4, 28 | IsbIfRecv = 4, 29 | IsbIfDrop = 5, 30 | IfMacAddr = 6, 31 | IsbFilterAccept = 6, 32 | IfEuiAddr = 7, 33 | IsbOsDrop = 7, 34 | IfSpeed = 8, 35 | IsbUsrDeliv = 8, 36 | IfTsresol = 9, 37 | IfFilter = 11, 38 | IfOs = 12, 39 | IfTsoffset = 14, 40 | Custom2988 = 2988, 41 | Custom2989 = 2989, 42 | Custom19372 = 19372, 43 | Custom19373 = 19373, 44 | } 45 | } 46 | 47 | /// The error type which is returned when calling functions on [PcapNGOption] 48 | #[derive(Debug, PartialEq)] 49 | pub enum PcapNGOptionError { 50 | InvalidLength, 51 | Utf8Error, 52 | } 53 | 54 | impl fmt::Display for PcapNGOptionError { 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | match self { 57 | PcapNGOptionError::InvalidLength => write!(f, "Invalid length"), 58 | PcapNGOptionError::Utf8Error => write!(f, "Invalid UTF-8 string"), 59 | } 60 | } 61 | } 62 | 63 | impl std::error::Error for PcapNGOptionError {} 64 | 65 | /// A PcapNG option 66 | #[derive(Debug)] 67 | pub struct PcapNGOption<'a> { 68 | /// The numeric code for the option 69 | /// 70 | /// Note that codes are relative to the block type, and same codes are used for different 71 | /// things (for ex 2 is `shb_hardware` if the block is a SHB, but 2 is `if_name` for an IDB) 72 | pub code: OptionCode, 73 | /// The declared length for the option 74 | /// 75 | /// Note that `value.len()` can be greater than `len`, because data is padded to a 32-bit boundary 76 | pub len: u16, 77 | /// The raw value (including padding) of the option 78 | /// 79 | /// See [PcapNGOption::as_bytes] to get the value truncated to `len`. 80 | pub value: Cow<'a, [u8]>, 81 | } 82 | 83 | impl PcapNGOption<'_> { 84 | /// Return a reference to the option value, as raw bytes (not related to the `len` field) 85 | #[inline] 86 | pub fn value(&self) -> &[u8] { 87 | self.value.as_ref() 88 | } 89 | 90 | /// Return a reference to the option value, using the `len` field to limit it, or None if length is invalid 91 | pub fn as_bytes(&self) -> Result<&[u8], PcapNGOptionError> { 92 | let len = usize::from(self.len); 93 | if len <= self.value.len() { 94 | Ok(&self.value[..len]) 95 | } else { 96 | Err(PcapNGOptionError::InvalidLength) 97 | } 98 | } 99 | 100 | /// Return the option value interpreted as string 101 | /// 102 | /// Returns an error if the length of the option is invalid, or if the value is not valid UTF-8. 103 | pub fn as_str(&self) -> Result<&str, PcapNGOptionError> { 104 | self.as_bytes() 105 | .and_then(|b| std::str::from_utf8(b).or(Err(PcapNGOptionError::Utf8Error))) 106 | } 107 | 108 | /// Return the option value interpreted as i32, or an error 109 | /// 110 | /// Option data length and declared must be exactly 4 bytes 111 | pub fn as_i32_le(&self) -> Result { 112 | if self.len != 4 { 113 | return Err(PcapNGOptionError::InvalidLength); 114 | } 115 | <[u8; 4]>::try_from(self.value()) 116 | .map(i32::from_le_bytes) 117 | .or(Err(PcapNGOptionError::InvalidLength)) 118 | } 119 | 120 | /// Return the option value interpreted as u32, or an error 121 | /// 122 | /// Option data length and declared must be exactly 4 bytes 123 | pub fn as_u32_le(&self) -> Result { 124 | if self.len != 4 { 125 | return Err(PcapNGOptionError::InvalidLength); 126 | } 127 | <[u8; 4]>::try_from(self.value()) 128 | .map(u32::from_le_bytes) 129 | .or(Err(PcapNGOptionError::InvalidLength)) 130 | } 131 | 132 | /// Return the option value interpreted as i64, or an error 133 | /// 134 | /// Option data length and declared must be exactly 8 bytes 135 | pub fn as_i64_le(&self) -> Result { 136 | if self.len != 8 { 137 | return Err(PcapNGOptionError::InvalidLength); 138 | } 139 | <[u8; 8]>::try_from(self.value()) 140 | .map(i64::from_le_bytes) 141 | .or(Err(PcapNGOptionError::InvalidLength)) 142 | } 143 | 144 | /// Return the option value interpreted as u64, or an error 145 | /// 146 | /// Option data length and declared must be exactly 8 bytes 147 | pub fn as_u64_le(&self) -> Result { 148 | if self.len != 8 { 149 | return Err(PcapNGOptionError::InvalidLength); 150 | } 151 | <[u8; 8]>::try_from(self.value()) 152 | .map(u64::from_le_bytes) 153 | .or(Err(PcapNGOptionError::InvalidLength)) 154 | } 155 | } 156 | 157 | /// Parse a pcap-ng Option (little-endian) 158 | #[inline] 159 | pub fn parse_option_le<'i, E: ParseError<&'i [u8]>>( 160 | i: &'i [u8], 161 | ) -> IResult<&'i [u8], PcapNGOption<'i>, E> { 162 | parse_option::(i) 163 | } 164 | 165 | /// Parse a pcap-ng Option (big-endian) 166 | #[inline] 167 | pub fn parse_option_be<'i, E: ParseError<&'i [u8]>>( 168 | i: &'i [u8], 169 | ) -> IResult<&'i [u8], PcapNGOption<'i>, E> { 170 | parse_option::(i) 171 | } 172 | 173 | pub(crate) fn parse_option<'i, En: PcapEndianness, E: ParseError<&'i [u8]>>( 174 | i: &'i [u8], 175 | ) -> IResult<&'i [u8], PcapNGOption<'i>, E> { 176 | let (i, code) = En::parse_u16(i)?; 177 | let (i, len) = En::parse_u16(i)?; 178 | let (i, value) = take(align32!(len as u32))(i)?; 179 | let option = PcapNGOption { 180 | code: OptionCode(code), 181 | len, 182 | value: Cow::Borrowed(value), 183 | }; 184 | Ok((i, option)) 185 | } 186 | 187 | pub(crate) fn opt_parse_options<'i, En: PcapEndianness, E: ParseError<&'i [u8]>>( 188 | i: &'i [u8], 189 | len: usize, 190 | opt_offset: usize, 191 | ) -> IResult<&'i [u8], Vec>, E> { 192 | if len > opt_offset { 193 | map_parser( 194 | take(len - opt_offset), 195 | many0(complete(parse_option::)), 196 | )(i) 197 | } else { 198 | Ok((i, Vec::new())) 199 | } 200 | } 201 | 202 | #[inline] 203 | pub(crate) fn options_find_map<'a, F, O>( 204 | options: &'a [PcapNGOption], 205 | code: OptionCode, 206 | f: F, 207 | ) -> Option> 208 | where 209 | F: Fn(&'a PcapNGOption) -> Result, 210 | { 211 | options 212 | .iter() 213 | .find_map(|opt| if opt.code == code { Some(f(opt)) } else { None }) 214 | } 215 | 216 | pub(crate) fn options_get_as_bytes<'a>( 217 | options: &'a [PcapNGOption], 218 | code: OptionCode, 219 | ) -> Option> { 220 | options_find_map(options, code, |opt| opt.as_bytes()) 221 | } 222 | 223 | pub(crate) fn options_get_as_str<'a>( 224 | options: &'a [PcapNGOption], 225 | code: OptionCode, 226 | ) -> Option> { 227 | options_find_map(options, code, |opt| opt.as_str()) 228 | } 229 | 230 | pub(crate) fn options_get_as_u8( 231 | options: &[PcapNGOption], 232 | code: OptionCode, 233 | ) -> Option> { 234 | options_find_map(options, code, |opt| { 235 | let value = opt.value(); 236 | if opt.len == 1 && !value.is_empty() { 237 | Ok(value[0]) 238 | } else { 239 | Err(PcapNGOptionError::InvalidLength) 240 | } 241 | }) 242 | } 243 | 244 | pub(crate) fn options_get_as_i64_le( 245 | options: &[PcapNGOption], 246 | code: OptionCode, 247 | ) -> Option> { 248 | options_find_map(options, code, |opt| opt.as_i64_le()) 249 | } 250 | 251 | pub(crate) fn options_get_as_u64_le( 252 | options: &[PcapNGOption], 253 | code: OptionCode, 254 | ) -> Option> { 255 | options_find_map(options, code, |opt| opt.as_u64_le()) 256 | } 257 | 258 | pub(crate) fn options_get_as_ts( 259 | options: &[PcapNGOption], 260 | code: OptionCode, 261 | ) -> Option> { 262 | options_find_map(options, code, |opt| { 263 | let value = opt.value(); 264 | if opt.len == 8 && value.len() == 8 { 265 | let bytes_ts_high = 266 | <[u8; 4]>::try_from(&value[..4]).or(Err(PcapNGOptionError::InvalidLength))?; 267 | let bytes_ts_low = 268 | <[u8; 4]>::try_from(&value[4..8]).or(Err(PcapNGOptionError::InvalidLength))?; 269 | let ts_high = u32::from_le_bytes(bytes_ts_high); 270 | let ts_low = u32::from_le_bytes(bytes_ts_low); 271 | Ok((ts_high, ts_low)) 272 | } else { 273 | Err(PcapNGOptionError::InvalidLength) 274 | } 275 | }) 276 | } 277 | -------------------------------------------------------------------------------- /src/pcapng/process_information.rs: -------------------------------------------------------------------------------- 1 | use nom::error::{ErrorKind, ParseError}; 2 | use nom::{Err, IResult}; 3 | 4 | use crate::endianness::{PcapBE, PcapEndianness, PcapLE}; 5 | use crate::{opt_parse_options, PcapError, PcapNGOption, PIB_MAGIC}; 6 | 7 | use super::*; 8 | 9 | #[derive(Debug)] 10 | pub struct ProcessInformationBlock<'a> { 11 | pub block_type: u32, 12 | pub block_len1: u32, 13 | pub process_id: u32, 14 | pub options: Vec>, 15 | pub block_len2: u32, 16 | } 17 | 18 | impl<'a, En: PcapEndianness> PcapNGBlockParser<'a, En, ProcessInformationBlock<'a>> 19 | for ProcessInformationBlock<'a> 20 | { 21 | const MAGIC: u32 = PIB_MAGIC; 22 | const HDR_SZ: usize = 4; 23 | 24 | fn inner_parse>( 25 | block_type: u32, 26 | block_len1: u32, 27 | i: &'a [u8], 28 | block_len2: u32, 29 | ) -> IResult<&'a [u8], ProcessInformationBlock<'a>, E> { 30 | // caller function already tested header type(magic) and length 31 | // read options 32 | let (i, process_id) = En::parse_u32(i)?; 33 | let (i, options) = opt_parse_options::(i, (block_len1 - 4) as usize, 12)?; 34 | if block_len2 != block_len1 { 35 | return Err(Err::Error(E::from_error_kind(i, ErrorKind::Verify))); 36 | } 37 | let block = ProcessInformationBlock { 38 | block_type, 39 | block_len1, 40 | process_id, 41 | options, 42 | block_len2, 43 | }; 44 | Ok((i, block)) 45 | } 46 | } 47 | 48 | /// Parse a ProcessInformation Block (little-endian) 49 | #[inline] 50 | pub fn parse_processinformationblock_le( 51 | i: &[u8], 52 | ) -> IResult<&[u8], ProcessInformationBlock, PcapError<&[u8]>> { 53 | ng_block_parser::()(i) 54 | } 55 | 56 | /// Parse a ProcessInformation Block (big-endian) 57 | #[inline] 58 | pub fn parse_processinformationblock_be( 59 | i: &[u8], 60 | ) -> IResult<&[u8], ProcessInformationBlock, PcapError<&[u8]>> { 61 | ng_block_parser::()(i) 62 | } 63 | -------------------------------------------------------------------------------- /src/pcapng/reader.rs: -------------------------------------------------------------------------------- 1 | use crate::blocks::PcapBlockOwned; 2 | use crate::error::PcapError; 3 | use crate::pcapng::*; 4 | use crate::traits::PcapReaderIterator; 5 | use circular::Buffer; 6 | use nom::{Needed, Offset}; 7 | use std::io::Read; 8 | 9 | /// Parsing iterator over pcap-ng data (streaming version) 10 | /// 11 | /// ## Pcap-NG Reader 12 | /// 13 | /// This reader is a streaming parser based on a circular buffer, which means memory 14 | /// usage is constant, and that it can be used to parse huge files or infinite streams. 15 | /// It creates an abstraction over any input providing the `Read` trait, and takes care 16 | /// of managing the circular buffer to provide an iterator-like interface. 17 | /// 18 | /// The first call to `next` should return the a Section Header Block (SHB), marking the start of a 19 | /// new section. 20 | /// For each section, calls to `next` will return blocks, some of them containing data (SPB, EPB), 21 | /// and others containing information (IDB, NRB, etc.). 22 | /// 23 | /// Some information must be stored (for ex. the data link type from the IDB) to be able to parse 24 | /// following block contents. Usually, a list of interfaces must be stored, with the data link type 25 | /// and capture length, for each section. These values are used when parsing Enhanced Packet Blocks 26 | /// (which gives an interface ID - the index, starting from 0) and Simple Packet Blocks (which 27 | /// assume an interface index of 0). 28 | /// 29 | /// The size of the circular buffer has to be big enough for at least one complete block. Using a 30 | /// larger value (at least 65k) is advised to avoid frequent reads and buffer shifts. 31 | /// 32 | /// **There are precautions to take when reading multiple blocks before consuming data. See 33 | /// [`PcapReaderIterator`] for details.** 34 | /// 35 | /// ## Example 36 | /// 37 | /// ```rust 38 | /// use pcap_parser::*; 39 | /// use pcap_parser::traits::PcapReaderIterator; 40 | /// use std::fs::File; 41 | /// 42 | /// # let path = "assets/test001-le.pcapng"; 43 | /// let file = File::open(path).unwrap(); 44 | /// let mut num_blocks = 0; 45 | /// let mut reader = PcapNGReader::new(65536, file).expect("PcapNGReader"); 46 | /// let mut if_linktypes = Vec::new(); 47 | /// let mut last_incomplete_index = 0; 48 | /// loop { 49 | /// match reader.next() { 50 | /// Ok((offset, block)) => { 51 | /// println!("got new block"); 52 | /// num_blocks += 1; 53 | /// match block { 54 | /// PcapBlockOwned::NG(Block::SectionHeader(ref _shb)) => { 55 | /// // starting a new section, clear known interfaces 56 | /// if_linktypes = Vec::new(); 57 | /// }, 58 | /// PcapBlockOwned::NG(Block::InterfaceDescription(ref idb)) => { 59 | /// if_linktypes.push(idb.linktype); 60 | /// }, 61 | /// PcapBlockOwned::NG(Block::EnhancedPacket(ref epb)) => { 62 | /// assert!((epb.if_id as usize) < if_linktypes.len()); 63 | /// let linktype = if_linktypes[epb.if_id as usize]; 64 | /// #[cfg(feature="data")] 65 | /// let res = pcap_parser::data::get_packetdata(epb.data, linktype, epb.caplen as usize); 66 | /// }, 67 | /// PcapBlockOwned::NG(Block::SimplePacket(ref spb)) => { 68 | /// assert!(if_linktypes.len() > 0); 69 | /// let linktype = if_linktypes[0]; 70 | /// let blen = (spb.block_len1 - 16) as usize; 71 | /// #[cfg(feature="data")] 72 | /// let res = pcap_parser::data::get_packetdata(spb.data, linktype, blen); 73 | /// }, 74 | /// PcapBlockOwned::NG(_) => { 75 | /// // can be statistics (ISB), name resolution (NRB), etc. 76 | /// eprintln!("unsupported block"); 77 | /// }, 78 | /// PcapBlockOwned::Legacy(_) 79 | /// | PcapBlockOwned::LegacyHeader(_) => unreachable!(), 80 | /// } 81 | /// reader.consume(offset); 82 | /// }, 83 | /// Err(PcapError::Eof) => break, 84 | /// Err(PcapError::Incomplete(_)) => { 85 | /// if last_incomplete_index == num_blocks { 86 | /// eprintln!("Could not read complete data block."); 87 | /// eprintln!("Hint: the reader buffer size may be too small, or the input file may be truncated."); 88 | /// break; 89 | /// } 90 | /// last_incomplete_index = num_blocks; 91 | /// reader.refill().expect("Could not refill reader"); 92 | /// continue; 93 | /// }, 94 | /// Err(e) => panic!("error while reading: {:?}", e), 95 | /// } 96 | /// } 97 | /// println!("num_blocks: {}", num_blocks); 98 | /// ``` 99 | pub struct PcapNGReader 100 | where 101 | R: Read, 102 | { 103 | info: CurrentSectionInfo, 104 | reader: R, 105 | buffer: Buffer, 106 | consumed: usize, 107 | reader_exhausted: bool, 108 | } 109 | 110 | impl PcapNGReader 111 | where 112 | R: Read, 113 | { 114 | /// Creates a new `PcapNGReader` with the provided buffer capacity. 115 | pub fn new(capacity: usize, reader: R) -> Result, PcapError<&'static [u8]>> { 116 | let buffer = Buffer::with_capacity(capacity); 117 | Self::from_buffer(buffer, reader) 118 | } 119 | /// Creates a new `PcapNGReader` using the provided `Buffer`. 120 | pub fn from_buffer( 121 | mut buffer: Buffer, 122 | mut reader: R, 123 | ) -> Result, PcapError<&'static [u8]>> { 124 | let sz = reader.read(buffer.space()).or(Err(PcapError::ReadError))?; 125 | buffer.fill(sz); 126 | // just check that first block is a valid one 127 | let (_rem, _shb) = match parse_sectionheaderblock(buffer.data()) { 128 | Ok((r, h)) => Ok((r, h)), 129 | Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(e.to_owned_vec()), 130 | Err(nom::Err::Incomplete(Needed::Size(n))) => Err(PcapError::Incomplete(n.into())), 131 | Err(nom::Err::Incomplete(Needed::Unknown)) => Err(PcapError::Incomplete(0)), 132 | }?; 133 | let info = CurrentSectionInfo::default(); 134 | // do not consume 135 | Ok(PcapNGReader { 136 | info, 137 | reader, 138 | buffer, 139 | consumed: 0, 140 | reader_exhausted: false, 141 | }) 142 | } 143 | } 144 | 145 | impl PcapReaderIterator for PcapNGReader 146 | where 147 | R: Read, 148 | { 149 | fn next(&mut self) -> Result<(usize, PcapBlockOwned), PcapError<&[u8]>> { 150 | // Return EOF if 151 | // 1) all bytes have been read 152 | // 2) no more data is available 153 | if self.buffer.available_data() == 0 154 | && (self.buffer.position() == 0 && self.reader_exhausted) 155 | { 156 | return Err(PcapError::Eof); 157 | } 158 | let data = self.buffer.data(); 159 | let parse = if self.info.big_endian { 160 | parse_block_be 161 | } else { 162 | parse_block_le 163 | }; 164 | match parse(data) { 165 | Ok((rem, b)) => { 166 | let offset = data.offset(rem); 167 | if let Block::SectionHeader(ref shb) = b { 168 | self.info.big_endian = shb.big_endian(); 169 | } 170 | Ok((offset, PcapBlockOwned::from(b))) 171 | } 172 | Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(e), 173 | Err(nom::Err::Incomplete(n)) => { 174 | if self.reader_exhausted { 175 | // expected more bytes but reader is EOF, truncated pcap? 176 | Err(PcapError::UnexpectedEof) 177 | } else { 178 | match n { 179 | Needed::Size(n) => { 180 | if self.buffer.available_data() + usize::from(n) 181 | >= self.buffer.capacity() 182 | { 183 | Err(PcapError::BufferTooSmall) 184 | } else { 185 | Err(PcapError::Incomplete(n.into())) 186 | } 187 | } 188 | Needed::Unknown => Err(PcapError::Incomplete(0)), 189 | } 190 | } 191 | } 192 | } 193 | } 194 | fn consume(&mut self, offset: usize) { 195 | self.consumed += offset; 196 | self.buffer.consume(offset); 197 | } 198 | fn consume_noshift(&mut self, offset: usize) { 199 | self.consumed += offset; 200 | self.buffer.consume_noshift(offset); 201 | } 202 | fn consumed(&self) -> usize { 203 | self.consumed 204 | } 205 | fn refill(&mut self) -> Result<(), PcapError<&[u8]>> { 206 | self.buffer.shift(); 207 | let space = self.buffer.space(); 208 | // check if available space is empty, so we can distinguish 209 | // a read() returning 0 because of EOF or because we requested 0 210 | if space.is_empty() { 211 | return Ok(()); 212 | } 213 | let sz = self.reader.read(space).or(Err(PcapError::ReadError))?; 214 | self.reader_exhausted = sz == 0; 215 | self.buffer.fill(sz); 216 | Ok(()) 217 | } 218 | fn position(&self) -> usize { 219 | self.buffer.position() 220 | } 221 | fn grow(&mut self, new_size: usize) -> bool { 222 | self.buffer.grow(new_size) 223 | } 224 | fn data(&self) -> &[u8] { 225 | self.buffer.data() 226 | } 227 | fn reader_exhausted(&self) -> bool { 228 | self.reader_exhausted 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/pcapng/section.rs: -------------------------------------------------------------------------------- 1 | use nom::{ 2 | combinator::complete, 3 | error::{make_error, ErrorKind}, 4 | multi::{many0, many1}, 5 | Err, IResult, 6 | }; 7 | 8 | use crate::{PcapBlock, PcapError}; 9 | 10 | use super::*; 11 | 12 | /// A Section (including all blocks) from a PcapNG file 13 | pub struct Section<'a> { 14 | /// The list of blocks 15 | pub blocks: Vec>, 16 | /// True if encoding is big-endian 17 | pub big_endian: bool, 18 | } 19 | 20 | impl<'a> Section<'a> { 21 | /// Returns the section header 22 | pub fn header(&self) -> Option<&SectionHeaderBlock> { 23 | if let Some(Block::SectionHeader(ref b)) = self.blocks.first() { 24 | Some(b) 25 | } else { 26 | None 27 | } 28 | } 29 | 30 | /// Returns an iterator over the section blocks 31 | pub fn iter(&'a self) -> SectionBlockIterator<'a> { 32 | SectionBlockIterator { 33 | section: self, 34 | index_block: 0, 35 | } 36 | } 37 | 38 | /// Returns an iterator over the interface description blocks 39 | pub fn iter_interfaces(&'a self) -> InterfaceBlockIterator<'a> { 40 | InterfaceBlockIterator { 41 | section: self, 42 | index_block: 0, 43 | } 44 | } 45 | } 46 | 47 | // Non-consuming iterator over blocks of a Section 48 | pub struct SectionBlockIterator<'a> { 49 | section: &'a Section<'a>, 50 | index_block: usize, 51 | } 52 | 53 | impl<'a> Iterator for SectionBlockIterator<'a> { 54 | type Item = PcapBlock<'a>; 55 | 56 | fn next(&mut self) -> Option> { 57 | let block = self.section.blocks.get(self.index_block); 58 | self.index_block += 1; 59 | block.map(PcapBlock::from) 60 | } 61 | } 62 | 63 | // Non-consuming iterator over interface description blocks of a Section 64 | pub struct InterfaceBlockIterator<'a> { 65 | section: &'a Section<'a>, 66 | index_block: usize, 67 | } 68 | 69 | impl<'a> Iterator for InterfaceBlockIterator<'a> { 70 | type Item = &'a InterfaceDescriptionBlock<'a>; 71 | 72 | fn next(&mut self) -> Option<&'a InterfaceDescriptionBlock<'a>> { 73 | if self.index_block >= self.section.blocks.len() { 74 | return None; 75 | } 76 | for block in &self.section.blocks[self.index_block..] { 77 | self.index_block += 1; 78 | if let Block::InterfaceDescription(ref idb) = block { 79 | return Some(idb); 80 | } 81 | } 82 | None 83 | } 84 | } 85 | 86 | /// Parse any block from a section (little-endian) 87 | pub fn parse_section_content_block_le(i: &[u8]) -> IResult<&[u8], Block, PcapError<&[u8]>> { 88 | let (rem, block) = parse_block_le(i)?; 89 | match block { 90 | Block::SectionHeader(_) => Err(Err::Error(make_error(i, ErrorKind::Tag))), 91 | _ => Ok((rem, block)), 92 | } 93 | } 94 | 95 | /// Parse any block from a section (big-endian) 96 | pub fn parse_section_content_block_be(i: &[u8]) -> IResult<&[u8], Block, PcapError<&[u8]>> { 97 | let (rem, block) = parse_block_be(i)?; 98 | match block { 99 | Block::SectionHeader(_) => Err(Err::Error(make_error(i, ErrorKind::Tag))), 100 | _ => Ok((rem, block)), 101 | } 102 | } 103 | 104 | /// Parse one section (little or big endian) 105 | pub fn parse_section(i: &[u8]) -> IResult<&[u8], Section, PcapError<&[u8]>> { 106 | let (rem, shb) = parse_sectionheaderblock(i)?; 107 | let big_endian = shb.big_endian(); 108 | let (rem, mut b) = if big_endian { 109 | many0(complete(parse_section_content_block_be))(rem)? 110 | } else { 111 | many0(complete(parse_section_content_block_le))(rem)? 112 | }; 113 | let mut blocks = Vec::with_capacity(b.len() + 1); 114 | blocks.push(Block::SectionHeader(shb)); 115 | blocks.append(&mut b); 116 | let section = Section { blocks, big_endian }; 117 | Ok((rem, section)) 118 | } 119 | 120 | /// Parse multiple sections (little or big endian) 121 | #[inline] 122 | pub fn parse_sections(i: &[u8]) -> IResult<&[u8], Vec
, PcapError<&[u8]>> { 123 | many1(complete(parse_section))(i) 124 | } 125 | -------------------------------------------------------------------------------- /src/pcapng/section_header.rs: -------------------------------------------------------------------------------- 1 | use nom::error::ParseError; 2 | use nom::number::streaming::{be_i64, be_u16, le_i64, le_u16, le_u32}; 3 | use nom::{Err, IResult}; 4 | 5 | use crate::endianness::{PcapBE, PcapLE}; 6 | use crate::utils::array_ref4; 7 | use crate::{opt_parse_options, PcapError, PcapNGOption, PcapNGOptionError, SHB_MAGIC}; 8 | 9 | use super::*; 10 | 11 | /// The Section Header Block (SHB) identifies the 12 | /// beginning of a section of the capture capture file. 13 | /// 14 | /// The 15 | /// Section Header Block does not contain data but it rather identifies a 16 | /// list of blocks (interfaces, packets) that are logically correlated. 17 | #[derive(Debug)] 18 | pub struct SectionHeaderBlock<'a> { 19 | pub block_type: u32, 20 | pub block_len1: u32, 21 | /// Byte-order magic 22 | pub bom: u32, 23 | pub major_version: u16, 24 | pub minor_version: u16, 25 | pub section_len: i64, 26 | pub options: Vec>, 27 | pub block_len2: u32, 28 | } 29 | 30 | impl SectionHeaderBlock<'_> { 31 | pub fn big_endian(&self) -> bool { 32 | self.bom != BOM_MAGIC 33 | } 34 | 35 | /// Return the `shb_hardware` option value, if present 36 | /// 37 | /// If the option is present multiple times, the first value is returned. 38 | /// 39 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 40 | /// or `Some(Err(_))` if value is present but invalid 41 | pub fn shb_hardware(&self) -> Option> { 42 | options_get_as_str(&self.options, OptionCode::ShbHardware) 43 | } 44 | 45 | /// Return the `shb_os` option value, if present 46 | /// 47 | /// If the option is present multiple times, the first value is returned. 48 | /// 49 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 50 | /// or `Some(Err(_))` if value is present but invalid 51 | pub fn shb_os(&self) -> Option> { 52 | options_get_as_str(&self.options, OptionCode::ShbOs) 53 | } 54 | 55 | /// Return the `shb_userappl` option value, if present 56 | /// 57 | /// If the option is present multiple times, the first value is returned. 58 | /// 59 | /// Returns `None` if option is not present, `Some(Ok(value))` if the value is present and valid, 60 | /// or `Some(Err(_))` if value is present but invalid 61 | pub fn shb_userappl(&self) -> Option> { 62 | options_get_as_str(&self.options, OptionCode::ShbUserAppl) 63 | } 64 | } 65 | 66 | impl<'a> PcapNGBlockParser<'a, PcapBE, SectionHeaderBlock<'a>> for SectionHeaderBlock<'a> { 67 | const HDR_SZ: usize = 28; 68 | const MAGIC: u32 = SHB_MAGIC; 69 | 70 | fn inner_parse>( 71 | block_type: u32, 72 | block_len1: u32, 73 | i: &'a [u8], 74 | block_len2: u32, 75 | ) -> IResult<&'a [u8], SectionHeaderBlock<'a>, E> { 76 | // caller function already tested header type(magic) and length 77 | // read end of header 78 | let (i, bom) = le_u32(i)?; 79 | let (i, major_version) = be_u16(i)?; 80 | let (i, minor_version) = be_u16(i)?; 81 | let (i, section_len) = be_i64(i)?; 82 | let (i, options) = opt_parse_options::(i, block_len1 as usize, 28)?; 83 | let block = SectionHeaderBlock { 84 | block_type, 85 | block_len1, 86 | bom, 87 | major_version, 88 | minor_version, 89 | section_len, 90 | options, 91 | block_len2, 92 | }; 93 | Ok((i, block)) 94 | } 95 | } 96 | 97 | impl<'a> PcapNGBlockParser<'a, PcapLE, SectionHeaderBlock<'a>> for SectionHeaderBlock<'a> { 98 | const HDR_SZ: usize = 28; 99 | const MAGIC: u32 = SHB_MAGIC; 100 | 101 | fn inner_parse>( 102 | block_type: u32, 103 | block_len1: u32, 104 | i: &'a [u8], 105 | block_len2: u32, 106 | ) -> IResult<&'a [u8], SectionHeaderBlock<'a>, E> { 107 | // caller function already tested header type(magic) and length 108 | // read end of header 109 | let (i, bom) = le_u32(i)?; 110 | let (i, major_version) = le_u16(i)?; 111 | let (i, minor_version) = le_u16(i)?; 112 | let (i, section_len) = le_i64(i)?; 113 | let (i, options) = opt_parse_options::(i, block_len1 as usize, 28)?; 114 | let block = SectionHeaderBlock { 115 | block_type, 116 | block_len1, 117 | bom, 118 | major_version, 119 | minor_version, 120 | section_len, 121 | options, 122 | block_len2, 123 | }; 124 | Ok((i, block)) 125 | } 126 | } 127 | 128 | /// Parse a Section Header Block (little endian) 129 | pub fn parse_sectionheaderblock_le( 130 | i: &[u8], 131 | ) -> IResult<&[u8], SectionHeaderBlock, PcapError<&[u8]>> { 132 | ng_block_parser::()(i) 133 | } 134 | 135 | /// Parse a Section Header Block (big endian) 136 | pub fn parse_sectionheaderblock_be( 137 | i: &[u8], 138 | ) -> IResult<&[u8], SectionHeaderBlock, PcapError<&[u8]>> { 139 | ng_block_parser::()(i) 140 | } 141 | 142 | /// Parse a SectionHeaderBlock (little or big endian) 143 | pub fn parse_sectionheaderblock(i: &[u8]) -> IResult<&[u8], SectionHeaderBlock, PcapError<&[u8]>> { 144 | if i.len() < 12 { 145 | return Err(Err::Incomplete(nom::Needed::new(12 - i.len()))); 146 | } 147 | let bom = u32::from_le_bytes(*array_ref4(i, 8)); 148 | if bom == BOM_MAGIC { 149 | parse_sectionheaderblock_le(i) 150 | } else if bom == u32::from_be(BOM_MAGIC) { 151 | parse_sectionheaderblock_be(i) 152 | } else { 153 | Err(Err::Error(PcapError::HeaderNotRecognized)) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/pcapng/simple_packet.rs: -------------------------------------------------------------------------------- 1 | use nom::bytes::streaming::take; 2 | use nom::error::ParseError; 3 | use nom::IResult; 4 | 5 | use crate::endianness::{PcapBE, PcapEndianness, PcapLE}; 6 | use crate::traits::PcapNGPacketBlock; 7 | use crate::{PcapError, SPB_MAGIC}; 8 | 9 | use super::*; 10 | 11 | /// The Simple Packet Block (SPB) is a lightweight container for storing 12 | /// the packets coming from the network. 13 | /// 14 | /// This struct is a thin abstraction layer, and stores the raw block data. 15 | /// For ex the `data` field is stored with the padding. 16 | /// It implements the `PcapNGPacketBlock` trait, which provides helper functions. 17 | #[derive(Debug)] 18 | pub struct SimplePacketBlock<'a> { 19 | /// Block type (little endian) 20 | pub block_type: u32, 21 | pub block_len1: u32, 22 | /// Original packet length 23 | pub origlen: u32, 24 | pub data: &'a [u8], 25 | pub block_len2: u32, 26 | } 27 | 28 | impl PcapNGPacketBlock for SimplePacketBlock<'_> { 29 | fn big_endian(&self) -> bool { 30 | self.block_type != SPB_MAGIC 31 | } 32 | fn truncated(&self) -> bool { 33 | self.origlen as usize <= self.data.len() 34 | } 35 | fn orig_len(&self) -> u32 { 36 | self.origlen 37 | } 38 | fn raw_packet_data(&self) -> &[u8] { 39 | self.data 40 | } 41 | fn packet_data(&self) -> &[u8] { 42 | let caplen = self.origlen as usize; 43 | if caplen < self.data.len() { 44 | &self.data[..caplen] 45 | } else { 46 | self.data 47 | } 48 | } 49 | } 50 | 51 | impl<'a, En: PcapEndianness> PcapNGBlockParser<'a, En, SimplePacketBlock<'a>> 52 | for SimplePacketBlock<'a> 53 | { 54 | const HDR_SZ: usize = 16; 55 | const MAGIC: u32 = SPB_MAGIC; 56 | 57 | fn inner_parse>( 58 | block_type: u32, 59 | block_len1: u32, 60 | i: &'a [u8], 61 | block_len2: u32, 62 | ) -> IResult<&'a [u8], SimplePacketBlock<'a>, E> { 63 | // caller function already tested header type(magic) and length 64 | // read end of header 65 | let (i, origlen) = En::parse_u32(i)?; 66 | let (i, data) = take((block_len1 as usize) - 16)(i)?; 67 | let block = SimplePacketBlock { 68 | block_type, 69 | block_len1, 70 | origlen, 71 | data, 72 | block_len2, 73 | }; 74 | Ok((i, block)) 75 | } 76 | } 77 | 78 | /// Parse a Simple Packet Block (little-endian) 79 | /// 80 | /// *Note: this function does not remove padding in the `data` field. 81 | /// Use `packet_data` to get field without padding.* 82 | pub fn parse_simplepacketblock_le(i: &[u8]) -> IResult<&[u8], SimplePacketBlock, PcapError<&[u8]>> { 83 | ng_block_parser::()(i) 84 | } 85 | 86 | /// Parse a Simple Packet Block (big-endian) 87 | /// 88 | /// *Note: this function does not remove padding* 89 | pub fn parse_simplepacketblock_be(i: &[u8]) -> IResult<&[u8], SimplePacketBlock, PcapError<&[u8]>> { 90 | ng_block_parser::()(i) 91 | } 92 | -------------------------------------------------------------------------------- /src/pcapng/systemd_journal_export.rs: -------------------------------------------------------------------------------- 1 | use nom::error::ParseError; 2 | use nom::IResult; 3 | 4 | use crate::endianness::{PcapBE, PcapEndianness, PcapLE}; 5 | use crate::{PcapError, SJE_MAGIC}; 6 | 7 | use super::*; 8 | 9 | #[derive(Debug)] 10 | pub struct SystemdJournalExportBlock<'a> { 11 | pub block_type: u32, 12 | pub block_len1: u32, 13 | pub data: &'a [u8], 14 | pub block_len2: u32, 15 | } 16 | 17 | impl<'a, En: PcapEndianness> PcapNGBlockParser<'a, En, SystemdJournalExportBlock<'a>> 18 | for SystemdJournalExportBlock<'a> 19 | { 20 | const HDR_SZ: usize = 12; 21 | const MAGIC: u32 = SJE_MAGIC; 22 | 23 | fn inner_parse>( 24 | block_type: u32, 25 | block_len1: u32, 26 | i: &'a [u8], 27 | block_len2: u32, 28 | ) -> IResult<&'a [u8], SystemdJournalExportBlock<'a>, E> { 29 | let block = SystemdJournalExportBlock { 30 | block_type, 31 | block_len1, 32 | data: i, 33 | block_len2, 34 | }; 35 | Ok((i, block)) 36 | } 37 | } 38 | 39 | /// Parse a SystemdJournalExport Block (little-endian) 40 | #[inline] 41 | pub fn parse_systemdjournalexportblock_le( 42 | i: &[u8], 43 | ) -> IResult<&[u8], SystemdJournalExportBlock, PcapError<&[u8]>> { 44 | ng_block_parser::()(i) 45 | } 46 | 47 | /// Parse a SystemdJournalExport Block (big-endian) 48 | #[inline] 49 | pub fn parse_systemdjournalexportblock_be( 50 | i: &[u8], 51 | ) -> IResult<&[u8], SystemdJournalExportBlock, PcapError<&[u8]>> { 52 | ng_block_parser::()(i) 53 | } 54 | -------------------------------------------------------------------------------- /src/pcapng/time.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use super::{OptionCode, PcapNGOption}; 4 | 5 | /// Compute the timestamp resolution, in units per second 6 | /// 7 | /// Return the resolution, or `None` if the resolution is invalid (for ex. greater than `2^64`) 8 | pub fn build_ts_resolution(ts_resol: u8) -> Option { 9 | let ts_mode = ts_resol & 0x80; 10 | let unit = if ts_mode == 0 { 11 | // 10^if_tsresol 12 | // check that if_tsresol <= 19 (10^19 is the largest power of 10 to fit in a u64) 13 | if ts_resol > 19 { 14 | return None; 15 | } 16 | 10u64.pow(ts_resol as u32) 17 | } else { 18 | // 2^if_tsresol 19 | // check that if_tsresol <= 63 20 | if ts_resol > 63 { 21 | return None; 22 | } 23 | 1 << ((ts_resol & 0x7f) as u64) 24 | }; 25 | Some(unit) 26 | } 27 | 28 | /// Given the timestamp parameters, return the timestamp seconds and fractional part (in resolution 29 | /// units) 30 | pub fn build_ts(ts_high: u32, ts_low: u32, ts_offset: u64, resolution: u64) -> (u32, u32) { 31 | let if_tsoffset = ts_offset; 32 | let ts: u64 = ((ts_high as u64) << 32) | (ts_low as u64); 33 | let ts_sec = (if_tsoffset + (ts / resolution)) as u32; 34 | let ts_fractional = (ts % resolution) as u32; 35 | (ts_sec, ts_fractional) 36 | } 37 | 38 | /// Given the timestamp parameters, return the timestamp as a `f64` value. 39 | /// 40 | /// The resolution is given in units per second. In pcap-ng files, it is stored in the 41 | /// Interface Description Block, and can be obtained using [`crate::InterfaceDescriptionBlock::ts_resolution`] 42 | pub fn build_ts_f64(ts_high: u32, ts_low: u32, ts_offset: u64, resolution: u64) -> f64 { 43 | let ts: u64 = ((ts_high as u64) << 32) | (ts_low as u64); 44 | let ts_sec = (ts_offset + (ts / resolution)) as u32; 45 | let ts_fractional = (ts % resolution) as u32; 46 | // XXX should we round to closest unit? 47 | ts_sec as f64 + ((ts_fractional as f64) / (resolution as f64)) 48 | } 49 | 50 | pub(crate) fn if_extract_tsoffset_and_tsresol(options: &[PcapNGOption]) -> (u8, i64) { 51 | let mut if_tsresol: u8 = 6; 52 | let mut if_tsoffset: i64 = 0; 53 | for opt in options { 54 | match opt.code { 55 | OptionCode::IfTsresol => { 56 | if !opt.value.is_empty() { 57 | if_tsresol = opt.value[0]; 58 | } 59 | } 60 | OptionCode::IfTsoffset => { 61 | if opt.value.len() >= 8 { 62 | let int_bytes = 63 | <[u8; 8]>::try_from(&opt.value[..8]).expect("Convert bytes to i64"); 64 | if_tsoffset = i64::from_le_bytes(int_bytes); 65 | } 66 | } 67 | _ => (), 68 | } 69 | } 70 | (if_tsresol, if_tsoffset) 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use hex_literal::hex; 76 | 77 | use super::*; 78 | 79 | #[test] 80 | fn decode_ts() { 81 | // from https://datatracker.ietf.org/doc/html/draft-ietf-opsawg-pcapng section 4.6 (ISB) 82 | // '97 c3 04 00 aa 47 ca 64', in Little Endian, decodes to 2012-06-29 07:28:25.298858 UTC. 83 | 84 | const INPUT_HIGH: [u8; 4] = hex!("97 c3 04 00"); 85 | const INPUT_LOW: [u8; 4] = hex!("aa 47 ca 64"); 86 | let ts_high = u32::from_le_bytes(INPUT_HIGH); 87 | let ts_low = u32::from_le_bytes(INPUT_LOW); 88 | let ts_offset = 0; 89 | let resolution = build_ts_resolution(6).unwrap(); 90 | // eprintln!("{ts_high:x?}"); 91 | 92 | let (ts_sec, ts_usec) = build_ts(ts_high, ts_low, ts_offset, resolution); 93 | eprintln!("{ts_sec}:{ts_usec}"); 94 | 95 | const EXPECTED_TS_SEC: u32 = 1340954905; 96 | const EXPECTED_TS_USEC: u32 = 298858; 97 | // // to obtain the above values value, add "chrono" to dev-dependencies and uncomment: 98 | // use chrono::DateTime; 99 | // let dt = DateTime::from_timestamp(ts_sec as i64, ts_usec * 1000).unwrap(); 100 | // assert_eq!(dt.to_string(), "2012-06-29 07:28:25.298858 UTC"); 101 | assert_eq!(ts_sec, EXPECTED_TS_SEC); 102 | assert_eq!(ts_usec, EXPECTED_TS_USEC); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/pcapng/unknown.rs: -------------------------------------------------------------------------------- 1 | use nom::error::ParseError; 2 | use nom::IResult; 3 | 4 | use crate::endianness::{PcapBE, PcapEndianness, PcapLE}; 5 | use crate::PcapError; 6 | 7 | use super::*; 8 | 9 | /// Unknown block (magic not recognized, or not yet implemented) 10 | #[derive(Debug)] 11 | pub struct UnknownBlock<'a> { 12 | /// Block type (little endian) 13 | pub block_type: u32, 14 | pub block_len1: u32, 15 | pub data: &'a [u8], 16 | pub block_len2: u32, 17 | } 18 | 19 | impl<'a, En: PcapEndianness> PcapNGBlockParser<'a, En, UnknownBlock<'a>> for UnknownBlock<'a> { 20 | const HDR_SZ: usize = 12; 21 | const MAGIC: u32 = 0; 22 | 23 | fn inner_parse>( 24 | block_type: u32, 25 | block_len1: u32, 26 | i: &'a [u8], 27 | block_len2: u32, 28 | ) -> IResult<&'a [u8], UnknownBlock<'a>, E> { 29 | let block = UnknownBlock { 30 | block_type, 31 | block_len1, 32 | data: i, 33 | block_len2, 34 | }; 35 | Ok((i, block)) 36 | } 37 | } 38 | 39 | /// Parse an unknown block (little-endian) 40 | pub fn parse_unknownblock_le(i: &[u8]) -> IResult<&[u8], UnknownBlock, PcapError<&[u8]>> { 41 | ng_block_parser::()(i) 42 | } 43 | 44 | /// Parse an unknown block (big-endian) 45 | pub fn parse_unknownblock_be(i: &[u8]) -> IResult<&[u8], UnknownBlock, PcapError<&[u8]>> { 46 | ng_block_parser::()(i) 47 | } 48 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use crate::blocks::PcapBlockOwned; 2 | use crate::error::PcapError; 3 | use crate::read_u32_e; 4 | use rusticata_macros::align32; 5 | 6 | /// Common methods for PcapNG blocks 7 | pub trait PcapNGBlock { 8 | /// Returns true if block is encoded as big-endian 9 | fn big_endian(&self) -> bool; 10 | /// Raw block data (including header) 11 | fn raw_data(&self) -> &[u8]; 12 | /// The type of this block 13 | #[inline] 14 | fn block_type(&self) -> u32 { 15 | read_u32_e!(&self.raw_data(), self.big_endian()) 16 | } 17 | /// The block length from the block header 18 | #[inline] 19 | fn block_length(&self) -> u32 { 20 | read_u32_e!(&self.raw_data()[4..8], self.big_endian()) 21 | } 22 | /// The block length from the block footer 23 | #[inline] 24 | fn block_length2(&self) -> u32 { 25 | let data = self.raw_data(); 26 | let data_len = data.len(); 27 | read_u32_e!(&data[data_len - 4..], self.big_endian()) 28 | } 29 | /// The length of inner data, if any 30 | fn data_len(&self) -> usize; 31 | /// The inner data, if any 32 | fn data(&self) -> &[u8]; 33 | /// The Header length (without options) 34 | fn header_len(&self) -> usize; 35 | /// Raw packet header (without options) 36 | #[inline] 37 | fn raw_header(&self) -> &[u8] { 38 | let len = self.header_len(); 39 | &self.raw_data()[..len] 40 | } 41 | /// Return the declared offset of options. 42 | /// *Warning: the offset can be out of bounds, caller must test value before accessing data* 43 | #[inline] 44 | fn offset_options(&self) -> usize { 45 | let len = self.data_len(); 46 | self.header_len() + align32!(len) 47 | } 48 | /// Network packet options. 49 | /// Can be empty if packet does not contain options 50 | #[inline] 51 | fn raw_options(&self) -> &[u8] { 52 | let offset = self.offset_options(); 53 | let data_len = self.data_len(); 54 | // if requested length is too big, truncate 55 | if offset + 4 >= data_len { 56 | return &[]; 57 | } 58 | &self.raw_data()[offset..data_len - 4] 59 | } 60 | } 61 | 62 | /// Common methods for PcapNG Packet blocks 63 | pub trait PcapNGPacketBlock { 64 | /// Return true if block is encoded as big-endian 65 | fn big_endian(&self) -> bool; 66 | /// Return true if block data was truncated (typically when snaplen < origlen) 67 | fn truncated(&self) -> bool { 68 | false 69 | } 70 | /// Return the original length of the packet 71 | fn orig_len(&self) -> u32; 72 | /// Return the raw captured packet data, with padding if present, and eventually truncated. 73 | fn raw_packet_data(&self) -> &[u8]; 74 | /// Return the captured packet data without padding 75 | /// 76 | /// If packet was truncated, the truncated data field is returned. 77 | fn packet_data(&self) -> &[u8]; 78 | } 79 | 80 | /// Streaming Iterator over pcap files 81 | /// 82 | /// Implementors of this trait are usually based on a circular buffer, which means memory 83 | /// usage is constant, and that it can be used to parse huge files or infinite streams. 84 | /// However, this also means some care must be taken so no reference (for ex a pcap block) is 85 | /// kept on the buffer before changing the buffer content. 86 | /// 87 | /// Each call to `next` will return the next block, 88 | /// and must be followed by call to `consume` to avoid reading the same data. 89 | /// `consume` takes care of shifting data in the buffer if required, but does not refill it. 90 | /// 91 | /// It is possible to read multiple blocks before consuming data. 92 | /// Call `consume_noshift` instead of `consume`. To refill the buffer, first ensures that you do 93 | /// not keep any reference over internal data (blocks or slices), and call `refill`. 94 | /// 95 | /// To determine when a refill is needed, either test `next()` for an incomplete read. You can also 96 | /// use `position` to implement a heuristic refill (for ex, when `position > capacity / 2`. 97 | /// 98 | /// **The blocks already read, and underlying data, must be discarded before calling 99 | /// `consume` or `refill`.** It is the caller's responsibility to call functions in the correct 100 | /// order. 101 | pub trait PcapReaderIterator { 102 | /// Get the next pcap block, if possible. Returns the number of bytes read and the block. 103 | /// 104 | /// The returned object is valid until `consume` or `refill` is called. 105 | fn next(&mut self) -> Result<(usize, PcapBlockOwned<'_>), PcapError<&[u8]>>; 106 | /// Consume data, and shift buffer if needed. 107 | /// 108 | /// If the position gets past the buffer's half, this will move the remaining data to the 109 | /// beginning of the buffer. 110 | /// 111 | /// **The blocks already read, and underlying data, must be discarded before calling 112 | /// this function.** 113 | fn consume(&mut self, offset: usize); 114 | /// Consume date, but do not change the buffer. Blocks already read are still valid. 115 | fn consume_noshift(&mut self, offset: usize); 116 | /// Get the number of consumed bytes 117 | fn consumed(&self) -> usize; 118 | /// Refill the internal buffer, shifting it if necessary. 119 | /// 120 | /// **The blocks already read, and underlying data, must be discarded before calling 121 | /// this function.** 122 | fn refill(&mut self) -> Result<(), PcapError<&[u8]>>; 123 | /// Get the position in the internal buffer. Can be used to determine if `refill` is required. 124 | fn position(&self) -> usize; 125 | /// Grow size of the internal buffer. 126 | fn grow(&mut self, new_size: usize) -> bool; 127 | /// Returns a slice with all the available data 128 | fn data(&self) -> &[u8]; 129 | /// Returns true if underlying reader is exhausted 130 | /// 131 | /// Note that exhausted reader only means that next `refill` will not 132 | /// add any data, but there can still be data not consumed in the current buffer. 133 | fn reader_exhausted(&self) -> bool; 134 | } 135 | 136 | /* ******************* */ 137 | 138 | #[cfg(test)] 139 | pub mod tests { 140 | use crate::pcap::parse_pcap_frame; 141 | use crate::pcapng::{parse_block_le, Block, SecretsType}; 142 | use crate::traits::PcapNGPacketBlock; 143 | use crate::utils::Data; 144 | use hex_literal::hex; 145 | // tls12-23.pcap frame 0 146 | pub const FRAME_PCAP: &[u8] = &hex!( 147 | " 148 | 34 4E 5B 5A E1 96 08 00 4A 00 00 00 4A 00 00 00 149 | 72 4D 4A D1 13 0D 4E 9C AE DE CB 73 08 00 45 00 150 | 00 3C DF 08 40 00 40 06 47 9F 0A 09 00 01 0A 09 151 | 00 02 D1 F4 11 51 34 1B 5B 17 00 00 00 00 A0 02 152 | 72 10 14 43 00 00 02 04 05 B4 04 02 08 0A E4 DB 153 | 6B 7B 00 00 00 00 01 03 03 07" 154 | ); 155 | // OpenVPN_UDP_tls-auth.pcapng EPB (first data block, file block 3) 156 | pub const FRAME_PCAPNG_EPB: &[u8] = &hex!( 157 | " 158 | 06 00 00 00 74 00 00 00 01 00 00 00 E9 D3 04 00 159 | 48 EE 39 44 54 00 00 00 54 00 00 00 08 00 27 4A 160 | BE 45 08 00 27 BB 22 84 08 00 45 00 00 46 00 00 161 | 40 00 40 11 48 89 C0 A8 38 67 C0 A8 38 66 81 AE 162 | 04 AA 00 32 53 B4 38 81 38 14 62 1D 67 46 2D DE 163 | 86 73 4D 2C BF F1 51 B2 B1 23 1B 61 E4 23 08 A2 164 | 72 81 8E 00 00 00 01 50 FF 26 2C 00 00 00 00 00 165 | 74 00 00 00" 166 | ); 167 | // test009.pcapng EPB (first data block) 168 | pub const FRAME_PCAPNG_EPB_WITH_OPTIONS: &[u8] = &hex!( 169 | " 170 | 06 00 00 00 F4 01 00 00 00 00 00 00 97 C3 04 00 171 | AA 47 CA 64 3A 01 00 00 3A 01 00 00 FF FF FF FF 172 | FF FF 00 0B 82 01 FC 42 08 00 45 00 01 2C A8 36 173 | 00 00 FA 11 17 8B 00 00 00 00 FF FF FF FF 00 44 174 | 00 43 01 18 59 1F 01 01 06 00 00 00 3D 1D 00 00 175 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 176 | 00 00 00 0B 82 01 FC 42 00 00 00 00 00 00 00 00 177 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 178 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 179 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 180 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 181 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 182 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 183 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 184 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 185 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 186 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 187 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 188 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 189 | 00 00 63 82 53 63 35 01 01 3D 07 01 00 0B 82 01 190 | FC 42 32 04 00 00 00 00 37 04 01 03 06 2A FF 00 191 | 00 00 00 00 00 00 00 00 01 00 09 00 74 65 73 74 192 | 30 30 39 2D 31 00 00 00 02 00 04 00 00 00 00 00 193 | 04 00 08 00 00 00 00 00 00 00 00 00 AC 0B 0D 00 194 | 61 20 66 61 6B 65 20 73 74 72 69 6E 67 00 00 00 195 | AD 0B 0F 00 73 6F 6D 65 20 66 61 6B 65 20 62 79 196 | 74 65 73 00 AC 4B 0E 00 6D 79 20 66 61 6B 65 20 197 | 73 74 72 69 6E 67 00 00 AD 4B 0D 00 6D 79 20 66 198 | 61 6B 65 20 62 79 74 65 73 00 00 00 23 01 0C 00 199 | 74 72 79 20 74 68 69 73 20 6F 6E 65 23 81 0C 00 200 | 61 6E 64 20 74 68 69 73 20 6F 6E 65 00 00 00 00 201 | F4 01 00 00" 202 | ); 203 | // block 3 from file dtls12-aes128ccm8-dsb.pcapng (wireshark repo) 204 | pub const FRAME_PCAPNG_DSB: &[u8] = &hex!( 205 | " 206 | 0a 00 00 00 c4 00 00 00 4b 53 4c 54 b0 00 00 00 207 | 43 4c 49 45 4e 54 5f 52 41 4e 44 4f 4d 20 35 38 208 | 38 65 35 66 39 64 63 37 37 38 63 65 66 32 32 34 209 | 30 35 66 34 32 66 39 62 65 61 32 35 39 32 38 62 210 | 64 30 33 31 32 63 65 31 34 64 36 34 32 64 30 33 211 | 34 64 32 34 66 34 66 61 62 36 37 32 66 63 20 37 212 | 30 35 37 66 33 64 37 30 36 63 66 30 36 38 30 61 213 | 34 30 65 34 66 32 65 30 37 34 37 63 65 37 38 63 214 | 65 39 38 64 61 32 36 32 32 65 62 39 61 39 35 34 215 | 33 66 37 66 31 35 34 36 33 37 34 34 31 35 37 32 216 | 35 36 61 37 39 36 64 62 35 30 62 62 65 36 35 63 217 | 64 62 64 63 32 39 32 61 30 39 33 33 35 62 34 0a 218 | c4 00 00 00" 219 | ); 220 | // SHB of test/captures/http-brotli.pcapng from wireshark repo 221 | pub const FRAME_PCAPNG_SHB: &[u8] = &hex!( 222 | " 223 | 0a 0d 0d 0a c4 00 00 00 4d 3c 2b 1a 01 00 00 00 224 | ff ff ff ff ff ff ff ff 02 00 37 00 49 6e 74 65 225 | 6c 28 52 29 20 43 6f 72 65 28 54 4d 29 20 69 37 226 | 2d 36 37 30 30 48 51 20 43 50 55 20 40 20 32 2e 227 | 36 30 47 48 7a 20 28 77 69 74 68 20 53 53 45 34 228 | 2e 32 29 00 03 00 2a 00 4c 69 6e 75 78 20 34 2e 229 | 32 30 2e 31 32 2d 67 65 6e 74 6f 6f 2d 61 6e 64 230 | 72 6f 6d 65 64 61 2d 32 30 31 39 30 33 30 35 2d 231 | 76 31 00 00 04 00 33 00 44 75 6d 70 63 61 70 20 232 | 28 57 69 72 65 73 68 61 72 6b 29 20 33 2e 31 2e 233 | 30 20 28 76 33 2e 31 2e 30 72 63 30 2d 34 36 38 234 | 2d 67 65 33 65 34 32 32 32 62 29 00 00 00 00 00 235 | c4 00 00 00" 236 | ); 237 | 238 | #[test] 239 | fn test_data() { 240 | let d = Data::Borrowed(&[0, 1, 2, 3]); 241 | assert_eq!(d.as_ref()[1], 1); 242 | assert_eq!(d[1], 1); 243 | assert_eq!(&d[1..=2], &[1, 2]); 244 | } 245 | #[test] 246 | fn test_pcap_packet_functions() { 247 | let (_, pkt) = parse_pcap_frame(FRAME_PCAP).expect("packet creation failed"); 248 | assert_eq!(pkt.origlen, 74); 249 | assert_eq!(pkt.ts_usec, 562_913); 250 | assert_eq!(pkt.ts_sec, 1_515_933_236); 251 | } 252 | #[test] 253 | fn test_pcapng_packet_functions() { 254 | let (rem, pkt) = parse_block_le(FRAME_PCAPNG_EPB).expect("packet creation failed"); 255 | assert!(rem.is_empty()); 256 | if let Block::EnhancedPacket(epb) = pkt { 257 | assert_eq!(epb.if_id, 1); 258 | assert_eq!(epb.origlen, 84); 259 | assert_eq!(epb.data.len(), 84); 260 | assert!(epb.options.is_empty()); 261 | } 262 | } 263 | #[test] 264 | fn test_pcapng_packet_epb_with_options() { 265 | let (rem, pkt) = 266 | parse_block_le(FRAME_PCAPNG_EPB_WITH_OPTIONS).expect("packet creation failed"); 267 | assert!(rem.is_empty()); 268 | if let Block::EnhancedPacket(epb) = pkt { 269 | assert_eq!(epb.if_id, 0); 270 | assert_eq!(epb.origlen, 314); 271 | assert_eq!(epb.data.len(), 316); // with padding 272 | assert_eq!(epb.packet_data().len(), 314); // without padding 273 | } 274 | } 275 | #[test] 276 | fn test_parse_enhancepacketblock() { 277 | let (rem, pkt) = 278 | parse_block_le(FRAME_PCAPNG_EPB_WITH_OPTIONS).expect("packet parsing failed"); 279 | assert!(rem.is_empty()); 280 | if let Block::EnhancedPacket(epb) = pkt { 281 | assert_eq!(epb.if_id, 0); 282 | assert_eq!(epb.origlen, 314); 283 | assert_eq!(epb.data.len(), 316); // with padding 284 | assert_eq!(epb.packet_data().len(), 314); // without padding 285 | println!("options: {:?}", epb.options); 286 | // use nom::HexDisplay; 287 | // println!("raw_options:\n{}", pkt.raw_options().to_hex(16)); 288 | } else { 289 | panic!("wrong packet type"); 290 | } 291 | } 292 | 293 | #[test] 294 | fn test_pcapng_decryptionsecretsblock() { 295 | let (rem, block) = parse_block_le(FRAME_PCAPNG_DSB).expect("could not parse DSB"); 296 | assert!(rem.is_empty()); 297 | if let Block::DecryptionSecrets(dsb) = block { 298 | assert_eq!(dsb.secrets_type, SecretsType::TlsKeyLog); 299 | assert!(std::str::from_utf8(dsb.data).is_ok()); 300 | } else { 301 | unreachable!(); 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::ops; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | /// A container for owned or borrowed data 5 | pub enum Data<'a> { 6 | Owned(Vec), 7 | Borrowed(&'a [u8]), 8 | } 9 | 10 | /// A container for owned or borrowed mutable data 11 | pub enum MutableData<'a> { 12 | Owned(Vec), 13 | Borrowed(&'a mut [u8]), 14 | } 15 | 16 | impl Data<'_> { 17 | #[inline] 18 | pub fn as_slice(&self) -> &[u8] { 19 | match self { 20 | Data::Owned(ref o) => o.deref(), 21 | Data::Borrowed(b) => b, 22 | } 23 | } 24 | #[inline] 25 | pub fn len(&self) -> usize { 26 | match self { 27 | Data::Owned(ref o) => o.len(), 28 | Data::Borrowed(b) => b.len(), 29 | } 30 | } 31 | #[inline] 32 | pub fn is_empty(&self) -> bool { 33 | match self { 34 | Data::Owned(ref o) => o.is_empty(), 35 | Data::Borrowed(b) => b.is_empty(), 36 | } 37 | } 38 | } 39 | 40 | impl<'a> MutableData<'a> { 41 | #[inline] 42 | pub fn as_slice(&self) -> &[u8] { 43 | match self { 44 | MutableData::Owned(ref o) => o.deref(), 45 | MutableData::Borrowed(ref b) => b, 46 | } 47 | } 48 | #[inline] 49 | pub fn as_mut_slice(&mut self) -> &mut [u8] { 50 | match self { 51 | MutableData::Owned(ref mut o) => o.deref_mut(), 52 | MutableData::Borrowed(ref mut b) => b, 53 | } 54 | } 55 | #[inline] 56 | pub fn len(&self) -> usize { 57 | match self { 58 | MutableData::Owned(ref o) => o.len(), 59 | MutableData::Borrowed(ref b) => b.len(), 60 | } 61 | } 62 | #[inline] 63 | pub fn is_empty(&self) -> bool { 64 | match self { 65 | MutableData::Owned(ref o) => o.is_empty(), 66 | MutableData::Borrowed(ref b) => b.is_empty(), 67 | } 68 | } 69 | /// Get an immutable version of the data 70 | pub fn into_immutable(self) -> Data<'a> { 71 | match self { 72 | MutableData::Owned(data) => Data::Owned(data), 73 | MutableData::Borrowed(data) => Data::Borrowed(data), 74 | } 75 | } 76 | } 77 | 78 | /* AsRef */ 79 | 80 | impl AsRef<[u8]> for Data<'_> { 81 | #[inline] 82 | fn as_ref(&self) -> &[u8] { 83 | self.as_slice() 84 | } 85 | } 86 | 87 | impl AsRef<[u8]> for MutableData<'_> { 88 | #[inline] 89 | fn as_ref(&self) -> &[u8] { 90 | self.as_slice() 91 | } 92 | } 93 | 94 | impl AsMut<[u8]> for MutableData<'_> { 95 | #[inline] 96 | fn as_mut(&mut self) -> &mut [u8] { 97 | self.as_mut_slice() 98 | } 99 | } 100 | 101 | /* Index */ 102 | 103 | macro_rules! impl_index { 104 | ($t:ident, $index_t:ty, $output_t:ty) => { 105 | impl<'p> ops::Index<$index_t> for $t<'p> { 106 | type Output = $output_t; 107 | #[inline] 108 | fn index(&self, index: $index_t) -> &$output_t { 109 | &self.as_slice().index(index) 110 | } 111 | } 112 | }; 113 | } 114 | 115 | macro_rules! impl_index_mut { 116 | ($t:ident, $index_t:ty, $output_t:ty) => { 117 | impl<'p> ops::IndexMut<$index_t> for $t<'p> { 118 | #[inline] 119 | fn index_mut(&mut self, index: $index_t) -> &mut $output_t { 120 | self.as_mut_slice().index_mut(index) 121 | } 122 | } 123 | }; 124 | } 125 | 126 | impl_index!(Data, usize, u8); 127 | impl_index!(Data, ops::Range, [u8]); 128 | impl_index!(Data, ops::RangeTo, [u8]); 129 | impl_index!(Data, ops::RangeFrom, [u8]); 130 | impl_index!(Data, ops::RangeFull, [u8]); 131 | impl_index!(Data, ops::RangeInclusive, [u8]); 132 | impl_index!(Data, ops::RangeToInclusive, [u8]); 133 | 134 | impl_index!(MutableData, usize, u8); 135 | impl_index!(MutableData, ops::Range, [u8]); 136 | impl_index!(MutableData, ops::RangeTo, [u8]); 137 | impl_index!(MutableData, ops::RangeFrom, [u8]); 138 | impl_index!(MutableData, ops::RangeFull, [u8]); 139 | impl_index!(MutableData, ops::RangeInclusive, [u8]); 140 | impl_index!(MutableData, ops::RangeToInclusive, [u8]); 141 | 142 | impl_index_mut!(MutableData, usize, u8); 143 | impl_index_mut!(MutableData, ops::Range, [u8]); 144 | impl_index_mut!(MutableData, ops::RangeTo, [u8]); 145 | impl_index_mut!(MutableData, ops::RangeFrom, [u8]); 146 | impl_index_mut!(MutableData, ops::RangeFull, [u8]); 147 | impl_index_mut!(MutableData, ops::RangeInclusive, [u8]); 148 | impl_index_mut!(MutableData, ops::RangeToInclusive, [u8]); 149 | 150 | /* ******************* */ 151 | 152 | #[doc(hidden)] 153 | #[macro_export] 154 | macro_rules! read_u32_e { 155 | ($data:expr, $endian:expr) => { 156 | if $endian { 157 | let data = $data; 158 | (data[0] as u32) << 24 159 | | (data[1] as u32) << 16 160 | | (data[2] as u32) << 8 161 | | (data[3] as u32) 162 | } else { 163 | let data = $data; 164 | (data[3] as u32) << 24 165 | | (data[2] as u32) << 16 166 | | (data[1] as u32) << 8 167 | | (data[0] as u32) 168 | } 169 | }; 170 | } 171 | 172 | #[doc(hidden)] 173 | #[macro_export] 174 | macro_rules! write_u32_e { 175 | ($data:expr, $val:expr, $endian:expr) => { 176 | let data = $data; 177 | let v = $val; 178 | let v1: u8 = ((v >> 24) & 0xff) as u8; 179 | let v2: u8 = ((v >> 16) & 0xff) as u8; 180 | let v3: u8 = ((v >> 8) & 0xff) as u8; 181 | let v4: u8 = ((v) & 0xff) as u8; 182 | if $endian { 183 | data[0] = v1; 184 | data[1] = v2; 185 | data[2] = v3; 186 | data[3] = v4; 187 | } else { 188 | data[0] = v4; 189 | data[1] = v3; 190 | data[2] = v2; 191 | data[3] = v1; 192 | } 193 | }; 194 | } 195 | 196 | /// Generate an array reference to a subset 197 | /// of a sliceable bit of data (which could be an array, or a slice, 198 | /// or a Vec). 199 | /// 200 | /// **Panics** if the slice is out of bounds. 201 | /// 202 | /// implementation inspired from arrayref::array_ref 203 | #[allow(unsafe_code)] 204 | #[inline] 205 | pub(crate) fn array_ref4(s: &[u8], offset: usize) -> &[u8; 4] { 206 | #[inline] 207 | unsafe fn as_array(slice: &[T]) -> &[T; 4] { 208 | &*(slice.as_ptr() as *const [_; 4]) 209 | } 210 | let slice = &s[offset..offset + 4]; 211 | #[allow(unused_unsafe)] 212 | unsafe { 213 | as_array(slice) 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /tests/pcap.rs: -------------------------------------------------------------------------------- 1 | use pcap_parser::traits::PcapReaderIterator; 2 | use pcap_parser::*; 3 | use std::fs::File; 4 | use std::io::{BufReader, Read}; 5 | 6 | static TEST_NTP: &[u8] = include_bytes!("../assets/ntp.pcap"); 7 | 8 | #[test] 9 | fn test_pcap_capture_from_file_and_iter_le() { 10 | let cap = PcapCapture::from_file(TEST_NTP).expect("could not parse file into PcapNGCapture"); 11 | for block in cap.iter() { 12 | match block { 13 | PcapBlock::LegacyHeader(_) => (), 14 | PcapBlock::Legacy(b) => { 15 | assert_eq!(b.caplen, 90); 16 | } 17 | PcapBlock::NG(_) => panic!("unexpected NG data"), 18 | } 19 | } 20 | } 21 | 22 | #[test] 23 | fn test_pcap_reader() { 24 | let path = "assets/ntp.pcap"; 25 | let file = File::open(path).unwrap(); 26 | let buffered = BufReader::new(file); 27 | let mut num_blocks = 0; 28 | let mut reader = LegacyPcapReader::new(65536, buffered).expect("LegacyPcapReader"); 29 | loop { 30 | match reader.next() { 31 | Ok((offset, block)) => { 32 | num_blocks += 1; 33 | match block { 34 | PcapBlockOwned::LegacyHeader(_) => (), 35 | PcapBlockOwned::Legacy(b) => { 36 | assert_eq!(b.caplen, 90); 37 | } 38 | PcapBlockOwned::NG(_) => panic!("unexpected NG data"), 39 | } 40 | reader.consume(offset); 41 | } 42 | Err(PcapError::Eof) => break, 43 | Err(PcapError::Incomplete(_)) => { 44 | reader.refill().unwrap(); 45 | } 46 | Err(e) => panic!("error while reading: {:?}", e), 47 | } 48 | } 49 | assert_eq!(num_blocks, 13); /* 1 (header) + 12 (data blocks) */ 50 | } 51 | 52 | #[test] 53 | fn test_truncated_pcap() { 54 | let path = "assets/ntp.pcap"; 55 | let mut file = File::open(path).unwrap(); 56 | // truncate pcap 57 | let mut buf = vec![0; 981]; 58 | file.read_exact(&mut buf).unwrap(); 59 | let mut reader = LegacyPcapReader::new(65536, &buf[..]).expect("LegacyPcapReader"); 60 | let mut incomplete_count: u32 = 0; 61 | loop { 62 | match reader.next() { 63 | Ok((offset, _block)) => { 64 | reader.consume(offset); 65 | } 66 | Err(PcapError::Eof) => unreachable!("should not parse without error"), 67 | Err(PcapError::Incomplete(_)) => { 68 | reader.refill().unwrap(); 69 | incomplete_count += 1; 70 | if incomplete_count > 1 << 20 { 71 | panic!("reader stuck in infinite loop"); 72 | } 73 | } 74 | Err(PcapError::UnexpectedEof) => return, 75 | Err(e) => panic!("error while reading: {:?}", e), 76 | } 77 | } 78 | } 79 | 80 | #[test] 81 | fn test_modified_format() { 82 | let path = "assets/modified-format.pcap"; 83 | let file = File::open(path).unwrap(); 84 | let buffered = BufReader::new(file); 85 | let mut num_blocks = 0; 86 | let mut reader = LegacyPcapReader::new(65536, buffered).expect("LegacyPcapReader"); 87 | loop { 88 | match reader.next() { 89 | Ok((offset, block)) => { 90 | num_blocks += 1; 91 | match block { 92 | PcapBlockOwned::LegacyHeader(_) => (), 93 | PcapBlockOwned::Legacy(b) => { 94 | assert_eq!(b.caplen, 98); 95 | } 96 | PcapBlockOwned::NG(_) => panic!("unexpected NG data"), 97 | } 98 | reader.consume(offset); 99 | } 100 | Err(PcapError::Eof) => break, 101 | Err(PcapError::Incomplete(_)) => { 102 | reader.refill().unwrap(); 103 | } 104 | Err(e) => panic!("error while reading: {:?}", e), 105 | } 106 | } 107 | assert_eq!(num_blocks, 2); 108 | } 109 | -------------------------------------------------------------------------------- /tests/reader.rs: -------------------------------------------------------------------------------- 1 | use pcap_parser::{create_reader, PcapError}; 2 | 3 | #[test] 4 | fn test_empty_reader_error() { 5 | let empty: &[u8] = &[]; 6 | let res = create_reader(1024, empty); 7 | assert!(res.is_err()); 8 | if let Err(err) = res { 9 | assert_eq!(err, PcapError::Eof); 10 | } else { 11 | unreachable!(); 12 | } 13 | } 14 | 15 | #[test] 16 | fn test_empty_reader_incomplete() { 17 | let empty: &[u8] = &[0]; 18 | let res = create_reader(1024, empty); 19 | assert!(res.is_err()); 20 | if let Err(err) = res { 21 | assert!(matches!(err, PcapError::Incomplete(_))); 22 | } else { 23 | unreachable!(); 24 | } 25 | } 26 | --------------------------------------------------------------------------------