├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── fuzz.sh ├── fuzz ├── .gitignore ├── Cargo.toml ├── README.md ├── hfuzz_workspace │ ├── arp │ │ └── input │ │ │ └── arp │ ├── ethernet │ │ └── input │ │ │ ├── ethernet │ │ │ └── ethernet-vlan │ ├── gre │ │ └── input │ │ │ ├── gre │ │ │ ├── gre-sequence │ │ │ └── ppp │ ├── icmp │ │ └── input │ │ │ └── icmp │ ├── ipv4 │ │ └── input │ │ │ ├── ipv4 │ │ │ └── ipv4-options │ ├── ipv6 │ │ └── input │ │ │ ├── ipv6 │ │ │ └── ipv6-extension-header │ ├── tcp │ │ └── input │ │ │ ├── tcp │ │ │ └── tcp-options │ └── udp │ │ └── input │ │ └── udp └── src │ ├── bin │ ├── arp.rs │ ├── ethernet.rs │ ├── gre.rs │ ├── icmp.rs │ ├── ipv4.rs │ ├── ipv6.rs │ ├── tcp.rs │ └── udp.rs │ └── lib.rs ├── rustfmt.toml ├── src ├── arp.rs ├── ethernet.rs ├── gre.rs ├── icmp.rs ├── ip.rs ├── lib.rs ├── tcp.rs ├── udp.rs └── util.rs ├── test.sh └── tests ├── README.md ├── pcaps ├── GRE.pcap ├── ICMP_across_dot1q.pcap ├── ICMPv6_echos.pcap ├── PPTP_negotiation.pcap ├── QinQ.pcap.pcap ├── TCP_SACK.pcap ├── connection termination.pcap ├── gre-erspan.pcap ├── gre-sample.pcap ├── gre_and_4over6.pcap ├── ipv4-udp-fragmented.pcap ├── ipv6-gtp6.pcap ├── ipv6-udp-fragmented.pcap ├── nb6-startup.pcap ├── vpws-selftest-customer.pcap ├── vpws-selftest-uplink.pcap └── vxlan.pcap └── tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | dist: bionic 4 | 5 | language: rust 6 | rust: 7 | - stable 8 | 9 | before_install: 10 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -y install libpcap-dev tshark; fi 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pdu" 3 | version = "1.4.2" 4 | description = "Small, fast, and correct L2/L3/L4 packet parser." 5 | readme = "README.md" 6 | authors = ["Alex Forster "] 7 | repository = "https://github.com/alexforster/pdu" 8 | homepage = "https://github.com/alexforster/pdu" 9 | keywords = ["ethernet", "ip", "tcp", "udp", "gre"] 10 | categories = ["parsing", "network-programming", "no-std"] 11 | license = "Apache-2.0" 12 | edition = "2018" 13 | 14 | [badges] 15 | travis-ci = { repository = "alexforster/pdu" } 16 | maintenance = { status = "passively-maintained" } 17 | 18 | [features] 19 | default = ["std"] 20 | std = [] 21 | 22 | [dev-dependencies] 23 | base16 = { version = "~0.2" } 24 | roxmltree = { version = "~0.14", features = ["std"] } 25 | pcap = { version = "~0.9" } 26 | 27 | [workspace] 28 | members = [ 29 | "fuzz" 30 | ] 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pdu 2 | 3 | Small, fast, and correct L2/L3/L4 packet parser. 4 | 5 | **Author:** Alex Forster \
6 | **License:** Apache-2.0 7 | 8 | [![build status](https://travis-ci.org/alexforster/pdu.svg?branch=master)](https://travis-ci.org/alexforster/pdu) 9 | [![crates.io version](https://img.shields.io/crates/v/pdu.svg)](https://crates.io/crates/pdu) 10 | [![docs.rs](https://docs.rs/pdu/badge.svg)](https://docs.rs/pdu) 11 | 12 | #### Small 13 | 14 | * Fully-featured `no_std` support 15 | * No Crate dependencies and no macros 16 | * Internet protocols only: application-layer protocols are out of scope 17 | 18 | #### Fast 19 | 20 | * Lazy parsing: only the fields that you access are parsed 21 | * Zero-copy construction: no heap allocations are performed 22 | 23 | #### Correct 24 | 25 | * Tested against [Wireshark](https://www.wireshark.org/docs/man-pages/tshark.html) to ensure all packet fields are parsed correctly 26 | * Fuzzed using [Honggfuzz](https://github.com/google/honggfuzz) to ensure invalid input does not cause panics 27 | * Does not use any `unsafe` code 28 | 29 | ## Supported Protocols 30 | 31 | The following protocol hierarchy can be parsed with this library: 32 | 33 | * Ethernet (including vlan) 34 | * ARP 35 | * IPv4 (including options) 36 | * TCP (including options) 37 | * UDP 38 | * ICMP 39 | * GREv0 40 | * ...Ethernet, IPv4, IPv6... 41 | * IPv6 (including extension headers) 42 | * TCP (including options) 43 | * UDP 44 | * ICMPv6 45 | * GREv0 46 | * ...Ethernet, IPv4, IPv6... 47 | 48 | In addition, unrecognized upper protocols are accessible as bytes via `Raw` 49 | enum variants. 50 | 51 | ## Getting Started 52 | 53 | #### `Cargo.toml` 54 | 55 | ```toml 56 | [dependencies] 57 | pdu = "1.1" 58 | ``` 59 | 60 | #### Examples 61 | 62 | ```rust 63 | use pdu::*; 64 | 65 | // parse a layer 2 (Ethernet) packet using EthernetPdu::new() 66 | 67 | fn main() { 68 | let packet: &[u8] = &[ 69 | 0x68, 0x5b, 0x35, 0xc0, 0x61, 0xb6, 0x00, 0x1d, 0x09, 0x94, 0x65, 0x38, 0x08, 0x00, 0x45, 0x00, 0x00, 70 | 0x3b, 0x2d, 0xfd, 0x00, 0x00, 0x40, 0x11, 0xbc, 0x43, 0x83, 0xb3, 0xc4, 0x2e, 0x83, 0xb3, 0xc4, 0xdc, 71 | 0x18, 0xdb, 0x18, 0xdb, 0x00, 0x27, 0xe0, 0x3e, 0x05, 0x1d, 0x07, 0x15, 0x08, 0x07, 0x65, 0x78, 0x61, 72 | 0x6d, 0x70, 0x6c, 0x65, 0x08, 0x07, 0x74, 0x65, 0x73, 0x74, 0x41, 0x70, 0x70, 0x08, 0x01, 0x31, 0x0a, 73 | 0x04, 0x1e, 0xcc, 0xe2, 0x51, 74 | ]; 75 | 76 | match EthernetPdu::new(&packet) { 77 | Ok(ethernet_pdu) => { 78 | println!("[ethernet] destination_address: {:x?}", ethernet_pdu.destination_address().as_ref()); 79 | println!("[ethernet] source_address: {:x?}", ethernet_pdu.source_address().as_ref()); 80 | println!("[ethernet] ethertype: 0x{:04x}", ethernet_pdu.ethertype()); 81 | if let Some(vlan) = ethernet_pdu.vlan() { 82 | println!("[ethernet] vlan: 0x{:04x}", vlan); 83 | } 84 | // upper-layer protocols can be accessed via the inner() method 85 | match ethernet_pdu.inner() { 86 | Ok(Ethernet::Ipv4(ipv4_pdu)) => { 87 | println!("[ipv4] source_address: {:x?}", ipv4_pdu.source_address().as_ref()); 88 | println!("[ipv4] destination_address: {:x?}", ipv4_pdu.destination_address().as_ref()); 89 | println!("[ipv4] protocol: 0x{:02x}", ipv4_pdu.protocol()); 90 | // upper-layer protocols can be accessed via the inner() method (not shown) 91 | } 92 | Ok(Ethernet::Ipv6(ipv6_pdu)) => { 93 | println!("[ipv6] source_address: {:x?}", ipv6_pdu.source_address().as_ref()); 94 | println!("[ipv6] destination_address: {:x?}", ipv6_pdu.destination_address().as_ref()); 95 | println!("[ipv6] protocol: 0x{:02x}", ipv6_pdu.computed_protocol()); 96 | // upper-layer protocols can be accessed via the inner() method (not shown) 97 | } 98 | Ok(other) => { 99 | panic!("Unexpected protocol {:?}", other); 100 | } 101 | Err(e) => { 102 | panic!("EthernetPdu::inner() parser failure: {:?}", e); 103 | } 104 | } 105 | } 106 | Err(e) => { 107 | panic!("EthernetPdu::new() parser failure: {:?}", e); 108 | } 109 | } 110 | } 111 | ``` 112 | 113 | ```rust 114 | use pdu::*; 115 | 116 | // parse a layer 3 (IP) packet using Ip::new() 117 | 118 | fn main() { 119 | let packet: &[u8] = &[ 120 | 0x45, 0x00, 0x00, 0x3b, 0x2d, 0xfd, 0x00, 0x00, 0x40, 0x11, 0xbc, 0x43, 0x83, 0xb3, 0xc4, 0x2e, 0x83, 0xb3, 121 | 0xc4, 0xdc, 0x18, 0xdb, 0x18, 0xdb, 0x00, 0x27, 0xe0, 0x3e, 0x05, 0x1d, 0x07, 0x15, 0x08, 0x07, 0x65, 0x78, 122 | 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x08, 0x07, 0x74, 0x65, 0x73, 0x74, 0x41, 0x70, 0x70, 0x08, 0x01, 0x31, 0x0a, 123 | 0x04, 0x1e, 0xcc, 0xe2, 0x51, 124 | ]; 125 | 126 | match Ip::new(&packet) { 127 | Ok(Ip::Ipv4(ipv4_pdu)) => { 128 | println!("[ipv4] source_address: {:x?}", ipv4_pdu.source_address().as_ref()); 129 | println!("[ipv4] destination_address: {:x?}", ipv4_pdu.destination_address().as_ref()); 130 | println!("[ipv4] protocol: 0x{:02x}", ipv4_pdu.protocol()); 131 | // upper-layer protocols can be accessed via the inner() method (not shown) 132 | } 133 | Ok(Ip::Ipv6(ipv6_pdu)) => { 134 | println!("[ipv6] source_address: {:x?}", ipv6_pdu.source_address().as_ref()); 135 | println!("[ipv6] destination_address: {:x?}", ipv6_pdu.destination_address().as_ref()); 136 | println!("[ipv6] protocol: 0x{:02x}", ipv6_pdu.computed_protocol()); 137 | // upper-layer protocols can be accessed via the inner() method (not shown) 138 | } 139 | Err(e) => { 140 | panic!("Ip::new() parser failure: {:?}", e); 141 | } 142 | } 143 | } 144 | ``` 145 | -------------------------------------------------------------------------------- /fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "clean" = "$1" ]; then 4 | echo $(pwd)/fuzz/hfuzz_target 5 | rm -rf $(pwd)/fuzz/hfuzz_target 6 | for FILE in $(pwd)/fuzz/hfuzz_workspace/*/*.*; do 7 | echo $FILE 8 | rm -f $FILE 9 | done 10 | exit 11 | fi 12 | 13 | DOCKER="docker" 14 | 15 | ${DOCKER} build -t pdu-fuzz - <<'EOF' 16 | FROM ubuntu:bionic 17 | ENV LANG=C.UTF-8 \ 18 | LC_ALL=C.UTF-8 19 | VOLUME /usr/local/src/pdu 20 | WORKDIR /usr/local/src/pdu 21 | SHELL ["/bin/bash", "-eu", "-o", "pipefail", "-c"] 22 | RUN \ 23 | export DEBIAN_FRONTEND=noninteractive; \ 24 | apt-get -q update; \ 25 | apt-get -q install -y curl build-essential linux-headers-generic pkg-config binutils-dev libunwind-dev libpcap-dev tshark; \ 26 | apt-get -q clean autoclean; 27 | RUN \ 28 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable; \ 29 | source $HOME/.cargo/env; \ 30 | cargo install honggfuzz; 31 | ENTRYPOINT \ 32 | mkdir -p /tmp/honggfuzz/$FUZZ_TARGET; \ 33 | source $HOME/.cargo/env; \ 34 | cd ./fuzz; \ 35 | RUSTFLAGS="-C link-dead-code" HFUZZ_RUN_ARGS="-t 5 -T --output /tmp/honggfuzz/$FUZZ_TARGET" cargo hfuzz run $FUZZ_TARGET 36 | EOF 37 | 38 | if [ -z "$1" ]; then 39 | echo "Usage: fuzz.sh [ clean | ethernet | arp | ipv4 | ipv6 | tcp | udp | icmp | gre ]" 40 | fi 41 | 42 | ${DOCKER} run --init --rm -v "$(pwd):/usr/local/src/pdu" -e FUZZ_TARGET=$1 pdu-fuzz 43 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | hfuzz_target/ 2 | hfuzz_workspace/**/*.cov 3 | !hfuzz_workspace/**/ 4 | !.gitignore 5 | .* 6 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzz" 3 | version = "0.0.0" 4 | authors = ["Alex Forster "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | pdu = { path = ".." } 10 | honggfuzz = { version = "~0.5" } 11 | -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | ### Fuzzing Harness 2 | 3 | This library uses Google's [Honggfuzz](https://google.github.io/honggfuzz/) to 4 | verify correct behavior in the face of invalid input. 5 | 6 | **Note:** you must first `cargo install honggfuzz` so that the `cargo hfuzz` 7 | subcommand is available. 8 | 9 | To fuzz this library, change into this working directory and then run... 10 | 11 | `RUSTFLAGS="-C link-dead-code" cargo hfuzz run ` 12 | 13 | ...where *name* is one of `arp`, `ethernet`, `gre`, `icmp`, `ipv4`, `ipv6`, 14 | `tcp`, or `udp`. 15 | -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/arp/input/arp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/arp/input/arp -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/ethernet/input/ethernet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/ethernet/input/ethernet -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/ethernet/input/ethernet-vlan: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/ethernet/input/ethernet-vlan -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/gre/input/gre: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/gre/input/gre-sequence: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/gre/input/gre-sequence -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/gre/input/ppp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/gre/input/ppp -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/icmp/input/icmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/icmp/input/icmp -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/ipv4/input/ipv4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/ipv4/input/ipv4 -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/ipv4/input/ipv4-options: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/ipv4/input/ipv4-options -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/ipv6/input/ipv6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/ipv6/input/ipv6 -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/ipv6/input/ipv6-extension-header: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/ipv6/input/ipv6-extension-header -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/tcp/input/tcp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/tcp/input/tcp -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/tcp/input/tcp-options: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/tcp/input/tcp-options -------------------------------------------------------------------------------- /fuzz/hfuzz_workspace/udp/input/udp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/fuzz/hfuzz_workspace/udp/input/udp -------------------------------------------------------------------------------- /fuzz/src/bin/arp.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use pdu::*; 20 | 21 | pub fn fuzz(data: &[u8]) { 22 | match ArpPdu::new(&data) { 23 | Ok(arp_pdu) => { 24 | arp_pdu.hardware_type(); 25 | arp_pdu.protocol_type(); 26 | arp_pdu.hardware_length(); 27 | arp_pdu.protocol_length(); 28 | arp_pdu.opcode(); 29 | arp_pdu.sender_hardware_address(); 30 | arp_pdu.sender_protocol_address(); 31 | arp_pdu.target_hardware_address(); 32 | arp_pdu.target_protocol_address(); 33 | } 34 | Err(_) => {} 35 | } 36 | } 37 | 38 | fn main() { 39 | loop { 40 | honggfuzz::fuzz!(|data: &[u8]| { 41 | fuzz(&data); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /fuzz/src/bin/ethernet.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use pdu::*; 20 | 21 | pub fn fuzz(data: &[u8]) { 22 | match EthernetPdu::new(&data) { 23 | Ok(ethernet_pdu) => { 24 | ethernet_pdu.computed_ihl(); 25 | ethernet_pdu.destination_address(); 26 | ethernet_pdu.source_address(); 27 | ethernet_pdu.ethertype(); 28 | ethernet_pdu.vlan(); 29 | ethernet_pdu.vlan_pcp(); 30 | ethernet_pdu.vlan_dei(); 31 | } 32 | Err(_) => {} 33 | } 34 | } 35 | 36 | fn main() { 37 | loop { 38 | honggfuzz::fuzz!(|data: &[u8]| { 39 | fuzz(&data); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /fuzz/src/bin/gre.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use pdu::*; 20 | 21 | pub fn fuzz(data: &[u8]) { 22 | match GrePdu::new(&data) { 23 | Ok(gre_pdu) => { 24 | gre_pdu.computed_ihl(); 25 | gre_pdu.version(); 26 | gre_pdu.ethertype(); 27 | gre_pdu.checksum(); 28 | gre_pdu.computed_checksum(); 29 | gre_pdu.key(); 30 | gre_pdu.sequence_number(); 31 | } 32 | Err(_) => {} 33 | } 34 | } 35 | 36 | fn main() { 37 | loop { 38 | honggfuzz::fuzz!(|data: &[u8]| { 39 | fuzz(&data); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /fuzz/src/bin/icmp.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use pdu::*; 20 | 21 | pub fn fuzz(data: &[u8]) { 22 | match IcmpPdu::new(&data) { 23 | Ok(icmp_pdu) => { 24 | icmp_pdu.message_type(); 25 | icmp_pdu.message_code(); 26 | icmp_pdu.checksum(); 27 | let ip = Ip::Ipv4( 28 | Ipv4Pdu::new(&[ 29 | 0x45u8, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 31 | ]) 32 | .unwrap(), 33 | ); 34 | icmp_pdu.computed_checksum(&ip); 35 | let ip = Ip::Ipv6( 36 | Ipv6Pdu::new(&[ 37 | 0x60u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 40 | ]) 41 | .unwrap(), 42 | ); 43 | icmp_pdu.computed_checksum(&ip); 44 | } 45 | Err(_) => {} 46 | } 47 | } 48 | 49 | fn main() { 50 | loop { 51 | honggfuzz::fuzz!(|data: &[u8]| { 52 | fuzz(&data); 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /fuzz/src/bin/ipv4.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use pdu::*; 20 | 21 | pub fn fuzz(data: &[u8]) { 22 | match Ipv4Pdu::new(&data) { 23 | Ok(ipv4_pdu) => { 24 | ipv4_pdu.version(); 25 | ipv4_pdu.ihl(); 26 | ipv4_pdu.computed_ihl(); 27 | ipv4_pdu.dscp(); 28 | ipv4_pdu.ecn(); 29 | ipv4_pdu.total_length(); 30 | ipv4_pdu.identification(); 31 | ipv4_pdu.dont_fragment(); 32 | ipv4_pdu.more_fragments(); 33 | ipv4_pdu.fragment_offset(); 34 | ipv4_pdu.ttl(); 35 | ipv4_pdu.protocol(); 36 | ipv4_pdu.checksum(); 37 | ipv4_pdu.computed_checksum(); 38 | ipv4_pdu.source_address(); 39 | ipv4_pdu.destination_address(); 40 | for option in ipv4_pdu.options() { 41 | match option { 42 | Ipv4Option::Raw { .. } => { 43 | continue; 44 | } 45 | } 46 | } 47 | } 48 | Err(_) => {} 49 | } 50 | } 51 | 52 | fn main() { 53 | loop { 54 | honggfuzz::fuzz!(|data: &[u8]| { 55 | fuzz(&data); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /fuzz/src/bin/ipv6.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use pdu::*; 20 | 21 | pub fn fuzz(data: &[u8]) { 22 | match Ipv6Pdu::new(&data) { 23 | Ok(ipv6_pdu) => { 24 | ipv6_pdu.version(); 25 | ipv6_pdu.dscp(); 26 | ipv6_pdu.ecn(); 27 | ipv6_pdu.flow_label(); 28 | ipv6_pdu.payload_length(); 29 | ipv6_pdu.next_header(); 30 | ipv6_pdu.computed_ihl(); 31 | ipv6_pdu.computed_protocol(); 32 | ipv6_pdu.computed_identification(); 33 | ipv6_pdu.computed_more_fragments(); 34 | ipv6_pdu.computed_fragment_offset(); 35 | ipv6_pdu.hop_limit(); 36 | ipv6_pdu.source_address(); 37 | ipv6_pdu.destination_address(); 38 | for extension_header in ipv6_pdu.extension_headers() { 39 | match extension_header { 40 | Ipv6ExtensionHeader::Raw { .. } => { 41 | continue; 42 | } 43 | Ipv6ExtensionHeader::Fragment { .. } => { 44 | continue; 45 | } 46 | } 47 | } 48 | } 49 | Err(_) => {} 50 | } 51 | } 52 | 53 | fn main() { 54 | loop { 55 | honggfuzz::fuzz!(|data: &[u8]| { 56 | fuzz(&data); 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /fuzz/src/bin/tcp.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use pdu::*; 20 | 21 | pub fn fuzz(data: &[u8]) { 22 | match TcpPdu::new(&data) { 23 | Ok(tcp_pdu) => { 24 | tcp_pdu.source_port(); 25 | tcp_pdu.destination_port(); 26 | tcp_pdu.sequence_number(); 27 | tcp_pdu.acknowledgement_number(); 28 | tcp_pdu.data_offset(); 29 | tcp_pdu.computed_data_offset(); 30 | tcp_pdu.flags(); 31 | tcp_pdu.fin(); 32 | tcp_pdu.syn(); 33 | tcp_pdu.rst(); 34 | tcp_pdu.psh(); 35 | tcp_pdu.ack(); 36 | tcp_pdu.urg(); 37 | tcp_pdu.ecn(); 38 | tcp_pdu.cwr(); 39 | tcp_pdu.window_size(); 40 | tcp_pdu.computed_window_size(14); 41 | tcp_pdu.checksum(); 42 | let ip = Ip::Ipv4( 43 | Ipv4Pdu::new(&[ 44 | 0x45u8, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | 0x00, 0x00, 0x00, 0x00, 46 | ]) 47 | .unwrap(), 48 | ); 49 | tcp_pdu.computed_checksum(&ip); 50 | let ip = Ip::Ipv6( 51 | Ipv6Pdu::new(&[ 52 | 0x60u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | ]) 56 | .unwrap(), 57 | ); 58 | tcp_pdu.computed_checksum(&ip); 59 | tcp_pdu.urgent_pointer(); 60 | for option in tcp_pdu.options() { 61 | match option { 62 | TcpOption::Raw { .. } => { 63 | continue; 64 | } 65 | TcpOption::NoOp => { 66 | continue; 67 | } 68 | TcpOption::Mss { .. } => { 69 | continue; 70 | } 71 | TcpOption::WindowScale { .. } => { 72 | continue; 73 | } 74 | TcpOption::SackPermitted => { 75 | continue; 76 | } 77 | TcpOption::Sack { .. } => { 78 | continue; 79 | } 80 | TcpOption::Timestamp { .. } => { 81 | continue; 82 | } 83 | } 84 | } 85 | } 86 | Err(_) => {} 87 | } 88 | } 89 | 90 | fn main() { 91 | loop { 92 | honggfuzz::fuzz!(|data: &[u8]| { 93 | fuzz(&data); 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /fuzz/src/bin/udp.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use pdu::*; 20 | 21 | pub fn fuzz(data: &[u8]) { 22 | match UdpPdu::new(&data) { 23 | Ok(udp_pdu) => { 24 | udp_pdu.source_port(); 25 | udp_pdu.destination_port(); 26 | udp_pdu.length(); 27 | udp_pdu.checksum(); 28 | let ip = Ip::Ipv4( 29 | Ipv4Pdu::new(&[ 30 | 0x45u8, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0x00, 0x00, 0x00, 0x00, 32 | ]) 33 | .unwrap(), 34 | ); 35 | udp_pdu.computed_checksum(&ip); 36 | let ip = Ip::Ipv6( 37 | Ipv6Pdu::new(&[ 38 | 0x60u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 41 | ]) 42 | .unwrap(), 43 | ); 44 | udp_pdu.computed_checksum(&ip); 45 | } 46 | Err(_) => {} 47 | } 48 | } 49 | 50 | fn main() { 51 | loop { 52 | honggfuzz::fuzz!(|data: &[u8]| { 53 | fuzz(&data); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /fuzz/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | 3 | max_width = 120 4 | comment_width = 120 5 | wrap_comments = true 6 | use_small_heuristics = "Max" 7 | fn_args_layout = "Compressed" 8 | -------------------------------------------------------------------------------- /src/arp.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use core::convert::TryInto; 20 | 21 | use crate::{Error, Result}; 22 | 23 | /// Represents an ARP payload 24 | #[derive(Debug, Copy, Clone)] 25 | pub struct ArpPdu<'a> { 26 | buffer: &'a [u8], 27 | } 28 | 29 | impl<'a> ArpPdu<'a> { 30 | /// Constructs an [`ArpPdu`] backed by the provided `buffer` 31 | pub fn new(buffer: &'a [u8]) -> Result { 32 | if buffer.len() < 12 { 33 | return Err(Error::Truncated); 34 | } 35 | let pdu = ArpPdu { buffer }; 36 | if pdu.hardware_length() != 6 { 37 | // we only support 6-octet hardware addresses 38 | return Err(Error::Malformed); 39 | } 40 | if pdu.protocol_length() != 4 { 41 | // we only support 4-octet protocol addresses 42 | return Err(Error::Malformed); 43 | } 44 | if buffer.len() < 28 { 45 | return Err(Error::Truncated); 46 | } 47 | Ok(pdu) 48 | } 49 | 50 | /// Returns a reference to the entire underlying buffer that was provided during construction 51 | pub fn buffer(&'a self) -> &'a [u8] { 52 | self.buffer 53 | } 54 | 55 | /// Consumes this object and returns a reference to the entire underlying buffer that was provided during 56 | /// construction 57 | pub fn into_buffer(self) -> &'a [u8] { 58 | self.buffer 59 | } 60 | 61 | /// Returns the slice of the underlying buffer that contains this PDU 62 | pub fn as_bytes(&'a self) -> &'a [u8] { 63 | self.clone().into_bytes() 64 | } 65 | 66 | /// Consumes this object and returns the slice of the underlying buffer that contains this PDU 67 | pub fn into_bytes(self) -> &'a [u8] { 68 | &self.buffer[0..28] 69 | } 70 | 71 | pub fn hardware_type(&'a self) -> u16 { 72 | u16::from_be_bytes(self.buffer[0..=1].try_into().unwrap()) 73 | } 74 | 75 | pub fn protocol_type(&'a self) -> u16 { 76 | u16::from_be_bytes(self.buffer[2..=3].try_into().unwrap()) 77 | } 78 | 79 | pub fn hardware_length(&'a self) -> u8 { 80 | self.buffer[4] 81 | } 82 | 83 | pub fn protocol_length(&'a self) -> u8 { 84 | self.buffer[5] 85 | } 86 | 87 | pub fn opcode(&'a self) -> u16 { 88 | u16::from_be_bytes(self.buffer[6..=7].try_into().unwrap()) 89 | } 90 | 91 | pub fn sender_hardware_address(&'a self) -> [u8; 6] { 92 | let mut sender_hardware_address = [0u8; 6]; 93 | sender_hardware_address.copy_from_slice(&self.buffer[8..14]); 94 | sender_hardware_address 95 | } 96 | 97 | pub fn sender_protocol_address(&'a self) -> [u8; 4] { 98 | let mut sender_protocol_address = [0u8; 4]; 99 | sender_protocol_address.copy_from_slice(&self.buffer[14..18]); 100 | sender_protocol_address 101 | } 102 | 103 | pub fn target_hardware_address(&'a self) -> [u8; 6] { 104 | let mut target_hardware_address = [0u8; 6]; 105 | target_hardware_address.copy_from_slice(&self.buffer[18..24]); 106 | target_hardware_address 107 | } 108 | 109 | pub fn target_protocol_address(&'a self) -> [u8; 4] { 110 | let mut target_protocol_address = [0u8; 4]; 111 | target_protocol_address.copy_from_slice(&self.buffer[24..28]); 112 | target_protocol_address 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/ethernet.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use core::convert::TryInto; 20 | 21 | use crate::{Error, Result}; 22 | 23 | /// Provides constants representing various EtherTypes supported by this crate 24 | #[allow(non_snake_case)] 25 | pub mod EtherType { 26 | pub const ARP: u16 = 0x0806; 27 | pub const IPV4: u16 = 0x0800; 28 | pub const IPV6: u16 = 0x86DD; 29 | pub const DOT1Q: u16 = 0x8100; 30 | pub const TEB: u16 = 0x6558; 31 | } 32 | 33 | /// Represents an Ethernet header and payload 34 | #[derive(Debug, Copy, Clone)] 35 | pub struct EthernetPdu<'a> { 36 | buffer: &'a [u8], 37 | } 38 | 39 | /// Contains the inner payload of an [`EthernetPdu`] 40 | #[derive(Debug, Copy, Clone)] 41 | pub enum Ethernet<'a> { 42 | Raw(&'a [u8]), 43 | Arp(super::ArpPdu<'a>), 44 | Ipv4(super::Ipv4Pdu<'a>), 45 | Ipv6(super::Ipv6Pdu<'a>), 46 | } 47 | 48 | impl<'a> EthernetPdu<'a> { 49 | /// Constructs an [`EthernetPdu`] backed by the provided `buffer` 50 | pub fn new(buffer: &'a [u8]) -> Result { 51 | if buffer.len() < 14 { 52 | return Err(Error::Truncated); 53 | } 54 | let pdu = EthernetPdu { buffer }; 55 | if pdu.tpid() == EtherType::DOT1Q && buffer.len() < 18 { 56 | return Err(Error::Truncated); 57 | } 58 | if pdu.ethertype() < 0x0600 { 59 | // we don't support 802.3 (LLC) frames 60 | return Err(Error::Malformed); 61 | } 62 | Ok(pdu) 63 | } 64 | 65 | /// Returns a reference to the entire underlying buffer that was provided during construction 66 | pub fn buffer(&'a self) -> &'a [u8] { 67 | self.buffer 68 | } 69 | 70 | /// Consumes this object and returns a reference to the entire underlying buffer that was provided during 71 | /// construction 72 | pub fn into_buffer(self) -> &'a [u8] { 73 | self.buffer 74 | } 75 | 76 | /// Returns the slice of the underlying buffer that contains the header part of this PDU 77 | pub fn as_bytes(&'a self) -> &'a [u8] { 78 | self.clone().into_bytes() 79 | } 80 | 81 | /// Consumes this object and returns the slice of the underlying buffer that contains the header part of this PDU 82 | pub fn into_bytes(self) -> &'a [u8] { 83 | &self.buffer[0..self.computed_ihl()] 84 | } 85 | 86 | /// Returns an object representing the inner payload of this PDU 87 | pub fn inner(&'a self) -> Result> { 88 | self.clone().into_inner() 89 | } 90 | 91 | /// Consumes this object and returns an object representing the inner payload of this PDU 92 | pub fn into_inner(self) -> Result> { 93 | let rest = &self.buffer[self.computed_ihl()..]; 94 | Ok(match self.ethertype() { 95 | EtherType::ARP => Ethernet::Arp(super::ArpPdu::new(rest)?), 96 | EtherType::IPV4 => Ethernet::Ipv4(super::Ipv4Pdu::new(rest)?), 97 | EtherType::IPV6 => Ethernet::Ipv6(super::Ipv6Pdu::new(rest)?), 98 | _ => Ethernet::Raw(rest), 99 | }) 100 | } 101 | 102 | pub fn computed_ihl(&'a self) -> usize { 103 | match self.tpid() { 104 | EtherType::DOT1Q => 18, 105 | _ => 14, 106 | } 107 | } 108 | 109 | pub fn source_address(&'a self) -> [u8; 6] { 110 | let mut source_address = [0u8; 6]; 111 | source_address.copy_from_slice(&self.buffer[6..12]); 112 | source_address 113 | } 114 | 115 | pub fn destination_address(&'a self) -> [u8; 6] { 116 | let mut destination_address = [0u8; 6]; 117 | destination_address.copy_from_slice(&self.buffer[0..6]); 118 | destination_address 119 | } 120 | 121 | pub fn tpid(&'a self) -> u16 { 122 | u16::from_be_bytes(self.buffer[12..=13].try_into().unwrap()) 123 | } 124 | 125 | pub fn ethertype(&'a self) -> u16 { 126 | match self.tpid() { 127 | EtherType::DOT1Q => u16::from_be_bytes(self.buffer[16..=17].try_into().unwrap()), 128 | ethertype => ethertype, 129 | } 130 | } 131 | 132 | pub fn vlan(&'a self) -> Option { 133 | match self.tpid() { 134 | EtherType::DOT1Q => Some(u16::from_be_bytes(self.buffer[14..=15].try_into().unwrap()) & 0x0FFF), 135 | _ => None, 136 | } 137 | } 138 | 139 | pub fn vlan_pcp(&'a self) -> Option { 140 | match self.tpid() { 141 | EtherType::DOT1Q => Some((self.buffer[14] & 0xE0) >> 5), 142 | _ => None, 143 | } 144 | } 145 | 146 | pub fn vlan_dei(&'a self) -> Option { 147 | match self.tpid() { 148 | EtherType::DOT1Q => Some(((self.buffer[14] & 0x10) >> 4) > 0), 149 | _ => None, 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/gre.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use core::convert::TryInto; 20 | 21 | use crate::{util, Error, Result}; 22 | 23 | /// Represents a GRE header and payload 24 | #[derive(Debug, Copy, Clone)] 25 | pub struct GrePdu<'a> { 26 | buffer: &'a [u8], 27 | } 28 | 29 | /// Contains the inner payload of a [`GrePdu`] 30 | #[derive(Debug, Copy, Clone)] 31 | pub enum Gre<'a> { 32 | Raw(&'a [u8]), 33 | Ethernet(super::EthernetPdu<'a>), 34 | Ipv4(super::Ipv4Pdu<'a>), 35 | Ipv6(super::Ipv6Pdu<'a>), 36 | } 37 | 38 | impl<'a> GrePdu<'a> { 39 | /// Constructs a [`GrePdu`] backed by the provided `buffer` 40 | pub fn new(buffer: &'a [u8]) -> Result { 41 | if buffer.len() < 4 { 42 | return Err(Error::Truncated); 43 | } 44 | if buffer[1] & 0x07 != 0 { 45 | // we only support rfc2784 GRE frames 46 | return Err(Error::Malformed); 47 | } 48 | let pdu = GrePdu { buffer }; 49 | if buffer.len() < pdu.computed_ihl() { 50 | return Err(Error::Truncated); 51 | } 52 | Ok(pdu) 53 | } 54 | 55 | /// Returns a reference to the entire underlying buffer that was provided during construction 56 | pub fn buffer(&'a self) -> &'a [u8] { 57 | self.buffer 58 | } 59 | 60 | /// Consumes this object and returns a reference to the entire underlying buffer that was provided during 61 | /// construction 62 | pub fn into_buffer(self) -> &'a [u8] { 63 | self.buffer 64 | } 65 | 66 | /// Returns the slice of the underlying buffer that contains the header part of this PDU 67 | pub fn as_bytes(&'a self) -> &'a [u8] { 68 | self.clone().into_bytes() 69 | } 70 | 71 | /// Consumes this object and returns the slice of the underlying buffer that contains the header part of this PDU 72 | pub fn into_bytes(self) -> &'a [u8] { 73 | &self.buffer[0..self.computed_ihl()] 74 | } 75 | 76 | /// Returns an object representing the inner payload of this PDU 77 | pub fn inner(&'a self) -> Result> { 78 | self.clone().into_inner() 79 | } 80 | 81 | /// Consumes this object and returns an object representing the inner payload of this PDU 82 | pub fn into_inner(self) -> Result> { 83 | let rest = &self.buffer[self.computed_ihl()..]; 84 | Ok(match self.ethertype() { 85 | super::EtherType::TEB => Gre::Ethernet(super::EthernetPdu::new(rest)?), 86 | super::EtherType::IPV4 => Gre::Ipv4(super::Ipv4Pdu::new(rest)?), 87 | super::EtherType::IPV6 => Gre::Ipv6(super::Ipv6Pdu::new(rest)?), 88 | _ => Gre::Raw(rest), 89 | }) 90 | } 91 | 92 | pub fn computed_ihl(&'a self) -> usize { 93 | let mut ihl = 4; 94 | if self.has_checksum() { 95 | ihl += 4; 96 | } 97 | if self.has_key() { 98 | ihl += 4; 99 | } 100 | if self.has_sequence_number() { 101 | ihl += 4; 102 | } 103 | ihl 104 | } 105 | 106 | pub fn version(&'a self) -> u8 { 107 | self.buffer[1] & 0x07 108 | } 109 | 110 | pub fn ethertype(&'a self) -> u16 { 111 | u16::from_be_bytes(self.buffer[2..=3].try_into().unwrap()) 112 | } 113 | 114 | pub fn has_checksum(&'a self) -> bool { 115 | (self.buffer[0] & 0x80) != 0 116 | } 117 | 118 | pub fn has_key(&'a self) -> bool { 119 | (self.buffer[0] & 0x20) != 0 120 | } 121 | 122 | pub fn has_sequence_number(&'a self) -> bool { 123 | (self.buffer[0] & 0x10) != 0 124 | } 125 | 126 | pub fn checksum(&'a self) -> Option { 127 | if self.has_checksum() { 128 | Some(u16::from_be_bytes(self.buffer[4..=5].try_into().unwrap())) 129 | } else { 130 | None 131 | } 132 | } 133 | 134 | pub fn computed_checksum(&'a self) -> Option { 135 | if self.has_checksum() { 136 | Some(util::checksum(&[&self.buffer[0..=3], &self.buffer[6..]])) 137 | } else { 138 | None 139 | } 140 | } 141 | 142 | pub fn key(&'a self) -> Option { 143 | if self.has_checksum() && self.has_key() { 144 | Some(u32::from_be_bytes(self.buffer[8..=11].try_into().unwrap())) 145 | } else if self.has_key() { 146 | Some(u32::from_be_bytes(self.buffer[4..=7].try_into().unwrap())) 147 | } else { 148 | None 149 | } 150 | } 151 | 152 | pub fn sequence_number(&'a self) -> Option { 153 | if self.has_sequence_number() && self.has_checksum() && self.has_key() { 154 | Some(u32::from_be_bytes(self.buffer[12..=15].try_into().unwrap())) 155 | } else if self.has_sequence_number() && (self.has_checksum() || self.has_key()) { 156 | Some(u32::from_be_bytes(self.buffer[8..=11].try_into().unwrap())) 157 | } else if self.has_sequence_number() { 158 | Some(u32::from_be_bytes(self.buffer[4..=7].try_into().unwrap())) 159 | } else { 160 | None 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/icmp.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use core::convert::TryInto; 20 | 21 | use crate::{util, Error, Result}; 22 | 23 | /// Represents an ICMP payload 24 | #[derive(Debug, Copy, Clone)] 25 | pub struct IcmpPdu<'a> { 26 | buffer: &'a [u8], 27 | } 28 | 29 | /// Contains the inner payload of an [`IcmpPdu`] 30 | #[derive(Debug, Copy, Clone)] 31 | pub enum Icmp<'a> { 32 | Raw(&'a [u8]), 33 | } 34 | 35 | impl<'a> IcmpPdu<'a> { 36 | /// Constructs a [`IcmpPdu`] backed by the provided `buffer` 37 | pub fn new(buffer: &'a [u8]) -> Result { 38 | if buffer.len() < 8 { 39 | return Err(Error::Truncated); 40 | } 41 | Ok(IcmpPdu { buffer }) 42 | } 43 | 44 | /// Returns a reference to the entire underlying buffer that was provided during construction 45 | pub fn buffer(&'a self) -> &'a [u8] { 46 | self.buffer 47 | } 48 | 49 | /// Consumes this object and returns a reference to the entire underlying buffer that was provided during 50 | /// construction 51 | pub fn into_buffer(self) -> &'a [u8] { 52 | self.buffer 53 | } 54 | 55 | /// Returns the slice of the underlying buffer that contains the header part of this PDU 56 | pub fn as_bytes(&'a self) -> &'a [u8] { 57 | self.clone().into_bytes() 58 | } 59 | 60 | /// Consumes this object and returns the slice of the underlying buffer that contains the header part of this PDU 61 | pub fn into_bytes(self) -> &'a [u8] { 62 | &self.buffer[0..8] 63 | } 64 | 65 | /// Returns an object representing the inner payload of this PDU 66 | pub fn inner(&'a self) -> Result> { 67 | self.clone().into_inner() 68 | } 69 | 70 | /// Consumes this object and returns an object representing the inner payload of this PDU 71 | pub fn into_inner(self) -> Result> { 72 | let rest = &self.buffer[4..]; 73 | Ok(Icmp::Raw(rest)) 74 | } 75 | 76 | pub fn message_type(&'a self) -> u8 { 77 | self.buffer[0] 78 | } 79 | 80 | pub fn message_code(&'a self) -> u8 { 81 | self.buffer[1] 82 | } 83 | 84 | pub fn checksum(&'a self) -> u16 { 85 | u16::from_be_bytes(self.buffer[2..=3].try_into().unwrap()) 86 | } 87 | 88 | pub fn computed_checksum(&'a self, ip: &crate::Ip) -> u16 { 89 | match ip { 90 | crate::Ip::Ipv4(_) => util::checksum(&[&self.buffer[0..=1], &self.buffer[4..]]), 91 | crate::Ip::Ipv6(ipv6) => util::checksum(&[ 92 | &ipv6.source_address().as_ref(), 93 | &ipv6.destination_address().as_ref(), 94 | &(ipv6.payload_length() as u32).to_be_bytes().as_ref(), 95 | &[0x0, 0x0, 0x0, ipv6.computed_protocol()].as_ref(), 96 | &self.buffer[0..=1], 97 | &self.buffer[4..], 98 | ]), 99 | } 100 | } 101 | 102 | #[deprecated(since = "1.3.0", note = "use IcmpPdu::inner()")] 103 | pub fn message(&'a self) -> &'a [u8] { 104 | &self.buffer[4..] 105 | } 106 | 107 | pub fn computed_data_offset(&'a self) -> usize { 108 | 4 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/ip.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use core::convert::TryInto; 20 | 21 | use crate::{util, Error, Result}; 22 | 23 | /// Provides constants representing various IP protocol numbers supported by this crate 24 | #[allow(non_snake_case)] 25 | pub mod IpProto { 26 | pub const TCP: u8 = 6; 27 | pub const UDP: u8 = 17; 28 | pub const ICMP: u8 = 1; 29 | pub const ICMP6: u8 = 58; 30 | pub const GRE: u8 = 47; 31 | } 32 | 33 | /// Contains either an [`Ipv4Pdu`] or [`Ipv6Pdu`] depending on address family 34 | #[derive(Debug, Copy, Clone)] 35 | pub enum Ip<'a> { 36 | Ipv4(Ipv4Pdu<'a>), 37 | Ipv6(Ipv6Pdu<'a>), 38 | } 39 | 40 | impl<'a> Ip<'a> { 41 | /// Constructs either an [`Ipv4Pdu`] or [`Ipv6Pdu`] backed by the provided `buffer` 42 | pub fn new(buffer: &'a [u8]) -> Result { 43 | if buffer.is_empty() { 44 | return Err(Error::Truncated); 45 | } 46 | match buffer[0] >> 4 { 47 | 4 => Ok(Ip::Ipv4(Ipv4Pdu::new(buffer)?)), 48 | 6 => Ok(Ip::Ipv6(Ipv6Pdu::new(buffer)?)), 49 | _ => Err(Error::Malformed), 50 | } 51 | } 52 | } 53 | 54 | /// Represents an IPv4 header and payload 55 | #[derive(Debug, Copy, Clone)] 56 | pub struct Ipv4Pdu<'a> { 57 | buffer: &'a [u8], 58 | } 59 | 60 | /// Contains the inner payload of an [`Ipv4Pdu`] 61 | #[derive(Debug, Copy, Clone)] 62 | pub enum Ipv4<'a> { 63 | Raw(&'a [u8]), 64 | Tcp(super::TcpPdu<'a>), 65 | Udp(super::UdpPdu<'a>), 66 | Icmp(super::IcmpPdu<'a>), 67 | Gre(super::GrePdu<'a>), 68 | } 69 | 70 | impl<'a> Ipv4Pdu<'a> { 71 | /// Constructs an [`Ipv4Pdu`] backed by the provided `buffer` 72 | pub fn new(buffer: &'a [u8]) -> Result { 73 | let pdu = Ipv4Pdu { buffer }; 74 | if buffer.len() < 20 || pdu.computed_ihl() < 20 { 75 | return Err(Error::Truncated); 76 | } 77 | if buffer.len() < (pdu.computed_ihl() as usize) || (pdu.total_length() as usize) < pdu.computed_ihl() { 78 | return Err(Error::Malformed); 79 | } 80 | if pdu.version() != 4 { 81 | return Err(Error::Malformed); 82 | } 83 | Ok(pdu) 84 | } 85 | 86 | /// Returns a reference to the entire underlying buffer that was provided during construction 87 | pub fn buffer(&'a self) -> &'a [u8] { 88 | self.buffer 89 | } 90 | 91 | /// Consumes this object and returns a reference to the entire underlying buffer that was provided during 92 | /// construction 93 | pub fn into_buffer(self) -> &'a [u8] { 94 | self.buffer 95 | } 96 | 97 | /// Returns the slice of the underlying buffer that contains the header part of this PDU 98 | pub fn as_bytes(&'a self) -> &'a [u8] { 99 | self.clone().into_bytes() 100 | } 101 | 102 | /// Consumes this object and returns the slice of the underlying buffer that contains the header part of this PDU 103 | pub fn into_bytes(self) -> &'a [u8] { 104 | &self.buffer[0..self.computed_ihl()] 105 | } 106 | 107 | /// Returns an object representing the inner payload of this PDU 108 | pub fn inner(&'a self) -> Result> { 109 | self.clone().into_inner() 110 | } 111 | 112 | /// Consumes this object and returns an object representing the inner payload of this PDU 113 | pub fn into_inner(self) -> Result> { 114 | let rest = &self.buffer[self.computed_ihl()..]; 115 | 116 | if self.fragment_offset() > 0 { 117 | Ok(Ipv4::Raw(rest)) 118 | } else { 119 | Ok(match self.protocol() { 120 | IpProto::TCP => Ipv4::Tcp(super::TcpPdu::new(rest)?), 121 | IpProto::UDP => Ipv4::Udp(super::UdpPdu::new(rest)?), 122 | IpProto::ICMP => Ipv4::Icmp(super::IcmpPdu::new(rest)?), 123 | IpProto::GRE => { 124 | if rest.len() > 1 && (rest[1] & 0x07) == 0 { 125 | Ipv4::Gre(super::GrePdu::new(rest)?) 126 | } else { 127 | Ipv4::Raw(rest) 128 | } 129 | } 130 | _ => Ipv4::Raw(rest), 131 | }) 132 | } 133 | } 134 | 135 | pub fn version(&'a self) -> u8 { 136 | self.buffer[0] >> 4 137 | } 138 | 139 | pub fn ihl(&'a self) -> u8 { 140 | self.buffer[0] & 0xF 141 | } 142 | 143 | pub fn computed_ihl(&'a self) -> usize { 144 | self.ihl() as usize * 4 145 | } 146 | 147 | pub fn dscp(&'a self) -> u8 { 148 | self.buffer[1] >> 2 149 | } 150 | 151 | pub fn ecn(&'a self) -> u8 { 152 | self.buffer[1] & 0x3 153 | } 154 | 155 | pub fn total_length(&'a self) -> u16 { 156 | u16::from_be_bytes(self.buffer[2..=3].try_into().unwrap()) 157 | } 158 | 159 | pub fn identification(&'a self) -> u16 { 160 | u16::from_be_bytes(self.buffer[4..=5].try_into().unwrap()) 161 | } 162 | 163 | pub fn dont_fragment(&'a self) -> bool { 164 | self.buffer[6] & 0x40 != 0 165 | } 166 | 167 | pub fn more_fragments(&'a self) -> bool { 168 | self.buffer[6] & 0x20 != 0 169 | } 170 | 171 | pub fn fragment_offset(&'a self) -> u16 { 172 | u16::from_be_bytes([self.buffer[6] & 0x1f, self.buffer[7]]) 173 | } 174 | 175 | pub fn computed_fragment_offset(&'a self) -> u16 { 176 | self.fragment_offset() * 8 177 | } 178 | 179 | pub fn ttl(&'a self) -> u8 { 180 | self.buffer[8] 181 | } 182 | 183 | pub fn protocol(&'a self) -> u8 { 184 | self.buffer[9] 185 | } 186 | 187 | pub fn checksum(&'a self) -> u16 { 188 | u16::from_be_bytes(self.buffer[10..=11].try_into().unwrap()) 189 | } 190 | 191 | pub fn computed_checksum(&'a self) -> u16 { 192 | util::checksum(&[&self.buffer[0..=9], &self.buffer[12..self.computed_ihl()]]) 193 | } 194 | 195 | pub fn source_address(&'a self) -> [u8; 4] { 196 | let mut source_address = [0u8; 4]; 197 | source_address.copy_from_slice(&self.buffer[12..16]); 198 | source_address 199 | } 200 | 201 | pub fn destination_address(&'a self) -> [u8; 4] { 202 | let mut destination_address = [0u8; 4]; 203 | destination_address.copy_from_slice(&self.buffer[16..20]); 204 | destination_address 205 | } 206 | 207 | pub fn options(&'a self) -> Ipv4OptionIterator<'a> { 208 | Ipv4OptionIterator { buffer: &self.buffer, pos: 20, ihl: self.computed_ihl() } 209 | } 210 | } 211 | 212 | /// Represents an IPv4 option 213 | #[derive(Debug, Copy, Clone)] 214 | pub enum Ipv4Option<'a> { 215 | Raw { option: u8, data: &'a [u8] }, 216 | } 217 | 218 | #[derive(Debug, Copy, Clone)] 219 | pub struct Ipv4OptionIterator<'a> { 220 | buffer: &'a [u8], 221 | pos: usize, 222 | ihl: usize, 223 | } 224 | 225 | impl<'a> Iterator for Ipv4OptionIterator<'a> { 226 | type Item = Ipv4Option<'a>; 227 | 228 | fn next(&mut self) -> Option { 229 | if self.pos < self.ihl { 230 | let pos = self.pos; 231 | let option = self.buffer[pos]; 232 | let len = match option { 233 | 0 | 1 => 1usize, 234 | _ => { 235 | if self.ihl <= (pos + 1) { 236 | return None; 237 | } 238 | let len = self.buffer[pos + 1] as usize; 239 | if len < 2 { 240 | return None; 241 | } 242 | len 243 | } 244 | }; 245 | if self.ihl < (pos + len) { 246 | return None; 247 | } 248 | self.pos += len; 249 | Some(Ipv4Option::Raw { option, data: &self.buffer[pos..(pos + len)] }) 250 | } else { 251 | None 252 | } 253 | } 254 | } 255 | 256 | /// Represents an IPv6 header and payload 257 | #[derive(Debug, Copy, Clone)] 258 | pub struct Ipv6Pdu<'a> { 259 | buffer: &'a [u8], 260 | } 261 | 262 | /// Contains the inner payload of an [`Ipv6Pdu`] 263 | #[derive(Debug, Copy, Clone)] 264 | pub enum Ipv6<'a> { 265 | Raw(&'a [u8]), 266 | Tcp(super::TcpPdu<'a>), 267 | Udp(super::UdpPdu<'a>), 268 | Icmp(super::IcmpPdu<'a>), 269 | Gre(super::GrePdu<'a>), 270 | } 271 | 272 | impl<'a> Ipv6Pdu<'a> { 273 | /// Constructs an [`Ipv6Pdu`] backed by the provided `buffer` 274 | pub fn new(buffer: &'a [u8]) -> Result { 275 | let pdu = Ipv6Pdu { buffer }; 276 | if buffer.len() < 40 { 277 | return Err(Error::Truncated); 278 | } 279 | if pdu.version() != 6 { 280 | return Err(Error::Malformed); 281 | } 282 | let mut position = 40; 283 | let mut next_header = buffer[6]; 284 | while let 0 | 43 | 44 | 59 | 60 = next_header { 285 | if buffer.len() <= (position + 1) { 286 | return Err(Error::Truncated); 287 | } 288 | next_header = buffer[position]; 289 | position += ((buffer[position + 1] as usize) + 1) * 8; 290 | } 291 | if buffer.len() < position { 292 | return Err(Error::Truncated); 293 | } 294 | if pdu.computed_ihl() != position { 295 | return Err(Error::Malformed); 296 | } 297 | Ok(pdu) 298 | } 299 | 300 | /// Returns a reference to the entire underlying buffer that was provided during construction 301 | pub fn buffer(&'a self) -> &'a [u8] { 302 | self.buffer 303 | } 304 | 305 | /// Consumes this object and returns a reference to the entire underlying buffer that was provided during 306 | /// construction 307 | pub fn into_buffer(self) -> &'a [u8] { 308 | self.buffer 309 | } 310 | 311 | /// Returns the slice of the underlying buffer that contains the header part of this PDU 312 | pub fn as_bytes(&'a self) -> &'a [u8] { 313 | self.clone().into_bytes() 314 | } 315 | 316 | /// Consumes this object and returns the slice of the underlying buffer that contains the header part of this PDU 317 | pub fn into_bytes(self) -> &'a [u8] { 318 | &self.buffer[0..self.computed_ihl()] 319 | } 320 | 321 | /// Returns an object representing the inner payload of this PDU 322 | pub fn inner(&'a self) -> Result> { 323 | self.clone().into_inner() 324 | } 325 | 326 | /// Consumes this object and returns an object representing the inner payload of this PDU 327 | pub fn into_inner(self) -> Result> { 328 | let rest = &self.buffer[self.computed_ihl()..]; 329 | 330 | if self.computed_fragment_offset().unwrap_or_default() > 0 { 331 | Ok(Ipv6::Raw(rest)) 332 | } else { 333 | Ok(match self.computed_protocol() { 334 | IpProto::TCP => Ipv6::Tcp(super::TcpPdu::new(rest)?), 335 | IpProto::UDP => Ipv6::Udp(super::UdpPdu::new(rest)?), 336 | IpProto::ICMP6 => Ipv6::Icmp(super::IcmpPdu::new(rest)?), 337 | IpProto::GRE => { 338 | if rest.len() > 1 && (rest[1] & 0x07) == 0 { 339 | Ipv6::Gre(super::GrePdu::new(rest)?) 340 | } else { 341 | Ipv6::Raw(rest) 342 | } 343 | } 344 | _ => Ipv6::Raw(rest), 345 | }) 346 | } 347 | } 348 | 349 | pub fn version(&'a self) -> u8 { 350 | self.buffer[0] >> 4 351 | } 352 | 353 | pub fn dscp(&'a self) -> u8 { 354 | ((self.buffer[0] << 4) | (self.buffer[1] >> 4)) >> 2 355 | } 356 | 357 | pub fn ecn(&'a self) -> u8 { 358 | (self.buffer[1] >> 4) & 0x3 359 | } 360 | 361 | pub fn flow_label(&'a self) -> u32 { 362 | u32::from_be_bytes([0, self.buffer[1] & 0xf, self.buffer[2], self.buffer[3]]) 363 | } 364 | 365 | pub fn payload_length(&'a self) -> u16 { 366 | u16::from_be_bytes(self.buffer[4..=5].try_into().unwrap()) 367 | } 368 | 369 | pub fn next_header(&'a self) -> u8 { 370 | self.buffer[6] 371 | } 372 | 373 | pub fn computed_ihl(&'a self) -> usize { 374 | let mut position = 40; 375 | let mut next_header = self.next_header(); 376 | while let 0 | 43 | 44 | 59 | 60 = next_header { 377 | next_header = self.buffer[position]; 378 | position += ((self.buffer[position + 1] as usize) + 1) * 8; 379 | } 380 | position 381 | } 382 | 383 | pub fn computed_protocol(&'a self) -> u8 { 384 | let mut position = 40; 385 | let mut next_header = self.next_header(); 386 | while let 0 | 43 | 44 | 59 | 60 = next_header { 387 | next_header = self.buffer[position]; 388 | position += ((self.buffer[position + 1] as usize) + 1) * 8; 389 | } 390 | next_header 391 | } 392 | 393 | pub fn computed_identification(&'a self) -> Option { 394 | for header in self.extension_headers() { 395 | if let Ipv6ExtensionHeader::Fragment { identification, .. } = header { 396 | return Some(identification); 397 | } 398 | } 399 | None 400 | } 401 | 402 | pub fn computed_more_fragments(&'a self) -> Option { 403 | for header in self.extension_headers() { 404 | if let Ipv6ExtensionHeader::Fragment { more_fragments, .. } = header { 405 | return Some(more_fragments); 406 | } 407 | } 408 | None 409 | } 410 | 411 | pub fn computed_fragment_offset(&'a self) -> Option { 412 | for header in self.extension_headers() { 413 | if let Ipv6ExtensionHeader::Fragment { offset, .. } = header { 414 | return Some(offset * 8); 415 | } 416 | } 417 | None 418 | } 419 | 420 | pub fn hop_limit(&'a self) -> u8 { 421 | self.buffer[7] 422 | } 423 | 424 | pub fn source_address(&'a self) -> [u8; 16] { 425 | let mut source_address = [0u8; 16]; 426 | source_address.copy_from_slice(&self.buffer[8..24]); 427 | source_address 428 | } 429 | 430 | pub fn destination_address(&'a self) -> [u8; 16] { 431 | let mut destination_address = [0u8; 16]; 432 | destination_address.copy_from_slice(&self.buffer[24..40]); 433 | destination_address 434 | } 435 | 436 | pub fn extension_headers(&'a self) -> Ipv6ExtensionHeaderIterator<'a> { 437 | Ipv6ExtensionHeaderIterator { buffer: self.buffer, pos: 40, next_header: self.next_header() } 438 | } 439 | } 440 | 441 | /// Represents an IPv6 extension header 442 | #[derive(Debug, Copy, Clone)] 443 | pub enum Ipv6ExtensionHeader<'a> { 444 | Raw { header: u8, data: &'a [u8] }, 445 | Fragment { identification: u32, offset: u16, more_fragments: bool }, 446 | } 447 | 448 | #[derive(Debug, Copy, Clone)] 449 | pub struct Ipv6ExtensionHeaderIterator<'a> { 450 | buffer: &'a [u8], 451 | pos: usize, 452 | next_header: u8, 453 | } 454 | 455 | impl<'a> Iterator for Ipv6ExtensionHeaderIterator<'a> { 456 | type Item = Ipv6ExtensionHeader<'a>; 457 | 458 | fn next(&mut self) -> Option { 459 | if let 0 | 43 | 44 | 59 | 60 = self.next_header { 460 | let header = self.next_header; 461 | self.next_header = self.buffer[self.pos]; 462 | let header_length = ((self.buffer[self.pos + 1] as usize) + 1) * 8; 463 | let pos = self.pos; 464 | self.pos += header_length; 465 | if header == 44 && header_length == 8 { 466 | let identification = u32::from_be_bytes([ 467 | self.buffer[pos + 4], 468 | self.buffer[pos + 5], 469 | self.buffer[pos + 6], 470 | self.buffer[pos + 7], 471 | ]); 472 | let offset = u16::from_be_bytes([self.buffer[pos + 2], self.buffer[pos + 3]]) >> 3; 473 | let more_fragments = self.buffer[pos + 3] & 0x1 > 0; 474 | Some(Ipv6ExtensionHeader::Fragment { identification, offset, more_fragments }) 475 | } else { 476 | Some(Ipv6ExtensionHeader::Raw { header, data: &self.buffer[pos..(pos + header_length)] }) 477 | } 478 | } else { 479 | None 480 | } 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! Small, fast, and correct L2/L3/L4 packet parser 20 | 21 | #![cfg_attr(not(any(feature = "std", test)), no_std)] 22 | 23 | mod ethernet; 24 | pub use ethernet::{EtherType, Ethernet, EthernetPdu}; 25 | 26 | mod arp; 27 | pub use arp::ArpPdu; 28 | 29 | mod ip; 30 | pub use ip::{Ip, IpProto, Ipv4, Ipv4Option, Ipv4Pdu, Ipv6, Ipv6ExtensionHeader, Ipv6Pdu}; 31 | 32 | mod tcp; 33 | pub use tcp::{Tcp, TcpFlag, TcpOption, TcpPdu}; 34 | 35 | mod udp; 36 | pub use udp::{Udp, UdpPdu}; 37 | 38 | mod icmp; 39 | pub use icmp::{Icmp, IcmpPdu}; 40 | 41 | mod gre; 42 | pub use gre::{Gre, GrePdu}; 43 | 44 | mod util; 45 | 46 | /// Defines the set of possible errors returned by packet parsers in this crate 47 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 48 | pub enum Error { 49 | Truncated, 50 | Malformed, 51 | } 52 | 53 | /// Defines the return type used by packet parsers in this crate 54 | pub type Result = core::result::Result; 55 | 56 | #[cfg(any(feature = "std", test))] 57 | impl std::error::Error for Error {} 58 | 59 | #[cfg(any(feature = "std", test))] 60 | impl std::fmt::Display for Error { 61 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 62 | match self { 63 | Error::Truncated => f.write_str("frame is truncated"), 64 | Error::Malformed => f.write_str("frame is malformed"), 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/tcp.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use core::convert::TryInto; 20 | 21 | use crate::{util, Error, Result}; 22 | 23 | /// Provides constants representing the set of TCP bitflags 24 | #[allow(non_snake_case)] 25 | pub mod TcpFlag { 26 | pub const FIN: u8 = 1; 27 | pub const SYN: u8 = 2; 28 | pub const RST: u8 = 4; 29 | pub const PSH: u8 = 8; 30 | pub const ACK: u8 = 16; 31 | pub const URG: u8 = 32; 32 | pub const ECN: u8 = 64; 33 | pub const CWR: u8 = 128; 34 | } 35 | 36 | /// Represents a TCP header and payload 37 | #[derive(Debug, Copy, Clone)] 38 | pub struct TcpPdu<'a> { 39 | buffer: &'a [u8], 40 | } 41 | 42 | /// Contains the inner payload of a [`TcpPdu`] 43 | #[derive(Debug, Copy, Clone)] 44 | pub enum Tcp<'a> { 45 | Raw(&'a [u8]), 46 | } 47 | 48 | impl<'a> TcpPdu<'a> { 49 | /// Constructs a [`TcpPdu`] backed by the provided `buffer` 50 | pub fn new(buffer: &'a [u8]) -> Result { 51 | let pdu = TcpPdu { buffer }; 52 | if buffer.len() < 20 || buffer.len() < pdu.computed_data_offset() { 53 | return Err(Error::Truncated); 54 | } 55 | Ok(pdu) 56 | } 57 | 58 | /// Returns a reference to the entire underlying buffer that was provided during construction 59 | pub fn buffer(&'a self) -> &'a [u8] { 60 | self.buffer 61 | } 62 | 63 | /// Consumes this object and returns a reference to the entire underlying buffer that was provided during 64 | /// construction 65 | pub fn into_buffer(self) -> &'a [u8] { 66 | self.buffer 67 | } 68 | 69 | /// Returns the slice of the underlying buffer that contains the header part of this PDU 70 | pub fn as_bytes(&'a self) -> &'a [u8] { 71 | self.clone().into_bytes() 72 | } 73 | 74 | /// Consumes this object and returns the slice of the underlying buffer that contains the header part of this PDU 75 | pub fn into_bytes(self) -> &'a [u8] { 76 | &self.buffer[0..self.computed_data_offset()] 77 | } 78 | 79 | /// Returns an object representing the inner payload of this PDU 80 | pub fn inner(&'a self) -> Result> { 81 | self.clone().into_inner() 82 | } 83 | 84 | /// Consumes this object and returns an object representing the inner payload of this PDU 85 | pub fn into_inner(self) -> Result> { 86 | let rest = &self.buffer[self.computed_data_offset()..]; 87 | Ok(Tcp::Raw(rest)) 88 | } 89 | 90 | pub fn source_port(&'a self) -> u16 { 91 | u16::from_be_bytes(self.buffer[0..=1].try_into().unwrap()) 92 | } 93 | 94 | pub fn destination_port(&'a self) -> u16 { 95 | u16::from_be_bytes(self.buffer[2..=3].try_into().unwrap()) 96 | } 97 | 98 | pub fn sequence_number(&'a self) -> u32 { 99 | u32::from_be_bytes(self.buffer[4..=7].try_into().unwrap()) 100 | } 101 | 102 | pub fn acknowledgement_number(&'a self) -> u32 { 103 | u32::from_be_bytes(self.buffer[8..=11].try_into().unwrap()) 104 | } 105 | 106 | pub fn data_offset(&'a self) -> u8 { 107 | self.buffer[12] >> 4 108 | } 109 | 110 | pub fn computed_data_offset(&'a self) -> usize { 111 | self.data_offset() as usize * 4 112 | } 113 | 114 | pub fn flags(&'a self) -> u8 { 115 | self.buffer[13] 116 | } 117 | 118 | pub fn fin(&'a self) -> bool { 119 | self.flags() & 0x1 != 0 120 | } 121 | 122 | pub fn syn(&'a self) -> bool { 123 | self.flags() & 0x2 != 0 124 | } 125 | 126 | pub fn rst(&'a self) -> bool { 127 | self.flags() & 0x4 != 0 128 | } 129 | 130 | pub fn psh(&'a self) -> bool { 131 | self.flags() & 0x8 != 0 132 | } 133 | 134 | pub fn ack(&'a self) -> bool { 135 | self.flags() & 0x10 != 0 136 | } 137 | 138 | pub fn urg(&'a self) -> bool { 139 | self.flags() & 0x20 != 0 140 | } 141 | 142 | pub fn ecn(&'a self) -> bool { 143 | self.flags() & 0x40 != 0 144 | } 145 | 146 | pub fn cwr(&'a self) -> bool { 147 | self.flags() & 0x80 != 0 148 | } 149 | 150 | pub fn window_size(&'a self) -> u16 { 151 | u16::from_be_bytes(self.buffer[14..=15].try_into().unwrap()) 152 | } 153 | 154 | pub fn computed_window_size(&'a self, shift: u8) -> u32 { 155 | (self.window_size() as u32) << (shift as u32) 156 | } 157 | 158 | pub fn checksum(&'a self) -> u16 { 159 | u16::from_be_bytes(self.buffer[16..=17].try_into().unwrap()) 160 | } 161 | 162 | pub fn computed_checksum(&'a self, ip: &crate::Ip) -> u16 { 163 | match ip { 164 | crate::Ip::Ipv4(ipv4) => util::checksum(&[ 165 | &ipv4.source_address().as_ref(), 166 | &ipv4.destination_address().as_ref(), 167 | &[0x00, ipv4.protocol()].as_ref(), 168 | &(ipv4.total_length() as usize - ipv4.computed_ihl()).to_be_bytes().as_ref(), 169 | &self.buffer[0..=15], 170 | &self.buffer[18..], 171 | ]), 172 | crate::Ip::Ipv6(ipv6) => util::checksum(&[ 173 | &ipv6.source_address().as_ref(), 174 | &ipv6.destination_address().as_ref(), 175 | &(ipv6.payload_length() as u32).to_be_bytes().as_ref(), 176 | &[0x0, 0x0, 0x0, ipv6.computed_protocol()].as_ref(), 177 | &self.buffer[0..=15], 178 | &self.buffer[18..], 179 | ]), 180 | } 181 | } 182 | 183 | pub fn urgent_pointer(&'a self) -> u16 { 184 | u16::from_be_bytes(self.buffer[18..=19].try_into().unwrap()) 185 | } 186 | 187 | pub fn options(&'a self) -> TcpOptionIterator<'a> { 188 | TcpOptionIterator { buffer: self.buffer, pos: 20, data_offset: self.computed_data_offset() } 189 | } 190 | } 191 | 192 | /// Represents a TCP option 193 | #[derive(Debug, Copy, Clone)] 194 | pub enum TcpOption<'a> { 195 | Raw { option: u8, data: &'a [u8] }, 196 | NoOp, 197 | Mss { size: u16 }, 198 | WindowScale { shift: u8 }, 199 | SackPermitted, 200 | Sack { blocks: [Option<(u32, u32)>; 4] }, 201 | Timestamp { val: u32, ecr: u32 }, 202 | } 203 | 204 | #[derive(Debug, Copy, Clone)] 205 | pub struct TcpOptionIterator<'a> { 206 | buffer: &'a [u8], 207 | pos: usize, 208 | data_offset: usize, 209 | } 210 | 211 | impl<'a> Iterator for TcpOptionIterator<'a> { 212 | type Item = TcpOption<'a>; 213 | 214 | fn next(&mut self) -> Option { 215 | if self.pos < self.data_offset { 216 | let pos = self.pos; 217 | let option = self.buffer[pos]; 218 | let len = match option { 219 | 0 | 1 => 1usize, 220 | _ => { 221 | if self.data_offset <= (pos + 1) { 222 | return None; 223 | } 224 | let len = self.buffer[pos + 1] as usize; 225 | if len < 2 { 226 | return None; 227 | } 228 | len 229 | } 230 | }; 231 | if self.data_offset < (pos + len) { 232 | return None; 233 | } 234 | self.pos += len; 235 | match option { 236 | 0 => None, 237 | 1 => Some(TcpOption::NoOp), 238 | 2 if len == 4 => Some(TcpOption::Mss { 239 | size: u16::from_be_bytes(self.buffer[pos + 2..=pos + 3].try_into().unwrap()), 240 | }), 241 | 3 if len == 3 => Some(TcpOption::WindowScale { shift: self.buffer[pos + 2] }), 242 | 4 => Some(TcpOption::SackPermitted), 243 | 5 if len == 10 => Some(TcpOption::Sack { 244 | blocks: [ 245 | Some(( 246 | u32::from_be_bytes(self.buffer[pos + 2..=pos + 5].try_into().unwrap()), 247 | u32::from_be_bytes(self.buffer[pos + 6..=pos + 9].try_into().unwrap()), 248 | )), 249 | None, 250 | None, 251 | None, 252 | ], 253 | }), 254 | 5 if len == 18 => Some(TcpOption::Sack { 255 | blocks: [ 256 | Some(( 257 | u32::from_be_bytes(self.buffer[pos + 2..=pos + 5].try_into().unwrap()), 258 | u32::from_be_bytes(self.buffer[pos + 6..=pos + 9].try_into().unwrap()), 259 | )), 260 | Some(( 261 | u32::from_be_bytes(self.buffer[pos + 10..=pos + 13].try_into().unwrap()), 262 | u32::from_be_bytes(self.buffer[pos + 14..=pos + 17].try_into().unwrap()), 263 | )), 264 | None, 265 | None, 266 | ], 267 | }), 268 | 5 if len == 26 => Some(TcpOption::Sack { 269 | blocks: [ 270 | Some(( 271 | u32::from_be_bytes(self.buffer[pos + 2..=pos + 5].try_into().unwrap()), 272 | u32::from_be_bytes(self.buffer[pos + 6..=pos + 9].try_into().unwrap()), 273 | )), 274 | Some(( 275 | u32::from_be_bytes(self.buffer[pos + 10..=pos + 13].try_into().unwrap()), 276 | u32::from_be_bytes(self.buffer[pos + 14..=pos + 17].try_into().unwrap()), 277 | )), 278 | Some(( 279 | u32::from_be_bytes(self.buffer[pos + 18..=pos + 21].try_into().unwrap()), 280 | u32::from_be_bytes(self.buffer[pos + 22..=pos + 25].try_into().unwrap()), 281 | )), 282 | None, 283 | ], 284 | }), 285 | 5 if len == 34 => Some(TcpOption::Sack { 286 | blocks: [ 287 | Some(( 288 | u32::from_be_bytes(self.buffer[pos + 2..=pos + 5].try_into().unwrap()), 289 | u32::from_be_bytes(self.buffer[pos + 6..=pos + 9].try_into().unwrap()), 290 | )), 291 | Some(( 292 | u32::from_be_bytes(self.buffer[pos + 10..=pos + 13].try_into().unwrap()), 293 | u32::from_be_bytes(self.buffer[pos + 14..=pos + 17].try_into().unwrap()), 294 | )), 295 | Some(( 296 | u32::from_be_bytes(self.buffer[pos + 18..=pos + 21].try_into().unwrap()), 297 | u32::from_be_bytes(self.buffer[pos + 22..=pos + 25].try_into().unwrap()), 298 | )), 299 | Some(( 300 | u32::from_be_bytes(self.buffer[pos + 26..=pos + 29].try_into().unwrap()), 301 | u32::from_be_bytes(self.buffer[pos + 30..=pos + 33].try_into().unwrap()), 302 | )), 303 | ], 304 | }), 305 | 8 if len == 10 => Some(TcpOption::Timestamp { 306 | val: u32::from_be_bytes(self.buffer[pos + 2..=pos + 5].try_into().unwrap()), 307 | ecr: u32::from_be_bytes(self.buffer[pos + 6..=pos + 9].try_into().unwrap()), 308 | }), 309 | _ => Some(TcpOption::Raw { option, data: &self.buffer[pos..(pos + len)] }), 310 | } 311 | } else { 312 | None 313 | } 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /src/udp.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use core::convert::TryInto; 20 | 21 | use crate::{util, Error, Result}; 22 | 23 | /// Represents a UDP header and payload 24 | #[derive(Debug, Copy, Clone)] 25 | pub struct UdpPdu<'a> { 26 | buffer: &'a [u8], 27 | } 28 | 29 | /// Contains the inner payload of a [`UdpPdu`] 30 | #[derive(Debug, Copy, Clone)] 31 | pub enum Udp<'a> { 32 | Raw(&'a [u8]), 33 | } 34 | 35 | impl<'a> UdpPdu<'a> { 36 | /// Constructs a [`UdpPdu`] backed by the provided `buffer` 37 | pub fn new(buffer: &'a [u8]) -> Result { 38 | let pdu = UdpPdu { buffer }; 39 | if buffer.len() < 8 { 40 | return Err(Error::Truncated); 41 | } 42 | Ok(pdu) 43 | } 44 | 45 | /// Returns a reference to the entire underlying buffer that was provided during construction 46 | pub fn buffer(&'a self) -> &'a [u8] { 47 | self.buffer 48 | } 49 | 50 | /// Consumes this object and returns a reference to the entire underlying buffer that was provided during 51 | /// construction 52 | pub fn into_buffer(self) -> &'a [u8] { 53 | self.buffer 54 | } 55 | 56 | /// Returns the slice of the underlying buffer that contains the header part of this PDU 57 | pub fn as_bytes(&'a self) -> &'a [u8] { 58 | self.clone().into_bytes() 59 | } 60 | 61 | /// Consumes this object and returns the slice of the underlying buffer that contains the header part of this PDU 62 | pub fn into_bytes(self) -> &'a [u8] { 63 | &self.buffer[0..8] 64 | } 65 | 66 | /// Returns an object representing the inner payload of this PDU 67 | pub fn inner(&'a self) -> Result> { 68 | self.clone().into_inner() 69 | } 70 | 71 | /// Consumes this object and returns an object representing the inner payload of this PDU 72 | pub fn into_inner(self) -> Result> { 73 | let rest = &self.buffer[8..]; 74 | Ok(Udp::Raw(rest)) 75 | } 76 | 77 | pub fn source_port(&'a self) -> u16 { 78 | u16::from_be_bytes(self.buffer[0..=1].try_into().unwrap()) 79 | } 80 | 81 | pub fn destination_port(&'a self) -> u16 { 82 | u16::from_be_bytes(self.buffer[2..=3].try_into().unwrap()) 83 | } 84 | 85 | pub fn length(&'a self) -> u16 { 86 | u16::from_be_bytes(self.buffer[4..=5].try_into().unwrap()) 87 | } 88 | 89 | pub fn checksum(&'a self) -> u16 { 90 | u16::from_be_bytes(self.buffer[6..=7].try_into().unwrap()) 91 | } 92 | 93 | pub fn computed_checksum(&'a self, ip: &crate::Ip) -> u16 { 94 | let mut csum = match ip { 95 | crate::Ip::Ipv4(ipv4) => util::checksum(&[ 96 | &ipv4.source_address().as_ref(), 97 | &ipv4.destination_address().as_ref(), 98 | &[0x00, ipv4.protocol()].as_ref(), 99 | &self.length().to_be_bytes().as_ref(), 100 | &self.buffer[0..=5], 101 | &self.buffer[8..], 102 | ]), 103 | crate::Ip::Ipv6(ipv6) => util::checksum(&[ 104 | &ipv6.source_address().as_ref(), 105 | &ipv6.destination_address().as_ref(), 106 | &(self.length() as u32).to_be_bytes().as_ref(), 107 | &[0x0, 0x0, 0x0, ipv6.computed_protocol()].as_ref(), 108 | &self.buffer[0..=5], 109 | &self.buffer[8..], 110 | ]), 111 | }; 112 | 113 | if csum == 0 { 114 | csum = 0xFFFF; 115 | } 116 | 117 | csum 118 | } 119 | 120 | pub fn computed_data_offset(&'a self) -> usize { 121 | 8 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use core::convert::TryInto; 20 | 21 | pub fn checksum(spans: I) -> u16 22 | where 23 | I: IntoIterator, 24 | J: AsRef<[u8]>, 25 | { 26 | let mut accum = 0u32; 27 | 28 | for span in spans.into_iter() { 29 | accum += sum(span.as_ref()) as u32; 30 | } 31 | 32 | accum = (accum >> 16) + (accum & 0xffff); 33 | !(((accum >> 16) as u16) + (accum as u16)) 34 | } 35 | 36 | fn sum(mut buffer: &[u8]) -> u16 { 37 | let mut accum = 0; 38 | 39 | while buffer.len() >= 32 { 40 | let mut b = &buffer[..32]; 41 | while b.len() >= 2 { 42 | accum += u16::from_be_bytes(b[0..=1].try_into().unwrap()) as u32; 43 | b = &b[2..]; 44 | } 45 | buffer = &buffer[32..]; 46 | } 47 | 48 | while buffer.len() >= 2 { 49 | accum += u16::from_be_bytes(buffer[0..=1].try_into().unwrap()) as u32; 50 | buffer = &buffer[2..]; 51 | } 52 | 53 | if let Some(&value) = buffer.first() { 54 | accum += (value as u32) << 8; 55 | } 56 | 57 | accum = (accum >> 16) + (accum & 0xffff); 58 | ((accum >> 16) as u16) + (accum as u16) 59 | } 60 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DOCKER="docker" 4 | 5 | ${DOCKER} build -t pdu-test - <<'EOF' 6 | FROM ubuntu:bionic 7 | ENV LANG=C.UTF-8 \ 8 | LC_ALL=C.UTF-8 9 | VOLUME /usr/local/src/pdu 10 | WORKDIR /usr/local/src/pdu 11 | SHELL ["/bin/bash", "-eu", "-o", "pipefail", "-c"] 12 | RUN \ 13 | export DEBIAN_FRONTEND=noninteractive; \ 14 | apt-get -q update; \ 15 | apt-get -q install -y curl build-essential linux-headers-generic pkg-config binutils-dev libunwind-dev libpcap-dev tshark; \ 16 | apt-get -q clean autoclean; 17 | RUN \ 18 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable; \ 19 | source $HOME/.cargo/env; \ 20 | cargo install honggfuzz; 21 | ENTRYPOINT \ 22 | source $HOME/.cargo/env; \ 23 | cargo test --verbose 24 | EOF 25 | 26 | ${DOCKER} run --init --rm -v "$(pwd):/usr/local/src/pdu" pdu-test 27 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | ### Test Harness 2 | 3 | This library implements a single integration test which parses realistic traffic 4 | samples and then compares the result against Wireshark's parser. New tests can 5 | be created by adding `.pcap` files to the `pcaps/` subdirectory. 6 | 7 | **Note:** Wireshark must be installed to run these tests, so that the `tshark` 8 | executable is available in the system path. 9 | 10 | *macOS* – `brew install wireshark`
11 | *Debian* – `apt-get install tshark`
12 | *RedHat* – `yum install wireshark`
13 | -------------------------------------------------------------------------------- /tests/pcaps/GRE.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/GRE.pcap -------------------------------------------------------------------------------- /tests/pcaps/ICMP_across_dot1q.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/ICMP_across_dot1q.pcap -------------------------------------------------------------------------------- /tests/pcaps/ICMPv6_echos.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/ICMPv6_echos.pcap -------------------------------------------------------------------------------- /tests/pcaps/PPTP_negotiation.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/PPTP_negotiation.pcap -------------------------------------------------------------------------------- /tests/pcaps/QinQ.pcap.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/QinQ.pcap.pcap -------------------------------------------------------------------------------- /tests/pcaps/TCP_SACK.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/TCP_SACK.pcap -------------------------------------------------------------------------------- /tests/pcaps/connection termination.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/connection termination.pcap -------------------------------------------------------------------------------- /tests/pcaps/gre-erspan.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/gre-erspan.pcap -------------------------------------------------------------------------------- /tests/pcaps/gre-sample.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/gre-sample.pcap -------------------------------------------------------------------------------- /tests/pcaps/gre_and_4over6.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/gre_and_4over6.pcap -------------------------------------------------------------------------------- /tests/pcaps/ipv4-udp-fragmented.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/ipv4-udp-fragmented.pcap -------------------------------------------------------------------------------- /tests/pcaps/ipv6-gtp6.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/ipv6-gtp6.pcap -------------------------------------------------------------------------------- /tests/pcaps/ipv6-udp-fragmented.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/ipv6-udp-fragmented.pcap -------------------------------------------------------------------------------- /tests/pcaps/nb6-startup.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/nb6-startup.pcap -------------------------------------------------------------------------------- /tests/pcaps/vpws-selftest-customer.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/vpws-selftest-customer.pcap -------------------------------------------------------------------------------- /tests/pcaps/vpws-selftest-uplink.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/vpws-selftest-uplink.pcap -------------------------------------------------------------------------------- /tests/pcaps/vxlan.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexforster/pdu/bc7092583f10c648d93fc5fd905b985982faa990/tests/pcaps/vxlan.pcap -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Alex Forster 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use pdu::*; 20 | 21 | use std::collections::VecDeque; 22 | use std::error::Error; 23 | use std::ffi; 24 | use std::fs; 25 | use std::path; 26 | use std::process::{Command, Stdio}; 27 | use std::result::Result; 28 | 29 | use base16; 30 | use pcap; 31 | use roxmltree as xml; 32 | 33 | fn hex_decode>(length: usize, input: &T) -> Vec { 34 | let input = input.as_ref(); 35 | let mut padding = Vec::new(); 36 | if input.len() % 2 != 0 { 37 | // left-pad input with an ASCII zero to make input length even 38 | padding.push(b'0'); 39 | } 40 | while length > 0 && (input.len() + padding.len()) < (length * 2) { 41 | // left-pad input with two ASCII zeros for correct output length 42 | padding.push(b'0'); 43 | padding.push(b'0'); 44 | } 45 | let result = base16::decode([&padding, input].concat().as_slice()).unwrap(); 46 | if length > 0 && result.len() > length { 47 | let mut result = VecDeque::from(result); 48 | while result.len() > length { 49 | result.pop_front(); 50 | } 51 | Vec::from(result) 52 | } else { 53 | result 54 | } 55 | } 56 | 57 | fn descendant_value(node: &xml::Node, proto: &str, field: &str, length: usize) -> Result, Box> { 58 | let descendant = node.descendants().find(|n| n.attribute("name") == Some(format!("{}.{}", proto, field).as_str())); 59 | eprintln!("{}.{} = {:?}", proto, field, descendant); 60 | let descendant = if let Some(descendant) = descendant { 61 | descendant 62 | } else { 63 | return Err(format!("{}.{} not found", proto, field).into()); 64 | }; 65 | let value = if let Some(value) = descendant.attribute("value") { 66 | value 67 | } else { 68 | return Err(format!("{}.{} has no 'value' attribute", proto, field).into()); 69 | }; 70 | Ok(hex_decode(length, value)) 71 | } 72 | 73 | fn descendant_show(node: &xml::Node, proto: &str, field: &str, length: usize) -> Result, Box> { 74 | let descendant = node.descendants().find(|n| n.attribute("name") == Some(format!("{}.{}", proto, field).as_str())); 75 | eprintln!("{}.{} = {:?}", proto, field, descendant); 76 | let descendant = if let Some(descendant) = descendant { 77 | descendant 78 | } else { 79 | return Err(format!("{}.{} not found", proto, field).into()); 80 | }; 81 | let value = if let Some(value) = descendant.attribute("show") { 82 | value 83 | } else { 84 | return Err(format!("{}.{} has no 'show' attribute", proto, field).into()); 85 | }; 86 | Ok(hex_decode(length, value)) 87 | } 88 | 89 | fn visit_ethernet_pdu(pdu: &EthernetPdu, mut nodes: VecDeque) -> Result<(), Box> { 90 | let node = nodes.pop_front().unwrap(); 91 | if node.attribute("name") == Some("_ws.malformed") { 92 | return Err("node: malformed".into()); 93 | } 94 | assert_eq!(node.attribute("name"), Some("eth")); 95 | 96 | assert_eq!(pdu.destination_address().as_ref(), descendant_value(&node, "eth", "dst", 6)?.as_slice()); 97 | assert_eq!(pdu.source_address().as_ref(), descendant_value(&node, "eth", "src", 6)?.as_slice()); 98 | assert_eq!(&pdu.tpid().to_be_bytes(), descendant_value(&node, "eth", "type", 2)?.as_slice()); 99 | 100 | if node.next_sibling_element().and_then(|sibling| sibling.attribute("name")) == Some("vlan") { 101 | let node = nodes.pop_front().unwrap(); 102 | assert_eq!(node.attribute("name"), Some("vlan")); 103 | 104 | assert_eq!(&pdu.vlan().unwrap().to_be_bytes(), descendant_value(&node, "vlan", "id", 2)?.as_slice()); 105 | assert_eq!(&pdu.vlan_pcp().unwrap().to_be_bytes(), descendant_value(&node, "vlan", "priority", 1)?.as_slice()); 106 | assert_eq!( 107 | (pdu.vlan_dei().unwrap() as u8).to_be_bytes(), 108 | descendant_value(&node, "vlan", "dei", 1)?.as_slice() 109 | ); 110 | assert_eq!(&pdu.ethertype().to_be_bytes(), descendant_value(&node, "vlan", "etype", 2)?.as_slice()); 111 | } else { 112 | assert_eq!(&pdu.ethertype().to_be_bytes(), descendant_value(&node, "eth", "type", 2)?.as_slice()); 113 | } 114 | 115 | match pdu.inner() { 116 | Ok(ethernet) => match ethernet { 117 | Ethernet::Raw(raw) => Ok(assert_eq!(&pdu.buffer()[pdu.computed_ihl()..], raw)), 118 | Ethernet::Arp(arp_pdu) => visit_arp_pdu(&arp_pdu, nodes), 119 | Ethernet::Ipv4(ipv4_pdu) => visit_ipv4_pdu(&ipv4_pdu, nodes), 120 | Ethernet::Ipv6(ipv6_pdu) => visit_ipv6_pdu(&ipv6_pdu, nodes), 121 | }, 122 | Err(e) => Err(e.into()), 123 | } 124 | } 125 | 126 | fn visit_arp_pdu(pdu: &ArpPdu, mut nodes: VecDeque) -> Result<(), Box> { 127 | let node = nodes.pop_front().unwrap(); 128 | if node.attribute("name") == Some("_ws.malformed") { 129 | return Err("node: malformed".into()); 130 | } 131 | assert_eq!(node.attribute("name"), Some("arp")); 132 | 133 | assert_eq!(pdu.hardware_type().to_be_bytes(), descendant_value(&node, "arp", "hw.type", 2)?.as_slice()); 134 | assert_eq!(pdu.protocol_type().to_be_bytes(), descendant_value(&node, "arp", "proto.type", 2)?.as_slice()); 135 | assert_eq!(pdu.hardware_length().to_be_bytes(), descendant_value(&node, "arp", "hw.size", 1)?.as_slice()); 136 | assert_eq!(pdu.protocol_length().to_be_bytes(), descendant_value(&node, "arp", "proto.size", 1)?.as_slice()); 137 | assert_eq!(pdu.opcode().to_be_bytes(), descendant_value(&node, "arp", "opcode", 2)?.as_slice()); 138 | assert_eq!(pdu.sender_hardware_address().as_ref(), descendant_value(&node, "arp", "src.hw_mac", 6)?.as_slice()); 139 | assert_eq!(pdu.sender_protocol_address().as_ref(), descendant_value(&node, "arp", "src.proto_ipv4", 4)?.as_slice()); 140 | assert_eq!(pdu.target_hardware_address().as_ref(), descendant_value(&node, "arp", "dst.hw_mac", 6)?.as_slice()); 141 | assert_eq!(pdu.target_protocol_address().as_ref(), descendant_value(&node, "arp", "dst.proto_ipv4", 4)?.as_slice()); 142 | 143 | Ok(()) 144 | } 145 | 146 | fn visit_ipv4_pdu(pdu: &Ipv4Pdu, mut nodes: VecDeque) -> Result<(), Box> { 147 | let node = nodes.pop_front().unwrap(); 148 | if node.attribute("name") == Some("_ws.malformed") { 149 | return Err("node: malformed".into()); 150 | } 151 | assert_eq!(node.attribute("name"), Some("ip")); 152 | 153 | assert_eq!(pdu.version().to_be_bytes(), descendant_value(&node, "ip", "version", 1)?.as_slice()); 154 | // wireshark 2.6 ip.hdr_len[value] is not correctly right-shifted by 4 155 | //assert_eq!(pdu.ihl().to_be_bytes(), descendant_value(&node, "ip", "hdr_len", 1)?.as_slice()); 156 | assert_eq!(pdu.dscp().to_be_bytes(), descendant_value(&node, "ip", "dsfield.dscp", 1)?.as_slice()); 157 | assert_eq!(pdu.ecn().to_be_bytes(), descendant_value(&node, "ip", "dsfield.ecn", 1)?.as_slice()); 158 | assert_eq!(pdu.total_length().to_be_bytes(), descendant_value(&node, "ip", "len", 2)?.as_slice()); 159 | assert_eq!(pdu.identification().to_be_bytes(), descendant_value(&node, "ip", "id", 2)?.as_slice()); 160 | assert_eq!((pdu.dont_fragment() as u8).to_be_bytes(), descendant_value(&node, "ip", "flags.df", 1)?.as_slice()); 161 | assert_eq!((pdu.more_fragments() as u8).to_be_bytes(), descendant_value(&node, "ip", "flags.mf", 1)?.as_slice()); 162 | assert_eq!(pdu.fragment_offset().to_be_bytes(), descendant_value(&node, "ip", "frag_offset", 2)?.as_slice()); 163 | assert_eq!(pdu.ttl().to_be_bytes(), descendant_value(&node, "ip", "ttl", 1)?.as_slice()); 164 | assert_eq!(pdu.protocol().to_be_bytes(), descendant_value(&node, "ip", "proto", 1)?.as_slice()); 165 | assert_eq!(pdu.checksum().to_be_bytes(), descendant_value(&node, "ip", "checksum", 2)?.as_slice()); 166 | if descendant_show(&node, "ip", "checksum.status", 1)?.eq(&[0x01]) { 167 | assert_eq!(pdu.computed_checksum().to_be_bytes(), descendant_value(&node, "ip", "checksum", 2)?.as_slice()); 168 | } 169 | assert_eq!(pdu.source_address().as_ref(), descendant_value(&node, "ip", "src", 4)?.as_slice()); 170 | assert_eq!(pdu.destination_address().as_ref(), descendant_value(&node, "ip", "dst", 4)?.as_slice()); 171 | 172 | if let Some(options) = node.children().find(|n| n.attribute("name") == Some("")) { 173 | let mut options = options.children().filter(|n| n.is_element()).collect::>(); 174 | for option in pdu.options() { 175 | let node = options.pop_front().unwrap(); 176 | match option { 177 | Ipv4Option::Raw { option, .. } => { 178 | assert_eq!(option.to_be_bytes(), descendant_value(&node, "ip", "opt.type", 1)?.as_slice()); 179 | } 180 | } 181 | } 182 | while options.front().is_some() && options.front().unwrap().attribute("name") == Some("") { 183 | options.pop_front().unwrap(); 184 | } 185 | assert!(options.is_empty()); 186 | } 187 | 188 | match pdu.inner() { 189 | Ok(ipv4) => match ipv4 { 190 | Ipv4::Raw(raw) => Ok(assert_eq!(&pdu.buffer()[pdu.computed_ihl()..], raw)), 191 | Ipv4::Tcp(tcp_pdu) => visit_tcp_pdu(&tcp_pdu, &Ip::Ipv4(*pdu), nodes), 192 | Ipv4::Udp(udp_pdu) => visit_udp_pdu(&udp_pdu, &Ip::Ipv4(*pdu), nodes), 193 | Ipv4::Icmp(icmp_pdu) => visit_icmp_pdu(&icmp_pdu, &Ip::Ipv4(*pdu), nodes), 194 | Ipv4::Gre(gre_pdu) => visit_gre_pdu(&gre_pdu, nodes), 195 | }, 196 | Err(e) => Err(e.into()), 197 | } 198 | } 199 | 200 | fn visit_ipv6_pdu(pdu: &Ipv6Pdu, mut nodes: VecDeque) -> Result<(), Box> { 201 | let node = nodes.pop_front().unwrap(); 202 | if node.attribute("name") == Some("_ws.malformed") { 203 | return Err("node: malformed".into()); 204 | } 205 | assert_eq!(node.attribute("name"), Some("ipv6")); 206 | 207 | assert_eq!(pdu.version().to_be_bytes(), descendant_value(&node, "ipv6", "version", 1)?.as_slice()); 208 | assert_eq!(pdu.dscp().to_be_bytes(), descendant_value(&node, "ipv6", "tclass.dscp", 1)?.as_slice()); 209 | assert_eq!(pdu.ecn().to_be_bytes(), descendant_value(&node, "ipv6", "tclass.ecn", 1)?.as_slice()); 210 | assert_eq!(pdu.flow_label().to_be_bytes(), descendant_value(&node, "ipv6", "flow", 4)?.as_slice()); 211 | assert_eq!(pdu.payload_length().to_be_bytes(), descendant_value(&node, "ipv6", "plen", 2)?.as_slice()); 212 | assert_eq!(pdu.next_header().to_be_bytes(), descendant_value(&node, "ipv6", "nxt", 1)?.as_slice()); 213 | assert_eq!(pdu.hop_limit().to_be_bytes(), descendant_value(&node, "ipv6", "hlim", 1)?.as_slice()); 214 | assert_eq!(pdu.source_address().as_ref(), descendant_value(&node, "ipv6", "src", 16)?.as_slice()); 215 | assert_eq!(pdu.destination_address().as_ref(), descendant_value(&node, "ipv6", "dst", 16)?.as_slice()); 216 | 217 | if let Some(fraghdr) = node.children().find(|n| n.attribute("name") == Some("ipv6.fraghdr")) { 218 | assert_eq!( 219 | pdu.computed_identification().unwrap().to_be_bytes(), 220 | descendant_value(&fraghdr, "ipv6", "fraghdr.ident", 4)?.as_slice() 221 | ); 222 | // wireshark 2.6 ipv6.fraghdr.offset[value] is not correctly multiplied by 8 223 | //assert_eq!( 224 | // pdu.computed_fragment_offset().unwrap().to_be_bytes(), 225 | // descendant_value(&fraghdr, "ipv6", "fraghdr.offset", 2)?.as_slice() 226 | //); 227 | assert_eq!( 228 | &[pdu.computed_more_fragments().unwrap() as u8], 229 | descendant_value(&fraghdr, "ipv6", "fraghdr.more", 1)?.as_slice() 230 | ); 231 | } 232 | 233 | match pdu.inner() { 234 | Ok(ipv6) => match ipv6 { 235 | Ipv6::Raw(raw) => Ok(assert_eq!(&pdu.buffer()[pdu.computed_ihl()..], raw)), 236 | Ipv6::Tcp(tcp_pdu) => visit_tcp_pdu(&tcp_pdu, &Ip::Ipv6(*pdu), nodes), 237 | Ipv6::Udp(udp_pdu) => visit_udp_pdu(&udp_pdu, &Ip::Ipv6(*pdu), nodes), 238 | Ipv6::Icmp(icmp_pdu) => visit_icmp_pdu(&icmp_pdu, &Ip::Ipv6(*pdu), nodes), 239 | Ipv6::Gre(gre_pdu) => visit_gre_pdu(&gre_pdu, nodes), 240 | }, 241 | Err(e) => Err(e.into()), 242 | } 243 | } 244 | 245 | fn visit_tcp_pdu(pdu: &TcpPdu, ip_pdu: &Ip, mut nodes: VecDeque) -> Result<(), Box> { 246 | let node = nodes.pop_front().unwrap(); 247 | if node.attribute("name") == Some("_ws.malformed") { 248 | return Err("node: malformed".into()); 249 | } 250 | assert_eq!(node.attribute("name"), Some("tcp")); 251 | 252 | assert_eq!(pdu.source_port().to_be_bytes(), descendant_value(&node, "tcp", "srcport", 2)?.as_slice()); 253 | assert_eq!(pdu.destination_port().to_be_bytes(), descendant_value(&node, "tcp", "dstport", 2)?.as_slice()); 254 | assert_eq!(pdu.sequence_number().to_be_bytes(), descendant_value(&node, "tcp", "seq", 4)?.as_slice()); 255 | assert_eq!(pdu.acknowledgement_number().to_be_bytes(), descendant_value(&node, "tcp", "ack", 4)?.as_slice()); 256 | // wireshark 2.6 tcp.hdr_len[value] is not correctly right-shifted by 4 257 | //assert_eq!(pdu.data_offset().to_be_bytes(), descendant_value(&node, "tcp", "hdr_len", 1)?.as_slice()); 258 | assert_eq!(pdu.flags().to_be_bytes(), descendant_value(&node, "tcp", "flags", 1)?.as_slice()); 259 | assert_eq!((pdu.fin() as u8).to_be_bytes(), descendant_value(&node, "tcp", "flags.fin", 1)?.as_slice()); 260 | assert_eq!((pdu.syn() as u8).to_be_bytes(), descendant_value(&node, "tcp", "flags.syn", 1)?.as_slice()); 261 | assert_eq!((pdu.rst() as u8).to_be_bytes(), descendant_value(&node, "tcp", "flags.reset", 1)?.as_slice()); 262 | assert_eq!((pdu.psh() as u8).to_be_bytes(), descendant_value(&node, "tcp", "flags.push", 1)?.as_slice()); 263 | assert_eq!((pdu.ack() as u8).to_be_bytes(), descendant_value(&node, "tcp", "flags.ack", 1)?.as_slice()); 264 | assert_eq!((pdu.urg() as u8).to_be_bytes(), descendant_value(&node, "tcp", "flags.urg", 1)?.as_slice()); 265 | assert_eq!((pdu.ecn() as u8).to_be_bytes(), descendant_value(&node, "tcp", "flags.ecn", 1)?.as_slice()); 266 | assert_eq!((pdu.cwr() as u8).to_be_bytes(), descendant_value(&node, "tcp", "flags.cwr", 1)?.as_slice()); 267 | assert_eq!(pdu.window_size().to_be_bytes(), descendant_value(&node, "tcp", "window_size_value", 2)?.as_slice()); 268 | assert_eq!(pdu.computed_window_size(0).to_be_bytes(), descendant_value(&node, "tcp", "window_size", 4)?.as_slice()); 269 | assert_eq!(pdu.checksum().to_be_bytes(), descendant_value(&node, "tcp", "checksum", 2)?.as_slice()); 270 | if descendant_show(&node, "tcp", "checksum.status", 1)?.eq(&[0x01]) { 271 | assert_eq!( 272 | pdu.computed_checksum(&ip_pdu).to_be_bytes(), 273 | descendant_value(&node, "tcp", "checksum", 2)?.as_slice() 274 | ); 275 | } 276 | assert_eq!(pdu.urgent_pointer().to_be_bytes(), descendant_value(&node, "tcp", "urgent_pointer", 2)?.as_slice()); 277 | 278 | let mut options = pdu.options().collect::>(); 279 | 280 | for node in node 281 | .descendants() 282 | .filter(|n| n.attribute("name").is_some() && n.attribute("name").unwrap().starts_with("tcp.options.")) 283 | { 284 | match node.attribute("name").unwrap() { 285 | "tcp.options.nop" => { 286 | if let Some(TcpOption::NoOp) = options.pop_front() { 287 | continue; 288 | } else { 289 | panic!("expected TcpOption::NoOp"); 290 | } 291 | } 292 | "tcp.options.mss" => { 293 | if let Some(TcpOption::Mss { size }) = options.pop_front() { 294 | assert_eq!(size.to_be_bytes(), descendant_value(&node, "tcp", "options.mss_val", 2)?.as_slice()); 295 | } else { 296 | panic!("expected TcpOption::Mss"); 297 | } 298 | } 299 | "tcp.options.wscale" => { 300 | if let Some(TcpOption::WindowScale { shift }) = options.pop_front() { 301 | assert_eq!( 302 | shift.to_be_bytes(), 303 | descendant_value(&node, "tcp", "options.wscale.shift", 1)?.as_slice() 304 | ); 305 | } else { 306 | panic!("expected TcpOption::WindowScale"); 307 | } 308 | } 309 | "tcp.options.sack_perm" => { 310 | if let Some(TcpOption::SackPermitted) = options.pop_front() { 311 | continue; 312 | } else { 313 | panic!("expected TcpOption::SackPermitted"); 314 | } 315 | } 316 | "tcp.options.sack" => { 317 | if let Some(TcpOption::Sack { blocks }) = options.pop_front() { 318 | match blocks { 319 | [Some((l, r)), None, None, None] | 320 | [Some((l, _)), Some((_, r)), None, None] | 321 | [Some((l, _)), Some((_, _)), Some((_, r)), None] | 322 | [Some((l, _)), Some((_, _)), Some((_, _)), Some((_, r))] => { 323 | assert_eq!( 324 | &l.to_be_bytes(), 325 | descendant_value(&node, "tcp", "options.sack_le", 4)?.as_slice() 326 | ); 327 | assert_eq!( 328 | &r.to_be_bytes(), 329 | descendant_value(&node, "tcp", "options.sack_re", 4)?.as_slice() 330 | ); 331 | } 332 | _ => panic!("TcpOption::Sack blocks are [None, None, None, None]"), 333 | } 334 | } else { 335 | panic!("expected TcpOption::Sack"); 336 | } 337 | } 338 | "tcp.options.timestamp" => { 339 | if let Some(TcpOption::Timestamp { val, ecr }) = options.pop_front() { 340 | assert_eq!( 341 | val.to_be_bytes(), 342 | descendant_value(&node, "tcp", "options.timestamp.tsval", 4)?.as_slice() 343 | ); 344 | assert_eq!( 345 | ecr.to_be_bytes(), 346 | descendant_value(&node, "tcp", "options.timestamp.tsecr", 4)?.as_slice() 347 | ); 348 | } else { 349 | panic!("expected TcpOption::Timestamp"); 350 | } 351 | } 352 | _ => {} 353 | } 354 | } 355 | 356 | assert!(options.is_empty()); 357 | 358 | Ok(()) 359 | } 360 | 361 | fn visit_udp_pdu(pdu: &UdpPdu, ip_pdu: &Ip, mut nodes: VecDeque) -> Result<(), Box> { 362 | let node = nodes.pop_front().unwrap(); 363 | if node.attribute("name") == Some("_ws.malformed") { 364 | return Err("node: malformed".into()); 365 | } 366 | assert_eq!(node.attribute("name"), Some("udp")); 367 | 368 | assert_eq!(pdu.source_port().to_be_bytes(), descendant_value(&node, "udp", "srcport", 2)?.as_slice()); 369 | assert_eq!(pdu.destination_port().to_be_bytes(), descendant_value(&node, "udp", "dstport", 2)?.as_slice()); 370 | assert_eq!(pdu.length().to_be_bytes(), descendant_value(&node, "udp", "length", 2)?.as_slice()); 371 | assert_eq!(pdu.checksum().to_be_bytes(), descendant_value(&node, "udp", "checksum", 2)?.as_slice()); 372 | if descendant_show(&node, "udp", "checksum.status", 1)?.eq(&[0x01]) { 373 | assert_eq!( 374 | pdu.computed_checksum(&ip_pdu).to_be_bytes(), 375 | descendant_value(&node, "udp", "checksum", 2)?.as_slice() 376 | ); 377 | } 378 | 379 | Ok(()) 380 | } 381 | 382 | fn visit_icmp_pdu(pdu: &IcmpPdu, ip_pdu: &Ip, mut nodes: VecDeque) -> Result<(), Box> { 383 | let node = nodes.pop_front().unwrap(); 384 | if node.attribute("name") == Some("_ws.malformed") { 385 | return Err("node: malformed".into()); 386 | } 387 | 388 | let proto = match node.attribute("name") { 389 | Some("icmp") => "icmp", 390 | Some("icmpv6") => "icmpv6", 391 | something_else => panic!("{:?}", something_else), 392 | }; 393 | 394 | assert_eq!(pdu.message_type().to_be_bytes(), descendant_value(&node, proto, "type", 1)?.as_slice()); 395 | assert_eq!(pdu.message_code().to_be_bytes(), descendant_value(&node, proto, "code", 1)?.as_slice()); 396 | assert_eq!(pdu.checksum().to_be_bytes(), descendant_value(&node, proto, "checksum", 2)?.as_slice()); 397 | if descendant_show(&node, proto, "checksum.status", 1)?.eq(&[0x01]) { 398 | assert_eq!( 399 | pdu.computed_checksum(&ip_pdu).to_be_bytes(), 400 | descendant_value(&node, proto, "checksum", 2)?.as_slice() 401 | ); 402 | } 403 | 404 | Ok(()) 405 | } 406 | 407 | fn visit_gre_pdu(pdu: &GrePdu, mut nodes: VecDeque) -> Result<(), Box> { 408 | let node = nodes.pop_front().unwrap(); 409 | if node.attribute("name") == Some("_ws.malformed") { 410 | return Err("node: malformed".into()); 411 | } 412 | assert_eq!(node.attribute("name"), Some("gre")); 413 | 414 | assert_eq!(pdu.version().to_be_bytes(), descendant_value(&node, "gre", "flags.version", 1)?.as_slice()); 415 | assert_eq!(pdu.ethertype().to_be_bytes(), descendant_value(&node, "gre", "proto", 2)?.as_slice()); 416 | 417 | if node.descendants().any(|n| n.attribute("name") == Some("gre.checksum")) { 418 | assert_eq!(pdu.checksum().unwrap().to_be_bytes(), descendant_value(&node, "gre", "checksum", 2)?.as_slice()); 419 | if descendant_show(&node, "gre", "checksum.status", 1)?.eq(&[0x01]) { 420 | assert_eq!( 421 | pdu.computed_checksum().unwrap().to_be_bytes(), 422 | descendant_value(&node, "gre", "checksum", 2)?.as_slice() 423 | ); 424 | } 425 | } 426 | 427 | if node.descendants().any(|n| n.attribute("name") == Some("gre.key")) { 428 | assert_eq!(pdu.key().unwrap().to_be_bytes(), descendant_value(&node, "gre", "key", 4)?.as_slice()); 429 | } 430 | 431 | if node.descendants().any(|n| n.attribute("name") == Some("gre.sequence_number")) { 432 | assert_eq!( 433 | pdu.sequence_number().unwrap().to_be_bytes(), 434 | descendant_value(&node, "gre", "sequence_number", 4)?.as_slice() 435 | ); 436 | } 437 | 438 | match pdu.inner() { 439 | Ok(gre) => match gre { 440 | Gre::Raw(raw) => Ok(assert_eq!(&pdu.buffer()[pdu.computed_ihl()..], raw)), 441 | Gre::Ethernet(ethernet_pdu) => visit_ethernet_pdu(ðernet_pdu, nodes), 442 | Gre::Ipv4(ipv4_pdu) => visit_ipv4_pdu(&ipv4_pdu, nodes), 443 | Gre::Ipv6(ipv6_pdu) => visit_ipv6_pdu(&ipv6_pdu, nodes), 444 | }, 445 | Err(e) => Err(e.into()), 446 | } 447 | } 448 | 449 | #[test] 450 | fn test_pcaps() -> Result<(), Box> { 451 | let crate_root = path::Path::new(env!("CARGO_MANIFEST_DIR")).to_owned(); 452 | 453 | let pcap_files = crate_root 454 | .join("tests/pcaps") 455 | .read_dir()? 456 | .filter_map(Result::ok) 457 | .filter(|f| f.path().is_file() && f.path().extension().unwrap_or_else(|| ffi::OsStr::new("")) == "pcap") 458 | .collect::>(); 459 | 460 | for pcap_file in pcap_files.iter() { 461 | let pcap_file = pcap_file.path().to_str().unwrap().to_string(); 462 | let mut tshark = Command::new("tshark"); 463 | tshark.args(&[ 464 | "-n", 465 | "-o", 466 | "ip.defragment:false", 467 | "-o", 468 | "ipv6.defragment:false", 469 | "-o", 470 | "tcp.desegment_tcp_streams:false", 471 | "-T", 472 | "pdml", 473 | "-r", 474 | &pcap_file, 475 | ]); 476 | tshark.stdout(Stdio::piped()); 477 | 478 | let output = tshark.output()?; 479 | if !output.status.success() { 480 | eprintln!("[{}] tshark error: {:?}", pcap_file, output); 481 | continue; 482 | } 483 | 484 | let dissections = String::from_utf8(output.stdout)?; 485 | let dissections = xml::Document::parse(dissections.as_str())?; 486 | let dissections: Vec = 487 | dissections.root().first_element_child().unwrap().children().filter(|n| n.is_element()).collect(); 488 | 489 | let mut pcap = match pcap::Capture::from_file(&pcap_file) { 490 | Ok(pcap) => pcap, 491 | Err(e) => { 492 | eprintln!("[{}] pcap error: {:?}", &pcap_file, e); 493 | continue; 494 | } 495 | }; 496 | 497 | let mut i = -1isize; 498 | for dissection in dissections.iter() { 499 | let data = pcap.next().unwrap().data; 500 | i += 1; 501 | let dissections: VecDeque = dissection 502 | .children() 503 | .filter(|n| n.is_element()) 504 | .skip(2) 505 | .filter(|n| n.attribute("name") != Some("fake-field-wrapper")) 506 | .collect(); 507 | 508 | if dissections.is_empty() { 509 | eprintln!("[{}] empty", &pcap_file); 510 | continue; 511 | } 512 | 513 | eprintln!("{} (#{})", &pcap_file, i + 1); 514 | let first_layer = dissections.front().unwrap().attribute("name"); 515 | if first_layer == Some("eth") { 516 | match EthernetPdu::new(&data) { 517 | Ok(ethernet_pdu) => match visit_ethernet_pdu(ðernet_pdu, dissections) { 518 | Ok(()) => {} 519 | Err(e) => { 520 | eprintln!("[{}#{}] validate error: {:?}", &pcap_file, i + 1, e); 521 | continue; 522 | } 523 | }, 524 | Err(e) => { 525 | eprintln!("[{}#{}] decode error: {:?}", &pcap_file, i + 1, e); 526 | continue; 527 | } 528 | } 529 | } else if first_layer == Some("ip") || first_layer == Some("ipv6") { 530 | match Ip::new(&data) { 531 | Ok(Ip::Ipv4(ipv4_pdu)) => match visit_ipv4_pdu(&ipv4_pdu, dissections) { 532 | Ok(()) => {} 533 | Err(e) => { 534 | eprintln!("[{}#{}] validate error: {:?}", &pcap_file, i + 1, e); 535 | continue; 536 | } 537 | }, 538 | Ok(Ip::Ipv6(ipv6_pdu)) => match visit_ipv6_pdu(&ipv6_pdu, dissections) { 539 | Ok(()) => {} 540 | Err(e) => { 541 | eprintln!("[{}#{}] validate error: {:?}", &pcap_file, i + 1, e); 542 | continue; 543 | } 544 | }, 545 | Err(e) => { 546 | eprintln!("[{}#{}] decode error: {:?}", &pcap_file, i + 1, e); 547 | continue; 548 | } 549 | } 550 | } else { 551 | eprintln!("[{}] unsupported first layer ({:?})", &pcap_file, first_layer); 552 | continue; 553 | } 554 | } 555 | } 556 | 557 | Ok(()) 558 | } 559 | --------------------------------------------------------------------------------