├── .gitignore ├── src ├── lib.rs ├── server.rs ├── packet.rs └── options.rs ├── Cargo.toml ├── examples ├── monitor.rs └── server.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod options; 2 | pub mod packet; 3 | pub mod server; 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | #[test] 8 | fn it_works() {} 9 | } 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dhcp4r" 3 | version = "0.2.3" 4 | authors = ["Richard Warburton "] 5 | description = "IPv4 DHCP library with working server example." 6 | edition = "2018" 7 | 8 | [profile.dev] 9 | opt-level = 0 10 | 11 | [profile.release] 12 | opt-level = 3 13 | 14 | 15 | # These URLs point to more information about the repository. 16 | #documentation = "..." 17 | #homepage = "..." 18 | 19 | # This points to a file in the repository (relative to this `Cargo.toml`). The 20 | # contents of this file are stored and indexed in the registry. 21 | # readme = "..." 22 | 23 | [dependencies] 24 | 25 | 26 | [dev-dependencies] 27 | time = "0.2" 28 | -------------------------------------------------------------------------------- /examples/monitor.rs: -------------------------------------------------------------------------------- 1 | extern crate dhcp4r; 2 | extern crate time; 3 | 4 | use dhcp4r::{options, packet, server}; 5 | use std::net::{Ipv4Addr, UdpSocket}; 6 | 7 | fn main() { 8 | server::Server::serve( 9 | UdpSocket::bind("0.0.0.0:67").unwrap(), 10 | Ipv4Addr::new(0, 0, 0, 0), 11 | Ipv4Addr::new(0, 0, 0, 0), 12 | MyServer {}, 13 | ); 14 | } 15 | 16 | struct MyServer {} 17 | 18 | impl server::Handler for MyServer { 19 | fn handle_request(&mut self, _: &server::Server, in_packet: packet::Packet) { 20 | match in_packet.message_type() { 21 | Ok(options::MessageType::Request) => { 22 | let req_ip = match in_packet.option(options::REQUESTED_IP_ADDRESS) { 23 | Some(options::DhcpOption::RequestedIpAddress(x)) => x.clone(), 24 | _ => in_packet.ciaddr, 25 | }; 26 | println!( 27 | "{}\t{}\t{}\tOnline", 28 | time::OffsetDateTime::try_now_local() 29 | .unwrap() 30 | .format("%Y-%m-%dT%H:%M:%S"), 31 | chaddr(&in_packet.chaddr), 32 | Ipv4Addr::from(req_ip) 33 | ); 34 | } 35 | _ => {} 36 | } 37 | } 38 | } 39 | 40 | /// Formats byte array machine address into hex pairs separated by colons. 41 | /// Array must be at least one byte long. 42 | fn chaddr(a: &[u8]) -> String { 43 | a[1..].iter().fold(format!("{:02x}", a[0]), |acc, &b| { 44 | format!("{}:{:02x}", acc, &b) 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RustyDHCP 2 | 3 | ![Rust](https://img.shields.io/badge/Language-Rust-orange) 4 | ![Dependencies](https://img.shields.io/badge/Dependencies-None-brightgreen) 5 | ![Crossplatform](https://img.shields.io/badge/Crossplatform-Yes-brightgreen) 6 | ![Cross Compilation](https://img.shields.io/badge/Cross%20Compilation-Supported-brightgreen) 7 | 8 | A simple and zero-dependency DHCP server written in Rust, with credit to Richard Warburton for contributions to parts of the code. 9 | 10 | ## Features 11 | 12 | - Lightweight and minimalistic DHCP server. 13 | - Zero external dependencies; just Rust! 14 | - Easy to use and configure. 15 | - Based on reliable networking libraries. 16 | - Fast and efficient. 17 | - Cross-platform support and cross-compilation. 18 | - Customizable Leases File: Support for a "leases" file that allows you to define permanent leases, ensuring clients always receive the same IP address. 19 | 20 | ## Table of Contents 21 | 22 | - [Installation](#installation) 23 | - [Usage](#usage) 24 | - [Configuration](#configuration) 25 | - [Contributions](#contributions) 26 | - [License](#license) 27 | 28 | ## Installation 29 | 30 | 1. Make sure you have Rust installed. If not, install it from [https://www.rust-lang.org/](https://www.rust-lang.org/). 31 | 32 | 2. Clone this repository: 33 | 34 | ```bash 35 | git clone https://github.com/pj1234678/RustyDHCP.git 36 | ``` 37 | 38 | 3. Build the server: 39 | 40 | ```bash 41 | cd RustyDHCP 42 | cargo build --release --example server 43 | ``` 44 | 45 | ## Usage 46 | 47 | 1. Start the DHCP server: 48 | 49 | ```bash 50 | sudo ./target/release/examples/server 51 | ``` 52 | 53 | The server will listen on the default DHCP ports (67 and 68) and start serving DHCP requests. 54 | 55 | 2. Make DHCP requests from clients, and the server will respond with IP addresses and other configuration details. 56 | 57 | ## Configuration 58 | 59 | To configure the server, edit the following fields in the `examples/server.rs` file: 60 | 61 | ```rust 62 | // Server configuration 63 | const SERVER_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 2, 1); 64 | const IP_START: [u8; 4] = [192, 168, 2, 2]; 65 | const SUBNET_MASK: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0); 66 | const DNS_IPS: [Ipv4Addr; 1] = [ 67 | // Google DNS servers 68 | Ipv4Addr::new(8, 8, 8, 8), 69 | ]; 70 | const ROUTER_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 2, 1); 71 | const BROADCAST_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 2, 255); 72 | const LEASE_DURATION_SECS: u32 = 86400; 73 | const LEASE_NUM: u32 = 252; 74 | ``` 75 | 76 | You can customize these configuration parameters according to your network requirements. 77 | 78 | 79 | 80 | To create a "leases" file with the example permanent lease, you can manually create a file named "leases" in the same directory as the compiled program with the following content: 81 | 82 | f4:5c:19:af:96:8d,192.168.2.90 83 | 84 | This lease format specifies the MAC address and the corresponding IP address for the client. The DHCP server will read this file to assign permanent leases based on its contents. 85 | 86 | 87 | ## Contributions 88 | 89 | This DHCP server has been made possible with contributions from the open-source community, including valuable code from Richard Warburton. Feel free to contribute to this project and make it even better! 90 | 91 | If you find a bug or have a feature request, please open an issue on the GitHub repository. 92 | 93 | ## License 94 | 95 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 96 | 97 | --- 98 | 99 | **Note:** Remember to use this DHCP server responsibly and comply with local network regulations and security practices. 100 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | //! This is a convenience module that simplifies the writing of a DHCP server service. 2 | 3 | use std::cell::Cell; 4 | use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; 5 | 6 | use crate::options; 7 | use crate::options::{DhcpOption, MessageType}; 8 | use crate::packet::*; 9 | 10 | pub struct Server { 11 | out_buf: Cell<[u8; 1500]>, 12 | socket: UdpSocket, 13 | src: SocketAddr, 14 | server_ip: Ipv4Addr, 15 | broadcast_ip: Ipv4Addr, 16 | } 17 | 18 | pub trait Handler { 19 | fn handle_request(&mut self, server: &Server, in_packet: Packet); 20 | } 21 | 22 | pub fn filter_options_by_req(opts: &mut Vec, req_params: &[u8]) { 23 | let mut pos = 0; 24 | let h = &[ 25 | options::DHCP_MESSAGE_TYPE, 26 | options::SERVER_IDENTIFIER, 27 | options::SUBNET_MASK, 28 | options::IP_ADDRESS_LEASE_TIME, 29 | options::DOMAIN_NAME_SERVER, 30 | options::ROUTER, 31 | ] as &[u8]; 32 | 33 | // Process options from req_params 34 | for r in req_params.iter() { 35 | let mut found = false; 36 | for (i, o) in opts[pos..].iter().enumerate() { 37 | if o.code() == *r { 38 | found = true; 39 | if pos + i != pos { 40 | opts.swap(pos + i, pos); 41 | } 42 | pos += 1; 43 | break; 44 | } 45 | } 46 | if !found { 47 | // Option not found, continue searching 48 | } 49 | } 50 | 51 | // Process options from h 52 | for r in h.iter() { 53 | let mut found = false; 54 | for (i, o) in opts[pos..].iter().enumerate() { 55 | if o.code() == *r { 56 | found = true; 57 | if pos + i != pos { 58 | opts.swap(pos + i, pos); 59 | } 60 | pos += 1; 61 | break; 62 | } 63 | } 64 | if !found { 65 | // Option not found, continue searching 66 | } 67 | } 68 | 69 | // Truncate the options list if necessary 70 | opts.truncate(pos); 71 | } 72 | 73 | impl Server { 74 | pub fn serve( 75 | udp_soc: UdpSocket, 76 | server_ip: Ipv4Addr, 77 | broadcast_ip: Ipv4Addr, 78 | mut handler: H, 79 | ) -> std::io::Error { 80 | let mut in_buf: [u8; 1500] = [0; 1500]; 81 | let mut s = Server { 82 | out_buf: Cell::new([0; 1500]), 83 | socket: udp_soc, 84 | server_ip, 85 | broadcast_ip, 86 | src: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0), 87 | }; 88 | loop { 89 | match s.socket.recv_from(&mut in_buf) { 90 | Err(e) => return e, 91 | Ok((l, src)) => { 92 | if let Ok(p) = Packet::from(&in_buf[..l]) { 93 | s.src = src; 94 | 95 | handler.handle_request(&s, p); 96 | } 97 | } 98 | } 99 | } 100 | } 101 | 102 | /// Constructs and sends a reply packet back to the client. 103 | /// additional_options should not include DHCP_MESSAGE_TYPE nor SERVER_IDENTIFIER as these 104 | /// are added automatically. 105 | pub fn reply( 106 | &self, 107 | msg_type: MessageType, 108 | additional_options: Vec, 109 | offer_ip: Ipv4Addr, 110 | req_packet: Packet, 111 | ) -> std::io::Result { 112 | let ciaddr = match msg_type { 113 | MessageType::Nak => Ipv4Addr::new(0, 0, 0, 0), 114 | _ => req_packet.ciaddr, 115 | }; 116 | 117 | //let mt = &[msg_type as u8]; 118 | 119 | let mut opts: Vec = Vec::with_capacity(additional_options.len() + 2); 120 | opts.push(DhcpOption::DhcpMessageType(msg_type)); 121 | opts.push(DhcpOption::ServerIdentifier(self.server_ip)); 122 | /*opts.push(DhcpOption { 123 | code: options::DHCP_MESSAGE_TYPE, 124 | data: mt, 125 | }); 126 | opts.push(DhcpOption { 127 | code: options::SERVER_IDENTIFIER, 128 | data: &self.server_ip, 129 | });*/ 130 | opts.extend(additional_options); 131 | 132 | if let Some(DhcpOption::ParameterRequestList(prl)) = 133 | req_packet.option(options::PARAMETER_REQUEST_LIST) 134 | { 135 | filter_options_by_req(&mut opts, prl); 136 | } 137 | 138 | self.send(Packet { 139 | reply: true, 140 | hops: 0, 141 | xid: req_packet.xid, 142 | secs: 0, 143 | broadcast: req_packet.broadcast, 144 | ciaddr, 145 | yiaddr: offer_ip, 146 | siaddr: Ipv4Addr::new(0, 0, 0, 0), 147 | giaddr: req_packet.giaddr, 148 | chaddr: req_packet.chaddr, 149 | options: opts, 150 | }) 151 | } 152 | 153 | /// Checks the packet see if it was intended for this DHCP server (as opposed to some other also on the network). 154 | pub fn for_this_server(&self, packet: &Packet) -> bool { 155 | match packet.option(options::SERVER_IDENTIFIER) { 156 | Some(DhcpOption::ServerIdentifier(x)) => x == &self.server_ip, 157 | _ => false, 158 | } 159 | } 160 | 161 | /// Encodes and sends a DHCP packet back to the client. 162 | pub fn send(&self, p: Packet) -> std::io::Result { 163 | let mut addr = self.src; 164 | if p.broadcast || addr.ip() == IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) { 165 | addr.set_ip(std::net::IpAddr::V4(self.broadcast_ip)); 166 | } 167 | println!("Sending Response to: {:?}", addr); // Print the address 168 | 169 | self.socket.send_to(p.encode(&mut self.out_buf.get()), addr) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /examples/server.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fs::File; 3 | use std::io::{BufRead, BufReader}; 4 | use std::net::{Ipv4Addr, UdpSocket}; 5 | use std::ops::Add; 6 | use std::time::{Duration, Instant}; 7 | 8 | use dhcp4r::{options, packet, server}; 9 | 10 | // Server configuration 11 | const SERVER_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 2, 1); 12 | const IP_START: [u8; 4] = [192, 168, 2, 2]; 13 | const SUBNET_MASK: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0); 14 | const DNS_IPS: [Ipv4Addr; 1] = [ 15 | // Google DNS servers 16 | Ipv4Addr::new(8, 8, 8, 8), 17 | ]; 18 | const ROUTER_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 2, 1); 19 | const BROADCAST_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 2, 255); 20 | const LEASE_DURATION_SECS: u32 = 86400; 21 | const LEASE_NUM: u32 = 252; 22 | 23 | // Derived constants 24 | const IP_START_NUM: u32 = u32::from_be_bytes(IP_START); 25 | const INFINITE_LEASE: Option = None; // Special value for infinite lease 26 | 27 | fn main() { 28 | let socket = UdpSocket::bind("0.0.0.0:67").unwrap(); 29 | socket.set_broadcast(true).unwrap(); 30 | 31 | let mut leases: HashMap)> = HashMap::new(); 32 | // Read and populate leases from the file 33 | if let Ok(file) = File::open("leases") { 34 | let reader = BufReader::new(file); 35 | for line in reader.lines() { 36 | if let Ok(line) = line { 37 | let parts: Vec<&str> = line.split(',').collect(); 38 | if parts.len() == 2 { 39 | let mac_parts: Vec = parts[0] 40 | .split(':') 41 | .filter_map(|part| u8::from_str_radix(part, 16).ok()) 42 | .collect(); 43 | 44 | if mac_parts.len() == 6 { 45 | let mut mac = [0u8; 6]; 46 | mac.copy_from_slice(&mac_parts); 47 | 48 | let ip = parts[1].trim().parse::().unwrap(); 49 | leases.insert(ip, (mac, INFINITE_LEASE)); 50 | } 51 | } 52 | } 53 | } 54 | } else { 55 | eprintln!("Failed to open leases file. Continuing..."); 56 | //return; 57 | } 58 | 59 | let ms = MyServer { 60 | leases, 61 | last_lease: 0, 62 | lease_duration: Duration::new(LEASE_DURATION_SECS as u64, 0), 63 | }; 64 | 65 | server::Server::serve(socket, SERVER_IP, BROADCAST_IP, ms); 66 | } 67 | 68 | struct MyServer { 69 | leases: HashMap)>, // Ipv4Addr -> (MAC address, lease duration) mapping 70 | last_lease: u32, 71 | lease_duration: Duration, 72 | } 73 | 74 | impl server::Handler for MyServer { 75 | fn handle_request(&mut self, server: &server::Server, in_packet: packet::Packet) { 76 | match in_packet.message_type() { 77 | Ok(options::MessageType::Discover) => { 78 | // Otherwise prefer existing (including expired if available) 79 | if let Some(ip) = self.current_lease(&in_packet.chaddr) { 80 | println!("Sending Reply to discover"); 81 | reply(server, options::MessageType::Offer, in_packet, &ip); 82 | return; 83 | } 84 | // Otherwise choose a free ip if available 85 | for _ in 0..LEASE_NUM { 86 | self.last_lease = (self.last_lease + 1) % LEASE_NUM; 87 | if self.available( 88 | &in_packet.chaddr, 89 | &((IP_START_NUM + &self.last_lease).into()), 90 | ) { 91 | println!("Sending Reply to discover"); 92 | reply( 93 | server, 94 | options::MessageType::Offer, 95 | in_packet, 96 | &((IP_START_NUM + &self.last_lease).into()), 97 | ); 98 | break; 99 | } 100 | } 101 | } 102 | 103 | Ok(options::MessageType::Request) => { 104 | // Ignore requests to alternative DHCP server 105 | if !server.for_this_server(&in_packet) { 106 | //println!("Not for this server"); 107 | // return; 108 | } 109 | 110 | let req_ip = match in_packet.option(options::REQUESTED_IP_ADDRESS) { 111 | Some(options::DhcpOption::RequestedIpAddress(x)) => *x, 112 | _ => in_packet.ciaddr, 113 | }; 114 | for (ip, (mac, _)) in &self.leases { 115 | println!("IP: {:?}, MAC: {:?}", ip, mac); 116 | } 117 | if let Some(ip) = self.current_lease(&in_packet.chaddr) { 118 | println!("Found Current Lease"); 119 | reply(server, options::MessageType::Ack, in_packet, &ip); 120 | return; 121 | } 122 | if !&self.available(&in_packet.chaddr, &req_ip) { 123 | println!("Sending Reply to Request"); 124 | nak(server, in_packet, "Requested IP not available"); 125 | return; 126 | } 127 | self.leases.insert( 128 | req_ip, 129 | ( 130 | in_packet.chaddr, 131 | Some(Instant::now().add(self.lease_duration)), 132 | ), 133 | ); 134 | println!("Sending Reply to Request"); 135 | reply(server, options::MessageType::Ack, in_packet, &req_ip); 136 | } 137 | 138 | Ok(options::MessageType::Release) | Ok(options::MessageType::Decline) => { 139 | // Ignore requests to alternative DHCP server 140 | if !server.for_this_server(&in_packet) { 141 | return; 142 | } 143 | if let Some(ip) = self.current_lease(&in_packet.chaddr) { 144 | self.leases.remove(&ip); 145 | } 146 | } 147 | 148 | // TODO - not necessary but support for dhcp4r::INFORM might be nice 149 | _ => {} 150 | } 151 | } 152 | } 153 | 154 | impl MyServer { 155 | fn available(&self, chaddr: &[u8; 6], addr: &Ipv4Addr) -> bool { 156 | let pos: u32 = (*addr).into(); 157 | pos >= IP_START_NUM 158 | && pos < IP_START_NUM + LEASE_NUM 159 | && match self.leases.get(addr) { 160 | Some((mac, expiry)) => { 161 | *mac == *chaddr || expiry.map_or(true, |exp| Instant::now().gt(&exp)) 162 | } 163 | None => true, 164 | } 165 | } 166 | fn current_lease(&self, chaddr: &[u8; 6]) -> Option { 167 | for (i, v) in &self.leases { 168 | if v.0 == *chaddr { 169 | return Some(*i); 170 | } 171 | } 172 | None 173 | } 174 | } 175 | 176 | fn reply( 177 | s: &server::Server, 178 | msg_type: options::MessageType, 179 | req_packet: packet::Packet, 180 | offer_ip: &Ipv4Addr, 181 | ) { 182 | let _ = s.reply( 183 | msg_type, 184 | vec![ 185 | options::DhcpOption::IpAddressLeaseTime(LEASE_DURATION_SECS), 186 | options::DhcpOption::SubnetMask(SUBNET_MASK), 187 | options::DhcpOption::Router(vec![ROUTER_IP]), 188 | options::DhcpOption::DomainNameServer(DNS_IPS.to_vec()), 189 | ], 190 | *offer_ip, 191 | req_packet, 192 | ); 193 | } 194 | 195 | fn nak(s: &server::Server, req_packet: packet::Packet, message: &str) { 196 | let _ = s.reply( 197 | options::MessageType::Nak, 198 | vec![options::DhcpOption::Message(message.to_string())], 199 | Ipv4Addr::new(0, 0, 0, 0), 200 | req_packet, 201 | ); 202 | } 203 | -------------------------------------------------------------------------------- /src/packet.rs: -------------------------------------------------------------------------------- 1 | use crate::options::*; 2 | 3 | use std::net::Ipv4Addr; 4 | 5 | pub enum CustomErr { 6 | NomError((I, ErrorKind)), 7 | NonUtf8String, 8 | UnrecognizedMessageType, 9 | InvalidHlen, 10 | } 11 | 12 | pub enum ErrorKind { 13 | Tag, 14 | MapRes, 15 | ManyTill, 16 | Eof, 17 | Custom(u32), 18 | } 19 | 20 | type IResult = Result<(I, O), CustomErr>; 21 | 22 | /// DHCP Packet Structure 23 | #[derive(Debug)] 24 | pub struct Packet { 25 | pub reply: bool, // false = request, true = reply 26 | pub hops: u8, 27 | pub xid: u32, // Random identifier 28 | pub secs: u16, 29 | pub broadcast: bool, 30 | pub ciaddr: Ipv4Addr, 31 | pub yiaddr: Ipv4Addr, 32 | pub siaddr: Ipv4Addr, 33 | pub giaddr: Ipv4Addr, 34 | pub chaddr: [u8; 6], 35 | pub options: Vec, 36 | } 37 | 38 | fn decode_reply(input: &[u8]) -> IResult<&[u8], bool> { 39 | let (input, reply) = custom_take(1usize)(input)?; 40 | Ok(( 41 | input, 42 | match reply[0] { 43 | BOOT_REPLY => true, 44 | BOOT_REQUEST => false, 45 | _ => { 46 | // @TODO: Throw an error 47 | false 48 | } 49 | }, 50 | )) 51 | } 52 | 53 | fn decode_ipv4(p: &[u8]) -> IResult<&[u8], Ipv4Addr> { 54 | let (input, addr) = custom_take(4usize)(p)?; 55 | Ok((input, Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]))) 56 | } 57 | fn custom_many0(mut f: F) -> impl FnMut(I) -> IResult> 58 | where 59 | I: Clone + PartialEq, 60 | F: FnMut(I) -> IResult, 61 | { 62 | move |input: I| { 63 | let mut acc = Vec::new(); 64 | let mut remaining = input.clone(); 65 | 66 | loop { 67 | match f(remaining.clone()) { 68 | Ok((input, o)) => { 69 | if input == remaining { 70 | return Ok((input, acc)); 71 | } 72 | acc.push(o); 73 | remaining = input; 74 | } 75 | Err(CustomErr::NomError(_)) => return Ok((remaining, acc)), 76 | Err(e) => return Err(e), 77 | } 78 | } 79 | } 80 | } 81 | pub fn decode_option(input: &[u8]) -> IResult<&[u8], DhcpOption> { 82 | let (input, code) = custom_be_u8(input)?; 83 | assert!(code != END); 84 | 85 | let (input, len) = custom_be_u8(input)?; 86 | let (input, data) = custom_take(len.into())(input)?; 87 | let option = match code { 88 | DHCP_MESSAGE_TYPE => { 89 | DhcpOption::DhcpMessageType(match MessageType::from(custom_be_u8(data)?.1) { 90 | Ok(x) => x, 91 | Err(_) => return Err(CustomErr::UnrecognizedMessageType), 92 | }) 93 | } 94 | SERVER_IDENTIFIER => DhcpOption::ServerIdentifier(decode_ipv4(data)?.1), 95 | PARAMETER_REQUEST_LIST => DhcpOption::ParameterRequestList(data.to_vec()), 96 | REQUESTED_IP_ADDRESS => DhcpOption::RequestedIpAddress(decode_ipv4(data)?.1), 97 | HOST_NAME => DhcpOption::HostName(match std::str::from_utf8(data) { 98 | Ok(s) => s.to_string(), 99 | Err(_) => return Err(CustomErr::NonUtf8String), 100 | }), 101 | ROUTER => DhcpOption::Router(custom_many0(decode_ipv4)(data)?.1), 102 | DOMAIN_NAME_SERVER => DhcpOption::DomainNameServer(custom_many0(decode_ipv4)(data)?.1), 103 | IP_ADDRESS_LEASE_TIME => DhcpOption::IpAddressLeaseTime(custom_be_u32(data)?.1), 104 | SUBNET_MASK => DhcpOption::SubnetMask(decode_ipv4(data)?.1), 105 | MESSAGE => DhcpOption::Message(match std::str::from_utf8(data) { 106 | Ok(s) => s.to_string(), 107 | Err(_) => return Err(CustomErr::NonUtf8String), 108 | }), 109 | _ => DhcpOption::Unrecognized(RawDhcpOption { 110 | code, 111 | data: data.to_vec(), 112 | }), 113 | }; 114 | Ok((input, option)) 115 | } 116 | fn custom_take<'a>(n: usize) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> { 117 | move |input: &'a [u8]| { 118 | if input.len() >= n { 119 | Ok((&input[n..], &input[0..n])) 120 | } else { 121 | Err(CustomErr::InvalidHlen) 122 | } 123 | } 124 | } 125 | 126 | fn custom_tag<'a>(tag: &'static [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> { 127 | move |input: &'a [u8]| { 128 | if input.starts_with(tag) { 129 | Ok((&input[tag.len()..], &input[..tag.len()])) 130 | } else { 131 | Err(CustomErr::NomError((input, ErrorKind::Tag))) 132 | } 133 | } 134 | } 135 | fn custom_be_u8(input: &[u8]) -> IResult<&[u8], u8> { 136 | if input.is_empty() { 137 | return Err(CustomErr::InvalidHlen); 138 | } 139 | 140 | Ok((&input[1..], input[0])) 141 | } 142 | 143 | fn custom_be_u16(input: &[u8]) -> IResult<&[u8], u16> { 144 | if input.len() < 2 { 145 | return Err(CustomErr::InvalidHlen); 146 | } 147 | 148 | let value = u16::from_be_bytes([input[0], input[1]]); 149 | Ok((&input[2..], value)) 150 | } 151 | fn custom_be_u32(input: &[u8]) -> IResult<&[u8], u32> { 152 | if input.len() < 4 { 153 | return Err(CustomErr::InvalidHlen); 154 | } 155 | 156 | let value = u32::from_be_bytes([input[0], input[1], input[2], input[3]]); 157 | Ok((&input[4..], value)) 158 | } 159 | 160 | /// Parses Packet from byte array 161 | fn decode(input: &[u8]) -> IResult<&[u8], Packet> { 162 | let (options_input, input) = custom_take(236usize)(input)?; 163 | 164 | let (input, reply) = decode_reply(input)?; 165 | let (input, _htype) = custom_take(1usize)(input)?; 166 | let (input, hlen) = custom_be_u8(input)?; 167 | let (input, hops) = custom_be_u8(input)?; 168 | let (input, xid) = custom_be_u32(input)?; 169 | let (input, secs) = custom_be_u16(input)?; 170 | let (input, flags) = custom_be_u16(input)?; 171 | let (input, ciaddr) = decode_ipv4(input)?; 172 | let (input, yiaddr) = decode_ipv4(input)?; 173 | let (input, siaddr) = decode_ipv4(input)?; 174 | let (input, giaddr) = decode_ipv4(input)?; 175 | 176 | if hlen != 6 { 177 | return Err(CustomErr::InvalidHlen); 178 | } 179 | let (_, chaddr) = custom_take(6usize)(input)?; 180 | 181 | let input = options_input; 182 | let (input, _) = custom_tag(&COOKIE)(input)?; 183 | 184 | let mut options = Vec::new(); 185 | let mut rest = input; 186 | 187 | while let Ok((new_rest, option)) = decode_option(rest) { 188 | rest = new_rest; 189 | options.push(option); 190 | if rest.starts_with(&[END]) { 191 | break; 192 | } 193 | } 194 | 195 | let input = rest.split_at(1).1; // Skip the END tag byte 196 | 197 | Ok(( 198 | input, 199 | Packet { 200 | reply, 201 | hops, 202 | secs, 203 | broadcast: flags & 128 == 128, 204 | ciaddr, 205 | yiaddr, 206 | siaddr, 207 | giaddr, 208 | options, 209 | chaddr: [ 210 | chaddr[0], chaddr[1], chaddr[2], chaddr[3], chaddr[4], chaddr[5], 211 | ], 212 | xid, 213 | }, 214 | )) 215 | } 216 | 217 | impl Packet { 218 | pub fn from(input: &[u8]) -> Result> { 219 | Ok(decode(input)?.1) 220 | } 221 | 222 | /// Extracts requested option payload from packet if available 223 | pub fn option(&self, code: u8) -> Option<&DhcpOption> { 224 | self.options.iter().find(|&option| option.code() == code) 225 | } 226 | 227 | /// Convenience function for extracting a packet's message type. 228 | pub fn message_type(&self) -> Result { 229 | match self.option(DHCP_MESSAGE_TYPE) { 230 | Some(DhcpOption::DhcpMessageType(msgtype)) => Ok(*msgtype), 231 | Some(option) => Err(format![ 232 | "Got wrong enum code {} for DHCP_MESSAGE_TYPE", 233 | option.code() 234 | ]), 235 | None => Err("Packet does not have MessageType option".to_string()), 236 | } 237 | } 238 | pub fn encode<'a>(&'a self, p: &'a mut [u8]) -> &[u8] { 239 | let broadcast_flag = if self.broadcast { 128 } else { 0 }; 240 | let mut length = 240; 241 | 242 | p[..12].copy_from_slice(&[ 243 | if self.reply { BOOT_REPLY } else { BOOT_REQUEST }, 244 | 1, 245 | 6, 246 | self.hops, 247 | ((self.xid >> 24) & 0xFF) as u8, 248 | ((self.xid >> 16) & 0xFF) as u8, 249 | ((self.xid >> 8) & 0xFF) as u8, 250 | (self.xid & 0xFF) as u8, 251 | (self.secs >> 8) as u8, 252 | (self.secs & 255) as u8, 253 | broadcast_flag, 254 | 0, 255 | ]); 256 | 257 | p[12..16].copy_from_slice(&self.ciaddr.octets()); 258 | p[16..20].copy_from_slice(&self.yiaddr.octets()); 259 | p[20..24].copy_from_slice(&self.siaddr.octets()); 260 | p[24..28].copy_from_slice(&self.giaddr.octets()); 261 | p[28..34].copy_from_slice(&self.chaddr); 262 | p[34..236].fill(0); 263 | p[236..240].copy_from_slice(&COOKIE); 264 | 265 | for option in &self.options { 266 | let option = option.to_raw(); 267 | let option_len = option.data.len(); 268 | if length + 2 + option_len >= 272 { 269 | break; 270 | } 271 | if let Some(dest) = p.get_mut(length..length + 2 + option_len) { 272 | dest[0] = option.code; 273 | dest[1] = option_len as u8; 274 | dest[2..].copy_from_slice(&option.data); 275 | } 276 | length += 2 + option_len; 277 | } 278 | 279 | if let Some(end_segment) = p.get_mut(length..length + 1) { 280 | end_segment[0] = END; 281 | } 282 | length += 1; 283 | 284 | if let Some(pad_segment) = p.get_mut(length..272) { 285 | pad_segment.fill(PAD); 286 | } 287 | 288 | &p[..length] 289 | } 290 | } 291 | 292 | const COOKIE: [u8; 4] = [99, 130, 83, 99]; 293 | 294 | const BOOT_REQUEST: u8 = 1; // From Client; 295 | const BOOT_REPLY: u8 = 2; // From Server; 296 | 297 | const END: u8 = 255; 298 | const PAD: u8 = 0; 299 | -------------------------------------------------------------------------------- /src/options.rs: -------------------------------------------------------------------------------- 1 | ///use num_traits::FromPrimitive; 2 | use std::net::Ipv4Addr; 3 | 4 | #[derive(PartialEq, Clone, Debug)] 5 | pub struct RawDhcpOption { 6 | pub code: u8, 7 | pub data: Vec, 8 | } 9 | 10 | #[derive(PartialEq, Debug)] 11 | pub enum DhcpOption { 12 | DhcpMessageType(MessageType), 13 | ServerIdentifier(Ipv4Addr), 14 | ParameterRequestList(Vec), 15 | RequestedIpAddress(Ipv4Addr), 16 | HostName(String), 17 | Router(Vec), 18 | DomainNameServer(Vec), 19 | IpAddressLeaseTime(u32), 20 | SubnetMask(Ipv4Addr), 21 | Message(String), 22 | Unrecognized(RawDhcpOption), 23 | } 24 | 25 | impl DhcpOption { 26 | pub fn to_raw(&self) -> RawDhcpOption { 27 | match self { 28 | Self::DhcpMessageType(mtype) => RawDhcpOption { 29 | code: DHCP_MESSAGE_TYPE, 30 | data: vec![*mtype as u8], 31 | }, 32 | Self::ServerIdentifier(addr) => RawDhcpOption { 33 | code: SERVER_IDENTIFIER, 34 | data: addr.octets().to_vec(), 35 | }, 36 | Self::ParameterRequestList(prl) => RawDhcpOption { 37 | code: PARAMETER_REQUEST_LIST, 38 | data: prl.clone(), 39 | }, 40 | Self::RequestedIpAddress(addr) => RawDhcpOption { 41 | code: REQUESTED_IP_ADDRESS, 42 | data: addr.octets().to_vec(), 43 | }, 44 | Self::HostName(name) => RawDhcpOption { 45 | code: HOST_NAME, 46 | data: name.as_bytes().to_vec(), 47 | }, 48 | Self::Router(addrs) => RawDhcpOption { 49 | code: ROUTER, 50 | data: { 51 | let mut v = vec![]; 52 | for a in addrs { 53 | v.extend(a.octets().iter()); 54 | } 55 | v 56 | }, 57 | }, 58 | Self::DomainNameServer(addrs) => RawDhcpOption { 59 | code: DOMAIN_NAME_SERVER, 60 | data: { 61 | let mut v = vec![]; 62 | for a in addrs { 63 | v.extend(a.octets().iter()); 64 | } 65 | v 66 | }, 67 | }, 68 | Self::IpAddressLeaseTime(secs) => RawDhcpOption { 69 | code: IP_ADDRESS_LEASE_TIME, 70 | data: secs.to_be_bytes().to_vec(), 71 | }, 72 | Self::SubnetMask(mask) => RawDhcpOption { 73 | code: SUBNET_MASK, 74 | data: mask.octets().to_vec(), 75 | }, 76 | Self::Message(msg) => RawDhcpOption { 77 | code: MESSAGE, 78 | data: msg.as_bytes().to_vec(), 79 | }, 80 | Self::Unrecognized(raw) => raw.clone(), 81 | } 82 | } 83 | 84 | pub fn code(&self) -> u8 { 85 | match self { 86 | Self::DhcpMessageType(_) => DHCP_MESSAGE_TYPE, 87 | Self::ServerIdentifier(_) => SERVER_IDENTIFIER, 88 | Self::ParameterRequestList(_) => PARAMETER_REQUEST_LIST, 89 | Self::RequestedIpAddress(_) => REQUESTED_IP_ADDRESS, 90 | Self::HostName(_) => HOST_NAME, 91 | Self::Router(_) => ROUTER, 92 | Self::DomainNameServer(_) => DOMAIN_NAME_SERVER, 93 | Self::IpAddressLeaseTime(_) => IP_ADDRESS_LEASE_TIME, 94 | Self::SubnetMask(_) => SUBNET_MASK, 95 | Self::Message(_) => MESSAGE, 96 | Self::Unrecognized(x) => x.code, 97 | } 98 | } 99 | } 100 | 101 | // DHCP Options; 102 | pub const SUBNET_MASK: u8 = 1; 103 | pub const TIME_OFFSET: u8 = 2; 104 | pub const ROUTER: u8 = 3; 105 | pub const TIME_SERVER: u8 = 4; 106 | pub const NAME_SERVER: u8 = 5; 107 | pub const DOMAIN_NAME_SERVER: u8 = 6; 108 | pub const LOG_SERVER: u8 = 7; 109 | pub const COOKIE_SERVER: u8 = 8; 110 | pub const LPR_SERVER: u8 = 9; 111 | pub const IMPRESS_SERVER: u8 = 10; 112 | pub const RESOURCE_LOCATION_SERVER: u8 = 11; 113 | pub const HOST_NAME: u8 = 12; 114 | pub const BOOT_FILE_SIZE: u8 = 13; 115 | pub const MERIT_DUMP_FILE: u8 = 14; 116 | pub const DOMAIN_NAME: u8 = 15; 117 | pub const SWAP_SERVER: u8 = 16; 118 | pub const ROOT_PATH: u8 = 17; 119 | pub const EXTENSIONS_PATH: u8 = 18; 120 | 121 | // IP LAYER PARAMETERS PER HOST; 122 | pub const IP_FORWARDING_ENABLE_DISABLE: u8 = 19; 123 | pub const NON_LOCAL_SOURCE_ROUTING_ENABLE_DISABLE: u8 = 20; 124 | pub const POLICY_FILTER: u8 = 21; 125 | pub const MAXIMUM_DATAGRAM_REASSEMBLY_SIZE: u8 = 22; 126 | pub const DEFAULT_IP_TIME_TO_LIVE: u8 = 23; 127 | pub const PATH_MTU_AGING_TIMEOUT: u8 = 24; 128 | pub const PATH_MTU_PLATEAU_TABLE: u8 = 25; 129 | 130 | // IP LAYER PARAMETERS PER INTERFACE; 131 | pub const INTERFACE_MTU: u8 = 26; 132 | pub const ALL_SUBNETS_ARE_LOCAL: u8 = 27; 133 | pub const BROADCAST_ADDRESS: u8 = 28; 134 | pub const PERFORM_MASK_DISCOVERY: u8 = 29; 135 | pub const MASK_SUPPLIER: u8 = 30; 136 | pub const PERFORM_ROUTER_DISCOVERY: u8 = 31; 137 | pub const ROUTER_SOLICITATION_ADDRESS: u8 = 32; 138 | pub const STATIC_ROUTE: u8 = 33; 139 | 140 | // LINK LAYER PARAMETERS PER INTERFACE; 141 | pub const TRAILER_ENCAPSULATION: u8 = 34; 142 | pub const ARP_CACHE_TIMEOUT: u8 = 35; 143 | pub const ETHERNET_ENCAPSULATION: u8 = 36; 144 | 145 | // TCP PARAMETERS; 146 | pub const TCP_DEFAULT_TTL: u8 = 37; 147 | pub const TCP_KEEPALIVE_INTERVAL: u8 = 38; 148 | pub const TCP_KEEPALIVE_GARBAGE: u8 = 39; 149 | 150 | // APPLICATION AND SERVICE PARAMETERS; 151 | pub const NETWORK_INFORMATION_SERVICE_DOMAIN: u8 = 40; 152 | pub const NETWORK_INFORMATION_SERVERS: u8 = 41; 153 | pub const NETWORK_TIME_PROTOCOL_SERVERS: u8 = 42; 154 | pub const VENDOR_SPECIFIC_INFORMATION: u8 = 43; 155 | pub const NETBIOS_OVER_TCPIP_NAME_SERVER: u8 = 44; 156 | pub const NETBIOS_OVER_TCPIP_DATAGRAM_DISTRIBUTION_SERVER: u8 = 45; 157 | pub const NETBIOS_OVER_TCPIP_NODE_TYPE: u8 = 46; 158 | pub const NETBIOS_OVER_TCPIP_SCOPE: u8 = 47; 159 | pub const XWINDOW_SYSTEM_FONT_SERVER: u8 = 48; 160 | pub const XWINDOW_SYSTEM_DISPLAY_MANAGER: u8 = 49; 161 | pub const NETWORK_INFORMATION_SERVICEPLUS_DOMAIN: u8 = 64; 162 | pub const NETWORK_INFORMATION_SERVICEPLUS_SERVERS: u8 = 65; 163 | pub const MOBILE_IP_HOME_AGENT: u8 = 68; 164 | pub const SIMPLE_MAIL_TRANSPORT_PROTOCOL: u8 = 69; 165 | pub const POST_OFFICE_PROTOCOL_SERVER: u8 = 70; 166 | pub const NETWORK_NEWS_TRANSPORT_PROTOCOL: u8 = 71; 167 | pub const DEFAULT_WORLD_WIDE_WEB_SERVER: u8 = 72; 168 | pub const DEFAULT_FINGER_SERVER: u8 = 73; 169 | pub const DEFAULT_INTERNET_RELAY_CHAT_SERVER: u8 = 74; 170 | pub const STREETTALK_SERVER: u8 = 75; 171 | pub const STREETTALK_DIRECTORY_ASSISTANCE: u8 = 76; 172 | 173 | pub const RELAY_AGENT_INFORMATION: u8 = 82; 174 | 175 | // DHCP EXTENSIONS 176 | pub const REQUESTED_IP_ADDRESS: u8 = 50; 177 | pub const IP_ADDRESS_LEASE_TIME: u8 = 51; 178 | pub const OVERLOAD: u8 = 52; 179 | pub const DHCP_MESSAGE_TYPE: u8 = 53; 180 | pub const SERVER_IDENTIFIER: u8 = 54; 181 | pub const PARAMETER_REQUEST_LIST: u8 = 55; 182 | pub const MESSAGE: u8 = 56; 183 | pub const MAXIMUM_DHCP_MESSAGE_SIZE: u8 = 57; 184 | pub const RENEWAL_TIME_VALUE: u8 = 58; 185 | pub const REBINDING_TIME_VALUE: u8 = 59; 186 | pub const VENDOR_CLASS_IDENTIFIER: u8 = 60; 187 | pub const CLIENT_IDENTIFIER: u8 = 61; 188 | 189 | pub const TFTP_SERVER_NAME: u8 = 66; 190 | pub const BOOTFILE_NAME: u8 = 67; 191 | 192 | pub const USER_CLASS: u8 = 77; 193 | 194 | pub const CLIENT_ARCHITECTURE: u8 = 93; 195 | 196 | pub const TZ_POSIX_STRING: u8 = 100; 197 | pub const TZ_DATABASE_STRING: u8 = 101; 198 | 199 | pub const CLASSLESS_ROUTE_FORMAT: u8 = 121; 200 | 201 | /// Returns title of DHCP Option code, if known. 202 | pub fn title(code: u8) -> Option<&'static str> { 203 | Some(match code { 204 | SUBNET_MASK => "Subnet Mask", 205 | 206 | TIME_OFFSET => "Time Offset", 207 | ROUTER => "Router", 208 | TIME_SERVER => "Time Server", 209 | NAME_SERVER => "Name Server", 210 | DOMAIN_NAME_SERVER => "Domain Name Server", 211 | LOG_SERVER => "Log Server", 212 | COOKIE_SERVER => "Cookie Server", 213 | LPR_SERVER => "LPR Server", 214 | IMPRESS_SERVER => "Impress Server", 215 | RESOURCE_LOCATION_SERVER => "Resource Location Server", 216 | HOST_NAME => "Host Name", 217 | BOOT_FILE_SIZE => "Boot File Size", 218 | MERIT_DUMP_FILE => "Merit Dump File", 219 | DOMAIN_NAME => "Domain Name", 220 | SWAP_SERVER => "Swap Server", 221 | ROOT_PATH => "Root Path", 222 | EXTENSIONS_PATH => "Extensions Path", 223 | 224 | // IP LAYER PARAMETERS PER HOST", 225 | IP_FORWARDING_ENABLE_DISABLE => "IP Forwarding Enable/Disable", 226 | NON_LOCAL_SOURCE_ROUTING_ENABLE_DISABLE => "Non-Local Source Routing Enable/Disable", 227 | POLICY_FILTER => "Policy Filter", 228 | MAXIMUM_DATAGRAM_REASSEMBLY_SIZE => "Maximum Datagram Reassembly Size", 229 | DEFAULT_IP_TIME_TO_LIVE => "Default IP Time-to-live", 230 | PATH_MTU_AGING_TIMEOUT => "Path MTU Aging Timeout", 231 | PATH_MTU_PLATEAU_TABLE => "Path MTU Plateau Table", 232 | 233 | // IP LAYER PARAMETERS PER INTERFACE", 234 | INTERFACE_MTU => "Interface MTU", 235 | ALL_SUBNETS_ARE_LOCAL => "All Subnets are Local", 236 | BROADCAST_ADDRESS => "Broadcast Address", 237 | PERFORM_MASK_DISCOVERY => "Perform Mask Discovery", 238 | MASK_SUPPLIER => "Mask Supplier", 239 | PERFORM_ROUTER_DISCOVERY => "Perform Router Discovery", 240 | ROUTER_SOLICITATION_ADDRESS => "Router Solicitation Address", 241 | STATIC_ROUTE => "Static Route", 242 | 243 | // LINK LAYER PARAMETERS PER INTERFACE", 244 | TRAILER_ENCAPSULATION => "Trailer Encapsulation", 245 | ARP_CACHE_TIMEOUT => "ARP Cache Timeout", 246 | ETHERNET_ENCAPSULATION => "Ethernet Encapsulation", 247 | 248 | // TCP PARAMETERS", 249 | TCP_DEFAULT_TTL => "TCP Default TTL", 250 | TCP_KEEPALIVE_INTERVAL => "TCP Keepalive Interval", 251 | TCP_KEEPALIVE_GARBAGE => "TCP Keepalive Garbage", 252 | 253 | // APPLICATION AND SERVICE PARAMETERS", 254 | NETWORK_INFORMATION_SERVICE_DOMAIN => "Network Information Service Domain", 255 | NETWORK_INFORMATION_SERVERS => "Network Information Servers", 256 | NETWORK_TIME_PROTOCOL_SERVERS => "Network Time Protocol Servers", 257 | VENDOR_SPECIFIC_INFORMATION => "Vendor Specific Information", 258 | NETBIOS_OVER_TCPIP_NAME_SERVER => "NetBIOS over TCP/IP Name Server", 259 | NETBIOS_OVER_TCPIP_DATAGRAM_DISTRIBUTION_SERVER => { 260 | "NetBIOS over TCP/IP Datagram Distribution Server" 261 | } 262 | NETBIOS_OVER_TCPIP_NODE_TYPE => "NetBIOS over TCP/IP Node Type", 263 | NETBIOS_OVER_TCPIP_SCOPE => "NetBIOS over TCP/IP Scope", 264 | XWINDOW_SYSTEM_FONT_SERVER => "X Window System Font Server", 265 | XWINDOW_SYSTEM_DISPLAY_MANAGER => "X Window System Display Manager", 266 | NETWORK_INFORMATION_SERVICEPLUS_DOMAIN => "Network Information Service+ Domain", 267 | NETWORK_INFORMATION_SERVICEPLUS_SERVERS => "Network Information Service+ Servers", 268 | MOBILE_IP_HOME_AGENT => "Mobile IP Home Agent", 269 | SIMPLE_MAIL_TRANSPORT_PROTOCOL => "Simple Mail Transport Protocol (SMTP) Server", 270 | POST_OFFICE_PROTOCOL_SERVER => "Post Office Protocol (POP3) Server", 271 | NETWORK_NEWS_TRANSPORT_PROTOCOL => "Network News Transport Protocol (NNTP) Server", 272 | DEFAULT_WORLD_WIDE_WEB_SERVER => "Default World Wide Web (WWW) Server", 273 | DEFAULT_FINGER_SERVER => "Default Finger Server", 274 | DEFAULT_INTERNET_RELAY_CHAT_SERVER => "Default Internet Relay Chat (IRC) Server", 275 | STREETTALK_SERVER => "StreetTalk Server", 276 | STREETTALK_DIRECTORY_ASSISTANCE => "StreetTalk Directory Assistance (STDA) Server", 277 | 278 | RELAY_AGENT_INFORMATION => "Relay Agent Information", 279 | 280 | // DHCP EXTENSIONS 281 | REQUESTED_IP_ADDRESS => "Requested IP Address", 282 | IP_ADDRESS_LEASE_TIME => "IP Address Lease Time", 283 | OVERLOAD => "Overload", 284 | DHCP_MESSAGE_TYPE => "DHCP Message Type", 285 | SERVER_IDENTIFIER => "Server Identifier", 286 | PARAMETER_REQUEST_LIST => "Parameter Request List", 287 | MESSAGE => "Message", 288 | MAXIMUM_DHCP_MESSAGE_SIZE => "Maximum DHCP Message Size", 289 | RENEWAL_TIME_VALUE => "Renewal (T1) Time Value", 290 | REBINDING_TIME_VALUE => "Rebinding (T2) Time Value", 291 | VENDOR_CLASS_IDENTIFIER => "Vendor class identifier", 292 | CLIENT_IDENTIFIER => "Client-identifier", 293 | 294 | // Find below 295 | TFTP_SERVER_NAME => "TFTP server name", 296 | BOOTFILE_NAME => "Bootfile name", 297 | 298 | USER_CLASS => "User Class", 299 | 300 | CLIENT_ARCHITECTURE => "Client Architecture", 301 | 302 | TZ_POSIX_STRING => "TZ-POSIX String", 303 | TZ_DATABASE_STRING => "TZ-Database String", 304 | CLASSLESS_ROUTE_FORMAT => "Classless Route Format", 305 | 306 | _ => return None, 307 | }) 308 | } 309 | 310 | /// 311 | /// DHCP Message Type. 312 | /// 313 | /// # Standards 314 | /// 315 | /// The semantics of the various DHCP message types are described in RFC 2131 (see Table 2). 316 | /// Their numeric values are described in Section 9.6 of RFC 2132, which begins: 317 | /// 318 | /// > This option is used to convey the type of the DHCP message. The code for this option is 53, 319 | /// > and its length is 1. 320 | /// 321 | #[derive(Copy, Clone, PartialEq, Debug)] 322 | pub enum MessageType { 323 | /// Client broadcast to locate available servers. 324 | Discover = 1, 325 | 326 | /// Server to client in response to DHCPDISCOVER with offer of configuration parameters. 327 | Offer = 2, 328 | 329 | /// Client message to servers either (a) requesting offered parameters from one server and 330 | /// implicitly declining offers from all others, (b) confirming correctness of previously 331 | /// allocated address after, e.g., system reboot, or (c) extending the lease on a particular 332 | /// network address. 333 | Request = 3, 334 | 335 | /// Client to server indicating network address is already in use. 336 | Decline = 4, 337 | 338 | /// Server to client with configuration parameters, including committed network address. 339 | Ack = 5, 340 | 341 | /// Server to client indicating client's notion of network address is incorrect (e.g., client 342 | /// has moved to new subnet) or client's lease as expired. 343 | Nak = 6, 344 | 345 | /// Client to server relinquishing network address and cancelling remaining lease. 346 | Release = 7, 347 | 348 | /// Client to server, asking only for local configuration parameters; client already has 349 | /// externally configured network address. 350 | Inform = 8, 351 | } 352 | 353 | impl MessageType { 354 | pub fn from(val: u8) -> Result { 355 | match val { 356 | 1 => Ok(MessageType::Discover), 357 | 2 => Ok(MessageType::Offer), 358 | 3 => Ok(MessageType::Request), 359 | 4 => Ok(MessageType::Decline), 360 | 5 => Ok(MessageType::Ack), 361 | 6 => Ok(MessageType::Nak), 362 | 7 => Ok(MessageType::Release), 363 | 8 => Ok(MessageType::Inform), 364 | _ => Err(format!("Invalid DHCP Message Type: {:?}", val)), 365 | } 366 | } 367 | } 368 | --------------------------------------------------------------------------------