dhcproto-0.9.0/.cargo_vcs_info.json0000644000000001360000000000100126640ustar { "git": { "sha1": "240925fcab750b0c42f57a8bde05c55a25f7fa5e" }, "path_in_vcs": "" }dhcproto-0.9.0/.editorconfig000064400000000000000000000004151046102023000141310ustar 00000000000000root = true # Unix-style newlines with a newline ending every file [*] end_of_line = lf insert_final_newline = true # Tab indentation (no size specified) [Makefile] indent_style = tab # Indentation override for all JSON [*.json] indent_style = space indent_size = 4 dhcproto-0.9.0/.github/workflows/actions.yml000064400000000000000000000043031046102023000172340ustar 00000000000000# Based on https://github.com/actions-rs/meta/blob/master/recipes/msrv.md on: [push, pull_request] name: Actions jobs: check: name: Check runs-on: ubuntu-latest strategy: matrix: rust: - stable - beta - nightly steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Run cargo check uses: actions-rs/cargo@v1 with: command: check args: --all-features test: name: Test Suite runs-on: ubuntu-latest strategy: matrix: rust: - stable - beta - nightly steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test args: --all-features fmt: name: Rustfmt runs-on: ubuntu-latest strategy: matrix: rust: - stable steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Install rustfmt run: rustup component add rustfmt - name: Run cargo fmt uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check clippy: name: Clippy runs-on: ubuntu-latest strategy: matrix: rust: - stable steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Install clippy run: rustup component add clippy - name: Run cargo clippy uses: actions-rs/cargo@v1 with: command: clippy args: -- -D warnings dhcproto-0.9.0/.gitignore000064400000000000000000000005141046102023000134440ustar 00000000000000.vscode/ .idea/ *.swp # Don't include Cargo.lock for library crates # see https://doc.rust-lang.org/cargo/faq.html#why-do-binaries-have-cargolock-in-version-control-but-not-libraries Cargo.lock # Compiled files *.o *.so *.rlib *.dll .DS_Store # Executables *.exe # Generated by Cargo /target **/target *.rs.bk .rls.toml rls/** dhcproto-0.9.0/.rustfmt.toml000064400000000000000000000000611046102023000141300ustar 00000000000000edition = "2018" use_field_init_shorthand = true dhcproto-0.9.0/CHANGELOG.md000064400000000000000000000054561046102023000132770ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased ## [0.9.0] ### Added - `PartialOrd`/`Ord` impls for `v4::DhcpOption`/`v6::DhcpOption`/`v4::OptionCode`/`v6::OptionCode` - `v6::DhcpOptions` methods `*_all` - `v6::Duid` & methods - `v6::Message` `Display` impl - `v6::RelayMessage` - `v4::NISServerAddr` added to options - `v4::Message::clear_sname`/`clear_fname` added - dhcpv4 opt client fqdn added. uses trust-dns-proto's `Name` type to decode the domain ### Changed - internally, v6 DhcpOptions are now kept sorted by OptionCode (may become `HashMap<_, Vec<_>>` in future) - `DhcpOptions::RelayMsg()` type changed to `RelayMessage` - moved Duid to duid module - added oro_codes ### Fixed - relay agent info will be added before END opt if present [see here](https://datatracker.ietf.org/doc/html/rfc3046#section-2.1) - fixed panic on .get for v6 options ## [0.8.0] ### Changed - dhcpv4 option variants added (breaking) - dhcpv4 message type variants added (breaking) - ClientNetworkInterface removed inner tuple - Change `has_msg_type` return type to just `bool` ### Fixed - v6 `set_xid_num` was taking bytes from the wrong end - dhcpv6 DomainSearchList (opt 24) ### Added - dhcpv4 opt 119 DomainSearch - dhcpv4 opt 114 CaptivePortal - dhcpv4 message variants 9-18 added, breaking change for `MessageType` - dhcpv4 added DhcpOption for 91/92/93/94/97 - UnknownOption encode/decode - dhcpv4 options 151-157 from bulkleasequery RFC - add `Display` impl for `v4::Message` ## [0.7.0] ### Added - **breaking** DHCP Inform message variant ### Changed - bug in `set_chaddr` where `hlen` was not set ## [0.6.0] ### Added - methods for `dhcpv6::UnknownOption` & `RelayMsg` ### Changed - exposed some dhcpv6 opt fields as `pub` - `InterfaceId` type changed from `String` to `Vec` - `VendorClass`/`UserClass` changed to `Vec>` ### Removed - `ElapsedTime` and `Preference` ## [0.5.0] ### Added - added `clear`/`is_empty`/`retain` to v4 opts & relay agent sub-opts ### Fixed - **breaking** options enum for `v4::DhcpOption` was decoding into the wrong variants for a few types ## [0.4.1] ### Added - expose methods so one can actually create RelayInfo/RelayAgentInformation - methods to get the data out of various Unknown variants for opts/relay - added option 118 subnet selection - return impl Iterator for relay/opt iterator methods - more docs for opts/relay info ### Changed - `DhcpOption` variants added for v4 - some opt method return types have changed `iter()`/`iter_mut()` ## [0.3.0] ### Added ### Changed - `sname`/`fname` types changed from `String` to `[u8]` - perf improved for `Decoder` dhcproto-0.9.0/Cargo.toml0000644000000032650000000000100106700ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "dhcproto" version = "0.9.0" authors = [ "Ian Laidlaw ", "Evan Cameron ", ] description = """ A DHCP parser and encoder for DHCPv4/DHCPv6. `dhcproto` aims to be a functionally complete DHCP implementation. """ readme = "README.md" keywords = [ "dhcp", "dhcpv4", "dhcpv6", ] categories = [ "network-programming", "encoding", "parser-implementations", ] license = "MIT" repository = "https://github.com/bluecatengineering/dhcproto" [[bench]] name = "decode" harness = false [[bench]] name = "encode" harness = false [dependencies.dhcproto-macros] version = "0.1.0" [dependencies.hex] version = "0.4.3" [dependencies.ipnet] version = "2.5" [dependencies.rand] version = "0.8" [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dependencies.thiserror] version = "1.0" [dependencies.trust-dns-proto] version = "0.22.0" default-features = false [dependencies.url] version = "2.2.2" [dev-dependencies.criterion] version = "0.3" [dev-dependencies.serde_json] version = "1.0" [features] default = [] serde = [ "dep:serde", "url/serde", "ipnet/serde", "trust-dns-proto/serde-config", ] dhcproto-0.9.0/Cargo.toml.orig000064400000000000000000000021441046102023000143440ustar 00000000000000[package] name = "dhcproto" version = "0.9.0" authors = ["Ian Laidlaw ", "Evan Cameron "] edition = "2021" description = """ A DHCP parser and encoder for DHCPv4/DHCPv6. `dhcproto` aims to be a functionally complete DHCP implementation. """ categories = ["network-programming", "encoding","parser-implementations"] repository = "https://github.com/bluecatengineering/dhcproto" keywords = ["dhcp","dhcpv4","dhcpv6"] license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] thiserror = "1.0" rand = "0.8" serde = { version = "1.0", features = ["derive"], optional = true } hex = "0.4.3" trust-dns-proto = { version = "0.22.0", default-features = false } url = "2.2.2" dhcproto-macros = { path = "./dhcproto-macros", version = "0.1.0" } ipnet = "2.5" [features] default = [] serde = ["dep:serde", "url/serde", "ipnet/serde", "trust-dns-proto/serde-config"] [dev-dependencies] criterion = "0.3" serde_json = "1.0" [[bench]] name = "decode" harness = false [[bench]] name = "encode" harness = false dhcproto-0.9.0/LICENSE000064400000000000000000000020331046102023000124570ustar 00000000000000Copyright (c) 2021 Bluecat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dhcproto-0.9.0/README.md000064400000000000000000000063451046102023000127430ustar 00000000000000# dhcproto A DHCP parser and encoder for DHCPv4/DHCPv6. `dhcproto` aims to be a functionally complete DHCP implementation. Many common option types are implemented, PRs are welcome to flesh out missing types. **Attention!** We are developing a DHCP server using this library called [dora](https://github.com/bluecatengineering/dora)! ## features - v4 is 100% safe rust (v6 uses `get_unchecked` after bounds have been checked) - v4 & v6 Message types - v4 & v6 message header getters/setters, all data mutable - option types with 100's of fully type safe variants (accepting PR's for unknown variants) - Long option encoding supported (RFC 3396) (allows encoding options longer than 255 bytes) - benchmarked encoding/decoding ## crates.io ## Minimum Rust Version This crate uses const generics, Rust 1.53 is required ## Examples ### (v4) Decoding/Encoding ```rust use dhcproto::v4::{Message, Encoder, Decoder, Decodable, Encodable}; // decode let bytes = dhcp_offer(); let msg = Message::decode(&mut Decoder::new(&bytes))?; // now encode let mut buf = Vec::new(); let mut e = Encoder::new(&mut buf); msg.encode(&mut e)?; ``` ### (v4) Constructing messages ```rust use dhcproto::{v4, Encodable, Encoder}; // hardware addr let chaddr = vec![ 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, ]; // construct a new Message let mut msg = v4::Message::default(); msg.set_flags(v4::Flags::default().set_broadcast()) // set broadcast to true .set_chaddr(&chaddr) // set chaddr .opts_mut() .insert(v4::DhcpOption::MessageType(v4::MessageType::Discover)); // set msg type // set some more options msg.opts_mut() .insert(v4::DhcpOption::ParameterRequestList(vec![ v4::OptionCode::SubnetMask, v4::OptionCode::Router, v4::OptionCode::DomainNameServer, v4::OptionCode::DomainName, ])); msg.opts_mut() .insert(v4::DhcpOption::ClientIdentifier(chaddr)); // now encode to bytes let mut buf = Vec::new(); let mut e = Encoder::new(&mut buf); msg.encode(&mut e)?; // buf now has the contents of the encoded DHCP message ``` ## RFCs supported DHCPv6: - - - - (message types only) - (message types/status codes only, no opt 53) - (message types only) - (message types only) DHCPv4: - - - - - - - - - (message types & opts) - - - (message types & opts 151-157) - (message types only, status codes for opt 151 unimplemented) - dhcproto-0.9.0/benches/decode.rs000064400000000000000000000223661046102023000146650ustar 00000000000000use criterion::{criterion_group, criterion_main, Criterion}; use dhcproto::Decodable; fn decode_benches(c: &mut Criterion) { let mut g = c.benchmark_group("decode"); g.bench_function("decode_offer", |b| { let offer: &[u8] = &[ 0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x15, 0x5c, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x0a, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x02, 0x36, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x33, 0x04, 0x00, 0x00, 0x00, 0x3c, 0x3a, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x3b, 0x04, 0x00, 0x00, 0x00, 0x34, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x06, 0x08, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x01, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; b.iter(|| { dhcproto::v4::Message::from_bytes(offer).unwrap(); }); }); g.bench_function("decode_bootreq", |b| { let bootreq: &[u8] = &[ 1u8, // op 2, // htype 3, // hlen 4, // ops 5, 6, 7, 8, // xid 9, 10, // secs 11, 12, // flags 13, 14, 15, 16, // ciaddr 17, 18, 19, 20, // yiaddr 21, 22, 23, 24, // siaddr 25, 26, 27, 28, // giaddr 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, // chaddr 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 0, // sname: "-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijk", 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 0, 0, 0, 0, 0, 0, 0, 0, // file: "mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}m", 99, 130, 83, 99, // magic cookie ]; b.iter(|| { dhcproto::v4::Message::from_bytes(bootreq).unwrap(); }); }); g.bench_function("decode_discover", |b| { let discover: &[u8] = &[ 0x01, 0x01, 0x06, 0x00, 0xa6, 0x80, 0x56, 0x74, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xc0, 0xde, 0xca, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x01, 0x37, 0x40, 0xfc, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x43, 0x42, 0x33, 0x04, 0x00, 0x00, 0x00, 0x01, 0xff, ]; b.iter(|| { dhcproto::v4::Message::from_bytes(discover).unwrap(); }); }); g.bench_function("decode_other_offer", |b| { let other_offer: &[u8] = &[ 0x02, 0x01, 0x06, 0x00, 0xa6, 0x80, 0x56, 0x74, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x95, 0xc0, 0xa8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xc0, 0xde, 0xca, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x02, 0x36, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x33, 0x04, 0x00, 0x00, 0x00, 0x78, 0x3a, 0x04, 0x00, 0x00, 0x00, 0x3c, 0x3b, 0x04, 0x00, 0x00, 0x00, 0x69, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x1c, 0x04, 0xc0, 0xa8, 0x00, 0xff, 0x06, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x03, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; b.iter(|| { dhcproto::v4::Message::from_bytes(other_offer).unwrap(); }); }); g.bench_function("decode_opts", |b| { let opts: &[u8] = &[ 53, 1, 2, 54, 4, 192, 168, 0, 1, 51, 4, 0, 0, 0, 60, 58, 4, 0, 0, 0, 30, 59, 4, 0, 0, 0, 52, 1, 4, 255, 255, 255, 0, 3, 4, 192, 168, 0, 1, 6, 8, 192, 168, 0, 1, 192, 168, 1, 1, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; b.iter(|| dhcproto::v4::DhcpOptions::from_bytes(opts).unwrap()); }); } criterion_group!(benches, decode_benches); criterion_main!(benches); dhcproto-0.9.0/benches/encode.rs000064400000000000000000000236501046102023000146740ustar 00000000000000use criterion::{criterion_group, criterion_main, Criterion}; use dhcproto::v4::Message; use dhcproto::{Decodable, Encodable, Encoder}; fn encode_benches(c: &mut Criterion) { let mut g = c.benchmark_group("encode"); g.bench_function("encode_offer", |b| { let offer: &[u8] = &[ 0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x15, 0x5c, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x0a, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x02, 0x36, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x33, 0x04, 0x00, 0x00, 0x00, 0x3c, 0x3a, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x3b, 0x04, 0x00, 0x00, 0x00, 0x34, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x06, 0x08, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x01, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; let message = Message::from_bytes(offer).unwrap(); let mut bytes = Vec::with_capacity(offer.len()); b.iter(|| { message.encode(&mut Encoder::new(&mut bytes)).unwrap(); }); }); g.bench_function("encode_bootreq", |b| { let bootreq: &[u8] = &[ 1u8, // op 2, // htype 3, // hlen 4, // ops 5, 6, 7, 8, // xid 9, 10, // secs 11, 12, // flags 13, 14, 15, 16, // ciaddr 17, 18, 19, 20, // yiaddr 21, 22, 23, 24, // siaddr 25, 26, 27, 28, // giaddr 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, // chaddr 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 0, // sname: "-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijk", 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 0, 0, 0, 0, 0, 0, 0, 0, // file: "mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}m", 99, 130, 83, 99, // magic cookie ]; let message = Message::from_bytes(bootreq).unwrap(); let mut bytes = Vec::with_capacity(bootreq.len()); b.iter(|| { message.encode(&mut Encoder::new(&mut bytes)).unwrap(); }); }); g.bench_function("encode_discover", |b| { let discover: &[u8] = &[ 0x01, 0x01, 0x06, 0x00, 0xa6, 0x80, 0x56, 0x74, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xc0, 0xde, 0xca, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x01, 0x37, 0x40, 0xfc, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x43, 0x42, 0x33, 0x04, 0x00, 0x00, 0x00, 0x01, 0xff, ]; let message = Message::from_bytes(discover).unwrap(); let mut bytes = Vec::with_capacity(discover.len()); b.iter(|| { message.encode(&mut Encoder::new(&mut bytes)).unwrap(); }); }); g.bench_function("encode_other_offer", |b| { let other_offer: &[u8] = &[ 0x02, 0x01, 0x06, 0x00, 0xa6, 0x80, 0x56, 0x74, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x95, 0xc0, 0xa8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xc0, 0xde, 0xca, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x02, 0x36, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x33, 0x04, 0x00, 0x00, 0x00, 0x78, 0x3a, 0x04, 0x00, 0x00, 0x00, 0x3c, 0x3b, 0x04, 0x00, 0x00, 0x00, 0x69, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x1c, 0x04, 0xc0, 0xa8, 0x00, 0xff, 0x06, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x03, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; let message = Message::from_bytes(other_offer).unwrap(); let mut bytes = Vec::with_capacity(other_offer.len()); b.iter(|| { message.encode(&mut Encoder::new(&mut bytes)).unwrap(); }); }); g.bench_function("encode_opts", |b| { let opts: &[u8] = &[ 53, 1, 2, 54, 4, 192, 168, 0, 1, 51, 4, 0, 0, 0, 60, 58, 4, 0, 0, 0, 30, 59, 4, 0, 0, 0, 52, 1, 4, 255, 255, 255, 0, 3, 4, 192, 168, 0, 1, 6, 8, 192, 168, 0, 1, 192, 168, 1, 1, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; let x = dhcproto::v4::DhcpOptions::from_bytes(opts).unwrap(); let mut bytes = Vec::with_capacity(opts.len()); b.iter(|| { x.encode(&mut Encoder::new(&mut bytes)).unwrap(); }); }); } criterion_group!(benches, encode_benches); criterion_main!(benches); dhcproto-0.9.0/rust-toolchain000064400000000000000000000000071046102023000143470ustar 000000000000001.67.0 dhcproto-0.9.0/src/decoder.rs000064400000000000000000000146071046102023000142260ustar 00000000000000//! Decodable trait & Decoder use crate::error::{DecodeError, DecodeResult}; use std::{ array::TryFromSliceError, convert::TryInto, ffi::{CStr, CString}, mem, net::{Ipv4Addr, Ipv6Addr}, str, }; /// A trait for types which are serializable to and from DHCP binary formats pub trait Decodable: Sized { /// Read the type from the stream fn decode(decoder: &mut Decoder<'_>) -> DecodeResult; /// Returns the object in binary form fn from_bytes(bytes: &[u8]) -> DecodeResult { let mut decoder = Decoder::new(bytes); Self::decode(&mut decoder) } } /// Decoder type. Wraps a buffer which only contains bytes that have not been read yet #[derive(Debug)] pub struct Decoder<'a> { buffer: &'a [u8], } impl<'a> Decoder<'a> { /// Create a new Decoder pub fn new(buffer: &'a [u8]) -> Self { Decoder { buffer } } /// peek at the next byte without advancing the internal pointer pub fn peek_u8(&self) -> DecodeResult { Ok(u8::from_be_bytes(self.peek::<{ mem::size_of::() }>()?)) } /// read a u8 pub fn read_u8(&mut self) -> DecodeResult { Ok(u8::from_be_bytes(self.read::<{ mem::size_of::() }>()?)) } /// read a u32 pub fn read_u32(&mut self) -> DecodeResult { Ok(u32::from_be_bytes( self.read::<{ mem::size_of::() }>()?, )) } /// read a i32 pub fn read_i32(&mut self) -> DecodeResult { Ok(i32::from_be_bytes( self.read::<{ mem::size_of::() }>()?, )) } /// read a u16 pub fn read_u16(&mut self) -> DecodeResult { Ok(u16::from_be_bytes( self.read::<{ mem::size_of::() }>()?, )) } /// read a u64 pub fn read_u64(&mut self) -> DecodeResult { Ok(u64::from_be_bytes( self.read::<{ mem::size_of::() }>()?, )) } /// read a `N` bytes into slice pub fn read(&mut self) -> DecodeResult<[u8; N]> { if N > self.buffer.len() { return Err(DecodeError::NotEnoughBytes); } let (slice, remaining) = self.buffer.split_at(N); self.buffer = remaining; // can't panic-- condition checked above Ok(slice.try_into().unwrap()) } /// peek a `N` bytes into slice pub fn peek(&self) -> DecodeResult<[u8; N]> { if N > self.buffer.len() { return Err(DecodeError::NotEnoughBytes); } // can't panic-- condition checked above Ok(self.buffer[..N].try_into().unwrap()) } /// read a `MAX` length bytes into nul terminated `CString` pub fn read_cstring(&mut self) -> DecodeResult> { let bytes = self.read::()?; let nul_idx = bytes.iter().position(|&b| b == 0); match nul_idx { Some(n) if n == 0 => Ok(None), Some(n) => Ok(Some(CStr::from_bytes_with_nul(&bytes[..=n])?.to_owned())), // TODO: error? None => Ok(None), } } pub fn read_nul_bytes(&mut self) -> DecodeResult>> { let bytes = self.read::()?; let nul_idx = bytes.iter().position(|&b| b == 0); match nul_idx { Some(n) if n == 0 => Ok(None), Some(n) => Ok(Some(bytes[..=n].to_vec())), // TODO: error? None => Ok(None), } } /// read `MAX` length bytes and read into utf-8 encoded `String` pub fn read_nul_string(&mut self) -> DecodeResult> { Ok(self .read_nul_bytes::()? .map(|ref bytes| str::from_utf8(bytes).map(|s| s.to_owned())) .transpose()?) } /// read a slice of bytes determined at runtime pub fn read_slice(&mut self, len: usize) -> DecodeResult<&'a [u8]> { if len > self.buffer.len() { return Err(DecodeError::NotEnoughBytes); } let (slice, remaining) = self.buffer.split_at(len); self.buffer = remaining; Ok(slice) } /// Read a utf-8 encoded String pub fn read_string(&mut self, len: usize) -> DecodeResult { Ok(self.read_str(len)?.to_owned()) } /// Read a utf-8 encoded String pub fn read_str(&mut self, len: usize) -> DecodeResult<&str> { Ok(str::from_utf8(self.read_slice(len)?)?) } /// Read an ipv4 addr pub fn read_ipv4(&mut self, length: usize) -> DecodeResult { if length != 4 { return Err(DecodeError::NotEnoughBytes); } let bytes = self.read::<4>()?; Ok(bytes.into()) } /// Read a list of ipv4 addrs pub fn read_ipv4s(&mut self, length: usize) -> DecodeResult> { // must be multiple of 4 if length % 4 != 0 { return Err(DecodeError::NotEnoughBytes); } let ips = self.read_slice(length)?; Ok(ips .chunks(4) .map(|bytes| [bytes[0], bytes[1], bytes[2], bytes[3]].into()) .collect()) } /// Read a list of ipv6 addrs pub fn read_ipv6s(&mut self, length: usize) -> DecodeResult> { // must be multiple of 16 if length % 16 != 0 { return Err(DecodeError::NotEnoughBytes); } let ips = self.read_slice(length)?; // type annotations needed below Ok(ips .chunks(16) .map(|bytes| Ok::<_, TryFromSliceError>(TryInto::<[u8; 16]>::try_into(bytes)?.into())) .collect::, _>>()?) } /// Read a list of ipv4 pairs pub fn read_pair_ipv4s(&mut self, length: usize) -> DecodeResult> { // must be multiple of 8 if length % 8 != 0 { return Err(DecodeError::NotEnoughBytes); } let ips = self.read_slice(length)?; Ok(ips .chunks(8) .map(|bytes| { ( [bytes[0], bytes[1], bytes[2], bytes[3]].into(), [bytes[4], bytes[5], bytes[6], bytes[7]].into(), ) }) .collect()) } /// Read a bool pub fn read_bool(&mut self) -> DecodeResult { Ok(self.read_u8()? == 1) } /// return slice of buffer start at index of unread data pub fn buffer(&self) -> &[u8] { self.buffer } } dhcproto-0.9.0/src/encoder.rs000064400000000000000000000133701046102023000142340ustar 00000000000000//! Encodable trait & Encoder use crate::error::{EncodeError, EncodeResult}; /// A trait for types which are deserializable to DHCP binary formats pub trait Encodable { /// Read the type from the stream fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()>; /// encode this type into a new `Vec` fn to_vec(&self) -> EncodeResult> { let mut buffer = Vec::with_capacity(512); let mut encoder = Encoder::new(&mut buffer); self.encode(&mut encoder)?; Ok(buffer) } } /// Encoder type, holds a mut ref to a buffer /// that it will write data to and an offset /// of the next position to write #[derive(Debug)] pub struct Encoder<'a> { buffer: &'a mut Vec, offset: usize, } impl<'a> Encoder<'a> { /// Create a new Encoder from a mutable buffer pub fn new(buffer: &'a mut Vec) -> Self { Self { buffer, offset: 0 } } /// Get a reference to the underlying buffer pub fn buffer(&self) -> &[u8] { self.buffer } /// write bytes to buffer /// Return: /// number of bytes written pub fn write_slice(&mut self, bytes: &[u8]) -> EncodeResult<()> { let additional = bytes.len(); // space already reserved, we may not need this if self.offset + additional <= self.buffer.len() { // if self.offset == self.buffer.len() indexing can panic for (byte, b) in self.buffer[self.offset..].iter_mut().zip(bytes.iter()) { *byte = *b; } } else { let expected_len = self.buffer.len() + additional; self.buffer.reserve(additional); self.buffer.extend_from_slice(bytes); debug_assert!(self.buffer.len() == expected_len); } let index = self .offset .checked_add(additional) .ok_or(EncodeError::AddOverflow)?; self.offset = index; Ok(()) } /// Write const number of bytes to buffer pub fn write(&mut self, bytes: [u8; N]) -> EncodeResult<()> { // TODO: refactor this and above method? // only difference is zip & extend let additional = bytes.len(); // space already reserved, we may not need this if self.offset + additional <= self.buffer.len() { // if self.offset == self.buffer.len() indexing can panic for (byte, b) in self.buffer[self.offset..].iter_mut().zip(bytes) { *byte = b; } } else { let expected_len = self.buffer.len() + additional; self.buffer.reserve(additional); self.buffer.extend(bytes); debug_assert!(self.buffer.len() == expected_len); } let index = self .offset .checked_add(additional) .ok_or(EncodeError::AddOverflow)?; self.offset = index; Ok(()) } /// write a u8 pub fn write_u8(&mut self, data: u8) -> EncodeResult<()> { self.write(data.to_be_bytes()) } /// write a u16 pub fn write_u16(&mut self, data: u16) -> EncodeResult<()> { self.write(data.to_be_bytes()) } /// write a u32 pub fn write_u32(&mut self, data: u32) -> EncodeResult<()> { self.write(data.to_be_bytes()) } /// write a u128 pub fn write_u128(&mut self, data: u128) -> EncodeResult<()> { self.write(data.to_be_bytes()) } /// write a u64 pub fn write_u64(&mut self, data: u64) -> EncodeResult<()> { self.write(data.to_be_bytes()) } /// write a i32 pub fn write_i32(&mut self, data: i32) -> EncodeResult<()> { self.write(data.to_be_bytes()) } /// Writes bytes to buffer and pads with 0 bytes up to some fill_len /// /// Returns /// Err - if bytes.len() is greater then fill_len pub fn write_fill_bytes(&mut self, bytes: &[u8], fill_len: usize) -> EncodeResult<()> { if bytes.len() > fill_len { return Err(EncodeError::StringSizeTooBig { len: bytes.len() }); } let nul_len = fill_len - bytes.len(); self.write_slice(bytes)?; for _ in 0..nul_len { self.write_u8(0)?; } Ok(()) } /// Writes value to buffer and pads with 0 bytes up to some fill_len /// if String is None then write fill_len 0 bytes /// /// Returns /// Err - if bytes.len() is greater then fill_len pub fn write_fill>( &mut self, s: &Option, fill_len: usize, ) -> EncodeResult<()> { match s { Some(sname) => { let bytes = sname.as_ref(); self.write_fill_bytes(bytes, fill_len)?; } None => { // should we keep some static [0;64] arrays around // to fill quickly? for _ in 0..fill_len { self.write_u8(0)?; } } } Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn basic_encode() -> EncodeResult<()> { let mut buf = vec![0, 1, 2, 3, 4, 5]; let mut enc = Encoder::new(&mut buf); enc.offset = 4; // write already reserved space enc.write_slice(&[5, 6])?; assert_eq!(enc.buffer, &mut vec![0, 1, 2, 3, 5, 6]); assert_eq!(enc.offset, 6); // reserve extra space enc.write_slice(&[7, 8])?; assert_eq!(enc.buffer, &mut vec![0, 1, 2, 3, 5, 6, 7, 8]); assert_eq!(enc.offset, 8); // start w/ empty buf let mut buf = vec![]; let mut enc = Encoder::new(&mut buf); // reserve space & write enc.write_slice(&[0, 1, 2, 3])?; assert_eq!(enc.buffer, &mut vec![0, 1, 2, 3]); assert_eq!(enc.offset, 4); Ok(()) } } dhcproto-0.9.0/src/error.rs000064400000000000000000000037261046102023000137520ustar 00000000000000//! Error types for Encoding/Decoding use std::io; use thiserror::Error; /// Convenience type for decode errors pub type DecodeResult = Result; /// Returned from types that decode #[derive(Error, Debug)] pub enum DecodeError { /// add overflow #[error("decoder checked_add failed")] AddOverflow, /// ran out of bytes #[error("parser ran out of data-- not enough bytes")] NotEnoughBytes, /// error converting from slice #[error("error converting from slice {0}")] SliceError(#[from] std::array::TryFromSliceError), /// error finding nul in string #[error("error getting null terminated string {0}")] NulError(#[from] std::ffi::FromBytesWithNulError), /// error converting to utf-8 #[error("error converting to UTF-8 {0}")] Utf8Error(#[from] std::str::Utf8Error), /// io error #[error("io error {0}")] IoError(#[from] io::Error), /// url parse error #[error("url parse error")] UrlParseError(#[from] url::ParseError), /// domain parse error #[error("domain parse error {0}")] DomainParseError(#[from] trust_dns_proto::error::ProtoError), /// Unknown decode error #[error("unknown error")] Unknown(Box), } /// Returned from types that encode #[derive(Error, Debug)] pub enum EncodeError { /// addition overflow #[error("encoder checked_add failed")] AddOverflow, /// string exceeds bounds #[error( "message is trying to write a string to the message that exceeds the max size of {len}" )] StringSizeTooBig { /// size of string len: usize, }, /// io error #[error("io error {0}")] IoError(#[from] io::Error), /// DNS encoding error from trust-dns #[error("domain encoding error {0}")] DomainEncodeError(#[from] trust_dns_proto::error::ProtoError), } /// Convenience type for encode errors pub type EncodeResult = Result; dhcproto-0.9.0/src/lib.rs000064400000000000000000000103561046102023000133640ustar 00000000000000#![warn( missing_debug_implementations, // missing_docs, // some variants still missing docs missing_copy_implementations, rust_2018_idioms, unreachable_pub, non_snake_case, non_upper_case_globals )] #![allow(clippy::cognitive_complexity)] #![deny(rustdoc::broken_intra_doc_links)] #![doc(test( no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) ))] //! # dhcproto //! //! A DHCP parser and encoder for DHCPv4 and DHCPv6. `dhcproto` aims to be a functionally complete DHCP implementation. //! //! ## DHCPv4 //! //! ```rust //! use dhcproto::v4::{Message, Encoder, Decoder, Decodable, Encodable}; //! # fn main() -> Result<(), Box> { //! // decode //! let bytes = dhcp_offer(); //! let msg = Message::decode(&mut Decoder::new(&bytes))?; //! // now encode //! let mut buf = Vec::new(); //! let mut e = Encoder::new(&mut buf); //! msg.encode(&mut e)?; //! # Ok(()) //! # } //! # fn dhcp_offer() -> Vec { //! # vec![ //! # 0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x15, 0x5c, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0xcc, 0x00, 0x0a, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, //! # 0x53, 0x63, 0x35, 0x01, 0x02, 0x36, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x33, 0x04, 0x00, //! # 0x00, 0x00, 0x3c, 0x3a, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x3b, 0x04, 0x00, 0x00, 0x00, //! # 0x34, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x06, //! # 0x08, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x01, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, //! # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //! # ] //! # } //! ``` //! //! ## DHCPv6 //! //! ```rust //! use dhcproto::v6::{Message, Encoder, Decoder, Decodable, Encodable}; //! # fn main() -> Result<(), Box> { //! // decode //! let bytes = solicit(); //! let msg = Message::decode(&mut Decoder::new(&bytes))?; //! // now encode //! let mut buf = Vec::new(); //! let mut e = Encoder::new(&mut buf); //! msg.encode(&mut e)?; //! # Ok(()) //! # } //! # fn solicit() -> Vec { //! # vec![ //! # 0x01, 0x10, 0x08, 0x74, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x39, //! # 0xcf, 0x88, 0x08, 0x00, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, //! # 0x00, 0x18, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x0c, 0x27, 0xfe, //! # 0x8f, 0x95, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x15, 0x18, //! # ] //! # } //! ``` pub use decoder::{Decodable, Decoder}; pub use encoder::{Encodable, Encoder}; pub mod decoder; pub mod encoder; pub mod error; pub mod v4; pub mod v6; pub use trust_dns_proto::error::ProtoError as NameError; pub use trust_dns_proto::rr::Name; dhcproto-0.9.0/src/v4/bulk_query.rs000064400000000000000000000065241046102023000153330ustar 00000000000000use std::fmt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Lease query data source flags #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Copy, Default, Clone, PartialEq, Eq)] pub struct DataSourceFlags(u8); impl fmt::Debug for DataSourceFlags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DataSourceFlags") .field("remote", &self.remote()) .finish() } } impl fmt::Display for DataSourceFlags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } impl DataSourceFlags { /// Create new DataSourceFlags from u8 pub fn new(n: u8) -> Self { Self(n) } /// get the status of the remote flag pub fn remote(&self) -> bool { (self.0 & 0x01) == 1 } /// set the remote bit, returns a new DataSourceFlags pub fn set_remote(mut self) -> Self { self.0 |= 0x01; self } } impl From for DataSourceFlags { fn from(n: u8) -> Self { Self(n) } } impl From for u8 { fn from(f: DataSourceFlags) -> Self { f.0 } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum QueryState { Available, Active, Expired, Release, Abandoned, Reset, Remote, Transitioning, Unknown(u8), } impl From for QueryState { fn from(n: u8) -> Self { use QueryState::*; match n { 1 => Available, 2 => Active, 3 => Expired, 4 => Release, 5 => Abandoned, 6 => Reset, 7 => Remote, 8 => Transitioning, _ => Unknown(n), } } } impl From for u8 { fn from(state: QueryState) -> Self { use QueryState::*; match state { Available => 1, Active => 2, Expired => 3, Release => 4, Abandoned => 5, Reset => 6, Remote => 7, Transitioning => 8, Unknown(code) => code, } } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Code { Success, UnspecFail, QueryTerminated, MalformedQuery, NotAllowed, Unknown(u8), } impl From for Code { fn from(n: u8) -> Self { use Code::*; match n { 0 => Success, 1 => UnspecFail, 2 => QueryTerminated, 3 => MalformedQuery, 4 => NotAllowed, _ => Unknown(n), } } } impl From for u8 { fn from(code: Code) -> Self { use Code::*; match code { Success => 0, UnspecFail => 1, QueryTerminated => 2, MalformedQuery => 3, NotAllowed => 4, Unknown(code) => code, } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_datasourceflags() { let flag = DataSourceFlags::default(); assert_eq!(flag.0, 0); let flag = flag.set_remote(); assert_eq!(flag.0, 0x01); assert!(flag.remote()); let flag = DataSourceFlags::new(0x80).set_remote(); assert_eq!(flag.0, 0x81); } } dhcproto-0.9.0/src/v4/flags.rs000064400000000000000000000035651046102023000142470ustar 00000000000000use std::fmt; use crate::{ decoder::{Decodable, Decoder}, encoder::{Encodable, Encoder}, error::{DecodeResult, EncodeResult}, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Represents available flags on message #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Copy, Default, Clone, PartialEq, Eq)] pub struct Flags(u16); impl fmt::Debug for Flags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Flags") .field("broadcast", &self.broadcast()) .finish() } } impl fmt::Display for Flags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } impl Flags { /// Create new Flags from u16 pub fn new(n: u16) -> Self { Self(n) } /// get the status of the broadcast flag pub fn broadcast(&self) -> bool { (self.0 & 0x80_00) >> (u16::BITS - 1) == 1 } /// set the broadcast bit, returns a new Flags pub fn set_broadcast(mut self) -> Self { self.0 |= 0x80_00; self } } impl From for Flags { fn from(n: u16) -> Self { Self(n) } } impl From for u16 { fn from(f: Flags) -> Self { f.0 } } impl Decodable for Flags { fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { Ok(decoder.read_u16()?.into()) } } impl Encodable for Flags { fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { e.write_u16((*self).into()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_broadcast() { let flag = Flags::default(); assert_eq!(flag.0, 0); let flag = flag.set_broadcast(); assert_eq!(flag.0, 0x80_00); assert!(flag.broadcast()); let flag = Flags::new(0x00_20).set_broadcast(); assert_eq!(flag.0, 0x80_20); } } dhcproto-0.9.0/src/v4/fqdn.rs000064400000000000000000000114151046102023000140740ustar 00000000000000use std::fmt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use trust_dns_proto::rr::Name; /// A client FQDN #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Clone, PartialEq, Eq, Debug)] pub struct ClientFQDN { pub(crate) flags: FqdnFlags, pub(crate) r1: u8, pub(crate) r2: u8, pub(crate) domain: Name, } impl ClientFQDN { // creates a new client fqdn setting the rcode1/rcode2 fields to 255 pub fn new(flags: FqdnFlags, domain: Name) -> Self { Self { flags, r1: 0xFF, r2: 0xFF, domain, } } pub fn flags(&self) -> FqdnFlags { self.flags } pub fn set_flags(&mut self, flags: FqdnFlags) -> &mut Self { self.flags = flags; self } pub fn r1(&self) -> u8 { self.r1 } pub fn set_r1(&mut self, rcode1: u8) -> &mut Self { self.r1 = rcode1; self } pub fn r2(&self) -> u8 { self.r2 } pub fn set_r2(&mut self, rcode2: u8) -> &mut Self { self.r2 = rcode2; self } pub fn domain(&self) -> &Name { &self.domain } pub fn set_domain(&mut self, domain: Name) -> &mut Self { self.domain = domain; self } pub fn domain_mut(&mut self) -> &mut Name { &mut self.domain } } /// Represents available flags on message #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Copy, Default, Clone, PartialEq, Eq, Hash)] pub struct FqdnFlags(u8); impl fmt::Debug for FqdnFlags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FqdnFlags") .field("N", &self.n()) .field("E", &self.e()) .field("O", &self.o()) .field("S", &self.s()) .finish() } } impl fmt::Display for FqdnFlags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } impl FqdnFlags { /// Create new FqdnFlags from u8 pub fn new(n: u8) -> Self { Self(n) } /// get the status of the n flag pub fn n(&self) -> bool { (self.0 & 0x08) > 0 } /// set the n bit, if true will also set the s bit to false. pub fn set_n(mut self, bit: bool) -> Self { if bit { self.0 |= 0x08; // 1000 self.set_s(false); } else { self.0 &= 0x07; // 0111 } self } pub fn set_n_mut(&mut self, bit: bool) -> &mut Self { *self = self.set_n(bit); self } /// get the status of the e flag pub fn e(&self) -> bool { (self.0 & 0x04) > 0 } /// set the e bit pub fn set_e(mut self, bit: bool) -> Self { if bit { self.0 |= 0x04; // 0100 } else { self.0 &= 0x0b; // 1011 } self } pub fn set_e_mut(&mut self, bit: bool) -> &mut Self { *self = self.set_e(bit); self } /// get the status of the o flag pub fn o(&self) -> bool { (self.0 & 0x02) > 0 } /// set the o bit pub fn set_o(mut self, bit: bool) -> Self { if bit { self.0 |= 0x02; // 0010 } else { self.0 &= 0x0d; // 1101 } self } pub fn set_o_mut(&mut self, bit: bool) -> &mut Self { *self = self.set_o(bit); self } /// get the status of the s flag pub fn s(&self) -> bool { (self.0 & 0x01) > 0 } /// set the s bit. Indicates whether the server should perform an A RR update pub fn set_s(mut self, bit: bool) -> Self { if bit { self.0 |= 0x01; // 0001 } else { self.0 &= 0x0e; // 1110 } self } pub fn set_s_mut(&mut self, bit: bool) -> &mut Self { *self = self.set_s(bit); self } } impl From for FqdnFlags { fn from(n: u8) -> Self { Self(n) } } impl From for u8 { fn from(f: FqdnFlags) -> Self { f.0 } } #[cfg(test)] mod tests { use super::*; #[test] fn test_fqdn_flags() { let mut flag = FqdnFlags::default(); assert_eq!(flag.0, 0); flag.set_n_mut(true); assert!(flag.n()); assert_eq!(flag.0, 0x08); flag.set_n_mut(false); assert!(!flag.n()); assert_eq!(flag.0, 0x00); let flag = FqdnFlags::new(0x40).set_s(true); assert!(!flag.e()); assert!(flag.s()); assert!(!flag.n()); assert!(!flag.o()); assert_eq!(flag.0, 0x41); let flag = flag.set_e(true); assert!(flag.e() && flag.s()); let flag = FqdnFlags::default().set_e(true); assert!(flag.e()); assert_eq!(flag.0, 0x04); let flag = flag.set_s(true); assert_eq!(flag.0, 0x05); } } dhcproto-0.9.0/src/v4/htype.rs000064400000000000000000000066551046102023000143070ustar 00000000000000use crate::{ decoder::{Decodable, Decoder}, encoder::{Encodable, Encoder}, error::{DecodeResult, EncodeResult}, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Hardware type of message #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Copy, Hash, Clone, PartialEq, Eq)] pub enum HType { /// 1 Ethernet Eth, /// 2 Experimental Ethernet ExperimentalEth, /// 3 Amateur Radio AX25 AmRadioAX25, /// 4 Proteon Token Ring ProteonTokenRing, /// 5 Chaos Chaos, /// 6 IEEE.802 IEEE802, /// 7 ARCNET ARCNET, /// 8 Hyperchannel Hyperchannel, /// 9 LANSTAR Lanstar, /// 10 Autonet Short Addr AutonetShortAddr, /// 11 LocalTalk LocalTalk, /// 12 LocalNet LocalNet, /// 13 Ultralink Ultralink, /// 14 SMDS SMDS, /// 15 FrameRelay FrameRelay, /// 17 HDLC HDLC, /// 18 FibreChannel FibreChannel, /// 20 SerialLine SerialLine, /// 22 Mil STD MilStd188220, /// 23 Metricom Metricom, /// 25 MAPOS MAPOS, /// 26 Twinaxial Twinaxial, /// 30 ARPSec ARPSec, /// 31 IPsec tunnel IPsecTunnel, /// 32 Infiniband Infiniband, /// 34 WeigandInt WiegandInt, /// 35 PureIP PureIP, /// Unknown or not yet implemented htype Unknown(u8), } impl From for HType { fn from(n: u8) -> Self { use HType::*; match n { 1 => Eth, 2 => ExperimentalEth, 3 => AmRadioAX25, 4 => ProteonTokenRing, 5 => Chaos, 6 => IEEE802, 7 => ARCNET, 8 => Hyperchannel, 9 => Lanstar, 10 => AutonetShortAddr, 11 => LocalTalk, 12 => LocalNet, 13 => Ultralink, 14 => SMDS, 15 => FrameRelay, 17 => HDLC, 18 => FibreChannel, 20 => SerialLine, 22 => MilStd188220, 23 => Metricom, 25 => MAPOS, 26 => Twinaxial, 30 => ARPSec, 31 => IPsecTunnel, 32 => Infiniband, 34 => WiegandInt, 35 => PureIP, n => Unknown(n), } } } impl From for u8 { fn from(n: HType) -> Self { use HType::*; match n { Eth => 1, ExperimentalEth => 2, AmRadioAX25 => 3, ProteonTokenRing => 4, Chaos => 5, IEEE802 => 6, ARCNET => 7, Hyperchannel => 8, Lanstar => 9, AutonetShortAddr => 10, LocalTalk => 11, LocalNet => 12, Ultralink => 13, SMDS => 14, FrameRelay => 15, HDLC => 17, FibreChannel => 18, SerialLine => 20, MilStd188220 => 22, Metricom => 23, MAPOS => 25, Twinaxial => 26, ARPSec => 30, IPsecTunnel => 31, Infiniband => 32, WiegandInt => 34, PureIP => 35, Unknown(n) => n, } } } impl Decodable for HType { fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { Ok(decoder.read_u8()?.into()) } } impl Encodable for HType { fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { e.write_u8((*self).into()) } } dhcproto-0.9.0/src/v4/mod.rs000064400000000000000000000675631046102023000137420ustar 00000000000000//! # DHCPv4 //! //! This module provides types and utility functions for encoding/decoding a DHCPv4 message. //! //! ## Example - constructing messages //! //! ```rust //! # fn main() -> Result<(), Box> { //! use dhcproto::{v4, Encodable, Encoder}; //! // arbitrary hardware addr //! let chaddr = vec![ //! 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, //! ]; //! // construct a new Message //! let mut msg = v4::Message::default(); //! msg.set_flags(v4::Flags::default().set_broadcast()) // set broadcast to true //! .set_chaddr(&chaddr) // set chaddr //! .opts_mut() //! .insert(v4::DhcpOption::MessageType(v4::MessageType::Discover)); // set msg type //! //! // set some more options //! msg.opts_mut() //! .insert(v4::DhcpOption::ParameterRequestList(vec![ //! v4::OptionCode::SubnetMask, //! v4::OptionCode::Router, //! v4::OptionCode::DomainNameServer, //! v4::OptionCode::DomainName, //! ])); //! msg.opts_mut() //! .insert(v4::DhcpOption::ClientIdentifier(chaddr)); //! //! // now encode to bytes //! let mut buf = Vec::new(); //! let mut e = Encoder::new(&mut buf); //! msg.encode(&mut e)?; //! //! // buf now has the contents of the encoded DHCP message //! # Ok(()) } //! ``` //! //! ## Example - decoding messages //! //! ```rust //! # fn bootreq() -> Vec { //! # vec![ //! # 1u8, // op //! # 2, // htype //! # 3, // hlen //! # 4, // ops //! # 5, 6, 7, 8, // xid //! # 9, 10, // secs //! # 11, 12, // flags //! # 13, 14, 15, 16, // ciaddr //! # 17, 18, 19, 20, // yiaddr //! # 21, 22, 23, 24, // siaddr //! # 25, 26, 27, 28, // giaddr //! # 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, // chaddr //! # 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, //! # 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, //! # 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, //! # 0, // sname: "-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijk", //! # 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, //! # 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, //! # 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, //! # 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, //! # 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, //! # 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, //! # 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, //! # 109, 0, 0, 0, 0, 0, 0, 0, //! # 0, // file: "mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}m", //! # 99, 130, 83, 99, // magic cookie //! # ] //! # } //! # fn main() -> Result<(), Box> { //! use dhcproto::{v4::Message, Decoder, Decodable}; //! let offer = bootreq(); //! let msg = Message::decode(&mut Decoder::new(&offer))?; //! # Ok(()) } //! ``` //! use std::{fmt, net::Ipv4Addr, str::Utf8Error}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; pub mod bulk_query; mod flags; pub mod fqdn; mod htype; mod opcode; mod options; pub mod relay; // re-export submodules from proto::msg pub use self::{flags::*, htype::*, opcode::*, options::*}; pub use crate::{ decoder::{Decodable, Decoder}, encoder::{Encodable, Encoder}, error::*, }; pub const MAGIC: [u8; 4] = [99, 130, 83, 99]; /// default dhcpv4 server port pub const SERVER_PORT: u16 = 67; /// default dhcpv4 client port pub const CLIENT_PORT: u16 = 68; /// [Dynamic Host Configuration Protocol](https://tools.ietf.org/html/rfc2131#section-2) /// ///```text /// 0 1 2 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | op (1) | htype (1) | hlen (1) | hops (1) | /// +---------------+---------------+---------------+---------------+ /// | xid (4) | /// +-------------------------------+-------------------------------+ /// | secs (2) | flags (2) | /// +-------------------------------+-------------------------------+ /// | ciaddr (4) | /// +---------------------------------------------------------------+ /// | yiaddr (4) | /// +---------------------------------------------------------------+ /// | siaddr (4) | /// +---------------------------------------------------------------+ /// | giaddr (4) | /// +---------------------------------------------------------------+ /// | chaddr (16) | /// +---------------------------------------------------------------+ /// | sname (64) | /// +---------------------------------------------------------------+ /// | file (128) | /// +---------------------------------------------------------------+ /// | options (variable) | /// +---------------------------------------------------------------+ /// ``` #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub struct Message { /// op code / message type opcode: Opcode, /// Hardware address type: htype: HType, /// Hardware address length hlen: u8, /// Client sets to zero, optionally used by relay agents when booting via a relay agent. hops: u8, /// Transaction ID, a random number chosen by the client xid: u32, /// seconds elapsed since client began address acquisition or renewal process secs: u16, /// Flags flags: Flags, /// Client IP ciaddr: Ipv4Addr, /// Your IP yiaddr: Ipv4Addr, /// Server IP siaddr: Ipv4Addr, /// Gateway IP giaddr: Ipv4Addr, /// Client hardware address chaddr: [u8; 16], /// Server hostname sname: Option>, // File name fname: Option>, magic: [u8; 4], opts: DhcpOptions, } impl Default for Message { fn default() -> Self { Self { opcode: Opcode::BootRequest, htype: HType::Eth, hlen: 0, hops: 0, xid: rand::random(), secs: 0, flags: Flags::default(), ciaddr: Ipv4Addr::UNSPECIFIED, yiaddr: Ipv4Addr::UNSPECIFIED, siaddr: Ipv4Addr::UNSPECIFIED, giaddr: Ipv4Addr::UNSPECIFIED, chaddr: [0; 16], sname: None, fname: None, magic: MAGIC, opts: DhcpOptions::default(), } } } impl Message { /// returns a new Message with OpCode set to BootRequest and a new random id /// # Panic /// panics if chaddr is greater len than 16 pub fn new( ciaddr: Ipv4Addr, yiaddr: Ipv4Addr, siaddr: Ipv4Addr, giaddr: Ipv4Addr, chaddr: &[u8], ) -> Self { Self::new_with_id(rand::random(), ciaddr, yiaddr, siaddr, giaddr, chaddr) } /// returns a new Message with OpCode set to BootRequest /// # Panic /// panics if chaddr is greater len than 16 pub fn new_with_id( xid: u32, ciaddr: Ipv4Addr, yiaddr: Ipv4Addr, siaddr: Ipv4Addr, giaddr: Ipv4Addr, chaddr: &[u8], ) -> Self { assert!(chaddr.len() <= 16); // copy our chaddr into static array let mut new_chaddr = [0; 16]; let len = chaddr.len(); new_chaddr[..len].copy_from_slice(chaddr); Self { hlen: len as u8, xid, flags: Flags::default(), ciaddr, yiaddr, siaddr, giaddr, chaddr: new_chaddr, ..Self::default() } } /// Get the message's opcode. /// op code / message type pub fn opcode(&self) -> Opcode { self.opcode } /// Set the message's opcode. /// op code / message type pub fn set_opcode(&mut self, opcode: Opcode) -> &mut Self { self.opcode = opcode; self } /// Get the message's hardware type. pub fn htype(&self) -> HType { self.htype } /// Set the message's hardware type. pub fn set_htype(&mut self, htype: HType) -> &mut Self { self.htype = htype; self } /// Get the message's hardware len (len of chaddr). pub fn hlen(&self) -> u8 { self.hlen } /// Get the message's hops. /// Client sets to zero, optionally used by relay agents when booting via a relay agent. pub fn hops(&self) -> u8 { self.hops } /// Set the message's hops. /// Client sets to zero, optionally used by relay agents when booting via a relay agent. pub fn set_hops(&mut self, hops: u8) -> &mut Self { self.hops = hops; self } /// Get the message's chaddr. pub fn chaddr(&self) -> &[u8] { &self.chaddr[..(self.hlen as usize)] } /// Set the message's chaddr. `chaddr` can only up to 16 bytes in length pub fn set_chaddr(&mut self, chaddr: &[u8]) -> &mut Self { let mut new_chaddr = [0; 16]; if chaddr.len() >= 16 { new_chaddr.copy_from_slice(&chaddr[..16]); } else { new_chaddr[..chaddr.len()].copy_from_slice(chaddr); } self.hlen = chaddr.len() as u8; self.chaddr = new_chaddr; self } /// Get the message's giaddr. /// Gateway IP pub fn giaddr(&self) -> Ipv4Addr { self.giaddr } /// Set the message's giaddr. /// Gateway IP pub fn set_giaddr>(&mut self, giaddr: I) -> &mut Self { self.giaddr = giaddr.into(); self } /// Get the message's siaddr. /// Server IP pub fn siaddr(&self) -> Ipv4Addr { self.siaddr } /// Set the message's siaddr. /// Server IP pub fn set_siaddr>(&mut self, siaddr: I) -> &mut Self { self.siaddr = siaddr.into(); self } /// Get the message's yiaddr. /// Your IP /// In an OFFER this is the ip the server is offering pub fn yiaddr(&self) -> Ipv4Addr { self.yiaddr } /// Set the message's siaddr. /// Your IP pub fn set_yiaddr>(&mut self, yiaddr: I) -> &mut Self { self.yiaddr = yiaddr.into(); self } /// Get the message's ciaddr. /// Client IP pub fn ciaddr(&self) -> Ipv4Addr { self.ciaddr } /// Set the message's siaddr. /// Client IP pub fn set_ciaddr>(&mut self, ciaddr: I) -> &mut Self { self.ciaddr = ciaddr.into(); self } /// clear addrs pub fn clear_addrs(&mut self) -> &mut Self { self.ciaddr = Ipv4Addr::UNSPECIFIED; self.yiaddr = Ipv4Addr::UNSPECIFIED; self.siaddr = Ipv4Addr::UNSPECIFIED; self.giaddr = Ipv4Addr::UNSPECIFIED; self } /// Get the message's flags. pub fn flags(&self) -> Flags { self.flags } /// Set the message's flags. pub fn set_flags(&mut self, flags: Flags) -> &mut Self { self.flags = flags; self } /// Get the message's secs. pub fn secs(&self) -> u16 { self.secs } /// Set the message's secs. pub fn set_secs(&mut self, secs: u16) -> &mut Self { self.secs = secs; self } /// Get the message's xid. /// Transaction ID, a random number chosen by the client pub fn xid(&self) -> u32 { self.xid } /// Set the message's xid. /// Transaction ID, a random number chosen by the client pub fn set_xid(&mut self, xid: u32) -> &mut Self { self.xid = xid; self } /// Get a reference to the message's fname. No particular encoding is enforced. pub fn fname(&self) -> Option<&[u8]> { self.fname.as_deref() } /// Clear the `fname` header field. pub fn clear_fname(&mut self) { self.fname = None; } /// Get a reference to the message's fname, UTF-8 encoded pub fn fname_str(&self) -> Option> { self.fname().map(std::str::from_utf8) } /// Set the message's fname using a UTF-8 string /// # Panic /// panics if file is greater than 128 bytes long pub fn set_fname_str>(&mut self, file: S) -> &mut Self { let file = file.as_ref().as_bytes(); assert!(file.len() <= 128); self.fname = Some(file.to_vec()); self } /// Set the message's fname. No particular encoding is enforced. /// # Panic /// panics if file is greater than 128 bytes long pub fn set_fname(&mut self, file: &[u8]) -> &mut Self { assert!(file.len() <= 128); self.fname = Some(file.to_vec()); self } /// Get a reference to the message's sname. No particular encoding is enforced. pub fn sname(&self) -> Option<&[u8]> { self.sname.as_deref() } /// Clear the `sname` header field. pub fn clear_sname(&mut self) { self.sname = None; } /// Get a reference to the message's sname as a UTF-8 encoded string. pub fn sname_str(&self) -> Option> { self.sname().map(std::str::from_utf8) } /// Set the message's sname. No particular encoding is enforced. /// # Panic /// panics will if sname is greater than 64 bytes long pub fn set_sname(&mut self, sname: &[u8]) -> &mut Self { assert!(sname.len() <= 64); self.sname = Some(sname.to_vec()); self } /// Set the message's sname using a UTF-8 string /// # Panic /// panics will if sname is greater than 64 bytes long pub fn set_sname_str>(&mut self, sname: S) -> &mut Self { let sname = sname.as_ref().as_bytes(); assert!(sname.len() <= 64); self.sname = Some(sname.to_vec()); self } /// Get a reference to the message's opts. pub fn opts(&self) -> &DhcpOptions { &self.opts } /// Set the DHCP options pub fn set_opts(&mut self, opts: DhcpOptions) -> &mut Self { self.opts = opts; self } /// Get a mutable reference to the message's options. pub fn opts_mut(&mut self) -> &mut DhcpOptions { &mut self.opts } } impl Decodable for Message { fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { Ok(Message { opcode: Opcode::decode(decoder)?, htype: decoder.read_u8()?.into(), hlen: decoder.read_u8()?, hops: decoder.read_u8()?, xid: decoder.read_u32()?, secs: decoder.read_u16()?, flags: decoder.read_u16()?.into(), ciaddr: decoder.read_u32()?.into(), yiaddr: decoder.read_u32()?.into(), siaddr: decoder.read_u32()?.into(), giaddr: decoder.read_u32()?.into(), chaddr: decoder.read::<16>()?, sname: decoder.read_nul_bytes::<64>()?, fname: decoder.read_nul_bytes::<128>()?, // TODO: check magic bytes against expected? magic: decoder.read::<4>()?, opts: DhcpOptions::decode(decoder)?, }) } } impl Encodable for Message { fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { self.opcode.encode(e)?; self.htype.encode(e)?; e.write_u8(self.hlen)?; e.write_u8(self.hops)?; e.write_u32(self.xid)?; e.write_u16(self.secs)?; e.write_u16(self.flags.into())?; e.write_u32(self.ciaddr.into())?; e.write_u32(self.yiaddr.into())?; e.write_u32(self.siaddr.into())?; e.write_u32(self.giaddr.into())?; e.write_slice(&self.chaddr[..])?; e.write_fill(&self.sname, 64)?; e.write_fill(&self.fname, 128)?; e.write(self.magic)?; self.opts.encode(e)?; Ok(()) } } impl fmt::Display for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Message") .field("xid", &self.xid()) .field("broadcast_flag", &self.flags().broadcast()) .field("ciaddr", &self.ciaddr()) .field("yiaddr", &self.yiaddr()) .field("siaddr", &self.siaddr()) .field("giaddr", &self.giaddr()) .field( "chaddr", &hex::encode(self.chaddr()) .chars() .enumerate() .flat_map(|(i, c)| { if i != 0 && i % 2 == 0 { Some(':') } else { None } .into_iter() .chain(std::iter::once(c)) }) .collect::(), ) .field( "opts", &self.opts().iter().map(|(_, v)| v).collect::>(), ) .finish() } } #[cfg(test)] mod tests { use super::*; type Result = std::result::Result>; fn decode_ipv4(input: Vec, expected: MessageType) -> Result<()> { // decode let msg = Message::decode(&mut Decoder::new(&input))?; dbg!(&msg); assert_eq!(msg.opts().msg_type().unwrap(), expected); // now encode let mut buf = Vec::new(); let mut e = Encoder::new(&mut buf); msg.encode(&mut e)?; println!("{buf:?}"); println!("{input:?}"); // decode again let res = Message::decode(&mut Decoder::new(&buf))?; // check Messages are equal after decoding/encoding assert_eq!(msg, res); Ok(()) } #[test] fn decode_offer() -> Result<()> { decode_ipv4(offer(), MessageType::Offer)?; Ok(()) } #[test] fn decode_discover() -> Result<()> { decode_ipv4(discover(), MessageType::Discover)?; Ok(()) } #[test] fn decode_offer_two() -> Result<()> { decode_ipv4(other_offer(), MessageType::Offer)?; Ok(()) } #[test] fn decode_bootreq() -> Result<()> { let offer = bootreq(); let msg = Message::decode(&mut Decoder::new(&offer))?; println!("{msg:?}"); // now encode let mut buf = Vec::new(); let mut e = Encoder::new(&mut buf); msg.encode(&mut e)?; assert_eq!(buf, bootreq()); Ok(()) } #[test] fn test_set_chaddr() -> Result<()> { let mut msg = Message::new( Ipv4Addr::UNSPECIFIED, Ipv4Addr::UNSPECIFIED, Ipv4Addr::UNSPECIFIED, Ipv4Addr::UNSPECIFIED, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], ); msg.set_chaddr(&[0, 1, 2, 3, 4, 5]); assert_eq!(msg.chaddr().len(), 6); Ok(()) } #[cfg(feature = "serde")] #[test] fn test_json() -> Result<()> { let msg = Message::decode(&mut Decoder::new(&offer()))?; let s = serde_json::to_string_pretty(&msg)?; println!("{s}"); let other = serde_json::from_str(&s)?; assert_eq!(msg, other); Ok(()) } fn offer() -> Vec { vec![ 0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x15, 0x5c, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x0a, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x02, 0x36, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x33, 0x04, 0x00, 0x00, 0x00, 0x3c, 0x3a, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x3b, 0x04, 0x00, 0x00, 0x00, 0x34, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x06, 0x08, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x01, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] } fn bootreq() -> Vec { vec![ 1u8, // op 2, // htype 3, // hlen 4, // ops 5, 6, 7, 8, // xid 9, 10, // secs 11, 12, // flags 13, 14, 15, 16, // ciaddr 17, 18, 19, 20, // yiaddr 21, 22, 23, 24, // siaddr 25, 26, 27, 28, // giaddr 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, // chaddr 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 0, // sname: "-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijk", 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 109, 0, 0, 0, 0, 0, 0, 0, 0, // file: "mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}mnopqrstuvwxyz{|}m", 99, 130, 83, 99, // magic cookie ] } fn discover() -> Vec { vec![ 0x01, 0x01, 0x06, 0x00, 0xa6, 0x80, 0x56, 0x74, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xc0, 0xde, 0xca, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x01, 0x37, 0x40, 0xfc, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x43, 0x42, 0x33, 0x04, 0x00, 0x00, 0x00, 0x01, 0xff, ] } fn other_offer() -> Vec { vec![ 0x02, 0x01, 0x06, 0x00, 0xa6, 0x80, 0x56, 0x74, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x95, 0xc0, 0xa8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xc0, 0xde, 0xca, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x02, 0x36, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x33, 0x04, 0x00, 0x00, 0x00, 0x78, 0x3a, 0x04, 0x00, 0x00, 0x00, 0x3c, 0x3b, 0x04, 0x00, 0x00, 0x00, 0x69, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x1c, 0x04, 0xc0, 0xa8, 0x00, 0xff, 0x06, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x03, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] } } dhcproto-0.9.0/src/v4/opcode.rs000064400000000000000000000024401046102023000144130ustar 00000000000000use crate::{ decoder::{Decodable, Decoder}, encoder::{Encodable, Encoder}, error::{DecodeResult, EncodeResult}, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Opcode of Message #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Opcode { /// BootRequest - BootRequest, /// BootReply - BootReply, /// Unknown or not yet implemented Unknown(u8), } impl Decodable for Opcode { fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { Ok(decoder.read_u8()?.into()) } } impl Encodable for Opcode { fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { e.write_u8((*self).into()) } } impl From for Opcode { fn from(opcode: u8) -> Self { match opcode { 1 => Opcode::BootRequest, 2 => Opcode::BootReply, _ => Opcode::Unknown(opcode), } } } impl From for u8 { fn from(opcode: Opcode) -> Self { match opcode { Opcode::BootRequest => 1, Opcode::BootReply => 2, Opcode::Unknown(opcode) => opcode, } } } dhcproto-0.9.0/src/v4/options.rs000064400000000000000000001530211046102023000146370ustar 00000000000000use std::{borrow::Cow, collections::HashMap, iter, net::Ipv4Addr}; use crate::{ decoder::{Decodable, Decoder}, encoder::{Encodable, Encoder}, error::{DecodeResult, EncodeResult}, v4::bulk_query, v4::{fqdn, relay}, }; use ipnet::Ipv4Net; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use trust_dns_proto::{ rr::Name, serialize::binary::{BinDecodable, BinDecoder, BinEncodable, BinEncoder, EncodeMode}, }; // declares DHCP Option codes. // generates: // * the `OptionCode` enum and its From, Into // * the DhcpOption enum // * From<&DhcpOption> for OptionCode // // Syntax is {N, Name, "DocString" [,(T0,..TN,)]} // where: // * N is the numeric code associated with this option // * Name is the name to use for the enum variants // * "Docstring" is the documentation string that will be added to the variant in the OptionCode enum // * (T0,..TN) is the associated variables (if any). e.g. Ipv4Addr for "SubnetMask" or bool for "IpForwarding". // can contain more than one type but needs to be enclosed in parenthesis even if it's just a single variable. dhcproto_macros::declare_codes!( {0, Pad, "Padding"}, {1, SubnetMask, "Subnet Mask", (Ipv4Addr)}, {2, TimeOffset, "Time Offset", (i32)}, {3, Router, "Router", (Vec)}, {4, TimeServer, "Router", (Vec)}, {5, NameServer, "Name Server", (Vec)}, {6, DomainNameServer, "Name Server", (Vec)}, {7, LogServer, "Log Server", (Vec)}, {8, QuoteServer, "Quote Server", (Vec)}, {9, LprServer, "LPR Server", (Vec)}, {10, ImpressServer, "Impress server", (Vec)}, {11, ResourceLocationServer, "Resource Location Server", (Vec)}, {12, Hostname, "Host name", (String)}, {13, BootFileSize, "Boot file size", (u16)}, {14, MeritDumpFile, "Merit Dump File", (String)}, {15, DomainName, "Domain Name", (String)}, {16, SwapServer, "Swap server", (Ipv4Addr)}, {17, RootPath, "Root Path", (String)}, {18, ExtensionsPath, "Extensions path", (String)}, {19, IpForwarding, "IP forwarding", (bool)}, {20, NonLocalSrcRouting, "Non-local source routing", (bool)}, // TODO: Policy filter is a varlen 8 bit ipv4 / 32-bit subnetmask // need to think of a good way to represent this Vec<(Ipv4Addr, Ipv4Addr)>? // can it be changed into Ipv4Net and a prefix mask field? //{21, // PolicyFilter, "Policy Filter", (Vec)}, {22, MaxDatagramSize, "Max Datagram reassembly size", (u16)}, {23, DefaultIpTtl, "Ip TTL", (u8)}, {26, InterfaceMtu, "Interface MTU", (u16)}, {27, AllSubnetsLocal, "All Subnets Local", (bool)}, {28, BroadcastAddr, "Broadcast address", (Ipv4Addr)}, {29, PerformMaskDiscovery, "Perform mask discovery", (bool)}, {30, MaskSupplier, "Mask supplier", (bool)}, {31, PerformRouterDiscovery, "Perform router discovery", (bool)}, {32, RouterSolicitationAddr, "Router solicitation address", (Ipv4Addr)}, {33, StaticRoutingTable, "Static routing table", (Vec<(Ipv4Addr, Ipv4Addr)>)}, {35, ArpCacheTimeout, "ARP timeout", (u32)}, {36, EthernetEncapsulation, "Ethernet encapsulation", (bool)}, {37, DefaultTcpTtl, "Default TCP TTL", (u8)}, {38, TcpKeepaliveInterval, "TCP keepalive interval", (u32)}, {39, TcpKeepaliveGarbage, "TCP keealive garbage", (bool)}, {40, NISDomain, "Network information service domain", (String)}, {41, NIS, "Network infomration servers", (Vec)}, {42, NTPServers, "NTP servers", (Vec)}, {43, VendorExtensions, "Vendor Extensions", (Vec)}, {44, NetBiosNameServers, "NetBIOS over TCP/IP name server", (Vec)}, {45, NetBiosDatagramDistributionServer, "NetBIOS over TCP/IP Datagram Distribution Server", (Vec)}, {46, NetBiosNodeType, "NetBIOS over TCP/IP Node Type", (NodeType)}, {47, NetBiosScope, "NetBIOS over TCP/IP Scope", (String)}, {48, XFontServer, "X Window System Font Server", (Vec)}, {49, XDisplayManager, "Window System Display Manager", (Vec)}, {50, RequestedIpAddress, "Requested IP Address", (Ipv4Addr)}, {51, AddressLeaseTime, "IP Address Lease Time", (u32)}, {52, OptionOverload, "Option Overload", (u8)}, {53, MessageType, "Message Type", (MessageType)}, {54, ServerIdentifier, "Server Identifier", (Ipv4Addr)}, {55, ParameterRequestList, "Parameter Request List", (Vec)}, {56, Message, "Message", (String)}, {57, MaxMessageSize, "Maximum DHCP Message Size", (u16)}, {58, Renewal, "Renewal (T1) Time Value", (u32)}, {59, Rebinding, "Rebinding (T2) Time Value", (u32)}, {60, ClassIdentifier, "Class-identifier", (Vec)}, {61, ClientIdentifier, "Client Identifier", (Vec)}, {65, NISServerAddr, "NIS-Server-Addr", (Vec)}, {66, TFTPServerName, "TFTP Server Name - ", (Vec)}, {67, BootfileName, "Bootfile Name - ", (Vec)}, {80, RapidCommit, "Rapid Commit - "}, {81, ClientFQDN, "FQDN - ", (fqdn::ClientFQDN)}, {82, RelayAgentInformation, "Relay Agent Information - ", (relay::RelayAgentInformation)}, {91, ClientLastTransactionTime, "client-last-transaction-time - ", (u32)}, {92, AssociatedIp, "associated-ip - ", (Vec)}, {93, ClientSystemArchitecture, "Client System Architecture - ", (Architecture)}, {94, ClientNetworkInterface, "Client Network Interface - ", (u8, u8, u8)}, {97, ClientMachineIdentifier, "Client Machine Identifier - ", (Vec)}, {114, CaptivePortal, "Captive Portal - ", (url::Url)}, {118, SubnetSelection, "Subnet selection - ", (Ipv4Addr)}, {119, DomainSearch, "Domain Search - ", (Vec)}, {121, ClasslessStaticRoute, "Classless Static Route - ", (Vec<(Ipv4Net, Ipv4Addr)>)}, {150, TFTPServerAddress, "TFTP Server Address - ", (Ipv4Addr)}, {151, BulkLeaseQueryStatusCode, "status-code - ", (bulk_query::Code, String)}, {152, BulkLeaseQueryBaseTime, "- ", (u32)}, {153, BulkLeasQueryStartTimeOfState, "- ", (u32)}, {154, BulkLeaseQueryQueryStartTime, "- ", (u32)}, {155, BulkLeaseQueryQueryEndTime, "- ", (u32)}, {156, BulkLeaseQueryDhcpState, "- ", (bulk_query::QueryState)}, {157, BulkLeaseQueryDataSource, "- ", (bulk_query::DataSourceFlags)}, {255, End, "end-of-list marker"} ); /// ex /// ```rust /// use dhcproto::v4; /// /// let mut msg = v4::Message::default(); /// msg.opts_mut() /// .insert(v4::DhcpOption::MessageType(v4::MessageType::Discover)); /// msg.opts_mut().insert(v4::DhcpOption::ClientIdentifier( /// vec![0, 1, 2, 3, 4, 5], /// )); /// msg.opts_mut() /// .insert(v4::DhcpOption::ParameterRequestList(vec![ /// v4::OptionCode::SubnetMask, /// v4::OptionCode::Router, /// v4::OptionCode::DomainNameServer, /// v4::OptionCode::DomainName, /// ])); /// ``` #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct DhcpOptions(HashMap); impl DhcpOptions { /// Create new [`DhcpOptions`] /// /// [`DhcpOptions`]: crate::v4::DhcpOptions pub fn new() -> Self { Self::default() } /// Get the data for a particular [`OptionCode`] /// /// [`OptionCode`]: crate::v4::OptionCode pub fn get(&self, code: OptionCode) -> Option<&DhcpOption> { self.0.get(&code) } /// Get the mutable data for a particular [`OptionCode`] /// /// [`OptionCode`]: crate::v4::OptionCode pub fn get_mut(&mut self, code: OptionCode) -> Option<&mut DhcpOption> { self.0.get_mut(&code) } /// remove option pub fn remove(&mut self, code: OptionCode) -> Option { self.0.remove(&code) } /// insert a new [`DhcpOption`] /// /// ``` /// # use dhcproto::v4::{MessageType, DhcpOption, DhcpOptions}; /// let mut opts = DhcpOptions::new(); /// opts.insert(DhcpOption::MessageType(MessageType::Discover)); /// ``` /// [`DhcpOption`]: crate::v4::DhcpOption pub fn insert(&mut self, opt: DhcpOption) -> Option { self.0.insert((&opt).into(), opt) } /// iterate over entries /// ``` /// # use dhcproto::v4::{MessageType, DhcpOption, DhcpOptions}; /// let mut opts = DhcpOptions::new(); /// opts.insert(DhcpOption::MessageType(MessageType::Offer)); /// opts.insert(DhcpOption::SubnetMask([198, 168, 0, 1].into())); /// for (code, opt) in opts.iter() { /// println!("{code:?} {opt:?}"); /// } /// ``` pub fn iter(&self) -> impl Iterator { self.0.iter() } /// iterate mutably over entries pub fn iter_mut(&mut self) -> impl Iterator { self.0.iter_mut() } /// return message type /// ``` /// # use dhcproto::v4::{MessageType, DhcpOption, DhcpOptions}; /// let mut opts = DhcpOptions::new(); /// opts.insert(DhcpOption::MessageType(MessageType::Offer)); /// assert_eq!(opts.msg_type(), Some(MessageType::Offer)); /// ``` pub fn msg_type(&self) -> Option { let opt = self.get(OptionCode::MessageType)?; match opt { DhcpOption::MessageType(mtype) => Some(*mtype), _ => unreachable!("cannot return different option for MessageType"), } } /// determine if options contains a specific message type /// ``` /// # use dhcproto::v4::{MessageType, DhcpOption, DhcpOptions}; /// let mut opts = DhcpOptions::new(); /// opts.insert(DhcpOption::MessageType(MessageType::Offer)); /// assert!(opts.has_msg_type(MessageType::Offer)); /// assert!(!opts.has_msg_type(MessageType::Decline)); /// ``` pub fn has_msg_type(&self, opt: MessageType) -> bool { matches!(self.get(OptionCode::MessageType), Some(DhcpOption::MessageType(msg)) if *msg == opt) } /// clear all options /// ``` /// # use dhcproto::v4::{MessageType, DhcpOption, DhcpOptions}; /// let mut opts = DhcpOptions::new(); /// opts.insert(DhcpOption::MessageType(MessageType::Discover)); /// assert!(opts.len() == 1); /// opts.clear(); // clear options /// assert!(opts.is_empty()); /// ``` pub fn clear(&mut self) { self.0.clear() } /// Returns `true` if there are no options /// ``` /// # use dhcproto::v4::{MessageType, DhcpOption, DhcpOptions}; /// let mut opts = DhcpOptions::new(); /// opts.insert(DhcpOption::MessageType(MessageType::Offer)); /// assert!(!opts.is_empty()); /// ``` pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Retains only the elements specified by the predicate pub fn retain(&mut self, pred: F) where F: FnMut(&OptionCode, &mut DhcpOption) -> bool, { self.0.retain(pred) } /// Returns number of Options /// ``` /// # use dhcproto::v4::{MessageType, DhcpOption, DhcpOptions}; /// let mut opts = DhcpOptions::new(); /// opts.insert(DhcpOption::MessageType(MessageType::Offer)); /// assert_eq!(opts.len(), 1); /// ``` pub fn len(&self) -> usize { self.0.len() } } impl IntoIterator for DhcpOptions { type Item = (OptionCode, DhcpOption); type IntoIter = std::collections::hash_map::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl FromIterator for DhcpOptions { fn from_iter>(iter: T) -> Self { DhcpOptions( iter.into_iter() .map(|opt| ((&opt).into(), opt)) .collect::>(), ) } } impl FromIterator<(OptionCode, DhcpOption)> for DhcpOptions { fn from_iter>(iter: T) -> Self { DhcpOptions(iter.into_iter().collect::>()) } } impl Decodable for DhcpOptions { fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { // represented as a vector in the actual message let mut opts = HashMap::new(); // should we error the whole parser if we fail to parse an // option or just stop parsing options? -- here we will just stop while let Ok(opt) = DhcpOption::decode(decoder) { // we throw away PAD bytes here match opt { DhcpOption::End => { break; } DhcpOption::Pad => {} _ => { opts.insert(OptionCode::from(&opt), opt); } } } Ok(DhcpOptions(opts)) } } impl Encodable for DhcpOptions { fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { if self.0.is_empty() { Ok(()) } else { // encode all opts adding the `End` afterwards // sum all bytes written match self.get(OptionCode::RelayAgentInformation) { // agent info must be placed last before `End` Some(agent_info) => self .0 .iter() .filter(|opt| *opt.0 != OptionCode::RelayAgentInformation) .chain(iter::once((&OptionCode::RelayAgentInformation, agent_info))) .chain(iter::once((&OptionCode::End, &DhcpOption::End))) .try_for_each(|(_, opt)| opt.encode(e)), None => self .0 .iter() .chain(iter::once((&OptionCode::End, &DhcpOption::End))) .try_for_each(|(_, opt)| opt.encode(e)), } } } } impl PartialOrd for OptionCode { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for OptionCode { fn cmp(&self, other: &Self) -> std::cmp::Ordering { u8::from(*self).cmp(&u8::from(*other)) } } impl PartialOrd for DhcpOption { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for DhcpOption { fn cmp(&self, other: &Self) -> std::cmp::Ordering { OptionCode::from(self).cmp(&OptionCode::from(other)) } } /// Architecture name from - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Architecture { /// Intel x86PC Intelx86PC, /// NEC/PC98 NECPC98, /// EFI Itanium Itanium, /// DEC Alpha DECAlpha, /// Arc x86 Arcx86, /// Intel Lean Client IntelLeanClient, /// EFI IA32 IA32, /// EFI BC BC, /// EFI Xscale Xscale, /// EFI x86-64 X86_64, /// Unknown Unknown(u16), } impl From for Architecture { fn from(n: u16) -> Self { use Architecture::*; match n { 0 => Intelx86PC, 1 => NECPC98, 2 => Itanium, 3 => DECAlpha, 4 => Arcx86, 5 => IntelLeanClient, 6 => IA32, 7 => BC, 8 => Xscale, 9 => X86_64, _ => Unknown(n), } } } impl From for u16 { fn from(n: Architecture) -> Self { use Architecture::*; match n { Intelx86PC => 0, NECPC98 => 1, Itanium => 2, DECAlpha => 3, Arcx86 => 4, IntelLeanClient => 5, IA32 => 6, BC => 7, Xscale => 8, X86_64 => 9, Unknown(n) => n, } } } /// NetBIOS allows several different node types #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum NodeType { /// Broadcast B, /// Peer-to-peer P, /// Mixed (B & P) M, /// Hybrid (P & B) H, /// Unknown Unknown(u8), } impl From for NodeType { fn from(n: u8) -> Self { use NodeType::*; match n { 1 => B, 2 => P, 4 => M, 8 => H, _ => Unknown(n), } } } impl From for u8 { fn from(n: NodeType) -> Self { use NodeType::*; match n { B => 1, P => 2, M => 4, H => 8, Unknown(n) => n, } } } #[inline] fn decode_inner( code: OptionCode, len: usize, decoder: &mut Decoder<'_>, ) -> DecodeResult { use DhcpOption::*; Ok(match code { OptionCode::Pad => Pad, OptionCode::SubnetMask => SubnetMask(decoder.read_ipv4(len)?), OptionCode::TimeOffset => TimeOffset(decoder.read_i32()?), OptionCode::Router => Router(decoder.read_ipv4s(len)?), OptionCode::TimeServer => TimeServer(decoder.read_ipv4s(len)?), OptionCode::NameServer => NameServer(decoder.read_ipv4s(len)?), OptionCode::DomainNameServer => DomainNameServer(decoder.read_ipv4s(len)?), OptionCode::LogServer => LogServer(decoder.read_ipv4s(len)?), OptionCode::QuoteServer => QuoteServer(decoder.read_ipv4s(len)?), OptionCode::LprServer => LprServer(decoder.read_ipv4s(len)?), OptionCode::ImpressServer => ImpressServer(decoder.read_ipv4s(len)?), OptionCode::ResourceLocationServer => ResourceLocationServer(decoder.read_ipv4s(len)?), OptionCode::Hostname => Hostname(decoder.read_string(len)?), OptionCode::BootFileSize => BootFileSize(decoder.read_u16()?), OptionCode::MeritDumpFile => MeritDumpFile(decoder.read_string(len)?), OptionCode::DomainName => DomainName(decoder.read_string(len)?), OptionCode::SwapServer => SwapServer(decoder.read_ipv4(len)?), OptionCode::RootPath => RootPath(decoder.read_string(len)?), OptionCode::ExtensionsPath => ExtensionsPath(decoder.read_string(len)?), OptionCode::IpForwarding => IpForwarding(decoder.read_bool()?), OptionCode::NonLocalSrcRouting => NonLocalSrcRouting(decoder.read_bool()?), OptionCode::MaxDatagramSize => MaxDatagramSize(decoder.read_u16()?), OptionCode::DefaultIpTtl => DefaultIpTtl(decoder.read_u8()?), OptionCode::InterfaceMtu => InterfaceMtu(decoder.read_u16()?), OptionCode::AllSubnetsLocal => AllSubnetsLocal(decoder.read_bool()?), OptionCode::BroadcastAddr => BroadcastAddr(decoder.read_ipv4(len)?), OptionCode::PerformMaskDiscovery => PerformMaskDiscovery(decoder.read_bool()?), OptionCode::MaskSupplier => MaskSupplier(decoder.read_bool()?), OptionCode::PerformRouterDiscovery => PerformRouterDiscovery(decoder.read_bool()?), OptionCode::RouterSolicitationAddr => RouterSolicitationAddr(decoder.read_ipv4(len)?), OptionCode::StaticRoutingTable => StaticRoutingTable(decoder.read_pair_ipv4s(len)?), OptionCode::ArpCacheTimeout => ArpCacheTimeout(decoder.read_u32()?), OptionCode::EthernetEncapsulation => EthernetEncapsulation(decoder.read_bool()?), OptionCode::DefaultTcpTtl => DefaultIpTtl(decoder.read_u8()?), OptionCode::TcpKeepaliveInterval => TcpKeepaliveInterval(decoder.read_u32()?), OptionCode::TcpKeepaliveGarbage => TcpKeepaliveGarbage(decoder.read_bool()?), OptionCode::NISDomain => NISDomain(decoder.read_string(len)?), OptionCode::NISServerAddr => NISServerAddr(decoder.read_ipv4s(len)?), OptionCode::TFTPServerName => TFTPServerName(decoder.read_slice(len)?.to_vec()), OptionCode::BootfileName => BootfileName(decoder.read_slice(len)?.to_vec()), OptionCode::NIS => NIS(decoder.read_ipv4s(len)?), OptionCode::NTPServers => NTPServers(decoder.read_ipv4s(len)?), OptionCode::VendorExtensions => VendorExtensions(decoder.read_slice(len)?.to_vec()), OptionCode::NetBiosNameServers => NetBiosNameServers(decoder.read_ipv4s(len)?), OptionCode::NetBiosDatagramDistributionServer => { NetBiosDatagramDistributionServer(decoder.read_ipv4s(len)?) } OptionCode::NetBiosNodeType => NetBiosNodeType(decoder.read_u8()?.into()), OptionCode::NetBiosScope => NetBiosScope(decoder.read_string(len)?), OptionCode::XFontServer => XFontServer(decoder.read_ipv4s(len)?), OptionCode::XDisplayManager => XDisplayManager(decoder.read_ipv4s(len)?), OptionCode::RequestedIpAddress => RequestedIpAddress(decoder.read_ipv4(len)?), OptionCode::AddressLeaseTime => AddressLeaseTime(decoder.read_u32()?), OptionCode::OptionOverload => OptionOverload(decoder.read_u8()?), OptionCode::MessageType => MessageType(decoder.read_u8()?.into()), OptionCode::ServerIdentifier => ServerIdentifier(decoder.read_ipv4(len)?), OptionCode::ParameterRequestList => ParameterRequestList( decoder .read_slice(len)? .iter() .map(|code| (*code).into()) .collect(), ), OptionCode::Message => Message(decoder.read_string(len)?), OptionCode::MaxMessageSize => MaxMessageSize(decoder.read_u16()?), OptionCode::Renewal => Renewal(decoder.read_u32()?), OptionCode::Rebinding => Rebinding(decoder.read_u32()?), OptionCode::ClassIdentifier => ClassIdentifier(decoder.read_slice(len)?.to_vec()), OptionCode::ClientIdentifier => ClientIdentifier(decoder.read_slice(len)?.to_vec()), OptionCode::RapidCommit => { debug_assert!(len == 0); RapidCommit } OptionCode::RelayAgentInformation => { let mut dec = Decoder::new(decoder.read_slice(len)?); RelayAgentInformation(relay::RelayAgentInformation::decode(&mut dec)?) } OptionCode::ClientLastTransactionTime => ClientLastTransactionTime(decoder.read_u32()?), OptionCode::AssociatedIp => AssociatedIp(decoder.read_ipv4s(len)?), OptionCode::ClientSystemArchitecture => { let ty = decoder.read_u16()?; ClientSystemArchitecture(ty.into()) } OptionCode::ClientNetworkInterface => { debug_assert!(len == 3); ClientNetworkInterface(decoder.read_u8()?, decoder.read_u8()?, decoder.read_u8()?) } OptionCode::ClientMachineIdentifier => { ClientMachineIdentifier(decoder.read_slice(len)?.to_vec()) } OptionCode::CaptivePortal => CaptivePortal(decoder.read_str(len)?.parse()?), OptionCode::SubnetSelection => SubnetSelection(decoder.read_ipv4(len)?), OptionCode::DomainSearch => { let mut name_decoder = BinDecoder::new(decoder.read_slice(len)?); let mut names = Vec::new(); while let Ok(name) = Name::read(&mut name_decoder) { names.push(name); } DomainSearch(names) } OptionCode::TFTPServerAddress => TFTPServerAddress(decoder.read_ipv4(len)?), OptionCode::BulkLeaseQueryStatusCode => { let code = decoder.read_u8()?.into(); // len - 1 because code is included in length let message = decoder.read_string(len - 1)?; BulkLeaseQueryStatusCode(code, message) } OptionCode::BulkLeaseQueryBaseTime => { debug_assert!(len == 4); BulkLeaseQueryBaseTime(decoder.read_u32()?) } OptionCode::BulkLeasQueryStartTimeOfState => { debug_assert!(len == 4); BulkLeasQueryStartTimeOfState(decoder.read_u32()?) } OptionCode::BulkLeaseQueryQueryStartTime => { debug_assert!(len == 4); BulkLeaseQueryQueryStartTime(decoder.read_u32()?) } OptionCode::BulkLeaseQueryQueryEndTime => { debug_assert!(len == 4); BulkLeaseQueryQueryEndTime(decoder.read_u32()?) } OptionCode::BulkLeaseQueryDhcpState => BulkLeaseQueryDhcpState(decoder.read_u8()?.into()), OptionCode::BulkLeaseQueryDataSource => { BulkLeaseQueryDataSource(bulk_query::DataSourceFlags::new(decoder.read_u8()?)) } OptionCode::ClientFQDN => { debug_assert!(len >= 3); let flags = decoder.read_u8()?.into(); let rcode1 = decoder.read_u8()?; let rcode2 = decoder.read_u8()?; let mut name_decoder = BinDecoder::new(decoder.read_slice(len - 3)?); let name = Name::read(&mut name_decoder)?; ClientFQDN(fqdn::ClientFQDN { flags, r1: rcode1, r2: rcode2, domain: name, }) } OptionCode::ClasslessStaticRoute => { let mut routes = Vec::new(); let mut route_dec = Decoder::new(decoder.read_slice(len)?); while let Ok(prefix_len) = route_dec.read_u8() { if prefix_len > 32 { break; } // Significant bytes to hold the prefix let sig_bytes = (prefix_len as usize + 7) / 8; let mut dest = [0u8; 4]; dest[0..sig_bytes].clone_from_slice(route_dec.read_slice(sig_bytes)?); let dest = Ipv4Net::new(dest.into(), prefix_len).unwrap(); let gw = route_dec.read_ipv4(4)?; routes.push((dest, gw)); } ClasslessStaticRoute(routes) } OptionCode::End => End, // not yet implemented OptionCode::Unknown(code) => { let data = decoder.read_slice(len)?.to_vec(); Unknown(UnknownOption { code, data }) } }) } impl Decodable for DhcpOption { #[inline] fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { #[derive(Debug)] struct Opt<'a> { code: u8, // will contain code + len + value buf: Cow<'a, [u8]>, } impl<'a> Opt<'a> { #[inline] fn as_option(&self) -> DecodeResult { let mut opt_decoder = Decoder::new(&self.buf); let code = opt_decoder.read_u8()?.into(); let _len = opt_decoder.read_u8()?; // throw out potentially invalid len decode_inner(code, opt_decoder.buffer().len(), &mut opt_decoder) } // can't implement Decodable b/c of lifetime issues fn decode(dec: &mut Decoder<'a>) -> DecodeResult { // TODO: necessary to call u8::from_be_bytes? let [code, len] = dec.peek::<2>()?; let buf = Cow::from(dec.read_slice(len as usize + 2)?); Ok(Opt { code, buf }) } } use DhcpOption::*; // read the code first, determines the variant // pad|end have no length, so we can't read len up here let mut last: Option> = None; while let Ok(code) = decoder.peek_u8() { match code.into() { OptionCode::End => { return match last { Some(prev) => prev.as_option(), None => { decoder.read_u8()?; Ok(End) } }; } OptionCode::Pad => { return match last { Some(prev) => prev.as_option(), None => { decoder.read_u8()?; Ok(Pad) } }; } _ => { last = Some(match last { None => Opt::decode(decoder)?, Some(mut prev) if code == prev.code => { let cur = Opt::decode(decoder)?; // concatention case - // store the len & value in buf prev.buf.to_mut().extend(&cur.buf[2..]); prev } Some(prev) => { // got different option, decode the one we've got // need to stop here so we don't consume the next option's buffer return prev.as_option(); } }); } } } last.ok_or(crate::error::DecodeError::NotEnoughBytes)? .as_option() } } /// Splits `bytes` into chunks of up to u8::MAX (255 is the max opt length), /// where each chunk is prepended by the length of the chunk and the code. /// ``` /// use dhcproto::{encoder::Encoder, v4::{OptionCode, encode_long_opt_bytes}}; /// /// let mut buf = Vec::new(); /// let mut e = Encoder::new(&mut buf); /// let msg = std::iter::repeat(b'a').take(300).collect::>(); /// let res = encode_long_opt_bytes(OptionCode::Message, &msg, &mut e); /// // [code, 255, b'a', ..., code, 45, b'a', ...] /// let mut x = vec![OptionCode::Message.into(), 255]; /// x.extend(std::iter::repeat(b'a').take(255)); /// x.push(OptionCode::Message.into()); /// x.push(45); /// x.extend(std::iter::repeat(b'a').take(45)); /// /// assert_eq!(buf, x); /// ``` #[inline] pub fn encode_long_opt_bytes( code: OptionCode, bytes: &[u8], e: &mut Encoder<'_>, ) -> EncodeResult<()> { for chunk in bytes.chunks(u8::MAX as usize) { e.write_u8(code.into())?; e.write_u8(chunk.len() as u8)?; e.write_slice(chunk)?; } Ok(()) } /// Splits `bytes` into chunks of up to u8::MAX / `factor` (255 is the max opt length), /// where each chunk is prepended by the length of the chunk and the code. /// /// `factor` here accounts for writing data where `T` is more than 1 byte. /// /// INVARIANT: `factor` must equal the number of bytes in each `T` /// ``` /// # use std::{iter, net::Ipv4Addr}; /// use dhcproto::{encoder::Encoder, v4::{OptionCode, encode_long_opt_chunks}}; /// /// let mut buf = Vec::new(); /// let mut e = Encoder::new(&mut buf); /// let opt = iter::repeat(Ipv4Addr::from([1,2,3,4])).take(80).collect::>(); /// let res = encode_long_opt_chunks(OptionCode::NIS, 4, &opt, |ip, e| e.write_u32((*ip).into()), &mut e); /// // [code, 252, 1,2,3,4,1,2,3,4 ..., code, 68, 1,2,3,4, ...] /// let mut x = vec![OptionCode::NIS.into(), 252]; /// x.extend(iter::repeat(Ipv4Addr::from([1,2,3,4])).map(|ip| u32::from(ip).to_be_bytes()).flatten().take(252)); /// x.push(OptionCode::NIS.into()); /// x.push(68); /// x.extend(iter::repeat(Ipv4Addr::from([1,2,3,4])).map(|ip| u32::from(ip).to_be_bytes()).flatten().take(68)); /// /// assert_eq!(buf, x); /// ``` #[inline] pub fn encode_long_opt_chunks<'a, T, F>( code: OptionCode, factor: usize, data: &[T], f: F, e: &mut Encoder<'a>, ) -> EncodeResult<()> where F: Fn(&T, &mut Encoder<'a>) -> EncodeResult<()>, { // TODO: consider using `mem::size_of::()` so we don't need factor // although, we would need to make OptionCode repr(u8) for chunk in data.chunks(u8::MAX as usize / factor) { e.write_u8(code.into())?; e.write_u8((chunk.len() * factor) as u8)?; for thing in chunk { f(thing, e)?; } } Ok(()) } impl Encodable for DhcpOption { fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { use DhcpOption::*; let code: OptionCode = self.into(); // pad has no length, so we can't read len up here. // don't want to have a fall-through case either // so we get exhaustiveness checking, so we'll parse // code in each match arm match self { Pad | End => { e.write_u8(code.into())?; } RapidCommit => { e.write_u8(code.into())?; e.write_u8(0)?; } SubnetMask(addr) | SwapServer(addr) | BroadcastAddr(addr) | RouterSolicitationAddr(addr) | RequestedIpAddress(addr) | ServerIdentifier(addr) | SubnetSelection(addr) | TFTPServerAddress(addr) => { e.write_u8(code.into())?; e.write_u8(4)?; e.write_u32((*addr).into())? } TimeOffset(offset) => { e.write_u8(code.into())?; e.write_u8(4)?; e.write_i32(*offset)? } TimeServer(ips) | NameServer(ips) | Router(ips) | DomainNameServer(ips) | LogServer(ips) | QuoteServer(ips) | LprServer(ips) | ImpressServer(ips) | ResourceLocationServer(ips) | XFontServer(ips) | XDisplayManager(ips) | NIS(ips) | NISServerAddr(ips) | NTPServers(ips) | NetBiosNameServers(ips) | NetBiosDatagramDistributionServer(ips) | AssociatedIp(ips) => { // let bytes = ips.iter().flat_map(|a| u32::from(*a).to_be_bytes()).collect::>(); encode_long_opt_chunks(code, 4, ips, |ip, e| e.write_u32((*ip).into()), e)?; // e.write_u8(code.into())?; // e.write_u8(ips.len() as u8 * 4)?; // for ip in ips { // e.write_u32((*ip).into())?; // } } Hostname(s) | MeritDumpFile(s) | DomainName(s) | ExtensionsPath(s) | NISDomain(s) | RootPath(s) | NetBiosScope(s) | Message(s) => { encode_long_opt_bytes(code, s.as_bytes(), e)?; } BootFileSize(num) | MaxDatagramSize(num) | InterfaceMtu(num) | MaxMessageSize(num) => { e.write_u8(code.into())?; e.write_u8(2)?; e.write_u16(*num)? } IpForwarding(b) | NonLocalSrcRouting(b) | AllSubnetsLocal(b) | PerformMaskDiscovery(b) | MaskSupplier(b) | PerformRouterDiscovery(b) | EthernetEncapsulation(b) | TcpKeepaliveGarbage(b) => { e.write_u8(code.into())?; e.write_u8(1)?; e.write_u8((*b).into())? } DefaultIpTtl(byte) | DefaultTcpTtl(byte) | OptionOverload(byte) => { e.write_u8(code.into())?; e.write_u8(1)?; e.write_u8(*byte)? } StaticRoutingTable(pair_ips) => { // let bytes = pair_ips.iter().flat_map(|(a, b)| u32::from(*a).to_be_bytes().into_iter().chain(u32::from(*b).to_be_bytes())).collect::>(); // encode_chunk_bytes(code, &bytes, e)?; encode_long_opt_chunks( code, 8, pair_ips, |(a, b), e| { e.write_u32((*a).into())?; e.write_u32((*b).into()) }, e, )?; } ArpCacheTimeout(num) | TcpKeepaliveInterval(num) | AddressLeaseTime(num) | Renewal(num) | Rebinding(num) | ClientLastTransactionTime(num) | BulkLeaseQueryBaseTime(num) | BulkLeasQueryStartTimeOfState(num) | BulkLeaseQueryQueryStartTime(num) | BulkLeaseQueryQueryEndTime(num) => { e.write_u8(code.into())?; e.write_u8(4)?; e.write_u32(*num)?; } VendorExtensions(bytes) | ClassIdentifier(bytes) | ClientIdentifier(bytes) | ClientMachineIdentifier(bytes) | TFTPServerName(bytes) | BootfileName(bytes) => { encode_long_opt_bytes(code, bytes, e)?; } ParameterRequestList(codes) => { encode_long_opt_chunks(code, 1, codes, |code, e| e.write_u8((*code).into()), e)?; } NetBiosNodeType(ntype) => { e.write_u8(code.into())?; e.write_u8(1)?; e.write_u8((*ntype).into())?; } MessageType(mtype) => { e.write_u8(code.into())?; e.write_u8(1)?; e.write_u8((*mtype).into())?; } RelayAgentInformation(relay) => { let mut buf = Vec::new(); let mut opt_enc = Encoder::new(&mut buf); relay.encode(&mut opt_enc)?; // data encoded to intermediate buf encode_long_opt_bytes(code, &buf, e)?; } ClientSystemArchitecture(arch) => { e.write_u8(code.into())?; e.write_u8(2)?; e.write_u16((*arch).into())?; } ClientNetworkInterface(ty, major, minor) => { e.write_u8(code.into())?; e.write_u8(3)?; e.write_u8(*ty)?; e.write_u8(*major)?; e.write_u8(*minor)?; } CaptivePortal(url) => { let url = url.to_string(); encode_long_opt_bytes(code, url.as_bytes(), e)?; } BulkLeaseQueryStatusCode(status_code, msg) => { e.write_u8(code.into())?; let msg = msg.as_bytes(); e.write_u8(msg.len() as u8 + 1)?; e.write_u8((*status_code).into())?; e.write_slice(msg)? } BulkLeaseQueryDhcpState(state) => { e.write_u8(code.into())?; e.write_u8(1)?; e.write_u8((*state).into())? } BulkLeaseQueryDataSource(src) => { e.write_u8(code.into())?; e.write_u8(1)?; e.write_u8((*src).into())? } DomainSearch(names) => { let mut buf = Vec::new(); let mut name_encoder = BinEncoder::new(&mut buf); for name in names { name.emit(&mut name_encoder)?; } encode_long_opt_bytes(code, &buf, e)?; } ClientFQDN(fqdn) => { let fqdn::ClientFQDN { flags, r1, r2, domain, } = fqdn; let mut buf = vec![(*flags).into(), *r1, *r2]; if flags.e() { // emits in canonical format // start encoding at byte 3 because we had some preamble let mut name_encoder = BinEncoder::with_offset(&mut buf, 3, EncodeMode::Normal); domain.emit_as_canonical(&mut name_encoder, true)?; } else { // TODO: not sure if this is correct buf.extend(domain.to_ascii().as_bytes()); } encode_long_opt_bytes(code, &buf, e)?; } ClasslessStaticRoute(routes) => { let mut buf = Vec::new(); let mut route_enc = Encoder::new(&mut buf); for (dest, gw) in routes { let byte_len = (dest.prefix_len() + 7) / 8; route_enc.write_u8(dest.prefix_len())?; route_enc.write_slice(&dest.addr().octets()[0..byte_len as usize])?; route_enc.write(gw.octets())?; } encode_long_opt_bytes(code, &buf, e)?; } // not yet implemented Unknown(opt) => { encode_long_opt_bytes(code, &opt.data, e)?; } }; Ok(()) } } /// An as-of-yet unimplemented option type #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct UnknownOption { code: u8, data: Vec, } impl UnknownOption { pub fn new(code: OptionCode, data: Vec) -> Self { Self { code: code.into(), data, } } /// return the option code pub fn code(&self) -> OptionCode { self.code.into() } /// return the data for this option pub fn data(&self) -> &[u8] { &self.data } /// consume into parts pub fn into_parts(self) -> (OptionCode, Vec) { (self.code.into(), self.data) } } impl Decodable for UnknownOption { fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { let code = decoder.read_u8()?; let length = decoder.read_u8()?; let bytes = decoder.read_slice(length as usize)?.to_vec(); Ok(UnknownOption { code, data: bytes }) } } impl Encodable for UnknownOption { fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { // TODO: account for >255 len e.write_u8(self.code)?; e.write_u8(self.data.len() as u8)?; e.write_slice(&self.data)?; Ok(()) } } /// The DHCP message type /// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum MessageType { /// DHCPDiscover Discover, /// DHCPOffer Offer, /// DHCPRequest Request, /// DHCPDecline Decline, /// DHCPAck Ack, /// DHCPNak Nak, /// DHCPRelease Release, /// DHCPInform Inform, /// DHCPForceRenew - ForceRenew, /// DHCPLeaseQuery - LeaseQuery, /// DHCPLeaseUnassigned LeaseUnassigned, /// DHCPLeaseUnknown LeaseUnknown, /// DHCPLeaseActive LeaseActive, /// DHCPBulkLeaseQuery - BulkLeaseQuery, /// DHCPLeaseQueryDone LeaseQueryDone, /// DHCPActiveLeaseQuery - ActiveLeaseQuery, /// DHCPLeaseQueryStatus LeaseQueryStatus, /// DHCPTLS Tls, /// an unknown message type Unknown(u8), } impl From for MessageType { fn from(n: u8) -> Self { match n { 1 => MessageType::Discover, 2 => MessageType::Offer, 3 => MessageType::Request, 4 => MessageType::Decline, 5 => MessageType::Ack, 6 => MessageType::Nak, 7 => MessageType::Release, 8 => MessageType::Inform, 9 => MessageType::ForceRenew, 10 => MessageType::LeaseQuery, 11 => MessageType::LeaseUnassigned, 12 => MessageType::LeaseUnknown, 13 => MessageType::LeaseActive, 14 => MessageType::BulkLeaseQuery, 15 => MessageType::LeaseQueryDone, 16 => MessageType::ActiveLeaseQuery, 17 => MessageType::LeaseQueryStatus, 18 => MessageType::Tls, n => MessageType::Unknown(n), } } } impl From for u8 { fn from(m: MessageType) -> Self { match m { MessageType::Discover => 1, MessageType::Offer => 2, MessageType::Request => 3, MessageType::Decline => 4, MessageType::Ack => 5, MessageType::Nak => 6, MessageType::Release => 7, MessageType::Inform => 8, MessageType::ForceRenew => 9, MessageType::LeaseQuery => 10, MessageType::LeaseUnassigned => 11, MessageType::LeaseUnknown => 12, MessageType::LeaseActive => 13, MessageType::BulkLeaseQuery => 14, MessageType::LeaseQueryDone => 15, MessageType::ActiveLeaseQuery => 16, MessageType::LeaseQueryStatus => 17, MessageType::Tls => 18, MessageType::Unknown(n) => n, } } } #[cfg(test)] mod tests { use std::collections::VecDeque; use super::*; use std::str::FromStr; type Result = std::result::Result>; fn test_opt(orig: DhcpOption, actual: Vec) -> Result<()> { let mut out = vec![]; let mut enc = Encoder::new(&mut out); orig.encode(&mut enc)?; println!("encoded {:?}", enc.buffer()); assert_eq!(out, actual); let decoded = DhcpOption::decode(&mut Decoder::new(&out))?; assert_eq!(decoded, orig); Ok(()) } #[test] fn test_opts() -> Result<()> { let (input, len) = binput(); println!("{input:?}"); let opts = DhcpOptions::decode(&mut Decoder::new(&input))?; println!("{opts:?}"); let mut output = Vec::new(); opts.encode(&mut Encoder::new(&mut output))?; // not comparing len as we don't add PAD bytes // assert_eq!(input.len(), len); assert_eq!(opts.len(), len); Ok(()) } #[test] fn test_long_opts() -> Result<()> { let (input, len) = long_opt(); let opts = DhcpOptions::decode(&mut Decoder::new(&input))?; let mut output = Vec::new(); opts.encode(&mut Encoder::new(&mut output))?; // not comparing len as we don't add PAD bytes // assert_eq!(input.len(), len); assert_eq!(opts.len(), len); Ok(()) } #[test] fn test_ips() -> Result<()> { test_opt( DhcpOption::DomainNameServer(vec![ "192.168.0.1".parse::().unwrap(), "192.168.1.1".parse::().unwrap(), ]), vec![6, 8, 192, 168, 0, 1, 192, 168, 1, 1], )?; Ok(()) } #[test] fn test_ips_long() -> Result<()> { let ip = "192.168.0.1".parse::().unwrap(); let list = std::iter::repeat(ip).take(64).collect(); let mut bytes = std::iter::repeat(ip) .take(63) .flat_map(|ip| u32::from(ip).to_be_bytes()) .collect::>(); bytes.push_front(252); bytes.push_front(6); bytes.push_back(6); bytes.push_back(4); bytes.extend(u32::from(ip).to_be_bytes()); test_opt( DhcpOption::DomainNameServer(list), bytes.drain(..).collect(), )?; Ok(()) } #[test] fn test_ip() -> Result<()> { test_opt( DhcpOption::ServerIdentifier("192.168.0.1".parse::().unwrap()), vec![54, 4, 192, 168, 0, 1], )?; Ok(()) } #[test] fn test_str() -> Result<()> { test_opt( DhcpOption::Hostname("foobar.com".to_string()), vec![12, 10, 102, 111, 111, 98, 97, 114, 46, 99, 111, 109], )?; Ok(()) } #[test] fn test_byte() -> Result<()> { test_opt(DhcpOption::DefaultIpTtl(10), vec![23, 1, 10])?; Ok(()) } #[test] fn test_num() -> Result<()> { test_opt(DhcpOption::Renewal(30), vec![58, 4, 0, 0, 0, 30])?; Ok(()) } #[test] fn test_mtype() -> Result<()> { test_opt(DhcpOption::MessageType(MessageType::Offer), vec![53, 1, 2])?; Ok(()) } #[test] fn test_ntype() -> Result<()> { test_opt(DhcpOption::NetBiosNodeType(NodeType::M), vec![46, 1, 4])?; Ok(()) } #[test] fn test_pair_ips() -> Result<()> { test_opt( DhcpOption::StaticRoutingTable(vec![( "192.168.1.1".parse::().unwrap(), "192.168.0.1".parse::().unwrap(), )]), vec![33, 8, 192, 168, 1, 1, 192, 168, 0, 1], )?; Ok(()) } #[test] fn test_arch() -> Result<()> { test_opt( DhcpOption::ClientSystemArchitecture(Architecture::Intelx86PC), vec![93, 2, 0, 0], )?; Ok(()) } #[test] fn test_captive_portal() -> Result<()> { let mut res = vec![114]; let url = "https://foobar.com/".as_bytes(); // note the ending slash res.push(url.len() as u8); res.extend(url); test_opt( DhcpOption::CaptivePortal("https://foobar.com".parse()?), // url parse will add trailing slash res, )?; Ok(()) } #[test] fn test_rapid_commit() -> Result<()> { test_opt(DhcpOption::RapidCommit, vec![80, 0])?; Ok(()) } #[test] fn test_status() -> Result<()> { let msg = "message".to_string(); test_opt( DhcpOption::BulkLeaseQueryStatusCode(bulk_query::Code::Success, msg.clone()), vec![ 151, (msg.as_bytes().len() + 1) as u8, 0, b'm', b'e', b's', b's', b'a', b'g', b'e', ], )?; Ok(()) } #[test] fn test_domainsearch() -> Result<()> { test_opt( DhcpOption::DomainSearch(vec![ Name::from_str("eng.apple.com.").unwrap(), Name::from_str("marketing.apple.com.").unwrap(), ]), vec![ 119, 27, 3, b'e', b'n', b'g', 5, b'a', b'p', b'p', b'l', b'e', 3, b'c', b'o', b'm', 0, 9, b'm', b'a', b'r', b'k', b'e', b't', b'i', b'n', b'g', 0xC0, 0x04, ], )?; Ok(()) } #[test] fn test_client_fqdn() -> Result<()> { test_opt( DhcpOption::ClientFQDN(fqdn::ClientFQDN { flags: fqdn::FqdnFlags::default().set_e(true), r1: 0, r2: 0, domain: Name::from_str("www.google.com.").unwrap(), }), vec![ 81, 19, 0x04, 0, 0, 3, b'w', b'w', b'w', 6, b'g', b'o', b'o', b'g', b'l', b'e', 3, b'c', b'o', b'm', 0, ], )?; Ok(()) } #[test] fn test_unknown() -> Result<()> { test_opt( DhcpOption::Unknown(UnknownOption { code: 240, data: vec![1, 2, 3, 4], }), vec![240, 4, 1, 2, 3, 4], )?; Ok(()) } #[test] fn test_nis_server_addr() -> Result<()> { test_opt( DhcpOption::NISServerAddr(vec![ Ipv4Addr::new(127, 0, 0, 1), Ipv4Addr::new(127, 0, 0, 2), ]), vec![65, 8, 127, 0, 0, 1, 127, 0, 0, 2], )?; Ok(()) } #[test] fn test_classless_static_route() -> Result<()> { test_opt( DhcpOption::ClasslessStaticRoute(vec![ ("10.0.0.0/8".parse()?, "192.168.1.1".parse()?), ("172.16.0.0/24".parse()?, "192.168.1.1".parse()?), ]), vec![ 121, 14, // Option & length 8, 10, 192, 168, 1, 1, // 10.0.0.0/8 -> 192.168.1.1 24, 172, 16, 0, 192, 168, 1, 1, // 172.16.0.0/24 -> 192.168.1.1 ], )?; Ok(()) } #[test] fn test_classless_static_route_long_opt() -> Result<()> { let buf = vec![ 121, 14, // Option & length 8, 10, 192, 168, 1, 1, // 10.0.0.0/8 -> 192.168.1.1 24, 172, 16, 0, 192, 168, 1, 1, // 172.16.0.0/24 -> 192.168.1.1 121, 14, // Option & length 8, 10, 192, 168, 1, 1, // 10.0.0.0/8 -> 192.168.1.1 24, 172, 16, 0, 192, 168, 1, 1, // 172.16.0.0/24 -> 192.168.1.1 ]; let mut dec = Decoder::new(&buf); let opt = DhcpOption::decode(&mut dec)?; assert_eq!( DhcpOption::ClasslessStaticRoute(vec![ ("10.0.0.0/8".parse()?, "192.168.1.1".parse()?), ("172.16.0.0/24".parse()?, "192.168.1.1".parse()?), ("10.0.0.0/8".parse()?, "192.168.1.1".parse()?), ("172.16.0.0/24".parse()?, "192.168.1.1".parse()?), ]), opt ); Ok(()) } fn binput() -> (Vec, usize) { ( vec![ 53, 1, 2, 54, 4, 192, 168, 0, 1, 51, 4, 0, 0, 0, 60, 58, 4, 0, 0, 0, 30, 59, 4, 0, 0, 0, 52, 1, 4, 255, 255, 255, 0, 3, 4, 192, 168, 0, 1, 6, 8, 192, 168, 0, 1, 192, 168, 1, 1, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], 8, ) } fn long_opt() -> (Vec, usize) { // domain name server encoded in long format: 6, 4, 192, 168, 0, 1, 6, 4, 192, 168, 1, 1 // instead of: 6, 8, 192, 168, 0, 1, 192, 168, 1, 1 ( vec![ 53, 1, 2, 54, 4, 192, 168, 0, 1, 51, 4, 0, 0, 0, 60, 58, 4, 0, 0, 0, 30, 59, 4, 0, 0, 0, 52, 1, 4, 255, 255, 255, 0, 3, 4, 192, 168, 0, 1, 6, 4, 192, 168, 0, 1, 6, 4, 192, 168, 1, 1, 6, 4, 192, 1, 1, 1, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], 8, ) } } dhcproto-0.9.0/src/v4/relay.rs000064400000000000000000000321631046102023000142630ustar 00000000000000//! use std::{collections::HashMap, fmt, net::Ipv4Addr}; use crate::{Decodable, Encodable}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Collection of relay agent information /// /// You can create/modify it, then insert into a message opts section /// in [`DhcpOption::RelayAgentInformation`] /// /// ```rust /// use dhcproto::v4::{self, relay::{RelayInfo, RelayAgentInformation}}; /// /// let mut info = RelayAgentInformation::default(); /// info.insert(RelayInfo::LinkSelection("1.2.3.4".parse().unwrap())); /// let mut msg = v4::Message::default(); /// msg.opts_mut() /// .insert(v4::DhcpOption::RelayAgentInformation(info)); /// ``` /// /// [`DhcpOption::RelayAgentInformation`]: crate::v4::DhcpOption::RelayAgentInformation #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct RelayAgentInformation(HashMap); impl RelayAgentInformation { /// Get the data for a particular [`RelayCode`] /// /// [`RelayCode`]: crate::v4::relay::RelayCode pub fn get(&self, code: RelayCode) -> Option<&RelayInfo> { self.0.get(&code) } /// Get the mutable data for a particular [`RelayCode`] /// /// [`RelayCode`]: crate::v4::relay::RelayCode pub fn get_mut(&mut self, code: RelayCode) -> Option<&mut RelayInfo> { self.0.get_mut(&code) } /// remove sub option pub fn remove(&mut self, code: RelayCode) -> Option { self.0.remove(&code) } /// insert a new [`RelayInfo`] /// /// [`RelayInfo`]: crate::v4::relay::RelayInfo pub fn insert(&mut self, info: RelayInfo) -> Option { self.0.insert((&info).into(), info) } /// iterate over entries pub fn iter(&self) -> impl Iterator { self.0.iter() } /// iterate mutably over entries pub fn iter_mut(&mut self) -> impl Iterator { self.0.iter_mut() } /// clear all options pub fn clear(&mut self) { self.0.clear() } /// Returns `true` if there are no options pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Retans only the elements specified by the predicate pub fn retain(&mut self, pred: F) where F: FnMut(&RelayCode, &mut RelayInfo) -> bool, { self.0.retain(pred) } } impl Decodable for RelayAgentInformation { fn decode(d: &mut crate::Decoder<'_>) -> super::DecodeResult { let mut opts = HashMap::new(); while let Ok(opt) = RelayInfo::decode(d) { opts.insert(RelayCode::from(&opt), opt); } Ok(RelayAgentInformation(opts)) } } impl Encodable for RelayAgentInformation { fn encode(&self, e: &mut crate::Encoder<'_>) -> super::EncodeResult<()> { self.0.iter().try_for_each(|(_, info)| info.encode(e)) } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub enum RelayInfo { /// 1 - AgentCircuitId(Vec), /// 2 - AgentRemoteId(Vec), /// 4 - DocsisDeviceClass(u32), /// 5 - LinkSelection(Ipv4Addr), /// 6 - SubscriberId(Vec), /// 10 - RelayAgentFlags(RelayFlags), /// 11 - ServerIdentifierOverride(Ipv4Addr), Unknown(UnknownInfo), // TODO: not tackling this at the moment // 7 - // RadiusAttributes, // 8 - // 9 // VendorSpecificInformation(Vec), // Authentication(Authentication), // 151 - // VirtualSubnet(VirtualSubnet), // 152 // VirtualSubnetControl(u8), } impl Decodable for RelayInfo { fn decode(d: &mut crate::Decoder<'_>) -> super::DecodeResult { use RelayInfo::*; // read the code first, determines the variant Ok(match d.read_u8()?.into() { RelayCode::AgentCircuitId => { let len = d.read_u8()? as usize; let data = d.read_slice(len)?.to_vec(); AgentCircuitId(data) } RelayCode::AgentRemoteId => { let len = d.read_u8()? as usize; let data = d.read_slice(len)?.to_vec(); AgentCircuitId(data) } RelayCode::DocsisDeviceClass => { let _ = d.read_u8()?; let device_id = d.read_u32()?; DocsisDeviceClass(device_id) } RelayCode::LinkSelection => { let len = d.read_u8()? as usize; LinkSelection(d.read_ipv4(len)?) } RelayCode::SubscriberId => { let len = d.read_u8()? as usize; let data = d.read_slice(len)?.to_vec(); SubscriberId(data) } RelayCode::RelayAgentFlags => { let _len = d.read_u8()?; let flags = d.read_u8()?; RelayAgentFlags(flags.into()) } RelayCode::ServerIdentifierOverride => { let len = d.read_u8()? as usize; ServerIdentifierOverride(d.read_ipv4(len)?) } // we have codes for these but not full type definitions yet code @ (RelayCode::Authentication | RelayCode::VirtualSubnet | RelayCode::VirtualSubnetControl | RelayCode::RadiusAttributes | RelayCode::VendorSpecificInformation) => { let length = d.read_u8()?; let bytes = d.read_slice(length as usize)?.to_vec(); Unknown(UnknownInfo { code: code.into(), data: bytes, }) } // not yet implemented RelayCode::Unknown(code) => { let length = d.read_u8()?; let bytes = d.read_slice(length as usize)?.to_vec(); Unknown(UnknownInfo { code, data: bytes }) } }) } } impl Encodable for RelayInfo { fn encode(&self, e: &mut crate::Encoder<'_>) -> super::EncodeResult<()> { use RelayInfo::*; let code: RelayCode = self.into(); e.write_u8(code.into())?; match self { AgentCircuitId(id) | AgentRemoteId(id) | SubscriberId(id) => { // length of bytes stored in Vec e.write_u8(id.len() as u8)?; e.write_slice(id)? } DocsisDeviceClass(n) => { e.write_u8(4)?; e.write_u32(*n)? } LinkSelection(addr) | ServerIdentifierOverride(addr) => { e.write_u8(4)?; e.write_u32((*addr).into())? } RelayAgentFlags(flags) => { e.write_u8(1)?; e.write_u8((*flags).into())? } // not yet implemented Unknown(opt) => { // length of bytes stored in Vec e.write_u8(opt.data.len() as u8)?; e.write_slice(&opt.data)? } }; Ok(()) } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Copy, Default, Clone, PartialEq, Eq)] pub struct RelayFlags(u8); impl fmt::Debug for RelayFlags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RelayFlags") .field("unicast", &self.unicast()) .finish() } } impl fmt::Display for RelayFlags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } impl RelayFlags { /// Create new RelayFlags from u8 pub fn new(n: u8) -> Self { Self(n) } /// get the status of the unicast flag pub fn unicast(&self) -> bool { (self.0 & 0x80) >> (u8::BITS - 1) == 1 } /// set the unicast bit, returns a new Flags pub fn set_unicast(mut self) -> Self { self.0 |= 0x80; self } } impl From for RelayFlags { fn from(n: u8) -> Self { Self(n) } } impl From for u8 { fn from(f: RelayFlags) -> Self { f.0 } } /// An as-of-yet unimplemented relay info #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct UnknownInfo { code: u8, data: Vec, } impl UnknownInfo { pub fn new(code: RelayCode, data: Vec) -> Self { Self { code: code.into(), data, } } /// return the relay code pub fn code(&self) -> RelayCode { self.code.into() } /// return the data for this code pub fn data(&self) -> &[u8] { &self.data } /// take ownership and return the parts of this pub fn into_parts(self) -> (RelayCode, Vec) { (self.code.into(), self.data) } } /// relay code, represented as a u8 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum RelayCode { AgentCircuitId, AgentRemoteId, DocsisDeviceClass, LinkSelection, SubscriberId, RadiusAttributes, Authentication, VendorSpecificInformation, RelayAgentFlags, ServerIdentifierOverride, VirtualSubnet, VirtualSubnetControl, /// unknown/unimplemented message type Unknown(u8), } impl From for RelayCode { fn from(n: u8) -> Self { use RelayCode::*; match n { 1 => AgentCircuitId, 2 => AgentRemoteId, 4 => DocsisDeviceClass, 5 => LinkSelection, 6 => SubscriberId, 7 => RadiusAttributes, 8 => Authentication, 9 => VendorSpecificInformation, 10 => RelayAgentFlags, 11 => ServerIdentifierOverride, 151 => VirtualSubnet, 152 => VirtualSubnetControl, _ => Unknown(n), } } } impl From for u8 { fn from(code: RelayCode) -> Self { use RelayCode::*; match code { AgentCircuitId => 1, AgentRemoteId => 2, DocsisDeviceClass => 4, LinkSelection => 5, SubscriberId => 6, RadiusAttributes => 7, Authentication => 8, VendorSpecificInformation => 9, RelayAgentFlags => 10, ServerIdentifierOverride => 11, VirtualSubnet => 151, VirtualSubnetControl => 152, Unknown(n) => n, } } } impl From<&RelayInfo> for RelayCode { fn from(info: &RelayInfo) -> Self { use RelayInfo::*; match info { AgentCircuitId(_) => RelayCode::AgentCircuitId, AgentRemoteId(_) => RelayCode::AgentRemoteId, DocsisDeviceClass(_) => RelayCode::DocsisDeviceClass, LinkSelection(_) => RelayCode::LinkSelection, SubscriberId(_) => RelayCode::SubscriberId, RelayAgentFlags(_) => RelayCode::RelayAgentFlags, ServerIdentifierOverride(_) => RelayCode::ServerIdentifierOverride, Unknown(unknown) => RelayCode::Unknown(unknown.code), } } } #[cfg(test)] mod tests { use super::*; type Result = std::result::Result>; #[test] fn test_unicast() { let flag = RelayFlags::default(); assert_eq!(flag.0, 0); let flag = flag.set_unicast(); assert_eq!(flag.0, 0x80); let flag = RelayFlags::new(0x00).set_unicast(); assert_eq!(flag.0, 0x80); assert!(flag.unicast()); } fn test_opt(opt: RelayInfo, actual: Vec) -> Result<()> { let mut out = vec![]; let mut enc = crate::Encoder::new(&mut out); opt.encode(&mut enc)?; println!("{:?}", enc.buffer()); assert_eq!(out, actual); let buf = RelayInfo::decode(&mut crate::Decoder::new(&out))?; assert_eq!(buf, opt); Ok(()) } #[test] fn test_ip() -> Result<()> { test_opt( RelayInfo::LinkSelection("192.168.0.1".parse::().unwrap()), vec![5, 4, 192, 168, 0, 1], )?; Ok(()) } #[test] fn test_str() -> Result<()> { test_opt( RelayInfo::AgentCircuitId(vec![0, 1, 2, 3, 4]), vec![1, 5, 0, 1, 2, 3, 4], )?; Ok(()) } #[test] fn test_flags() -> Result<()> { test_opt( RelayInfo::RelayAgentFlags(RelayFlags::default().set_unicast()), vec![10, 1, 0x80], )?; Ok(()) } #[test] fn test_unknown() -> Result<()> { test_opt( RelayInfo::Unknown(UnknownInfo::new(RelayCode::Unknown(149), vec![1, 2, 3, 4])), vec![149, 4, 1, 2, 3, 4], )?; Ok(()) } } dhcproto-0.9.0/src/v6/duid.rs000064400000000000000000000042171046102023000140750ustar 00000000000000use std::net::Ipv6Addr; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::{v4::HType, Encoder}; /// Duid helper type #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub struct Duid(Vec); // TODO: define specific duid types impl Duid { /// new DUID link layer address with time pub fn link_layer_time(htype: HType, time: u32, addr: Ipv6Addr) -> Self { let mut buf = Vec::new(); let mut e = Encoder::new(&mut buf); e.write_u16(1).unwrap(); // duid type e.write_u16(u8::from(htype) as u16).unwrap(); e.write_u32(time).unwrap(); e.write_u128(addr.into()).unwrap(); Self(buf) } /// new DUID enterprise number pub fn enterprise(enterprise: u32, id: &[u8]) -> Self { let mut buf = Vec::new(); let mut e = Encoder::new(&mut buf); e.write_u16(2).unwrap(); // duid type e.write_u32(enterprise).unwrap(); e.write_slice(id).unwrap(); Self(buf) } /// new link layer DUID pub fn link_layer(htype: HType, addr: Ipv6Addr) -> Self { let mut buf = Vec::new(); let mut e = Encoder::new(&mut buf); e.write_u16(3).unwrap(); // duid type e.write_u16(u8::from(htype) as u16).unwrap(); e.write_u128(addr.into()).unwrap(); Self(buf) } /// new DUID-UUID /// `uuid` must be 16 bytes long pub fn uuid(uuid: &[u8]) -> Self { assert!(uuid.len() == 16); let mut buf = Vec::new(); let mut e = Encoder::new(&mut buf); e.write_u16(4).unwrap(); // duid type e.write_slice(uuid).unwrap(); Self(buf) } /// create a DUID of unknown type pub fn unknown(duid: &[u8]) -> Self { Self(duid.to_vec()) } /// total length of contained DUID pub fn len(&self) -> usize { self.0.len() } /// is contained DUID empty pub fn is_empty(&self) -> bool { self.len() == 0 } } impl AsRef<[u8]> for Duid { fn as_ref(&self) -> &[u8] { &self.0 } } impl From> for Duid { fn from(v: Vec) -> Self { Self(v) } } dhcproto-0.9.0/src/v6/mod.rs000064400000000000000000000447541046102023000137410ustar 00000000000000//! # DHCPv6 //! //! This module provides types and utility functions for encoding/decoding a DHCPv4 message. //! //! ## Example - constructing messages //! //! ```rust //! # fn main() -> Result<(), Box> { //! use dhcproto::{v6, Encodable, Encoder}; //! // arbitrary DUID //! let duid = vec![ //! 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, //! ]; //! // construct a new Message with a random xid //! let mut msg = v6::Message::new(v6::MessageType::Solicit); //! // set an option //! msg.opts_mut() //! .insert(v6::DhcpOption::ClientId(duid)); //! //! // now encode to bytes //! let mut buf = Vec::new(); //! let mut e = Encoder::new(&mut buf); //! msg.encode(&mut e)?; //! //! // buf now has the contents of the encoded DHCP message //! # Ok(()) } //! ``` //! //! ## Example - encoding/decoding messages //! //! ```rust //! # fn solicit() -> Vec { //! # vec![ //! # 0x01, 0x10, 0x08, 0x74, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x39, //! # 0xcf, 0x88, 0x08, 0x00, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, //! # 0x00, 0x18, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x0c, 0x27, 0xfe, //! # 0x8f, 0x95, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x15, 0x18, //! # ] //! # } //! # fn main() -> Result<(), Box> { //! use dhcproto::{v6::Message, Decoder, Decodable, Encoder, Encodable}; //! // example message //! let solicit = solicit(); //! // decode //! let msg = Message::decode(&mut Decoder::new(&solicit))?; //! // now encode //! let mut buf = Vec::new(); //! let mut e = Encoder::new(&mut buf); //! msg.encode(&mut e)?; //! //! assert_eq!(solicit, buf); //! # Ok(()) } //! ``` //! pub mod duid; mod option_codes; mod options; mod oro_codes; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use std::{convert::TryInto, fmt, net::Ipv6Addr}; // re-export submodules from v6 pub use self::option_codes::*; pub use self::options::*; pub use self::oro_codes::*; pub use crate::{ decoder::{Decodable, Decoder}, encoder::{Encodable, Encoder}, error::*, }; /// default dhcpv6 server port pub const SERVER_PORT: u16 = 547; /// default dhcpv6 client port pub const CLIENT_PORT: u16 = 546; /// See RFC 8415 for updated DHCPv6 info /// [DHCP for Ipv6](https://datatracker.ietf.org/doc/html/rfc8415) /// /// All DHCP messages sent between clients and servers share an identical /// fixed-format header and a variable-format area for options. /// /// All values in the message header and in options are in network byte /// order. /// /// Options are stored serially in the "options" field, with no padding /// between the options. Options are byte-aligned but are not aligned in /// any other way (such as on 2-byte or 4-byte boundaries). /// /// The following diagram illustrates the format of DHCP messages sent /// between clients and servers: /// /// ```text /// 0 1 2 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | msg-type | transaction-id | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | /// . options . /// . (variable number and length) . /// | | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// msg-type Identifies the DHCP message type; the /// available message types are listed in /// Section 7.3. A 1-octet field. /// /// transaction-id The transaction ID for this message exchange. /// A 3-octet field. /// /// options Options carried in this message; options are /// described in Section 21. A variable-length /// field (4 octets less than the size of the /// message). /// ``` #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub struct Message { /// message type /// msg_type: MessageType, /// transaction id /// trns id must be the same for all messages in a DHCP transaction /// xid: [u8; 3], /// Options /// opts: DhcpOptions, } impl Default for Message { fn default() -> Self { Self { msg_type: MessageType::Solicit, xid: rand::random(), opts: DhcpOptions::new(), } } } impl Message { /// returns a new `Message` with a random xid and empty opt section pub fn new(msg_type: MessageType) -> Self { Self { msg_type, ..Self::default() } } /// returns a new `Message` with a given xid and message type and empty opt section pub fn new_with_id(msg_type: MessageType, xid: [u8; 3]) -> Self { Self { msg_type, xid, ..Self::default() } } /// Get the message's message type. pub fn msg_type(&self) -> MessageType { self.msg_type } /// Set message type pub fn set_msg_type(&mut self, msg_type: MessageType) -> &mut Self { self.msg_type = msg_type; self } /// Get the message's transaction id. pub fn xid(&self) -> [u8; 3] { self.xid } /// Get the msgs transaction id as a number pub fn xid_num(&self) -> u32 { u32::from_be_bytes([0, self.xid[0], self.xid[1], self.xid[2]]) } /// Set transaction id pub fn set_xid(&mut self, xid: [u8; 3]) -> &mut Self { self.xid = xid; self } /// Set transaction id from u32, will only use last 3 bytes pub fn set_xid_num(&mut self, xid: u32) -> &mut Self { let arr = xid.to_be_bytes(); self.xid = arr[1..=3] .try_into() .expect("a u32 has 4 bytes so this shouldn't fail"); self } /// Get a reference to the message's options. pub fn opts(&self) -> &DhcpOptions { &self.opts } /// Set DHCP opts pub fn set_opts(&mut self, opts: DhcpOptions) -> &mut Self { self.opts = opts; self } /// Get a mutable reference to the message's options. pub fn opts_mut(&mut self) -> &mut DhcpOptions { &mut self.opts } } /// DHCPv6 message types /// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum MessageType { // RFC 3315 /// client solicit - Solicit, /// server advertise - Advertise, /// request - Request, /// confirm - Confirm, /// renew - Renew, /// rebind - Rebind, /// reply - Reply, /// release message type - Release, /// decline - Decline, /// reconfigure - Reconfigure, /// information request - InformationRequest, /// relay forward - RelayForw, /// relay reply - RelayRepl, // RFC 5007 /// lease query - LeaseQuery, /// lease query reply - LeaseQueryReply, // RFC 5460 /// lease query done - LeaseQueryDone, /// lease query data - LeaseQueryData, // RFC 6977 /// reconfigure request - ReconfigureRequest, /// reconfigure reply - ReconfigureReply, // RFC 7341 /// dhcpv4 query - DHCPv4Query, /// dhcpv4 response - DHCPv4Response, /// unknown/unimplemented message type Unknown(u8), } impl From for MessageType { fn from(n: u8) -> Self { use MessageType::*; match n { // RFC 3315 1 => Solicit, 2 => Advertise, 3 => Request, 4 => Confirm, 5 => Renew, 6 => Rebind, 7 => Reply, 8 => Release, 9 => Decline, 10 => Reconfigure, 11 => InformationRequest, 12 => RelayForw, 13 => RelayRepl, // RFC 5007 14 => LeaseQuery, 15 => LeaseQueryReply, // RFC 5460 16 => LeaseQueryDone, 17 => LeaseQueryData, // RFC 6977 18 => ReconfigureRequest, 19 => ReconfigureReply, // RFC 7341 20 => DHCPv4Query, 21 => DHCPv4Response, n => Unknown(n), } } } impl From for u8 { fn from(m: MessageType) -> Self { use MessageType::*; match m { // RFC 3315 Solicit => 1, Advertise => 2, Request => 3, Confirm => 4, Renew => 5, Rebind => 6, Reply => 7, Release => 8, Decline => 9, Reconfigure => 10, InformationRequest => 11, RelayForw => 12, RelayRepl => 13, // RFC 5007 LeaseQuery => 14, LeaseQueryReply => 15, // RFC 5460 LeaseQueryDone => 16, LeaseQueryData => 17, // RFC 6977 ReconfigureRequest => 18, ReconfigureReply => 19, // RFC 7341 DHCPv4Query => 20, DHCPv4Response => 21, Unknown(n) => n, } } } impl Decodable for Message { fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { Ok(Message { msg_type: decoder.read_u8()?.into(), xid: decoder.read::<3>()?, opts: DhcpOptions::decode(decoder)?, }) } } impl Encodable for Message { fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { e.write_u8(self.msg_type.into())?; e.write(self.xid)?; self.opts.encode(e)?; Ok(()) } } impl fmt::Display for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Message") .field("xid", &self.xid_num()) .field("msg_type", &self.msg_type()) .field("opts", &self.opts()) .finish() } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub struct RelayMessage { /// message type /// msg_type: MessageType, /// hop count /// hop_count: u8, /// link address /// link_addr: Ipv6Addr, /// peer address /// peer_addr: Ipv6Addr, /// Options /// opts: DhcpOptions, } impl RelayMessage { pub fn msg_type(&self) -> MessageType { self.msg_type } pub fn hop_count(&self) -> u8 { self.hop_count } pub fn link_addr(&self) -> Ipv6Addr { self.link_addr } pub fn peer_addr(&self) -> Ipv6Addr { self.peer_addr } /// Get a reference to the message's options. pub fn opts(&self) -> &DhcpOptions { &self.opts } /// Set DHCP opts pub fn set_opts(&mut self, opts: DhcpOptions) -> &mut Self { self.opts = opts; self } /// Get a mutable reference to the message's options. pub fn opts_mut(&mut self) -> &mut DhcpOptions { &mut self.opts } } impl Decodable for RelayMessage { fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { Ok(Self { msg_type: decoder.read_u8()?.into(), hop_count: decoder.read_u8()?, link_addr: decoder.read::<16>()?.into(), peer_addr: decoder.read::<16>()?.into(), opts: DhcpOptions::decode(decoder)?, }) } } impl Encodable for RelayMessage { fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { e.write_u8(self.msg_type.into())?; e.write_u8(self.hop_count)?; e.write_slice(&self.link_addr.octets())?; e.write_slice(&self.peer_addr.octets())?; self.opts.encode(e)?; Ok(()) } } impl fmt::Display for RelayMessage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RelayMessage") .field("msg_type", &self.msg_type()) .field("hop_count", &self.hop_count()) .field("link_addr", &self.link_addr()) .field("peer_addr", &self.peer_addr()) .field("opts", &self.opts()) .finish() } } #[cfg(test)] mod tests { use super::*; type Result = std::result::Result>; fn decode_ipv6(input: Vec, mtype: MessageType) -> Result<()> { // decode let msg = Message::decode(&mut Decoder::new(&input))?; dbg!(&msg); assert_eq!(mtype, msg.msg_type); // now encode let mut buf = Vec::new(); let mut e = Encoder::new(&mut buf); msg.encode(&mut e)?; println!("{buf:?}"); println!("{input:?}"); // no PAD bytes or hashmap with ipv6 so the lens will be exact assert_eq!(buf.len(), input.len()); // decode again let res = Message::decode(&mut Decoder::new(&buf))?; // check Messages are equal after decoding/encoding assert_eq!(msg, res); Ok(()) } #[test] fn decode_solicit() -> Result<()> { decode_ipv6(solicit(), MessageType::Solicit)?; Ok(()) } #[test] fn decode_advertise() -> Result<()> { decode_ipv6(advertise(), MessageType::Advertise)?; Ok(()) } #[test] fn decode_request() -> Result<()> { decode_ipv6(request(), MessageType::Request)?; Ok(()) } #[test] fn decode_reply() -> Result<()> { decode_ipv6(reply(), MessageType::Reply)?; Ok(()) } #[test] fn xid_num() { let mut msg = Message::default(); msg.set_xid_num(16_777_215); assert_eq!(msg.xid_num(), 16_777_215); msg.set_xid_num(16_777_000); assert_eq!(msg.xid_num(), 16_777_000); msg.set_xid_num(8); assert_eq!(msg.xid_num(), 8); } #[cfg(feature = "serde")] #[test] fn test_json_v6() -> Result<()> { let msg = Message::decode(&mut Decoder::new(&solicit()))?; let s = serde_json::to_string_pretty(&msg)?; println!("{s}"); let other = serde_json::from_str(&s)?; assert_eq!(msg, other); Ok(()) } fn solicit() -> Vec { vec![ 0x01, 0x10, 0x08, 0x74, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x39, 0xcf, 0x88, 0x08, 0x00, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, 0x00, 0x18, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x0c, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x15, 0x18, ] } fn advertise() -> Vec { vec![ 0x02, 0x10, 0x08, 0x74, 0x00, 0x19, 0x00, 0x29, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x19, 0x00, 0x00, 0x11, 0x94, 0x00, 0x00, 0x1c, 0x20, 0x40, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x39, 0xcf, 0x88, 0x08, 0x00, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x38, 0x25, 0xe8, 0x08, 0x00, 0x27, 0xd4, 0x10, 0xbb, ] } fn request() -> Vec { vec![ 0x03, 0x49, 0x17, 0x4e, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x39, 0xcf, 0x88, 0x08, 0x00, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x38, 0x25, 0xe8, 0x08, 0x00, 0x27, 0xd4, 0x10, 0xbb, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, 0x00, 0x18, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x29, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x15, 0x18, 0x00, 0x1a, 0x00, 0x19, 0x00, 0x00, 0x1c, 0x20, 0x00, 0x00, 0x1d, 0x4c, 0x40, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] } fn reply() -> Vec { vec![ 0x07, 0x49, 0x17, 0x4e, 0x00, 0x19, 0x00, 0x29, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x19, 0x00, 0x00, 0x11, 0x94, 0x00, 0x00, 0x1c, 0x20, 0x40, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x39, 0xcf, 0x88, 0x08, 0x00, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x38, 0x25, 0xe8, 0x08, 0x00, 0x27, 0xd4, 0x10, 0xbb, ] } } dhcproto-0.9.0/src/v6/option_codes.rs000064400000000000000000000323311046102023000156330ustar 00000000000000use crate::v6::{options::DhcpOption, UnknownOption}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// option code type #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum OptionCode { ClientId, ServerId, IANA, IATA, IAAddr, ORO, Preference, ElapsedTime, RelayMsg, Authentication, ServerUnicast, StatusCode, RapidCommit, UserClass, VendorClass, VendorOpts, InterfaceId, ReconfMsg, ReconfAccept, SipServerD, SipServerA, DomainNameServers, DomainSearchList, IAPD, IAPrefix, NisServers, NispServers, NisDomainName, NispDomainName, SntpServers, InformationRefreshTime, BcmcsServerD, BcmcsServerA, GeoconfCivic, RemoteId, SubscriberId, ClientFqdn, PanaAgent, NewPosixTimezone, NewTzdbTimezone, ERO, LqQuery, ClientData, CltTime, LqRelayData, LqClientLink, Mip6Hnidf, Mip6Vdinf, V6Lost, CapwapAcV6, RelayId, Ipv6AddressMoS, Ipv6FQDNMoS, NtpServer, V6AccessDomain, SipUaCsList, OptBootfileUrl, OptBootfileParam, ClientArchType, Nii, Geolocation, AftrName, ErpLocalDomainName, Rsoo, PdExclude, Vss, Mip6Idinf, Mip6Udinf, Mip6Hnp, Mip6Haa, Mip6Haf, RdnssSelection, KrbPrincipalName, KrbRealmName, KrbDefaultRealmName, KrbKdc, ClientLinklayerAddr, LinkAddress, Radius, SolMaxRt, InfMaxRt, Addrsel, AddrselTable, V6PcpServer, Dhcpv4Msg, Dhcp4ODhcp6Server, S46Rule, S46Br, S46Dmr, S46V4v6bind, S46Portparams, S46ContMape, S46ContMapt, S46ContLw, _4Rd, _4RdMapRule, _4RdNonMapRule, LqBaseTime, LqStartTime, LqEndTime, DhcpCaptivePortal, MplParameters, AniAtt, AniNetworkName, AniApName, AniApBssid, AniOperatorId, AniOperatorRealm, S46Priority, MudUrlV6, V6Prefix64, FBindingStatus, FConnectFlags, Fdnsremovalinfo, FDNSHostName, FDNSZoneName, Fdnsflags, Fexpirationtime, FMaxUnackedBndupd, FMclt, FPartnerLifetime, FPartnerLifetimeSent, FPartnerDownTime, FPartnerRawCltTime, FProtocolVersion, FKeepaliveTime, FReconfigureData, FRelationshipName, FServerFlags, FServerState, FStartTimeOfState, FStateExpirationTime, RelayPort, Ipv6AddressANDSF, Unknown(u16), } impl From for u16 { fn from(opt: OptionCode) -> Self { use OptionCode::*; match opt { ClientId => 1, ServerId => 2, IANA => 3, IATA => 4, IAAddr => 5, ORO => 6, Preference => 7, ElapsedTime => 8, RelayMsg => 9, Authentication => 11, ServerUnicast => 12, StatusCode => 13, RapidCommit => 14, UserClass => 15, VendorClass => 16, VendorOpts => 17, InterfaceId => 18, ReconfMsg => 19, ReconfAccept => 20, SipServerD => 21, SipServerA => 22, DomainNameServers => 23, DomainSearchList => 24, IAPD => 25, IAPrefix => 26, NisServers => 27, NispServers => 28, NisDomainName => 29, NispDomainName => 30, SntpServers => 31, InformationRefreshTime => 32, BcmcsServerD => 33, BcmcsServerA => 34, GeoconfCivic => 36, RemoteId => 37, SubscriberId => 38, ClientFqdn => 39, PanaAgent => 40, NewPosixTimezone => 41, NewTzdbTimezone => 42, ERO => 43, LqQuery => 44, ClientData => 45, CltTime => 46, LqRelayData => 47, LqClientLink => 48, Mip6Hnidf => 49, Mip6Vdinf => 50, V6Lost => 51, CapwapAcV6 => 52, RelayId => 53, Ipv6AddressMoS => 54, Ipv6FQDNMoS => 55, NtpServer => 56, V6AccessDomain => 57, SipUaCsList => 58, OptBootfileUrl => 59, OptBootfileParam => 60, ClientArchType => 61, Nii => 62, Geolocation => 63, AftrName => 64, ErpLocalDomainName => 65, Rsoo => 66, PdExclude => 67, Vss => 68, Mip6Idinf => 69, Mip6Udinf => 70, Mip6Hnp => 71, Mip6Haa => 72, Mip6Haf => 73, RdnssSelection => 74, KrbPrincipalName => 75, KrbRealmName => 76, KrbDefaultRealmName => 77, KrbKdc => 78, ClientLinklayerAddr => 79, LinkAddress => 80, Radius => 81, SolMaxRt => 82, InfMaxRt => 83, Addrsel => 84, AddrselTable => 85, V6PcpServer => 86, Dhcpv4Msg => 87, Dhcp4ODhcp6Server => 88, S46Rule => 89, S46Br => 90, S46Dmr => 91, S46V4v6bind => 92, S46Portparams => 93, S46ContMape => 94, S46ContMapt => 95, S46ContLw => 96, _4Rd => 97, _4RdMapRule => 98, _4RdNonMapRule => 99, LqBaseTime => 100, LqStartTime => 101, LqEndTime => 102, DhcpCaptivePortal => 103, MplParameters => 104, AniAtt => 105, AniNetworkName => 106, AniApName => 107, AniApBssid => 108, AniOperatorId => 109, AniOperatorRealm => 110, S46Priority => 111, MudUrlV6 => 112, V6Prefix64 => 113, FBindingStatus => 114, FConnectFlags => 115, Fdnsremovalinfo => 116, FDNSHostName => 117, FDNSZoneName => 118, Fdnsflags => 119, Fexpirationtime => 120, FMaxUnackedBndupd => 121, FMclt => 122, FPartnerLifetime => 123, FPartnerLifetimeSent => 124, FPartnerDownTime => 125, FPartnerRawCltTime => 126, FProtocolVersion => 127, FKeepaliveTime => 128, FReconfigureData => 129, FRelationshipName => 130, FServerFlags => 131, FServerState => 132, FStartTimeOfState => 133, FStateExpirationTime => 134, RelayPort => 135, Ipv6AddressANDSF => 143, Unknown(n) => n, } } } impl From for OptionCode { fn from(n: u16) -> Self { use OptionCode::*; match n { 1 => ClientId, 2 => ServerId, 3 => IANA, 4 => IATA, 5 => IAAddr, 6 => ORO, 7 => Preference, 8 => ElapsedTime, 9 => RelayMsg, 11 => Authentication, 12 => ServerUnicast, 13 => StatusCode, 14 => RapidCommit, 15 => UserClass, 16 => VendorClass, 17 => VendorOpts, 18 => InterfaceId, 19 => ReconfMsg, 20 => ReconfAccept, 21 => SipServerD, 22 => SipServerA, 23 => DomainNameServers, 24 => DomainSearchList, 25 => IAPD, 26 => IAPrefix, 27 => NisServers, 28 => NispServers, 29 => NisDomainName, 30 => NispDomainName, 31 => SntpServers, 32 => InformationRefreshTime, 33 => BcmcsServerD, 34 => BcmcsServerA, 36 => GeoconfCivic, 37 => RemoteId, 38 => SubscriberId, 39 => ClientFqdn, 40 => PanaAgent, 41 => NewPosixTimezone, 42 => NewTzdbTimezone, 43 => ERO, 44 => LqQuery, 45 => ClientData, 46 => CltTime, 47 => LqRelayData, 48 => LqClientLink, 49 => Mip6Hnidf, 50 => Mip6Vdinf, 51 => V6Lost, 52 => CapwapAcV6, 53 => RelayId, 54 => Ipv6AddressMoS, 55 => Ipv6FQDNMoS, 56 => NtpServer, 57 => V6AccessDomain, 58 => SipUaCsList, 59 => OptBootfileUrl, 60 => OptBootfileParam, 61 => ClientArchType, 62 => Nii, 63 => Geolocation, 64 => AftrName, 65 => ErpLocalDomainName, 66 => Rsoo, 67 => PdExclude, 68 => Vss, 69 => Mip6Idinf, 70 => Mip6Udinf, 71 => Mip6Hnp, 72 => Mip6Haa, 73 => Mip6Haf, 74 => RdnssSelection, 75 => KrbPrincipalName, 76 => KrbRealmName, 77 => KrbDefaultRealmName, 78 => KrbKdc, 79 => ClientLinklayerAddr, 80 => LinkAddress, 81 => Radius, 82 => SolMaxRt, 83 => InfMaxRt, 84 => Addrsel, 85 => AddrselTable, 86 => V6PcpServer, 87 => Dhcpv4Msg, 88 => Dhcp4ODhcp6Server, 89 => S46Rule, 90 => S46Br, 91 => S46Dmr, 92 => S46V4v6bind, 93 => S46Portparams, 94 => S46ContMape, 95 => S46ContMapt, 96 => S46ContLw, 97 => _4Rd, 98 => _4RdMapRule, 99 => _4RdNonMapRule, 100 => LqBaseTime, 101 => LqStartTime, 102 => LqEndTime, 103 => DhcpCaptivePortal, 104 => MplParameters, 105 => AniAtt, 106 => AniNetworkName, 107 => AniApName, 108 => AniApBssid, 109 => AniOperatorId, 110 => AniOperatorRealm, 111 => S46Priority, 112 => MudUrlV6, 113 => V6Prefix64, 114 => FBindingStatus, 115 => FConnectFlags, 116 => Fdnsremovalinfo, 117 => FDNSHostName, 118 => FDNSZoneName, 119 => Fdnsflags, 120 => Fexpirationtime, 121 => FMaxUnackedBndupd, 122 => FMclt, 123 => FPartnerLifetime, 124 => FPartnerLifetimeSent, 125 => FPartnerDownTime, 126 => FPartnerRawCltTime, 127 => FProtocolVersion, 128 => FKeepaliveTime, 129 => FReconfigureData, 130 => FRelationshipName, 131 => FServerFlags, 132 => FServerState, 133 => FStartTimeOfState, 134 => FStateExpirationTime, 135 => RelayPort, 143 => Ipv6AddressANDSF, _ => Unknown(n), } } } impl PartialOrd for OptionCode { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for OptionCode { fn cmp(&self, other: &Self) -> std::cmp::Ordering { u16::from(*self).cmp(&u16::from(*other)) } } impl From<&DhcpOption> for OptionCode { fn from(opt: &DhcpOption) -> Self { use DhcpOption::*; match opt { ClientId(_) => OptionCode::ClientId, ServerId(_) => OptionCode::ServerId, IANA(_) => OptionCode::IANA, IATA(_) => OptionCode::IATA, IAAddr(_) => OptionCode::IAAddr, ORO(_) => OptionCode::ORO, Preference(_) => OptionCode::Preference, ElapsedTime(_) => OptionCode::ElapsedTime, RelayMsg(_) => OptionCode::RelayMsg, Authentication(_) => OptionCode::Authentication, ServerUnicast(_) => OptionCode::ServerUnicast, StatusCode(_) => OptionCode::StatusCode, RapidCommit => OptionCode::RapidCommit, UserClass(_) => OptionCode::UserClass, VendorClass(_) => OptionCode::VendorClass, VendorOpts(_) => OptionCode::VendorOpts, InterfaceId(_) => OptionCode::InterfaceId, ReconfMsg(_) => OptionCode::ReconfMsg, ReconfAccept => OptionCode::ReconfAccept, DomainNameServers(_) => OptionCode::DomainNameServers, DomainSearchList(_) => OptionCode::DomainSearchList, IAPD(_) => OptionCode::IAPD, IAPrefix(_) => OptionCode::IAPrefix, InformationRefreshTime(_) => OptionCode::InformationRefreshTime, // SolMaxRt(_) => OptionCode::SolMaxRt, // InfMaxRt(_) => OptionCode::InfMaxRt, // LqQuery(_) => OptionCode::LqQuery, // ClientData(_) => OptionCode::ClientData, // CltTime(_) => OptionCode::CltTime, // LqRelayData(_) => OptionCode::LqRelayData, // LqClientLink(_) => OptionCode::LqClientLink, // RelayId(_) => OptionCode::RelayId, // LinkAddress(_) => OptionCode::LinkAddress, Unknown(UnknownOption { code, .. }) => OptionCode::Unknown(*code), } } } dhcproto-0.9.0/src/v6/options.rs000064400000000000000000000735421046102023000146520ustar 00000000000000#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use trust_dns_proto::{ rr::Name, serialize::binary::{BinDecodable, BinDecoder, BinEncodable, BinEncoder}, }; use std::{cmp::Ordering, net::Ipv6Addr, ops::RangeInclusive}; use crate::v6::option_codes::OptionCode; use crate::{ decoder::{Decodable, Decoder}, encoder::{Encodable, Encoder}, error::{DecodeResult, EncodeResult}, v6::{MessageType, RelayMessage}, }; // server can send multiple IA_NA options to request multiple addresses // so we must be able to handle multiple of the same option type // // TODO: consider HashMap> /// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct DhcpOptions(Vec); // vec maintains sorted on OptionCode impl DhcpOptions { /// construct empty DhcpOptions pub fn new() -> Self { Self::default() } /// get the first element matching this option code pub fn get(&self, code: OptionCode) -> Option<&DhcpOption> { let first = first(&self.0, |x| OptionCode::from(x).cmp(&code))?; // get_unchecked? self.0.get(first) } /// get all elements matching this option code pub fn get_all(&self, code: OptionCode) -> Option<&[DhcpOption]> { let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&code))?; Some(&self.0[range]) } /// get the first element matching this option code pub fn get_mut(&mut self, code: OptionCode) -> Option<&mut DhcpOption> { let first = first(&self.0, |x| OptionCode::from(x).cmp(&code))?; self.0.get_mut(first) } /// get all elements matching this option code pub fn get_mut_all(&mut self, code: OptionCode) -> Option<&mut [DhcpOption]> { let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&code))?; Some(&mut self.0[range]) } /// remove the first element with a matching option code pub fn remove(&mut self, code: OptionCode) -> Option { let first = first(&self.0, |x| OptionCode::from(x).cmp(&code))?; Some(self.0.remove(first)) } /// remove all elements with a matching option code pub fn remove_all( &mut self, code: OptionCode, ) -> Option + '_> { let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&code))?; Some(self.0.drain(range)) } /// insert a new option into the list of opts pub fn insert(&mut self, opt: DhcpOption) { let i = self.0.partition_point(|x| x < &opt); self.0.insert(i, opt) } /// return a reference to an iterator pub fn iter(&self) -> impl Iterator { self.0.iter() } /// return a mutable ref to an iterator pub fn iter_mut(&mut self) -> impl Iterator { self.0.iter_mut() } } impl IntoIterator for DhcpOptions { type Item = DhcpOption; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl FromIterator for DhcpOptions { fn from_iter>(iter: T) -> Self { let mut opts = iter.into_iter().collect::>(); opts.sort_unstable(); DhcpOptions(opts) } } /// DHCPv6 option types #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub enum DhcpOption { /// 1 - ClientId(Vec), // should duid for this be bytes or string? /// 2 - ServerId(Vec), /// 3 - IANA(IANA), /// 4 - IATA(IATA), /// 5 - IAAddr(IAAddr), /// 6 - ORO(ORO), /// 7 - Preference(u8), /// 8 - /// Elapsed time in millis ElapsedTime(u16), /// 9 - RelayMsg(RelayMessage), /// 11 - Authentication(Authentication), /// 12 - ServerUnicast(Ipv6Addr), /// 13 - StatusCode(StatusCode), /// 14 - RapidCommit, /// 15 - UserClass(UserClass), /// 16 - VendorClass(VendorClass), /// 17 - VendorOpts(VendorOpts), /// 18 - InterfaceId(Vec), /// 19 - ReconfMsg(MessageType), /// 20 - ReconfAccept, /// 23 - DomainNameServers(Vec), /// 24 - DomainSearchList(Vec), /// 25 - IAPD(IAPD), /// 26 - IAPrefix(IAPrefix), InformationRefreshTime(u32), // SolMaxRt(u32), // InfMaxRt(u32), // LqQuery(_), // ClientData(_), // CltTime(_), // LqRelayData(_), // LqClientLink(_), // RelayId(_), // LinkAddress(_), /// An unknown or unimplemented option type Unknown(UnknownOption), } impl PartialOrd for DhcpOption { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for DhcpOption { fn cmp(&self, other: &Self) -> std::cmp::Ordering { OptionCode::from(self).cmp(&OptionCode::from(other)) } } /// wrapper around interface id #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InterfaceId { pub id: String, } /// vendor options #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub struct VendorOpts { pub num: u32, // encapsulated options values pub opts: DhcpOptions, } /// vendor class #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VendorClass { pub num: u32, pub data: Vec>, // each item in data is [len (2 bytes) | data] } /// user class #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct UserClass { pub data: Vec>, // each item in data is [len (2 bytes) | data] } #[inline] fn decode_data(decoder: &'_ mut Decoder<'_>) -> Vec> { let mut data = Vec::new(); while let Ok(len) = decoder.read_u16() { // if we can read the len and the string match decoder.read_slice(len as usize) { Ok(s) => data.push(s.to_vec()), // push, otherwise stop _ => break, } } data } /// Server Unicast #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StatusCode { pub status: Status, // 2 + len pub msg: String, } /// Status code for Server Unicast #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Status { Success, UnspecFail, NoAddrsAvail, NoBinding, NotOnLink, UseMulticast, NoPrefixAvail, UnknownQueryType, MalformedQuery, NotConfigured, NotAllowed, QueryTerminated, DataMissing, CatchUpComplete, NotSupported, TLSConnectionRefused, AddressInUse, ConfigurationConflict, MissingBindingInformation, OutdatedBindingInformation, ServerShuttingDown, DNSUpdateNotSupported, ExcessiveTimeSkew, /// unknown/unimplemented message type Unknown(u16), } impl From for Status { fn from(n: u16) -> Self { use Status::*; match n { 0 => Success, 1 => UnspecFail, 2 => NoAddrsAvail, 3 => NoBinding, 4 => NotOnLink, 5 => UseMulticast, 6 => NoPrefixAvail, 7 => UnknownQueryType, 8 => MalformedQuery, 9 => NotConfigured, 10 => NotAllowed, 11 => QueryTerminated, 12 => DataMissing, 13 => CatchUpComplete, 14 => NotSupported, 15 => TLSConnectionRefused, 16 => AddressInUse, 17 => ConfigurationConflict, 18 => MissingBindingInformation, 19 => OutdatedBindingInformation, 20 => ServerShuttingDown, 21 => DNSUpdateNotSupported, 22 => ExcessiveTimeSkew, _ => Unknown(n), } } } impl From for u16 { fn from(n: Status) -> Self { use Status::*; match n { Success => 0, UnspecFail => 1, NoAddrsAvail => 2, NoBinding => 3, NotOnLink => 4, UseMulticast => 5, NoPrefixAvail => 6, UnknownQueryType => 7, MalformedQuery => 8, NotConfigured => 9, NotAllowed => 10, QueryTerminated => 11, DataMissing => 12, CatchUpComplete => 13, NotSupported => 14, TLSConnectionRefused => 15, AddressInUse => 16, ConfigurationConflict => 17, MissingBindingInformation => 18, OutdatedBindingInformation => 19, ServerShuttingDown => 20, DNSUpdateNotSupported => 21, ExcessiveTimeSkew => 22, Unknown(n) => n, } } } /// Authentication #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Authentication { pub proto: u8, pub algo: u8, pub rdm: u8, pub replay_detection: u64, // 11 + len pub info: Vec, } impl Decodable for Authentication { fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { let len = decoder.buffer().len(); Ok(Authentication { proto: decoder.read_u8()?, algo: decoder.read_u8()?, rdm: decoder.read_u8()?, replay_detection: decoder.read_u64()?, info: decoder.read_slice(len - 11)?.to_vec(), }) } } /// Option Request Option /// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ORO { // 2 * num opts pub opts: Vec, } impl Decodable for ORO { fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { let len = decoder.buffer().len(); Ok(ORO { opts: { decoder .read_slice(len)? .chunks_exact(2) // TODO: use .array_chunks::<2>() when stable .map(|code| OptionCode::from(u16::from_be_bytes([code[0], code[1]]))) .collect() }, }) } } /// Identity Association for Temporary Addresses #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub struct IATA { pub id: u32, // 4 + opts.len() // should this be Vec ? // the RFC suggests it 'encapsulates options' pub opts: DhcpOptions, } impl Decodable for IATA { fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { Ok(IATA { id: decoder.read_u32()?, opts: DhcpOptions::decode(decoder)?, }) } } /// Identity Association for Non-Temporary Addresses #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub struct IANA { pub id: u32, pub t1: u32, pub t2: u32, // 12 + opts.len() pub opts: DhcpOptions, } impl Decodable for IANA { fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { Ok(IANA { id: decoder.read_u32()?, t1: decoder.read_u32()?, t2: decoder.read_u32()?, opts: DhcpOptions::decode(decoder)?, }) } } /// Identity Association Prefix Delegation #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub struct IAPD { pub id: u32, pub t1: u32, pub t2: u32, // 12 + opts.len() pub opts: DhcpOptions, } impl Decodable for IAPD { fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { Ok(IAPD { id: decoder.read_u32()?, t1: decoder.read_u32()?, t2: decoder.read_u32()?, opts: DhcpOptions::decode(decoder)?, }) } } /// Identity Association Prefix Delegation Prefix Option #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub struct IAPrefix { pub preferred_lifetime: u32, pub valid_lifetime: u32, pub prefix_len: u8, pub prefix_ip: Ipv6Addr, // 25 + opts.len() pub opts: DhcpOptions, } impl Decodable for IAPrefix { fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { Ok(IAPrefix { preferred_lifetime: decoder.read_u32()?, valid_lifetime: decoder.read_u32()?, prefix_len: decoder.read_u8()?, prefix_ip: decoder.read::<16>()?.into(), opts: DhcpOptions::decode(decoder)?, }) } } /// Identity Association Address #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub struct IAAddr { pub addr: Ipv6Addr, pub preferred_life: u32, pub valid_life: u32, // 24 + opts.len() // should this be DhcpOptions ? // the RFC suggests it 'encapsulates options' pub opts: DhcpOptions, } impl Decodable for IAAddr { fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { Ok(IAAddr { addr: decoder.read::<16>()?.into(), preferred_life: decoder.read_u32()?, valid_life: decoder.read_u32()?, opts: DhcpOptions::decode(decoder)?, }) } } /// fallback for options not yet implemented #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct UnknownOption { pub(crate) code: u16, pub(crate) data: Vec, } impl UnknownOption { pub fn new(code: OptionCode, data: Vec) -> Self { Self { code: code.into(), data, } } /// return the option code pub fn code(&self) -> OptionCode { self.code.into() } /// return the data for this option pub fn data(&self) -> &[u8] { &self.data } /// consume option into its components pub fn into_parts(self) -> (OptionCode, Vec) { (self.code.into(), self.data) } } impl Decodable for DhcpOptions { fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { let mut opts = Vec::new(); while let Ok(opt) = DhcpOption::decode(decoder) { opts.push(opt); } // sorts by OptionCode opts.sort_unstable(); Ok(DhcpOptions(opts)) } } impl Encodable for DhcpOptions { fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { self.0.iter().try_for_each(|opt| opt.encode(e)) } } impl Decodable for DhcpOption { fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { let code = decoder.read_u16()?.into(); let len = decoder.read_u16()? as usize; Ok(match code { OptionCode::ClientId => DhcpOption::ClientId(decoder.read_slice(len)?.to_vec()), OptionCode::ServerId => DhcpOption::ServerId(decoder.read_slice(len)?.to_vec()), OptionCode::IANA => { let mut dec = Decoder::new(decoder.read_slice(len)?); DhcpOption::IANA(IANA::decode(&mut dec)?) } OptionCode::IATA => { let mut dec = Decoder::new(decoder.read_slice(len)?); DhcpOption::IATA(IATA::decode(&mut dec)?) } OptionCode::IAAddr => { let mut dec = Decoder::new(decoder.read_slice(len)?); DhcpOption::IAAddr(IAAddr::decode(&mut dec)?) } OptionCode::ORO => { let mut dec = Decoder::new(decoder.read_slice(len)?); DhcpOption::ORO(ORO::decode(&mut dec)?) } OptionCode::Preference => DhcpOption::Preference(decoder.read_u8()?), OptionCode::ElapsedTime => DhcpOption::ElapsedTime(decoder.read_u16()?), OptionCode::RelayMsg => { let mut relay_dec = Decoder::new(decoder.read_slice(len)?); DhcpOption::RelayMsg(RelayMessage::decode(&mut relay_dec)?) } OptionCode::Authentication => { let mut dec = Decoder::new(decoder.read_slice(len)?); DhcpOption::Authentication(Authentication::decode(&mut dec)?) } OptionCode::ServerUnicast => DhcpOption::ServerUnicast(decoder.read::<16>()?.into()), OptionCode::StatusCode => DhcpOption::StatusCode(StatusCode { status: decoder.read_u16()?.into(), msg: decoder.read_string(len - 1)?, }), OptionCode::RapidCommit => DhcpOption::RapidCommit, OptionCode::UserClass => { let buf = decoder.read_slice(len)?; DhcpOption::UserClass(UserClass { data: decode_data(&mut Decoder::new(buf)), }) } OptionCode::VendorClass => { let num = decoder.read_u32()?; let buf = decoder.read_slice(len - 4)?; DhcpOption::VendorClass(VendorClass { num, data: decode_data(&mut Decoder::new(buf)), }) } OptionCode::VendorOpts => DhcpOption::VendorOpts(VendorOpts { num: decoder.read_u32()?, opts: { let mut opt_decoder = Decoder::new(decoder.read_slice(len - 4)?); DhcpOptions::decode(&mut opt_decoder)? }, }), OptionCode::InterfaceId => DhcpOption::InterfaceId(decoder.read_slice(len)?.to_vec()), OptionCode::ReconfMsg => DhcpOption::ReconfMsg(decoder.read_u8()?.into()), OptionCode::ReconfAccept => DhcpOption::ReconfAccept, OptionCode::DomainNameServers => { DhcpOption::DomainNameServers(decoder.read_ipv6s(len)?) } OptionCode::IAPD => { let mut dec = Decoder::new(decoder.read_slice(len)?); DhcpOption::IAPD(IAPD::decode(&mut dec)?) } OptionCode::IAPrefix => { let mut dec = Decoder::new(decoder.read_slice(len)?); DhcpOption::IAPrefix(IAPrefix::decode(&mut dec)?) } OptionCode::DomainSearchList => { let mut name_decoder = BinDecoder::new(decoder.read_slice(len)?); let mut names = Vec::new(); while let Ok(name) = Name::read(&mut name_decoder) { names.push(name); } DhcpOption::DomainSearchList(names) } // not yet implemented OptionCode::Unknown(code) => DhcpOption::Unknown(UnknownOption { code, data: decoder.read_slice(len)?.to_vec(), }), _ => DhcpOption::Unknown(UnknownOption { code: code.into(), data: decoder.read_slice(len)?.to_vec(), }), }) } } impl Encodable for DhcpOption { fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { let code: OptionCode = self.into(); e.write_u16(code.into())?; match self { DhcpOption::ClientId(duid) | DhcpOption::ServerId(duid) => { e.write_u16(duid.len() as u16)?; e.write_slice(duid)?; } DhcpOption::IANA(IANA { id, t1, t2, opts }) | DhcpOption::IAPD(IAPD { id, t1, t2, opts }) => { // write len let mut buf = Vec::new(); let mut opt_enc = Encoder::new(&mut buf); opts.encode(&mut opt_enc)?; // buf now has total len e.write_u16(12 + buf.len() as u16)?; // write data e.write_u32(*id)?; e.write_u32(*t1)?; e.write_u32(*t2)?; e.write_slice(&buf)?; } DhcpOption::IATA(IATA { id, opts }) => { // write len let mut buf = Vec::new(); let mut opt_enc = Encoder::new(&mut buf); opts.encode(&mut opt_enc)?; // buf now has total len e.write_u16(4 + buf.len() as u16)?; // data e.write_u32(*id)?; e.write_slice(&buf)?; } DhcpOption::IAAddr(IAAddr { addr, preferred_life, valid_life, opts, }) => { // write len let mut buf = Vec::new(); let mut opt_enc = Encoder::new(&mut buf); opts.encode(&mut opt_enc)?; // buf now has total len e.write_u16(24 + buf.len() as u16)?; // data e.write_u128((*addr).into())?; e.write_u32(*preferred_life)?; e.write_u32(*valid_life)?; e.write_slice(&buf)?; } DhcpOption::ORO(ORO { opts }) => { // write len e.write_u16(2 * opts.len() as u16)?; // data for code in opts { e.write_u16(u16::from(*code))?; } } DhcpOption::Preference(pref) => { e.write_u16(1)?; e.write_u8(*pref)?; } DhcpOption::ElapsedTime(elapsed) => { e.write_u16(2)?; e.write_u16(*elapsed)?; } DhcpOption::RelayMsg(msg) => { let mut buf = Vec::new(); let mut relay_enc = Encoder::new(&mut buf); msg.encode(&mut relay_enc)?; e.write_u16(buf.len() as u16)?; e.write_slice(&buf)?; } DhcpOption::Authentication(Authentication { proto, algo, rdm, replay_detection, info, }) => { e.write_u16(11 + info.len() as u16)?; e.write_u8(*proto)?; e.write_u8(*algo)?; e.write_u8(*rdm)?; e.write_u64(*replay_detection)?; e.write_slice(info)?; } DhcpOption::ServerUnicast(addr) => { e.write_u16(16)?; e.write_u128((*addr).into())?; } DhcpOption::StatusCode(StatusCode { status, msg }) => { e.write_u16(2 + msg.len() as u16)?; e.write_u16((*status).into())?; e.write_slice(msg.as_bytes())?; } DhcpOption::RapidCommit => { e.write_u16(0)?; } DhcpOption::UserClass(UserClass { data }) => { e.write_u16(data.len() as u16)?; for s in data { e.write_u16(s.len() as u16)?; e.write_slice(s)?; } } DhcpOption::VendorClass(VendorClass { num, data }) => { e.write_u16(4 + data.len() as u16)?; e.write_u32(*num)?; for s in data { e.write_u16(s.len() as u16)?; e.write_slice(s)?; } } DhcpOption::VendorOpts(VendorOpts { num, opts }) => { let mut buf = Vec::new(); let mut opt_enc = Encoder::new(&mut buf); opts.encode(&mut opt_enc)?; // buf now has total len e.write_u16(4 + buf.len() as u16)?; e.write_u32(*num)?; e.write_slice(&buf)?; } DhcpOption::InterfaceId(id) => { e.write_u16(id.len() as u16)?; e.write_slice(id)?; } DhcpOption::ReconfMsg(msg_type) => { e.write_u16(1)?; e.write_u8((*msg_type).into())?; } DhcpOption::ReconfAccept => { e.write_u16(0)?; } DhcpOption::DomainNameServers(addrs) => { e.write_u16(addrs.len() as u16 * 16)?; for addr in addrs { e.write_u128((*addr).into())?; } } DhcpOption::DomainSearchList(names) => { let mut buf = Vec::new(); let mut name_encoder = BinEncoder::new(&mut buf); for name in names { name.emit(&mut name_encoder)?; } e.write_u16(buf.len() as u16)?; e.write_slice(&buf)?; } DhcpOption::IAPrefix(IAPrefix { preferred_lifetime, valid_lifetime, prefix_len, prefix_ip, opts, }) => { let mut buf = Vec::new(); let mut opt_enc = Encoder::new(&mut buf); opts.encode(&mut opt_enc)?; // buf now has total len e.write_u16(25 + buf.len() as u16)?; // write data e.write_u32(*preferred_lifetime)?; e.write_u32(*valid_lifetime)?; e.write_u8(*prefix_len)?; e.write_u128((*prefix_ip).into())?; e.write_slice(&buf)?; } DhcpOption::InformationRefreshTime(time) => { e.write_u16(4)?; e.write_u32(*time)?; } DhcpOption::Unknown(UnknownOption { data, .. }) => { e.write_u16(data.len() as u16)?; e.write_slice(data)?; } }; Ok(()) } } #[inline] fn first(arr: &[T], f: F) -> Option where T: Ord, F: Fn(&T) -> Ordering, { if arr.is_empty() { return None; } let mut l = 0; let mut r = arr.len() - 1; while l <= r { let mid = (l + r) >> 1; // SAFETY: we know it is within the length let mid_cmp = f(unsafe { arr.get_unchecked(mid) }); let prev_cmp = if mid > 0 { f(unsafe { arr.get_unchecked(mid - 1) }) == Ordering::Less } else { false }; match mid_cmp { Ordering::Less => l = mid + 1, Ordering::Equal if (mid == 0 || prev_cmp) => return Some(mid), Ordering::Greater | Ordering::Equal if mid == 0 => return None, Ordering::Greater | Ordering::Equal => r = mid - 1, } } None } #[inline] fn last(arr: &[T], f: F) -> Option where T: Ord, F: Fn(&T) -> Ordering, { if arr.is_empty() { return None; } let n = arr.len(); let mut l = 0; let mut r = n - 1; while l <= r { let mid = (l + r) >> 1; // SAFETY: we know it is within the length let mid_cmp = f(unsafe { arr.get_unchecked(mid) }); let nxt_cmp = if mid < n { f(unsafe { arr.get_unchecked(mid + 1) }) == Ordering::Greater } else { false }; match mid_cmp { Ordering::Greater => r = mid - 1, Ordering::Equal if (mid == n - 1 || nxt_cmp) => return Some(mid), Ordering::Less | Ordering::Equal if mid == n - 1 => return None, Ordering::Less | Ordering::Equal => l = mid + 1, } } None } #[inline] fn range_binsearch(arr: &[T], f: F) -> Option> where T: Ord, F: Fn(&T) -> Ordering, { let first = first(arr, &f)?; let last = last(arr, &f)?; Some(first..=last) } #[cfg(test)] mod tests { use super::*; #[test] fn test_range_binsearch() { let arr = vec![0, 1, 1, 1, 1, 4, 6, 7, 9, 9, 10]; assert_eq!(Some(1..=4), range_binsearch(&arr, |x| x.cmp(&1))); let arr = vec![0, 1, 1, 1, 1, 4, 6, 7, 9, 9, 10]; assert_eq!(Some(0..=0), range_binsearch(&arr, |x| x.cmp(&0))); let arr = vec![0, 1, 1, 1, 1, 4, 6, 7, 9, 9, 10]; assert_eq!(Some(5..=5), range_binsearch(&arr, |x| x.cmp(&4))); let arr = vec![1, 2, 2, 2, 2, 3, 4, 7, 8, 8]; assert_eq!(Some(8..=9), range_binsearch(&arr, |x| x.cmp(&8))); let arr = vec![1, 2, 2, 2, 2, 3, 4, 7, 8, 8]; assert_eq!(Some(1..=4), range_binsearch(&arr, |x| x.cmp(&2))); let arr = vec![1, 2, 2, 2, 2, 3, 4, 7, 8, 8]; assert_eq!(Some(7..=7), range_binsearch(&arr, |x| x.cmp(&7))); let arr: Vec = vec![]; assert_eq!(None, range_binsearch(&arr, |x| x.cmp(&1))); let arr = vec![1, 1, 2, 2]; assert_eq!(None, range_binsearch(&arr, |x| x.cmp(&3))); } } dhcproto-0.9.0/src/v6/oro_codes.rs000064400000000000000000000236761046102023000151360ustar 00000000000000/// Valid Option Codes for ORO /// https://datatracker.ietf.org/doc/html/rfc8415#section-24 #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::v6::OptionCode; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum OROCode { /// Optional VendorOpts, SipServerD, SipServerA, DomainNameServers, DomainSearchList, NisServers, NispServers, NisDomainName, NispDomainName, SntpServers, /// Required for Information-request InformationRefreshTime, BcmcsServerD, BcmcsServerA, GeoconfCivic, ClientFqdn, PanaAgent, NewPosixTimezone, NewTzdbTimezone, Mip6Hnidf, Mip6Vdinf, V6Lost, CapwapAcV6, Ipv6AddressMoS, Ipv6FQDNMoS, NtpServer, V6AccessDomain, SipUaCsList, OptBootfileUrl, OptBootfileParam, Nii, Geolocation, AftrName, ErpLocalDomainName, PdExclude, Mip6Idinf, Mip6Udinf, Mip6Hnp, Mip6Haa, Mip6Haf, RdnssSelection, KrbPrincipalName, KrbRealmName, KrbDefaultRealmName, KrbKdc, /// Required for Solicit SolMaxRt, /// Required for Information-request InfMaxRt, Addrsel, AddrselTable, V6PcpServer, Dhcp4ODhcp6Server, S46ContMape, S46ContMapt, S46ContLw, _4Rd, _4RdMapRule, _4RdNonMapRule, DhcpCaptivePortal, MplParameters, S46Priority, V6Prefix64, Ipv6AddressANDSF, /// Avalible for future codes. Unknown(u16), } impl From for u16 { fn from(opt: OROCode) -> Self { OptionCode::from(opt).into() } } // should this be a TryFrom? impl From for OROCode { fn from(opt: u16) -> Self { OptionCode::from(opt) .try_into() .unwrap_or(OROCode::Unknown(opt)) } } impl TryFrom for OROCode { type Error = &'static str; fn try_from(opt: OptionCode) -> Result { match opt { OptionCode::VendorOpts => Ok(OROCode::VendorOpts), OptionCode::SipServerD => Ok(OROCode::SipServerD), OptionCode::SipServerA => Ok(OROCode::SipServerA), OptionCode::DomainNameServers => Ok(OROCode::DomainNameServers), OptionCode::DomainSearchList => Ok(OROCode::DomainSearchList), OptionCode::NisServers => Ok(OROCode::NisServers), OptionCode::NispServers => Ok(OROCode::NispServers), OptionCode::NisDomainName => Ok(OROCode::NisDomainName), OptionCode::NispDomainName => Ok(OROCode::NispDomainName), OptionCode::SntpServers => Ok(OROCode::SntpServers), OptionCode::InformationRefreshTime => Ok(OROCode::InformationRefreshTime), OptionCode::BcmcsServerD => Ok(OROCode::BcmcsServerD), OptionCode::BcmcsServerA => Ok(OROCode::BcmcsServerA), OptionCode::GeoconfCivic => Ok(OROCode::GeoconfCivic), OptionCode::ClientFqdn => Ok(OROCode::ClientFqdn), OptionCode::PanaAgent => Ok(OROCode::PanaAgent), OptionCode::NewPosixTimezone => Ok(OROCode::NewPosixTimezone), OptionCode::NewTzdbTimezone => Ok(OROCode::NewTzdbTimezone), OptionCode::Mip6Hnidf => Ok(OROCode::Mip6Hnidf), OptionCode::Mip6Vdinf => Ok(OROCode::Mip6Vdinf), OptionCode::V6Lost => Ok(OROCode::V6Lost), OptionCode::CapwapAcV6 => Ok(OROCode::CapwapAcV6), OptionCode::Ipv6AddressMoS => Ok(OROCode::Ipv6AddressMoS), OptionCode::Ipv6FQDNMoS => Ok(OROCode::Ipv6FQDNMoS), OptionCode::NtpServer => Ok(OROCode::NtpServer), OptionCode::V6AccessDomain => Ok(OROCode::V6AccessDomain), OptionCode::SipUaCsList => Ok(OROCode::SipUaCsList), OptionCode::OptBootfileUrl => Ok(OROCode::OptBootfileUrl), OptionCode::OptBootfileParam => Ok(OROCode::OptBootfileParam), OptionCode::Nii => Ok(OROCode::Nii), OptionCode::Geolocation => Ok(OROCode::Geolocation), OptionCode::AftrName => Ok(OROCode::AftrName), OptionCode::ErpLocalDomainName => Ok(OROCode::ErpLocalDomainName), OptionCode::PdExclude => Ok(OROCode::PdExclude), OptionCode::Mip6Idinf => Ok(OROCode::Mip6Idinf), OptionCode::Mip6Udinf => Ok(OROCode::Mip6Udinf), OptionCode::Mip6Hnp => Ok(OROCode::Mip6Hnp), OptionCode::Mip6Haa => Ok(OROCode::Mip6Haa), OptionCode::Mip6Haf => Ok(OROCode::Mip6Haf), OptionCode::RdnssSelection => Ok(OROCode::RdnssSelection), OptionCode::KrbPrincipalName => Ok(OROCode::KrbPrincipalName), OptionCode::KrbRealmName => Ok(OROCode::KrbRealmName), OptionCode::KrbDefaultRealmName => Ok(OROCode::KrbDefaultRealmName), OptionCode::KrbKdc => Ok(OROCode::KrbKdc), OptionCode::SolMaxRt => Ok(OROCode::SolMaxRt), OptionCode::InfMaxRt => Ok(OROCode::InfMaxRt), OptionCode::Addrsel => Ok(OROCode::Addrsel), OptionCode::AddrselTable => Ok(OROCode::AddrselTable), OptionCode::V6PcpServer => Ok(OROCode::V6PcpServer), OptionCode::Dhcp4ODhcp6Server => Ok(OROCode::Dhcp4ODhcp6Server), OptionCode::S46ContMape => Ok(OROCode::S46ContMape), OptionCode::S46ContMapt => Ok(OROCode::S46ContMapt), OptionCode::S46ContLw => Ok(OROCode::S46ContLw), OptionCode::_4Rd => Ok(OROCode::_4Rd), OptionCode::_4RdMapRule => Ok(OROCode::_4RdMapRule), OptionCode::_4RdNonMapRule => Ok(OROCode::_4RdNonMapRule), OptionCode::DhcpCaptivePortal => Ok(OROCode::DhcpCaptivePortal), OptionCode::MplParameters => Ok(OROCode::MplParameters), OptionCode::S46Priority => Ok(OROCode::S46Priority), OptionCode::V6Prefix64 => Ok(OROCode::V6Prefix64), OptionCode::Ipv6AddressANDSF => Ok(OROCode::Ipv6AddressANDSF), OptionCode::Unknown(u16) => Ok(OROCode::Unknown(u16)), _ => Err("conversion error, is not a valid OROCode"), } } } impl From for OptionCode { fn from(opt: OROCode) -> OptionCode { match opt { OROCode::VendorOpts => OptionCode::VendorOpts, OROCode::SipServerD => OptionCode::SipServerD, OROCode::SipServerA => OptionCode::SipServerA, OROCode::DomainNameServers => OptionCode::DomainNameServers, OROCode::DomainSearchList => OptionCode::DomainSearchList, OROCode::NisServers => OptionCode::NisServers, OROCode::NispServers => OptionCode::NispServers, OROCode::NisDomainName => OptionCode::NisDomainName, OROCode::NispDomainName => OptionCode::NispDomainName, OROCode::SntpServers => OptionCode::SntpServers, OROCode::InformationRefreshTime => OptionCode::InformationRefreshTime, OROCode::BcmcsServerD => OptionCode::BcmcsServerD, OROCode::BcmcsServerA => OptionCode::BcmcsServerA, OROCode::GeoconfCivic => OptionCode::GeoconfCivic, OROCode::ClientFqdn => OptionCode::ClientFqdn, OROCode::PanaAgent => OptionCode::PanaAgent, OROCode::NewPosixTimezone => OptionCode::NewPosixTimezone, OROCode::NewTzdbTimezone => OptionCode::NewTzdbTimezone, OROCode::Mip6Hnidf => OptionCode::Mip6Hnidf, OROCode::Mip6Vdinf => OptionCode::Mip6Vdinf, OROCode::V6Lost => OptionCode::V6Lost, OROCode::CapwapAcV6 => OptionCode::CapwapAcV6, OROCode::Ipv6AddressMoS => OptionCode::Ipv6AddressMoS, OROCode::Ipv6FQDNMoS => OptionCode::Ipv6FQDNMoS, OROCode::NtpServer => OptionCode::NtpServer, OROCode::V6AccessDomain => OptionCode::V6AccessDomain, OROCode::SipUaCsList => OptionCode::SipUaCsList, OROCode::OptBootfileUrl => OptionCode::OptBootfileUrl, OROCode::OptBootfileParam => OptionCode::OptBootfileParam, OROCode::Nii => OptionCode::Nii, OROCode::Geolocation => OptionCode::Geolocation, OROCode::AftrName => OptionCode::AftrName, OROCode::ErpLocalDomainName => OptionCode::ErpLocalDomainName, OROCode::PdExclude => OptionCode::PdExclude, OROCode::Mip6Idinf => OptionCode::Mip6Idinf, OROCode::Mip6Udinf => OptionCode::Mip6Udinf, OROCode::Mip6Hnp => OptionCode::Mip6Hnp, OROCode::Mip6Haa => OptionCode::Mip6Haa, OROCode::Mip6Haf => OptionCode::Mip6Haf, OROCode::RdnssSelection => OptionCode::RdnssSelection, OROCode::KrbPrincipalName => OptionCode::KrbPrincipalName, OROCode::KrbRealmName => OptionCode::KrbRealmName, OROCode::KrbDefaultRealmName => OptionCode::KrbDefaultRealmName, OROCode::KrbKdc => OptionCode::KrbKdc, OROCode::SolMaxRt => OptionCode::SolMaxRt, OROCode::InfMaxRt => OptionCode::InfMaxRt, OROCode::Addrsel => OptionCode::Addrsel, OROCode::AddrselTable => OptionCode::AddrselTable, OROCode::V6PcpServer => OptionCode::V6PcpServer, OROCode::Dhcp4ODhcp6Server => OptionCode::Dhcp4ODhcp6Server, OROCode::S46ContMape => OptionCode::S46ContMape, OROCode::S46ContMapt => OptionCode::S46ContMapt, OROCode::S46ContLw => OptionCode::S46ContLw, OROCode::_4Rd => OptionCode::_4Rd, OROCode::_4RdMapRule => OptionCode::_4RdMapRule, OROCode::_4RdNonMapRule => OptionCode::_4RdNonMapRule, OROCode::DhcpCaptivePortal => OptionCode::DhcpCaptivePortal, OROCode::MplParameters => OptionCode::MplParameters, OROCode::S46Priority => OptionCode::S46Priority, OROCode::V6Prefix64 => OptionCode::V6Prefix64, OROCode::Ipv6AddressANDSF => OptionCode::Ipv6AddressANDSF, OROCode::Unknown(u16) => OptionCode::Unknown(u16), } } }