dhcpv4.rs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. use crate::{Error, Result};
  2. use crate::wire::{IpProtocol, IpAddress,
  3. Ipv4Cidr, Ipv4Address, Ipv4Repr,
  4. UdpRepr, UDP_HEADER_LEN,
  5. DhcpPacket, DhcpRepr, DhcpMessageType, DHCP_CLIENT_PORT, DHCP_SERVER_PORT, DHCP_MAX_DNS_SERVER_COUNT};
  6. use crate::wire::dhcpv4::{field as dhcpv4_field};
  7. use crate::socket::{SocketMeta, Context};
  8. use crate::time::{Instant, Duration};
  9. use crate::socket::SocketHandle;
  10. use super::{PollAt, Socket};
  11. const DISCOVER_TIMEOUT: Duration = Duration::from_secs(10);
  12. // timeout doubles every 2 tries.
  13. // total time 5 + 5 + 10 + 10 + 20 = 50s
  14. const REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
  15. const REQUEST_RETRIES: u16 = 5;
  16. const MIN_RENEW_TIMEOUT: Duration = Duration::from_secs(60);
  17. const DEFAULT_LEASE_DURATION: Duration = Duration::from_secs(120);
  18. const PARAMETER_REQUEST_LIST: &[u8] = &[
  19. dhcpv4_field::OPT_SUBNET_MASK,
  20. dhcpv4_field::OPT_ROUTER,
  21. dhcpv4_field::OPT_DOMAIN_NAME_SERVER,
  22. ];
  23. /// IPv4 configuration data provided by the DHCP server.
  24. #[derive(Debug, Eq, PartialEq)]
  25. #[cfg_attr(feature = "defmt", derive(defmt::Format))]
  26. pub struct Config {
  27. /// IP address
  28. pub address: Ipv4Cidr,
  29. /// Router address, also known as default gateway. Does not necessarily
  30. /// match the DHCP server's address.
  31. pub router: Option<Ipv4Address>,
  32. /// DNS servers
  33. pub dns_servers: [Option<Ipv4Address>; DHCP_MAX_DNS_SERVER_COUNT],
  34. }
  35. /// Information on how to reach a DHCP server.
  36. #[derive(Debug, Clone, Copy)]
  37. #[cfg_attr(feature = "defmt", derive(defmt::Format))]
  38. struct ServerInfo {
  39. /// IP address to use as destination in outgoing packets
  40. address: Ipv4Address,
  41. /// Server identifier to use in outgoing packets. Usually equal to server_address,
  42. /// but may differ in some situations (eg DHCP relays)
  43. identifier: Ipv4Address,
  44. }
  45. #[derive(Debug)]
  46. #[cfg_attr(feature = "defmt", derive(defmt::Format))]
  47. struct DiscoverState {
  48. /// When to send next request
  49. retry_at: Instant,
  50. }
  51. #[derive(Debug)]
  52. #[cfg_attr(feature = "defmt", derive(defmt::Format))]
  53. struct RequestState {
  54. /// When to send next request
  55. retry_at: Instant,
  56. /// How many retries have been done
  57. retry: u16,
  58. /// Server we're trying to request from
  59. server: ServerInfo,
  60. /// IP address that we're trying to request.
  61. requested_ip: Ipv4Address,
  62. }
  63. #[derive(Debug)]
  64. #[cfg_attr(feature = "defmt", derive(defmt::Format))]
  65. struct RenewState {
  66. /// Server that gave us the lease
  67. server: ServerInfo,
  68. /// Active network config
  69. config: Config,
  70. /// Renew timer. When reached, we will start attempting
  71. /// to renew this lease with the DHCP server.
  72. /// Must be less or equal than `expires_at`.
  73. renew_at: Instant,
  74. /// Expiration timer. When reached, this lease is no longer valid, so it must be
  75. /// thrown away and the ethernet interface deconfigured.
  76. expires_at: Instant,
  77. }
  78. #[derive(Debug)]
  79. #[cfg_attr(feature = "defmt", derive(defmt::Format))]
  80. enum ClientState {
  81. /// Discovering the DHCP server
  82. Discovering(DiscoverState),
  83. /// Requesting an address
  84. Requesting(RequestState),
  85. /// Having an address, refresh it periodically.
  86. Renewing(RenewState),
  87. }
  88. /// Return value for the `Dhcpv4Socket::poll` function
  89. pub enum Event<'a> {
  90. /// Configuration has been lost (for example, the lease has expired)
  91. Deconfigured,
  92. /// Configuration has been newly acquired, or modified.
  93. Configured(&'a Config),
  94. }
  95. #[derive(Debug)]
  96. pub struct Dhcpv4Socket {
  97. pub(crate) meta: SocketMeta,
  98. /// State of the DHCP client.
  99. state: ClientState,
  100. /// Set to true on config/state change, cleared back to false by the `config` function.
  101. config_changed: bool,
  102. /// xid of the last sent message.
  103. transaction_id: u32,
  104. /// Max lease duration. If set, it sets a maximum cap to the server-provided lease duration.
  105. /// Useful to react faster to IP configuration changes and to test whether renews work correctly.
  106. max_lease_duration: Option<Duration>,
  107. }
  108. /// DHCP client socket.
  109. ///
  110. /// The socket acquires an IP address configuration through DHCP autonomously.
  111. /// You must query the configuration with `.poll()` after every call to `Interface::poll()`,
  112. /// and apply the configuration to the `Interface`.
  113. impl Dhcpv4Socket {
  114. /// Create a DHCPv4 socket
  115. #[allow(clippy::new_without_default)]
  116. pub fn new() -> Self {
  117. Dhcpv4Socket {
  118. meta: SocketMeta::default(),
  119. state: ClientState::Discovering(DiscoverState{
  120. retry_at: Instant::from_millis(0),
  121. }),
  122. config_changed: true,
  123. transaction_id: 1,
  124. max_lease_duration: None,
  125. }
  126. }
  127. pub fn max_lease_duration(&self) -> Option<Duration> {
  128. self.max_lease_duration
  129. }
  130. pub fn set_max_lease_duration(&mut self, max_lease_duration: Option<Duration>) {
  131. self.max_lease_duration = max_lease_duration;
  132. }
  133. pub(crate) fn poll_at(&self, _cx: &Context) -> PollAt {
  134. let t = match &self.state {
  135. ClientState::Discovering(state) => state.retry_at,
  136. ClientState::Requesting(state) => state.retry_at,
  137. ClientState::Renewing(state) => state.renew_at.min(state.expires_at),
  138. };
  139. PollAt::Time(t)
  140. }
  141. pub(crate) fn process(&mut self, cx: &Context, ip_repr: &Ipv4Repr, repr: &UdpRepr, payload: &[u8]) -> Result<()> {
  142. let src_ip = ip_repr.src_addr;
  143. // This is enforced in interface.rs.
  144. assert!(repr.src_port == DHCP_SERVER_PORT && repr.dst_port == DHCP_CLIENT_PORT);
  145. let dhcp_packet = match DhcpPacket::new_checked(payload) {
  146. Ok(dhcp_packet) => dhcp_packet,
  147. Err(e) => {
  148. net_debug!("DHCP invalid pkt from {}: {:?}", src_ip, e);
  149. return Ok(());
  150. }
  151. };
  152. let dhcp_repr = match DhcpRepr::parse(&dhcp_packet) {
  153. Ok(dhcp_repr) => dhcp_repr,
  154. Err(e) => {
  155. net_debug!("DHCP error parsing pkt from {}: {:?}", src_ip, e);
  156. return Ok(());
  157. }
  158. };
  159. if dhcp_repr.client_hardware_address != cx.ethernet_address.unwrap() { return Ok(()) }
  160. if dhcp_repr.transaction_id != self.transaction_id { return Ok(()) }
  161. let server_identifier = match dhcp_repr.server_identifier {
  162. Some(server_identifier) => server_identifier,
  163. None => {
  164. net_debug!("DHCP ignoring {:?} because missing server_identifier", dhcp_repr.message_type);
  165. return Ok(());
  166. }
  167. };
  168. net_debug!("DHCP recv {:?} from {} ({})", dhcp_repr.message_type, src_ip, server_identifier);
  169. match (&mut self.state, dhcp_repr.message_type){
  170. (ClientState::Discovering(_state), DhcpMessageType::Offer) => {
  171. if !dhcp_repr.your_ip.is_unicast() {
  172. net_debug!("DHCP ignoring OFFER because your_ip is not unicast");
  173. return Ok(())
  174. }
  175. self.state = ClientState::Requesting(RequestState {
  176. retry_at: cx.now,
  177. retry: 0,
  178. server: ServerInfo {
  179. address: src_ip,
  180. identifier: server_identifier,
  181. },
  182. requested_ip: dhcp_repr.your_ip // use the offered ip
  183. });
  184. }
  185. (ClientState::Requesting(state), DhcpMessageType::Ack) => {
  186. if let Some((config, renew_at, expires_at)) = Self::parse_ack(cx.now, &dhcp_repr, self.max_lease_duration) {
  187. self.config_changed = true;
  188. self.state = ClientState::Renewing(RenewState{
  189. server: state.server,
  190. config,
  191. renew_at,
  192. expires_at,
  193. });
  194. }
  195. }
  196. (ClientState::Requesting(_), DhcpMessageType::Nak) => {
  197. self.reset();
  198. }
  199. (ClientState::Renewing(state), DhcpMessageType::Ack) => {
  200. if let Some((config, renew_at, expires_at)) = Self::parse_ack(cx.now, &dhcp_repr, self.max_lease_duration) {
  201. state.renew_at = renew_at;
  202. state.expires_at = expires_at;
  203. if state.config != config {
  204. self.config_changed = true;
  205. state.config = config;
  206. }
  207. }
  208. }
  209. (ClientState::Renewing(_), DhcpMessageType::Nak) => {
  210. self.reset();
  211. }
  212. _ => {
  213. net_debug!("DHCP ignoring {:?}: unexpected in current state", dhcp_repr.message_type);
  214. }
  215. }
  216. Ok(())
  217. }
  218. fn parse_ack(now: Instant, dhcp_repr: &DhcpRepr, max_lease_duration: Option<Duration>) -> Option<(Config, Instant, Instant)> {
  219. let subnet_mask = match dhcp_repr.subnet_mask {
  220. Some(subnet_mask) => subnet_mask,
  221. None => {
  222. net_debug!("DHCP ignoring ACK because missing subnet_mask");
  223. return None
  224. }
  225. };
  226. let prefix_len = match IpAddress::Ipv4(subnet_mask).to_prefix_len() {
  227. Some(prefix_len) => prefix_len,
  228. None => {
  229. net_debug!("DHCP ignoring ACK because subnet_mask is not a valid mask");
  230. return None
  231. }
  232. };
  233. if !dhcp_repr.your_ip.is_unicast() {
  234. net_debug!("DHCP ignoring ACK because your_ip is not unicast");
  235. return None
  236. }
  237. let mut lease_duration = dhcp_repr.lease_duration.map(|d| Duration::from_secs(d as _)).unwrap_or(DEFAULT_LEASE_DURATION);
  238. if let Some(max_lease_duration) = max_lease_duration {
  239. lease_duration = lease_duration.min(max_lease_duration);
  240. }
  241. // Cleanup the DNS servers list, keeping only unicasts/
  242. // TP-Link TD-W8970 sends 0.0.0.0 as second DNS server if there's only one configured :(
  243. let mut dns_servers = [None; DHCP_MAX_DNS_SERVER_COUNT];
  244. if let Some(received) = dhcp_repr.dns_servers {
  245. let mut i = 0;
  246. for addr in received.iter() {
  247. if let Some(addr) = addr{
  248. if addr.is_unicast() {
  249. // This can never be out-of-bounds since both arrays have length DHCP_MAX_DNS_SERVER_COUNT
  250. dns_servers[i] = Some(*addr);
  251. i += 1;
  252. }
  253. }
  254. }
  255. }
  256. let config = Config{
  257. address: Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len),
  258. router: dhcp_repr.router,
  259. dns_servers: dns_servers
  260. };
  261. // RFC 2131 indicates clients should renew a lease halfway through its expiration.
  262. let renew_at = now + lease_duration / 2;
  263. let expires_at = now + lease_duration;
  264. Some((config, renew_at, expires_at))
  265. }
  266. pub(crate) fn dispatch<F>(&mut self, cx: &Context, emit: F) -> Result<()>
  267. where F: FnOnce((Ipv4Repr, UdpRepr, DhcpRepr)) -> Result<()> {
  268. // note: Dhcpv4Socket is only usable in ethernet mediums, so the
  269. // unwrap can never fail.
  270. let ethernet_addr = cx.ethernet_address.unwrap();
  271. // Worst case biggest IPv4 header length.
  272. // 0x0f * 4 = 60 bytes.
  273. const MAX_IPV4_HEADER_LEN: usize = 60;
  274. // We don't directly increment transaction_id because sending the packet
  275. // may fail. We only want to update state after succesfully sending.
  276. let next_transaction_id = self.transaction_id + 1;
  277. let mut dhcp_repr = DhcpRepr {
  278. message_type: DhcpMessageType::Discover,
  279. transaction_id: next_transaction_id,
  280. client_hardware_address: ethernet_addr,
  281. client_ip: Ipv4Address::UNSPECIFIED,
  282. your_ip: Ipv4Address::UNSPECIFIED,
  283. server_ip: Ipv4Address::UNSPECIFIED,
  284. router: None,
  285. subnet_mask: None,
  286. relay_agent_ip: Ipv4Address::UNSPECIFIED,
  287. broadcast: true,
  288. requested_ip: None,
  289. client_identifier: Some(ethernet_addr),
  290. server_identifier: None,
  291. parameter_request_list: Some(PARAMETER_REQUEST_LIST),
  292. max_size: Some((cx.caps.ip_mtu() - MAX_IPV4_HEADER_LEN - UDP_HEADER_LEN) as u16),
  293. lease_duration: None,
  294. dns_servers: None,
  295. };
  296. let udp_repr = UdpRepr {
  297. src_port: DHCP_CLIENT_PORT,
  298. dst_port: DHCP_SERVER_PORT,
  299. };
  300. let mut ipv4_repr = Ipv4Repr {
  301. src_addr: Ipv4Address::UNSPECIFIED,
  302. dst_addr: Ipv4Address::BROADCAST,
  303. protocol: IpProtocol::Udp,
  304. payload_len: 0, // filled right before emit
  305. hop_limit: 64,
  306. };
  307. match &mut self.state {
  308. ClientState::Discovering(state) => {
  309. if cx.now < state.retry_at {
  310. return Err(Error::Exhausted)
  311. }
  312. // send packet
  313. net_debug!("DHCP send DISCOVER to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr);
  314. ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
  315. emit((ipv4_repr, udp_repr, dhcp_repr))?;
  316. // Update state AFTER the packet has been successfully sent.
  317. state.retry_at = cx.now + DISCOVER_TIMEOUT;
  318. self.transaction_id = next_transaction_id;
  319. Ok(())
  320. }
  321. ClientState::Requesting(state) => {
  322. if cx.now < state.retry_at {
  323. return Err(Error::Exhausted)
  324. }
  325. if state.retry >= REQUEST_RETRIES {
  326. net_debug!("DHCP request retries exceeded, restarting discovery");
  327. self.reset();
  328. // return Ok so we get polled again
  329. return Ok(())
  330. }
  331. dhcp_repr.message_type = DhcpMessageType::Request;
  332. dhcp_repr.broadcast = false;
  333. dhcp_repr.requested_ip = Some(state.requested_ip);
  334. dhcp_repr.server_identifier = Some(state.server.identifier);
  335. net_debug!("DHCP send request to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr);
  336. ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
  337. emit((ipv4_repr, udp_repr, dhcp_repr))?;
  338. // Exponential backoff: Double every 2 retries.
  339. state.retry_at = cx.now + (REQUEST_TIMEOUT << (state.retry as u32 / 2));
  340. state.retry += 1;
  341. self.transaction_id = next_transaction_id;
  342. Ok(())
  343. }
  344. ClientState::Renewing(state) => {
  345. if state.expires_at <= cx.now {
  346. net_debug!("DHCP lease expired");
  347. self.reset();
  348. // return Ok so we get polled again
  349. return Ok(())
  350. }
  351. if cx.now < state.renew_at {
  352. return Err(Error::Exhausted)
  353. }
  354. ipv4_repr.src_addr = state.config.address.address();
  355. ipv4_repr.dst_addr = state.server.address;
  356. dhcp_repr.message_type = DhcpMessageType::Request;
  357. dhcp_repr.client_ip = state.config.address.address();
  358. dhcp_repr.broadcast = false;
  359. net_debug!("DHCP send renew to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr);
  360. ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
  361. emit((ipv4_repr, udp_repr, dhcp_repr))?;
  362. // In both RENEWING and REBINDING states, if the client receives no
  363. // response to its DHCPREQUEST message, the client SHOULD wait one-half
  364. // of the remaining time until T2 (in RENEWING state) and one-half of
  365. // the remaining lease time (in REBINDING state), down to a minimum of
  366. // 60 seconds, before retransmitting the DHCPREQUEST message.
  367. state.renew_at = cx.now + MIN_RENEW_TIMEOUT.max((state.expires_at - cx.now) / 2);
  368. self.transaction_id = next_transaction_id;
  369. Ok(())
  370. }
  371. }
  372. }
  373. /// Return the socket handle.
  374. #[inline]
  375. pub fn handle(&self) -> SocketHandle {
  376. self.meta.handle
  377. }
  378. /// Reset state and restart discovery phase.
  379. ///
  380. /// Use this to speed up acquisition of an address in a new
  381. /// network if a link was down and it is now back up.
  382. pub fn reset(&mut self) {
  383. net_trace!("DHCP reset");
  384. if let ClientState::Renewing(_) = &self.state {
  385. self.config_changed = true;
  386. }
  387. self.state = ClientState::Discovering(DiscoverState{
  388. retry_at: Instant::from_millis(0),
  389. });
  390. }
  391. /// Query the socket for configuration changes.
  392. ///
  393. /// The socket has an internal "configuration changed" flag. If
  394. /// set, this function returns the configuration and resets the flag.
  395. pub fn poll(&mut self) -> Option<Event<'_>> {
  396. if !self.config_changed {
  397. None
  398. } else if let ClientState::Renewing(state) = &self.state {
  399. self.config_changed = false;
  400. Some(Event::Configured(&state.config))
  401. } else {
  402. self.config_changed = false;
  403. Some(Event::Deconfigured)
  404. }
  405. }
  406. }
  407. impl<'a> Into<Socket<'a>> for Dhcpv4Socket {
  408. fn into(self) -> Socket<'a> {
  409. Socket::Dhcpv4(self)
  410. }
  411. }