From a7d79185024e785b0a21069386b8806edfe703fe Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Fri, 9 Aug 2024 21:38:14 +0200 Subject: [PATCH 1/2] 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. --- Cargo.toml | 5 +++++ build.rs | 1 + gen_config.py | 1 + src/lib.rs | 1 + src/socket/dhcpv4.rs | 29 ++++++++++++++++++++++++----- src/wire/dhcpv4.rs | 22 ++++++++++++++++++++++ 6 files changed, 54 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f3e266bd..b6868342f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -249,6 +249,11 @@ dns-max-name-size-64 = [] dns-max-name-size-128 = [] dns-max-name-size-255 = [] # Default +dhcp-max-domain-name-size-32 = [] +dhcp-max-domain-name-size-64 = [] # Default +dhcp-max-domain-name-size-128 = [] +dhcp-max-domain-name-size-256 = [] + rpl-relations-buffer-count-1 = [] rpl-relations-buffer-count-2 = [] rpl-relations-buffer-count-4 = [] diff --git a/build.rs b/build.rs index e1746d23f..69098aee5 100644 --- a/build.rs +++ b/build.rs @@ -19,6 +19,7 @@ static CONFIGS: &[(&str, usize)] = &[ ("DNS_MAX_RESULT_COUNT", 1), ("DNS_MAX_SERVER_COUNT", 1), ("DNS_MAX_NAME_SIZE", 255), + ("DHCP_MAX_DOMAIN_NAME_SIZE", 64), ("RPL_RELATIONS_BUFFER_COUNT", 16), ("RPL_PARENTS_BUFFER_COUNT", 8), // END AUTOGENERATED CONFIG FEATURES diff --git a/gen_config.py b/gen_config.py index 1407ca2d6..9792ee9fd 100644 --- a/gen_config.py +++ b/gen_config.py @@ -40,6 +40,7 @@ def feature(name, default, min, max, pow2=None): feature("dns_max_result_count", default=1, min=1, max=32, pow2=4) feature("dns_max_server_count", default=1, min=1, max=32, pow2=4) feature("dns_max_name_size", default=255, min=64, max=255, pow2=True) +feature("dhcp_max_domain_name_size", default=64, min=32, max=256, pow2=True) feature("rpl_relations_buffer_count", default=16, min=1, max=128, pow2=True) feature("rpl_parents_buffer_count", default=8, min=2, max=32, pow2=True) diff --git a/src/lib.rs b/src/lib.rs index 2e99c9576..a6a10edc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,6 +136,7 @@ pub mod config { pub const DNS_MAX_NAME_SIZE: usize = 255; pub const DNS_MAX_RESULT_COUNT: usize = 1; pub const DNS_MAX_SERVER_COUNT: usize = 1; + pub const DHCP_MAX_DOMAIN_NAME_SIZE: usize = 64; pub const FRAGMENTATION_BUFFER_SIZE: usize = 1500; pub const IFACE_MAX_ADDR_COUNT: usize = 8; pub const IFACE_MAX_MULTICAST_GROUP_COUNT: usize = 4; diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs index b1b3cb583..fda4c5783 100644 --- a/src/socket/dhcpv4.rs +++ b/src/socket/dhcpv4.rs @@ -1,6 +1,8 @@ +use core::str::FromStr; #[cfg(feature = "async")] use core::task::Waker; +use crate::config::DHCP_MAX_DOMAIN_NAME_SIZE; use crate::iface::Context; use crate::time::{Duration, Instant}; use crate::wire::dhcpv4::field as dhcpv4_field; @@ -9,7 +11,7 @@ use crate::wire::{ UdpRepr, DHCP_CLIENT_PORT, DHCP_MAX_DNS_SERVER_COUNT, 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 +24,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 +41,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 +499,10 @@ 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 + .map(String::from_str) + .and_then(Result::ok), packet: None, }; @@ -589,6 +598,7 @@ impl<'a> Socket<'a> { renew_duration: None, rebind_duration: None, dns_servers: None, + domain_name: None, additional_options: self.outgoing_options, }; @@ -739,6 +749,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 +790,7 @@ impl<'a> Socket<'a> { #[cfg(test)] mod test { + use core::str::FromStr; use std::ops::{Deref, DerefMut}; use super::*; @@ -886,6 +898,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 +982,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 +993,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 +1008,7 @@ mod test { router: Some(SERVER_IP), subnet_mask: Some(MASK_24), dns_servers: Some(Vec::from_slice(DNS_IPS).unwrap()), + domain_name: Some(DOMAIN_NAME), lease_duration: Some(1000), ..DHCP_DEFAULT @@ -1007,7 +1022,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 +1036,7 @@ mod test { router: Some(SERVER_IP), subnet_mask: Some(MASK_24), dns_servers: Some(Vec::from_slice(DNS_IPS).unwrap()), + domain_name: Some(DOMAIN_NAME), lease_duration: Some(1000), ..DHCP_DEFAULT @@ -1042,7 +1058,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 +1070,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 +1113,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 +1149,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 +1188,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..58fb30074 100644 --- a/src/wire/dhcpv4.rs +++ b/src/wire/dhcpv4.rs @@ -647,6 +647,8 @@ pub struct Repr<'a> { pub parameter_request_list: Option<&'a [u8]>, /// DNS servers pub dns_servers: Option>, + /// Domain name + pub domain_name: Option<&'a str>, /// The maximum size dhcp packet the interface can receive pub max_size: Option, /// The DHCP IP lease duration, specified in seconds. @@ -692,6 +694,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.as_bytes().len(); + } if let Some(list) = self.parameter_request_list { len += list.len() + 2; } @@ -738,6 +744,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 +809,11 @@ impl<'a> Repr<'a> { net_trace!("DHCP domain name servers contained invalid address"); } } + (field::OPT_DOMAIN_NAME, _) => { + if let Ok(name) = core::str::from_utf8(data) { + domain_name = Some(name); + } + } _ => {} } } @@ -824,6 +836,7 @@ impl<'a> Repr<'a> { client_identifier, parameter_request_list, dns_servers, + domain_name, max_size, lease_duration, renew_duration, @@ -940,6 +953,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 +1187,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 +1218,7 @@ mod test { server_identifier: None, parameter_request_list: Some(&[1, 3, 6, 42]), dns_servers: None, + domain_name: None, additional_options: &[], } } From 35bfdcbcf85333a1755aa59e38a42e59cc21681e Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 17 Aug 2024 10:02:43 +0200 Subject: [PATCH 2/2] Make DHCP option 15 optional --- Cargo.toml | 3 ++- src/socket/dhcpv4.rs | 6 ++++++ src/wire/dhcpv4.rs | 12 +++++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b6868342f..9db413e0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ defmt = ["dep:defmt", "heapless/defmt-03"] "proto-sixlowpan" = ["proto-ipv6"] "proto-sixlowpan-fragmentation" = ["proto-sixlowpan", "_proto-fragmentation"] "proto-dns" = [] +"proto-domainname" = [] "proto-ipsec" = ["proto-ipsec-ah", "proto-ipsec-esp"] "proto-ipsec-ah" = [] "proto-ipsec-esp" = [] @@ -96,7 +97,7 @@ default = [ "std", "log", # needed for `cargo test --no-default-features --features default` :/ "medium-ethernet", "medium-ip", "medium-ieee802154", "phy-raw_socket", "phy-tuntap_interface", - "proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6", "proto-dns", + "proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6", "proto-dns", "proto-domainname", "proto-ipv4-fragmentation", "proto-sixlowpan-fragmentation", "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4", "socket-dns", "socket-mdns", "packetmeta-id", "async" diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs index fda4c5783..a92ad21ae 100644 --- a/src/socket/dhcpv4.rs +++ b/src/socket/dhcpv4.rs @@ -2,6 +2,7 @@ use core::str::FromStr; #[cfg(feature = "async")] use core::task::Waker; +#[cfg(feature = "proto-domainname")] use crate::config::DHCP_MAX_DOMAIN_NAME_SIZE; use crate::iface::Context; use crate::time::{Duration, Instant}; @@ -24,6 +25,7 @@ const DEFAULT_PARAMETER_REQUEST_LIST: &[u8] = &[ dhcpv4_field::OPT_SUBNET_MASK, dhcpv4_field::OPT_ROUTER, dhcpv4_field::OPT_DOMAIN_NAME_SERVER, + #[cfg(feature = "proto-domainname")] dhcpv4_field::OPT_DOMAIN_NAME, ]; @@ -42,6 +44,7 @@ pub struct Config<'a> { /// DNS servers pub dns_servers: Vec, /// Domain name + #[cfg(feature = "proto-domainname")] pub domain_name: Option>, /// Received DHCP packet pub packet: Option>, @@ -499,6 +502,7 @@ impl<'a> Socket<'a> { address: Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len), router: dhcp_repr.router, dns_servers, + #[cfg(feature = "proto-domainname")] domain_name: dhcp_repr .domain_name .map(String::from_str) @@ -598,6 +602,7 @@ impl<'a> Socket<'a> { renew_duration: None, rebind_duration: None, dns_servers: None, + #[cfg(feature = "proto-domainname")] domain_name: None, additional_options: self.outgoing_options, }; @@ -749,6 +754,7 @@ impl<'a> Socket<'a> { address: state.config.address, router: state.config.router, dns_servers: state.config.dns_servers.clone(), + #[cfg(feature = "proto-domainname")] domain_name: state.config.domain_name.clone(), packet: self .receive_packet_buffer diff --git a/src/wire/dhcpv4.rs b/src/wire/dhcpv4.rs index 58fb30074..377ac1cbe 100644 --- a/src/wire/dhcpv4.rs +++ b/src/wire/dhcpv4.rs @@ -648,6 +648,7 @@ pub struct Repr<'a> { /// DNS servers pub dns_servers: Option>, /// Domain name + #[cfg(feature = "proto-domainname")] pub domain_name: Option<&'a str>, /// The maximum size dhcp packet the interface can receive pub max_size: Option, @@ -694,6 +695,7 @@ impl<'a> Repr<'a> { len += 2; len += dns_servers.iter().count() * core::mem::size_of::(); } + #[cfg(feature = "proto-domainname")] if let Some(domain_name) = &self.domain_name { len += 2; len += domain_name.as_bytes().len(); @@ -744,6 +746,7 @@ impl<'a> Repr<'a> { let mut subnet_mask = None; let mut parameter_request_list = None; let mut dns_servers = None; + #[cfg(feature = "proto-domainname")] let mut domain_name = None; let mut max_size = None; let mut lease_duration = None; @@ -809,10 +812,9 @@ impl<'a> Repr<'a> { net_trace!("DHCP domain name servers contained invalid address"); } } + #[cfg(feature = "proto-domainname")] (field::OPT_DOMAIN_NAME, _) => { - if let Ok(name) = core::str::from_utf8(data) { - domain_name = Some(name); - } + domain_name = core::str::from_utf8(data).ok(); } _ => {} } @@ -836,6 +838,7 @@ impl<'a> Repr<'a> { client_identifier, parameter_request_list, dns_servers, + #[cfg(feature = "proto-domainname")] domain_name, max_size, lease_duration, @@ -953,6 +956,7 @@ impl<'a> Repr<'a> { })?; } + #[cfg(feature = "proto-domainname")] if let Some(domain_name) = &self.domain_name { options.emit(DhcpOption { kind: field::OPT_DOMAIN_NAME, @@ -1187,6 +1191,7 @@ mod test { server_identifier: None, parameter_request_list: None, dns_servers: None, + #[cfg(feature = "proto-domainname")] domain_name: None, max_size: None, renew_duration: None, @@ -1218,6 +1223,7 @@ mod test { server_identifier: None, parameter_request_list: Some(&[1, 3, 6, 42]), dns_servers: None, + #[cfg(feature = "proto-domainname")] domain_name: None, additional_options: &[], }