From cf5b0a7818c6cfb0b669cf4134b5e13f90cff8d5 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Fri, 9 Aug 2024 21:38:14 +0200 Subject: [PATCH] Enable support for DHCP option 15 (domain name) This makes `smoltcp` work in industrial networks with Windows DHCPs as they do not support option 119. --- src/socket/dhcpv4.rs | 27 +++++++++++++++++++++------ src/wire/dhcpv4.rs | 30 +++++++++++++++++++++++++++++- src/wire/mod.rs | 3 ++- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs index b1b3cb583..109386e54 100644 --- a/src/socket/dhcpv4.rs +++ b/src/socket/dhcpv4.rs @@ -6,10 +6,11 @@ use crate::time::{Duration, Instant}; use crate::wire::dhcpv4::field as dhcpv4_field; use crate::wire::{ DhcpMessageType, DhcpPacket, DhcpRepr, IpAddress, IpProtocol, Ipv4Address, Ipv4Cidr, Ipv4Repr, - UdpRepr, DHCP_CLIENT_PORT, DHCP_MAX_DNS_SERVER_COUNT, DHCP_SERVER_PORT, UDP_HEADER_LEN, + UdpRepr, DHCP_CLIENT_PORT, DHCP_MAX_DNS_SERVER_COUNT, DHCP_MAX_DOMAIN_NAME_LEN, + DHCP_SERVER_PORT, UDP_HEADER_LEN, }; use crate::wire::{DhcpOption, HardwareAddress}; -use heapless::Vec; +use heapless::{String, Vec}; #[cfg(feature = "async")] use super::WakerRegistration; @@ -22,6 +23,7 @@ const DEFAULT_PARAMETER_REQUEST_LIST: &[u8] = &[ dhcpv4_field::OPT_SUBNET_MASK, dhcpv4_field::OPT_ROUTER, dhcpv4_field::OPT_DOMAIN_NAME_SERVER, + dhcpv4_field::OPT_DOMAIN_NAME, ]; /// IPv4 configuration data provided by the DHCP server. @@ -38,6 +40,8 @@ pub struct Config<'a> { pub router: Option, /// DNS servers pub dns_servers: Vec, + /// Domain name + pub domain_name: Option>, /// Received DHCP packet pub packet: Option>, } @@ -494,6 +498,7 @@ impl<'a> Socket<'a> { address: Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len), router: dhcp_repr.router, dns_servers, + domain_name: dhcp_repr.domain_name.clone(), packet: None, }; @@ -589,6 +594,7 @@ impl<'a> Socket<'a> { renew_duration: None, rebind_duration: None, dns_servers: None, + domain_name: None, additional_options: self.outgoing_options, }; @@ -739,6 +745,7 @@ impl<'a> Socket<'a> { address: state.config.address, router: state.config.router, dns_servers: state.config.dns_servers.clone(), + domain_name: state.config.domain_name.clone(), packet: self .receive_packet_buffer .as_deref() @@ -779,6 +786,7 @@ impl<'a> Socket<'a> { #[cfg(test)] mod test { + use core::str::FromStr; use std::ops::{Deref, DerefMut}; use super::*; @@ -886,6 +894,7 @@ mod test { const DNS_IP_2: Ipv4Address = Ipv4Address([1, 1, 1, 2]); const DNS_IP_3: Ipv4Address = Ipv4Address([1, 1, 1, 3]); const DNS_IPS: &[Ipv4Address] = &[DNS_IP_1, DNS_IP_2, DNS_IP_3]; + const DOMAIN_NAME: &str = "my.domain"; const MASK_24: Ipv4Address = Ipv4Address([255, 255, 255, 0]); @@ -969,6 +978,7 @@ mod test { server_identifier: None, parameter_request_list: None, dns_servers: None, + domain_name: None, max_size: None, renew_duration: None, rebind_duration: None, @@ -979,7 +989,7 @@ mod test { const DHCP_DISCOVER: DhcpRepr = DhcpRepr { message_type: DhcpMessageType::Discover, client_identifier: Some(MY_MAC), - parameter_request_list: Some(&[1, 3, 6]), + parameter_request_list: Some(&[1, 3, 6, 15]), max_size: Some(1432), ..DHCP_DEFAULT }; @@ -994,6 +1004,7 @@ mod test { router: Some(SERVER_IP), subnet_mask: Some(MASK_24), dns_servers: Some(Vec::from_slice(DNS_IPS).unwrap()), + domain_name: Some(String::from_str(DOMAIN_NAME).unwrap()), lease_duration: Some(1000), ..DHCP_DEFAULT @@ -1007,7 +1018,7 @@ mod test { max_size: Some(1432), requested_ip: Some(MY_IP), - parameter_request_list: Some(&[1, 3, 6]), + parameter_request_list: Some(&[1, 3, 6, 15]), ..DHCP_DEFAULT }; @@ -1021,6 +1032,7 @@ mod test { router: Some(SERVER_IP), subnet_mask: Some(MASK_24), dns_servers: Some(Vec::from_slice(DNS_IPS).unwrap()), + domain_name: Some(String::from_str(DOMAIN_NAME).unwrap()), lease_duration: Some(1000), ..DHCP_DEFAULT @@ -1042,7 +1054,7 @@ mod test { max_size: Some(1432), requested_ip: None, - parameter_request_list: Some(&[1, 3, 6]), + parameter_request_list: Some(&[1, 3, 6, 15]), ..DHCP_DEFAULT }; @@ -1054,7 +1066,7 @@ mod test { max_size: Some(1432), requested_ip: None, - parameter_request_list: Some(&[1, 3, 6]), + parameter_request_list: Some(&[1, 3, 6, 15]), ..DHCP_DEFAULT }; @@ -1097,6 +1109,7 @@ mod test { }, address: Ipv4Cidr::new(MY_IP, 24), dns_servers: Vec::from_slice(DNS_IPS).unwrap(), + domain_name: Some(String::from_str(DOMAIN_NAME).unwrap()), router: Some(SERVER_IP), packet: None, }, @@ -1132,6 +1145,7 @@ mod test { }, address: Ipv4Cidr::new(MY_IP, 24), dns_servers: Vec::from_slice(DNS_IPS).unwrap(), + domain_name: Some(String::from_str(DOMAIN_NAME).unwrap()), router: Some(SERVER_IP), packet: None, })) @@ -1170,6 +1184,7 @@ mod test { }, address: Ipv4Cidr::new(MY_IP, 24), dns_servers: Vec::from_slice(DNS_IPS).unwrap(), + domain_name: Some(String::from_str(DOMAIN_NAME).unwrap()), router: Some(SERVER_IP), packet: None, })) diff --git a/src/wire/dhcpv4.rs b/src/wire/dhcpv4.rs index b00f26ff7..3b7ff331c 100644 --- a/src/wire/dhcpv4.rs +++ b/src/wire/dhcpv4.rs @@ -3,7 +3,7 @@ use bitflags::bitflags; use byteorder::{ByteOrder, NetworkEndian}; use core::iter; -use heapless::Vec; +use heapless::{String, Vec}; use super::{Error, Result}; use crate::wire::arp::Hardware; @@ -12,6 +12,7 @@ use crate::wire::{EthernetAddress, Ipv4Address}; pub const SERVER_PORT: u16 = 67; pub const CLIENT_PORT: u16 = 68; pub const MAX_DNS_SERVER_COUNT: usize = 3; +pub const MAX_DOMAIN_NAME_LEN: usize = 255; const DHCP_MAGIC_NUMBER: u32 = 0x63825363; @@ -647,6 +648,8 @@ pub struct Repr<'a> { pub parameter_request_list: Option<&'a [u8]>, /// DNS servers pub dns_servers: Option>, + /// Domain name + pub domain_name: Option>, /// The maximum size dhcp packet the interface can receive pub max_size: Option, /// The DHCP IP lease duration, specified in seconds. @@ -692,6 +695,10 @@ impl<'a> Repr<'a> { len += 2; len += dns_servers.iter().count() * core::mem::size_of::(); } + if let Some(domain_name) = &self.domain_name { + len += 2; + len += domain_name.len(); + } if let Some(list) = self.parameter_request_list { len += list.len() + 2; } @@ -738,6 +745,7 @@ impl<'a> Repr<'a> { let mut subnet_mask = None; let mut parameter_request_list = None; let mut dns_servers = None; + let mut domain_name = None; let mut max_size = None; let mut lease_duration = None; let mut renew_duration = None; @@ -802,6 +810,16 @@ impl<'a> Repr<'a> { net_trace!("DHCP domain name servers contained invalid address"); } } + (field::OPT_DOMAIN_NAME, _) => { + let mut name = String::new(); + + if let Ok(n) = core::str::from_utf8(data) { + if data.len() <= MAX_DOMAIN_NAME_LEN { + name.push_str(n).ok(); + domain_name = Some(name); + } + } + } _ => {} } } @@ -824,6 +842,7 @@ impl<'a> Repr<'a> { client_identifier, parameter_request_list, dns_servers, + domain_name, max_size, lease_duration, renew_duration, @@ -940,6 +959,13 @@ impl<'a> Repr<'a> { })?; } + if let Some(domain_name) = &self.domain_name { + options.emit(DhcpOption { + kind: field::OPT_DOMAIN_NAME, + data: domain_name.as_bytes(), + })?; + } + for option in self.additional_options { options.emit(*option)?; } @@ -1167,6 +1193,7 @@ mod test { server_identifier: None, parameter_request_list: None, dns_servers: None, + domain_name: None, max_size: None, renew_duration: None, rebind_duration: None, @@ -1197,6 +1224,7 @@ mod test { server_identifier: None, parameter_request_list: Some(&[1, 3, 6, 42]), dns_servers: None, + domain_name: None, additional_options: &[], } } diff --git a/src/wire/mod.rs b/src/wire/mod.rs index c197afed9..d78220a50 100644 --- a/src/wire/mod.rs +++ b/src/wire/mod.rs @@ -272,7 +272,8 @@ pub use self::tcp::{ pub use self::dhcpv4::{ DhcpOption, DhcpOptionWriter, Flags as DhcpFlags, MessageType as DhcpMessageType, OpCode as DhcpOpCode, Packet as DhcpPacket, Repr as DhcpRepr, CLIENT_PORT as DHCP_CLIENT_PORT, - MAX_DNS_SERVER_COUNT as DHCP_MAX_DNS_SERVER_COUNT, SERVER_PORT as DHCP_SERVER_PORT, + MAX_DNS_SERVER_COUNT as DHCP_MAX_DNS_SERVER_COUNT, + MAX_DOMAIN_NAME_LEN as DHCP_MAX_DOMAIN_NAME_LEN, SERVER_PORT as DHCP_SERVER_PORT, }; #[cfg(feature = "proto-dns")]