ya_smoltcp/socket/
dhcpv4.rs

1use crate::iface::Context;
2use crate::time::{Duration, Instant};
3use crate::wire::dhcpv4::field as dhcpv4_field;
4use crate::wire::HardwareAddress;
5use crate::wire::{
6    DhcpMessageType, DhcpPacket, DhcpRepr, IpAddress, IpProtocol, Ipv4Address, Ipv4Cidr, Ipv4Repr,
7    UdpRepr, DHCP_CLIENT_PORT, DHCP_MAX_DNS_SERVER_COUNT, DHCP_SERVER_PORT, UDP_HEADER_LEN,
8};
9use crate::{Error, Result};
10
11use super::PollAt;
12
13const DISCOVER_TIMEOUT: Duration = Duration::from_secs(10);
14
15// timeout doubles every 2 tries.
16// total time 5 + 5 + 10 + 10 + 20 = 50s
17const REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
18const REQUEST_RETRIES: u16 = 5;
19
20const MIN_RENEW_TIMEOUT: Duration = Duration::from_secs(60);
21
22const DEFAULT_LEASE_DURATION: Duration = Duration::from_secs(120);
23
24const PARAMETER_REQUEST_LIST: &[u8] = &[
25    dhcpv4_field::OPT_SUBNET_MASK,
26    dhcpv4_field::OPT_ROUTER,
27    dhcpv4_field::OPT_DOMAIN_NAME_SERVER,
28];
29
30/// IPv4 configuration data provided by the DHCP server.
31#[derive(Debug, Eq, PartialEq, Clone, Copy)]
32#[cfg_attr(feature = "defmt", derive(defmt::Format))]
33pub struct Config {
34    /// IP address
35    pub address: Ipv4Cidr,
36    /// Router address, also known as default gateway. Does not necessarily
37    /// match the DHCP server's address.
38    pub router: Option<Ipv4Address>,
39    /// DNS servers
40    pub dns_servers: [Option<Ipv4Address>; DHCP_MAX_DNS_SERVER_COUNT],
41}
42
43/// Information on how to reach a DHCP server.
44#[derive(Debug, Clone, Copy)]
45#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46struct ServerInfo {
47    /// IP address to use as destination in outgoing packets
48    address: Ipv4Address,
49    /// Server identifier to use in outgoing packets. Usually equal to server_address,
50    /// but may differ in some situations (eg DHCP relays)
51    identifier: Ipv4Address,
52}
53
54#[derive(Debug)]
55#[cfg_attr(feature = "defmt", derive(defmt::Format))]
56struct DiscoverState {
57    /// When to send next request
58    retry_at: Instant,
59}
60
61#[derive(Debug)]
62#[cfg_attr(feature = "defmt", derive(defmt::Format))]
63struct RequestState {
64    /// When to send next request
65    retry_at: Instant,
66    /// How many retries have been done
67    retry: u16,
68    /// Server we're trying to request from
69    server: ServerInfo,
70    /// IP address that we're trying to request.
71    requested_ip: Ipv4Address,
72}
73
74#[derive(Debug)]
75#[cfg_attr(feature = "defmt", derive(defmt::Format))]
76struct RenewState {
77    /// Server that gave us the lease
78    server: ServerInfo,
79    /// Active network config
80    config: Config,
81
82    /// Renew timer. When reached, we will start attempting
83    /// to renew this lease with the DHCP server.
84    /// Must be less or equal than `expires_at`.
85    renew_at: Instant,
86    /// Expiration timer. When reached, this lease is no longer valid, so it must be
87    /// thrown away and the ethernet interface deconfigured.
88    expires_at: Instant,
89}
90
91#[derive(Debug)]
92#[cfg_attr(feature = "defmt", derive(defmt::Format))]
93enum ClientState {
94    /// Discovering the DHCP server
95    Discovering(DiscoverState),
96    /// Requesting an address
97    Requesting(RequestState),
98    /// Having an address, refresh it periodically.
99    Renewing(RenewState),
100}
101
102/// Return value for the `Dhcpv4Socket::poll` function
103#[derive(Debug, PartialEq, Eq)]
104#[cfg_attr(feature = "defmt", derive(defmt::Format))]
105pub enum Event {
106    /// Configuration has been lost (for example, the lease has expired)
107    Deconfigured,
108    /// Configuration has been newly acquired, or modified.
109    Configured(Config),
110}
111
112#[derive(Debug)]
113pub struct Dhcpv4Socket {
114    /// State of the DHCP client.
115    state: ClientState,
116    /// Set to true on config/state change, cleared back to false by the `config` function.
117    config_changed: bool,
118    /// xid of the last sent message.
119    transaction_id: u32,
120
121    /// Max lease duration. If set, it sets a maximum cap to the server-provided lease duration.
122    /// Useful to react faster to IP configuration changes and to test whether renews work correctly.
123    max_lease_duration: Option<Duration>,
124
125    /// Ignore NAKs.
126    ignore_naks: bool,
127}
128
129/// DHCP client socket.
130///
131/// The socket acquires an IP address configuration through DHCP autonomously.
132/// You must query the configuration with `.poll()` after every call to `Interface::poll()`,
133/// and apply the configuration to the `Interface`.
134impl Dhcpv4Socket {
135    /// Create a DHCPv4 socket
136    #[allow(clippy::new_without_default)]
137    pub fn new() -> Self {
138        Dhcpv4Socket {
139            state: ClientState::Discovering(DiscoverState {
140                retry_at: Instant::from_millis(0),
141            }),
142            config_changed: true,
143            transaction_id: 1,
144            max_lease_duration: None,
145            ignore_naks: false,
146        }
147    }
148
149    /// Get the configured max lease duration.
150    ///
151    /// See also [`Self::set_max_lease_duration()`]
152    pub fn max_lease_duration(&self) -> Option<Duration> {
153        self.max_lease_duration
154    }
155
156    /// Set the max lease duration.
157    ///
158    /// When set, the lease duration will be capped at the configured duration if the
159    /// DHCP server gives us a longer lease. This is generally not recommended, but
160    /// can be useful for debugging or reacting faster to network configuration changes.
161    ///
162    /// If None, no max is applied (the lease duration from the DHCP server is used.)
163    pub fn set_max_lease_duration(&mut self, max_lease_duration: Option<Duration>) {
164        self.max_lease_duration = max_lease_duration;
165    }
166
167    /// Get whether to ignore NAKs.
168    ///
169    /// See also [`Self::set_ignore_naks()`]
170    pub fn ignore_naks(&self) -> bool {
171        self.ignore_naks
172    }
173
174    /// Set whether to ignore NAKs.
175    ///
176    /// This is not compliant with the DHCP RFCs, since theoretically
177    /// we must stop using the assigned IP when receiving a NAK. This
178    /// can increase reliability on broken networks with buggy routers
179    /// or rogue DHCP servers, however.
180    pub fn set_ignore_naks(&mut self, ignore_naks: bool) {
181        self.ignore_naks = ignore_naks;
182    }
183
184    pub(crate) fn poll_at(&self, _cx: &mut Context) -> PollAt {
185        let t = match &self.state {
186            ClientState::Discovering(state) => state.retry_at,
187            ClientState::Requesting(state) => state.retry_at,
188            ClientState::Renewing(state) => state.renew_at.min(state.expires_at),
189        };
190        PollAt::Time(t)
191    }
192
193    pub(crate) fn process(
194        &mut self,
195        cx: &mut Context,
196        ip_repr: &Ipv4Repr,
197        repr: &UdpRepr,
198        payload: &[u8],
199    ) -> Result<()> {
200        let src_ip = ip_repr.src_addr;
201
202        // This is enforced in interface.rs.
203        assert!(repr.src_port == DHCP_SERVER_PORT && repr.dst_port == DHCP_CLIENT_PORT);
204
205        let dhcp_packet = match DhcpPacket::new_checked(payload) {
206            Ok(dhcp_packet) => dhcp_packet,
207            Err(e) => {
208                net_debug!("DHCP invalid pkt from {}: {:?}", src_ip, e);
209                return Ok(());
210            }
211        };
212        let dhcp_repr = match DhcpRepr::parse(&dhcp_packet) {
213            Ok(dhcp_repr) => dhcp_repr,
214            Err(e) => {
215                net_debug!("DHCP error parsing pkt from {}: {:?}", src_ip, e);
216                return Ok(());
217            }
218        };
219        let hardware_addr = if let Some(HardwareAddress::Ethernet(addr)) = cx.hardware_addr() {
220            addr
221        } else {
222            return Err(Error::Malformed);
223        };
224
225        if dhcp_repr.client_hardware_address != hardware_addr {
226            return Ok(());
227        }
228        if dhcp_repr.transaction_id != self.transaction_id {
229            return Ok(());
230        }
231        let server_identifier = match dhcp_repr.server_identifier {
232            Some(server_identifier) => server_identifier,
233            None => {
234                net_debug!(
235                    "DHCP ignoring {:?} because missing server_identifier",
236                    dhcp_repr.message_type
237                );
238                return Ok(());
239            }
240        };
241
242        net_debug!(
243            "DHCP recv {:?} from {}: {:?}",
244            dhcp_repr.message_type,
245            src_ip,
246            dhcp_repr
247        );
248
249        match (&mut self.state, dhcp_repr.message_type) {
250            (ClientState::Discovering(_state), DhcpMessageType::Offer) => {
251                if !dhcp_repr.your_ip.is_unicast() {
252                    net_debug!("DHCP ignoring OFFER because your_ip is not unicast");
253                    return Ok(());
254                }
255
256                self.state = ClientState::Requesting(RequestState {
257                    retry_at: cx.now(),
258                    retry: 0,
259                    server: ServerInfo {
260                        address: src_ip,
261                        identifier: server_identifier,
262                    },
263                    requested_ip: dhcp_repr.your_ip, // use the offered ip
264                });
265            }
266            (ClientState::Requesting(state), DhcpMessageType::Ack) => {
267                if let Some((config, renew_at, expires_at)) =
268                    Self::parse_ack(cx.now(), &dhcp_repr, self.max_lease_duration)
269                {
270                    self.config_changed = true;
271                    self.state = ClientState::Renewing(RenewState {
272                        server: state.server,
273                        config,
274                        renew_at,
275                        expires_at,
276                    });
277                }
278            }
279            (ClientState::Requesting(_), DhcpMessageType::Nak) => {
280                if !self.ignore_naks {
281                    self.reset();
282                }
283            }
284            (ClientState::Renewing(state), DhcpMessageType::Ack) => {
285                if let Some((config, renew_at, expires_at)) =
286                    Self::parse_ack(cx.now(), &dhcp_repr, self.max_lease_duration)
287                {
288                    state.renew_at = renew_at;
289                    state.expires_at = expires_at;
290                    if state.config != config {
291                        self.config_changed = true;
292                        state.config = config;
293                    }
294                }
295            }
296            (ClientState::Renewing(_), DhcpMessageType::Nak) => {
297                if !self.ignore_naks {
298                    self.reset();
299                }
300            }
301            _ => {
302                net_debug!(
303                    "DHCP ignoring {:?}: unexpected in current state",
304                    dhcp_repr.message_type
305                );
306            }
307        }
308
309        Ok(())
310    }
311
312    fn parse_ack(
313        now: Instant,
314        dhcp_repr: &DhcpRepr,
315        max_lease_duration: Option<Duration>,
316    ) -> Option<(Config, Instant, Instant)> {
317        let subnet_mask = match dhcp_repr.subnet_mask {
318            Some(subnet_mask) => subnet_mask,
319            None => {
320                net_debug!("DHCP ignoring ACK because missing subnet_mask");
321                return None;
322            }
323        };
324
325        let prefix_len = match IpAddress::Ipv4(subnet_mask).prefix_len() {
326            Some(prefix_len) => prefix_len,
327            None => {
328                net_debug!("DHCP ignoring ACK because subnet_mask is not a valid mask");
329                return None;
330            }
331        };
332
333        if !dhcp_repr.your_ip.is_unicast() {
334            net_debug!("DHCP ignoring ACK because your_ip is not unicast");
335            return None;
336        }
337
338        let mut lease_duration = dhcp_repr
339            .lease_duration
340            .map(|d| Duration::from_secs(d as _))
341            .unwrap_or(DEFAULT_LEASE_DURATION);
342        if let Some(max_lease_duration) = max_lease_duration {
343            lease_duration = lease_duration.min(max_lease_duration);
344        }
345
346        // Cleanup the DNS servers list, keeping only unicasts/
347        // TP-Link TD-W8970 sends 0.0.0.0 as second DNS server if there's only one configured :(
348        let mut dns_servers = [None; DHCP_MAX_DNS_SERVER_COUNT];
349        if let Some(received) = dhcp_repr.dns_servers {
350            let mut i = 0;
351            for addr in received.iter().flatten() {
352                if addr.is_unicast() {
353                    // This can never be out-of-bounds since both arrays have length DHCP_MAX_DNS_SERVER_COUNT
354                    dns_servers[i] = Some(*addr);
355                    i += 1;
356                }
357            }
358        }
359        let config = Config {
360            address: Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len),
361            router: dhcp_repr.router,
362            dns_servers: dns_servers,
363        };
364
365        // RFC 2131 indicates clients should renew a lease halfway through its expiration.
366        let renew_at = now + lease_duration / 2;
367        let expires_at = now + lease_duration;
368
369        Some((config, renew_at, expires_at))
370    }
371
372    #[cfg(not(test))]
373    fn random_transaction_id(cx: &mut Context) -> u32 {
374        cx.rand().rand_u32()
375    }
376
377    #[cfg(test)]
378    fn random_transaction_id(_cx: &mut Context) -> u32 {
379        0x12345678
380    }
381
382    pub(crate) fn dispatch<F>(&mut self, cx: &mut Context, emit: F) -> Result<()>
383    where
384        F: FnOnce(&mut Context, (Ipv4Repr, UdpRepr, DhcpRepr)) -> Result<()>,
385    {
386        // note: Dhcpv4Socket is only usable in ethernet mediums, so the
387        // unwrap can never fail.
388        let ethernet_addr = if let Some(HardwareAddress::Ethernet(addr)) = cx.hardware_addr() {
389            addr
390        } else {
391            return Err(Error::Malformed);
392        };
393
394        // Worst case biggest IPv4 header length.
395        // 0x0f * 4 = 60 bytes.
396        const MAX_IPV4_HEADER_LEN: usize = 60;
397
398        // We don't directly modify self.transaction_id because sending the packet
399        // may fail. We only want to update state after succesfully sending.
400        let next_transaction_id = Self::random_transaction_id(cx);
401
402        let mut dhcp_repr = DhcpRepr {
403            message_type: DhcpMessageType::Discover,
404            transaction_id: next_transaction_id,
405            client_hardware_address: ethernet_addr,
406            client_ip: Ipv4Address::UNSPECIFIED,
407            your_ip: Ipv4Address::UNSPECIFIED,
408            server_ip: Ipv4Address::UNSPECIFIED,
409            router: None,
410            subnet_mask: None,
411            relay_agent_ip: Ipv4Address::UNSPECIFIED,
412            broadcast: false,
413            requested_ip: None,
414            client_identifier: Some(ethernet_addr),
415            server_identifier: None,
416            parameter_request_list: Some(PARAMETER_REQUEST_LIST),
417            max_size: Some((cx.ip_mtu() - MAX_IPV4_HEADER_LEN - UDP_HEADER_LEN) as u16),
418            lease_duration: None,
419            dns_servers: None,
420        };
421
422        let udp_repr = UdpRepr {
423            src_port: DHCP_CLIENT_PORT,
424            dst_port: DHCP_SERVER_PORT,
425        };
426
427        let mut ipv4_repr = Ipv4Repr {
428            src_addr: Ipv4Address::UNSPECIFIED,
429            dst_addr: Ipv4Address::BROADCAST,
430            protocol: IpProtocol::Udp,
431            payload_len: 0, // filled right before emit
432            hop_limit: 64,
433        };
434
435        match &mut self.state {
436            ClientState::Discovering(state) => {
437                if cx.now() < state.retry_at {
438                    return Err(Error::Exhausted);
439                }
440
441                // send packet
442                net_debug!(
443                    "DHCP send DISCOVER to {}: {:?}",
444                    ipv4_repr.dst_addr,
445                    dhcp_repr
446                );
447                ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
448                emit(cx, (ipv4_repr, udp_repr, dhcp_repr))?;
449
450                // Update state AFTER the packet has been successfully sent.
451                state.retry_at = cx.now() + DISCOVER_TIMEOUT;
452                self.transaction_id = next_transaction_id;
453                Ok(())
454            }
455            ClientState::Requesting(state) => {
456                if cx.now() < state.retry_at {
457                    return Err(Error::Exhausted);
458                }
459
460                if state.retry >= REQUEST_RETRIES {
461                    net_debug!("DHCP request retries exceeded, restarting discovery");
462                    self.reset();
463                    // return Ok so we get polled again
464                    return Ok(());
465                }
466
467                dhcp_repr.message_type = DhcpMessageType::Request;
468                dhcp_repr.requested_ip = Some(state.requested_ip);
469                dhcp_repr.server_identifier = Some(state.server.identifier);
470
471                net_debug!(
472                    "DHCP send request to {}: {:?}",
473                    ipv4_repr.dst_addr,
474                    dhcp_repr
475                );
476                ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
477                emit(cx, (ipv4_repr, udp_repr, dhcp_repr))?;
478
479                // Exponential backoff: Double every 2 retries.
480                state.retry_at = cx.now() + (REQUEST_TIMEOUT << (state.retry as u32 / 2));
481                state.retry += 1;
482
483                self.transaction_id = next_transaction_id;
484                Ok(())
485            }
486            ClientState::Renewing(state) => {
487                if state.expires_at <= cx.now() {
488                    net_debug!("DHCP lease expired");
489                    self.reset();
490                    // return Ok so we get polled again
491                    return Ok(());
492                }
493
494                if cx.now() < state.renew_at {
495                    return Err(Error::Exhausted);
496                }
497
498                ipv4_repr.src_addr = state.config.address.address();
499                ipv4_repr.dst_addr = state.server.address;
500                dhcp_repr.message_type = DhcpMessageType::Request;
501                dhcp_repr.client_ip = state.config.address.address();
502
503                net_debug!("DHCP send renew to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr);
504                ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
505                emit(cx, (ipv4_repr, udp_repr, dhcp_repr))?;
506
507                // In both RENEWING and REBINDING states, if the client receives no
508                // response to its DHCPREQUEST message, the client SHOULD wait one-half
509                // of the remaining time until T2 (in RENEWING state) and one-half of
510                // the remaining lease time (in REBINDING state), down to a minimum of
511                // 60 seconds, before retransmitting the DHCPREQUEST message.
512                state.renew_at =
513                    cx.now() + MIN_RENEW_TIMEOUT.max((state.expires_at - cx.now()) / 2);
514
515                self.transaction_id = next_transaction_id;
516                Ok(())
517            }
518        }
519    }
520
521    /// Reset state and restart discovery phase.
522    ///
523    /// Use this to speed up acquisition of an address in a new
524    /// network if a link was down and it is now back up.
525    pub fn reset(&mut self) {
526        net_trace!("DHCP reset");
527        if let ClientState::Renewing(_) = &self.state {
528            self.config_changed = true;
529        }
530        self.state = ClientState::Discovering(DiscoverState {
531            retry_at: Instant::from_millis(0),
532        });
533    }
534
535    /// Query the socket for configuration changes.
536    ///
537    /// The socket has an internal "configuration changed" flag. If
538    /// set, this function returns the configuration and resets the flag.
539    pub fn poll(&mut self) -> Option<Event> {
540        if !self.config_changed {
541            None
542        } else if let ClientState::Renewing(state) = &self.state {
543            self.config_changed = false;
544            Some(Event::Configured(state.config))
545        } else {
546            self.config_changed = false;
547            Some(Event::Deconfigured)
548        }
549    }
550}
551
552#[cfg(test)]
553mod test {
554
555    use std::ops::{Deref, DerefMut};
556
557    use super::*;
558    use crate::wire::EthernetAddress;
559
560    // =========================================================================================//
561    // Helper functions
562
563    struct TestSocket {
564        socket: Dhcpv4Socket,
565        cx: Context<'static>,
566    }
567
568    impl Deref for TestSocket {
569        type Target = Dhcpv4Socket;
570        fn deref(&self) -> &Self::Target {
571            &self.socket
572        }
573    }
574
575    impl DerefMut for TestSocket {
576        fn deref_mut(&mut self) -> &mut Self::Target {
577            &mut self.socket
578        }
579    }
580
581    fn send(
582        s: &mut TestSocket,
583        timestamp: Instant,
584        (ip_repr, udp_repr, dhcp_repr): (Ipv4Repr, UdpRepr, DhcpRepr),
585    ) -> Result<()> {
586        s.cx.set_now(timestamp);
587
588        net_trace!("send: {:?}", ip_repr);
589        net_trace!("      {:?}", udp_repr);
590        net_trace!("      {:?}", dhcp_repr);
591
592        let mut payload = vec![0; dhcp_repr.buffer_len()];
593        dhcp_repr
594            .emit(&mut DhcpPacket::new_unchecked(&mut payload))
595            .unwrap();
596
597        s.socket.process(&mut s.cx, &ip_repr, &udp_repr, &payload)
598    }
599
600    fn recv(s: &mut TestSocket, timestamp: Instant, reprs: &[(Ipv4Repr, UdpRepr, DhcpRepr)]) {
601        s.cx.set_now(timestamp);
602
603        let mut i = 0;
604
605        while s.socket.poll_at(&mut s.cx) <= PollAt::Time(timestamp) {
606            let _ = s
607                .socket
608                .dispatch(&mut s.cx, |_, (mut ip_repr, udp_repr, dhcp_repr)| {
609                    assert_eq!(ip_repr.protocol, IpProtocol::Udp);
610                    assert_eq!(
611                        ip_repr.payload_len,
612                        udp_repr.header_len() + dhcp_repr.buffer_len()
613                    );
614
615                    // We validated the payload len, change it to 0 to make equality testing easier
616                    ip_repr.payload_len = 0;
617
618                    net_trace!("recv: {:?}", ip_repr);
619                    net_trace!("      {:?}", udp_repr);
620                    net_trace!("      {:?}", dhcp_repr);
621
622                    let got_repr = (ip_repr, udp_repr, dhcp_repr);
623                    match reprs.get(i) {
624                        Some(want_repr) => assert_eq!(want_repr, &got_repr),
625                        None => panic!("Too many reprs emitted"),
626                    }
627                    i += 1;
628                    Ok(())
629                });
630        }
631
632        assert_eq!(i, reprs.len());
633    }
634
635    macro_rules! send {
636        ($socket:ident, $repr:expr) =>
637            (send!($socket, time 0, $repr));
638        ($socket:ident, $repr:expr, $result:expr) =>
639            (send!($socket, time 0, $repr, $result));
640        ($socket:ident, time $time:expr, $repr:expr) =>
641            (send!($socket, time $time, $repr, Ok(( ))));
642        ($socket:ident, time $time:expr, $repr:expr, $result:expr) =>
643            (assert_eq!(send(&mut $socket, Instant::from_millis($time), $repr), $result));
644    }
645
646    macro_rules! recv {
647        ($socket:ident, $reprs:expr) => ({
648            recv!($socket, time 0, $reprs);
649        });
650        ($socket:ident, time $time:expr, $reprs:expr) => ({
651            recv(&mut $socket, Instant::from_millis($time), &$reprs);
652        });
653    }
654
655    // =========================================================================================//
656    // Constants
657
658    const TXID: u32 = 0x12345678;
659
660    const MY_IP: Ipv4Address = Ipv4Address([192, 168, 1, 42]);
661    const SERVER_IP: Ipv4Address = Ipv4Address([192, 168, 1, 1]);
662    const DNS_IP_1: Ipv4Address = Ipv4Address([1, 1, 1, 1]);
663    const DNS_IP_2: Ipv4Address = Ipv4Address([1, 1, 1, 2]);
664    const DNS_IP_3: Ipv4Address = Ipv4Address([1, 1, 1, 3]);
665    const DNS_IPS: [Option<Ipv4Address>; DHCP_MAX_DNS_SERVER_COUNT] =
666        [Some(DNS_IP_1), Some(DNS_IP_2), Some(DNS_IP_3)];
667    const MASK_24: Ipv4Address = Ipv4Address([255, 255, 255, 0]);
668
669    const MY_MAC: EthernetAddress = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
670
671    const IP_BROADCAST: Ipv4Repr = Ipv4Repr {
672        src_addr: Ipv4Address::UNSPECIFIED,
673        dst_addr: Ipv4Address::BROADCAST,
674        protocol: IpProtocol::Udp,
675        payload_len: 0,
676        hop_limit: 64,
677    };
678
679    const IP_SERVER_BROADCAST: Ipv4Repr = Ipv4Repr {
680        src_addr: SERVER_IP,
681        dst_addr: Ipv4Address::BROADCAST,
682        protocol: IpProtocol::Udp,
683        payload_len: 0,
684        hop_limit: 64,
685    };
686
687    const IP_RECV: Ipv4Repr = Ipv4Repr {
688        src_addr: SERVER_IP,
689        dst_addr: MY_IP,
690        protocol: IpProtocol::Udp,
691        payload_len: 0,
692        hop_limit: 64,
693    };
694
695    const IP_SEND: Ipv4Repr = Ipv4Repr {
696        src_addr: MY_IP,
697        dst_addr: SERVER_IP,
698        protocol: IpProtocol::Udp,
699        payload_len: 0,
700        hop_limit: 64,
701    };
702
703    const UDP_SEND: UdpRepr = UdpRepr {
704        src_port: 68,
705        dst_port: 67,
706    };
707    const UDP_RECV: UdpRepr = UdpRepr {
708        src_port: 67,
709        dst_port: 68,
710    };
711
712    const DHCP_DEFAULT: DhcpRepr = DhcpRepr {
713        message_type: DhcpMessageType::Unknown(99),
714        transaction_id: TXID,
715        client_hardware_address: MY_MAC,
716        client_ip: Ipv4Address::UNSPECIFIED,
717        your_ip: Ipv4Address::UNSPECIFIED,
718        server_ip: Ipv4Address::UNSPECIFIED,
719        router: None,
720        subnet_mask: None,
721        relay_agent_ip: Ipv4Address::UNSPECIFIED,
722        broadcast: false,
723        requested_ip: None,
724        client_identifier: None,
725        server_identifier: None,
726        parameter_request_list: None,
727        dns_servers: None,
728        max_size: None,
729        lease_duration: None,
730    };
731
732    const DHCP_DISCOVER: DhcpRepr = DhcpRepr {
733        message_type: DhcpMessageType::Discover,
734        client_identifier: Some(MY_MAC),
735        parameter_request_list: Some(&[1, 3, 6]),
736        max_size: Some(1432),
737        ..DHCP_DEFAULT
738    };
739
740    const DHCP_OFFER: DhcpRepr = DhcpRepr {
741        message_type: DhcpMessageType::Offer,
742        server_ip: SERVER_IP,
743        server_identifier: Some(SERVER_IP),
744
745        your_ip: MY_IP,
746        router: Some(SERVER_IP),
747        subnet_mask: Some(MASK_24),
748        dns_servers: Some(DNS_IPS),
749        lease_duration: Some(1000),
750
751        ..DHCP_DEFAULT
752    };
753
754    const DHCP_REQUEST: DhcpRepr = DhcpRepr {
755        message_type: DhcpMessageType::Request,
756        client_identifier: Some(MY_MAC),
757        server_identifier: Some(SERVER_IP),
758        max_size: Some(1432),
759
760        requested_ip: Some(MY_IP),
761        parameter_request_list: Some(&[1, 3, 6]),
762        ..DHCP_DEFAULT
763    };
764
765    const DHCP_ACK: DhcpRepr = DhcpRepr {
766        message_type: DhcpMessageType::Ack,
767        server_ip: SERVER_IP,
768        server_identifier: Some(SERVER_IP),
769
770        your_ip: MY_IP,
771        router: Some(SERVER_IP),
772        subnet_mask: Some(MASK_24),
773        dns_servers: Some(DNS_IPS),
774        lease_duration: Some(1000),
775
776        ..DHCP_DEFAULT
777    };
778
779    const DHCP_NAK: DhcpRepr = DhcpRepr {
780        message_type: DhcpMessageType::Nak,
781        server_ip: SERVER_IP,
782        server_identifier: Some(SERVER_IP),
783        ..DHCP_DEFAULT
784    };
785
786    const DHCP_RENEW: DhcpRepr = DhcpRepr {
787        message_type: DhcpMessageType::Request,
788        client_identifier: Some(MY_MAC),
789        // NO server_identifier in renew requests, only in first one!
790        client_ip: MY_IP,
791        max_size: Some(1432),
792
793        requested_ip: None,
794        parameter_request_list: Some(&[1, 3, 6]),
795        ..DHCP_DEFAULT
796    };
797
798    // =========================================================================================//
799    // Tests
800
801    fn socket() -> TestSocket {
802        let mut s = Dhcpv4Socket::new();
803        assert_eq!(s.poll(), Some(Event::Deconfigured));
804        TestSocket {
805            socket: s,
806            cx: Context::mock(),
807        }
808    }
809
810    fn socket_bound() -> TestSocket {
811        let mut s = socket();
812        s.state = ClientState::Renewing(RenewState {
813            config: Config {
814                address: Ipv4Cidr::new(MY_IP, 24),
815                dns_servers: DNS_IPS,
816                router: Some(SERVER_IP),
817            },
818            server: ServerInfo {
819                address: SERVER_IP,
820                identifier: SERVER_IP,
821            },
822            renew_at: Instant::from_secs(500),
823            expires_at: Instant::from_secs(1000),
824        });
825
826        s
827    }
828
829    #[test]
830    fn test_bind() {
831        let mut s = socket();
832
833        recv!(s, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
834        assert_eq!(s.poll(), None);
835        send!(s, (IP_RECV, UDP_RECV, DHCP_OFFER));
836        assert_eq!(s.poll(), None);
837        recv!(s, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
838        assert_eq!(s.poll(), None);
839        send!(s, (IP_RECV, UDP_RECV, DHCP_ACK));
840
841        assert_eq!(
842            s.poll(),
843            Some(Event::Configured(Config {
844                address: Ipv4Cidr::new(MY_IP, 24),
845                dns_servers: DNS_IPS,
846                router: Some(SERVER_IP),
847            }))
848        );
849
850        match &s.state {
851            ClientState::Renewing(r) => {
852                assert_eq!(r.renew_at, Instant::from_secs(500));
853                assert_eq!(r.expires_at, Instant::from_secs(1000));
854            }
855            _ => panic!("Invalid state"),
856        }
857    }
858
859    #[test]
860    fn test_discover_retransmit() {
861        let mut s = socket();
862
863        recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
864        recv!(s, time 1_000, []);
865        recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
866        recv!(s, time 11_000, []);
867        recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
868
869        // check after retransmits it still works
870        send!(s, time 20_000, (IP_RECV, UDP_RECV, DHCP_OFFER));
871        recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
872    }
873
874    #[test]
875    fn test_request_retransmit() {
876        let mut s = socket();
877
878        recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
879        send!(s, time 0, (IP_RECV, UDP_RECV, DHCP_OFFER));
880        recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
881        recv!(s, time 1_000, []);
882        recv!(s, time 5_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
883        recv!(s, time 6_000, []);
884        recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
885        recv!(s, time 15_000, []);
886        recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
887
888        // check after retransmits it still works
889        send!(s, time 20_000, (IP_RECV, UDP_RECV, DHCP_ACK));
890
891        match &s.state {
892            ClientState::Renewing(r) => {
893                assert_eq!(r.renew_at, Instant::from_secs(20 + 500));
894                assert_eq!(r.expires_at, Instant::from_secs(20 + 1000));
895            }
896            _ => panic!("Invalid state"),
897        }
898    }
899
900    #[test]
901    fn test_request_timeout() {
902        let mut s = socket();
903
904        recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
905        send!(s, time 0, (IP_RECV, UDP_RECV, DHCP_OFFER));
906        recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
907        recv!(s, time 5_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
908        recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
909        recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
910        recv!(s, time 30_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
911
912        // After 5 tries and 70 seconds, it gives up.
913        // 5 + 5 + 10 + 10 + 20 = 70
914        recv!(s, time 70_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
915
916        // check it still works
917        send!(s, time 60_000, (IP_RECV, UDP_RECV, DHCP_OFFER));
918        recv!(s, time 60_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
919    }
920
921    #[test]
922    fn test_request_nak() {
923        let mut s = socket();
924
925        recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
926        send!(s, time 0, (IP_RECV, UDP_RECV, DHCP_OFFER));
927        recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]);
928        send!(s, time 0, (IP_SERVER_BROADCAST, UDP_RECV, DHCP_NAK));
929        recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
930    }
931
932    #[test]
933    fn test_renew() {
934        let mut s = socket_bound();
935
936        recv!(s, []);
937        assert_eq!(s.poll(), None);
938        recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
939        assert_eq!(s.poll(), None);
940
941        match &s.state {
942            ClientState::Renewing(r) => {
943                // the expiration still hasn't been bumped, because
944                // we haven't received the ACK yet
945                assert_eq!(r.expires_at, Instant::from_secs(1000));
946            }
947            _ => panic!("Invalid state"),
948        }
949
950        send!(s, time 500_000, (IP_RECV, UDP_RECV, DHCP_ACK));
951        assert_eq!(s.poll(), None);
952
953        match &s.state {
954            ClientState::Renewing(r) => {
955                // NOW the expiration gets bumped
956                assert_eq!(r.renew_at, Instant::from_secs(500 + 500));
957                assert_eq!(r.expires_at, Instant::from_secs(500 + 1000));
958            }
959            _ => panic!("Invalid state"),
960        }
961    }
962
963    #[test]
964    fn test_renew_retransmit() {
965        let mut s = socket_bound();
966
967        recv!(s, []);
968        recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
969        recv!(s, time 749_000, []);
970        recv!(s, time 750_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
971        recv!(s, time 874_000, []);
972        recv!(s, time 875_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
973
974        // check it still works
975        send!(s, time 875_000, (IP_RECV, UDP_RECV, DHCP_ACK));
976        match &s.state {
977            ClientState::Renewing(r) => {
978                // NOW the expiration gets bumped
979                assert_eq!(r.renew_at, Instant::from_secs(875 + 500));
980                assert_eq!(r.expires_at, Instant::from_secs(875 + 1000));
981            }
982            _ => panic!("Invalid state"),
983        }
984    }
985
986    #[test]
987    fn test_renew_timeout() {
988        let mut s = socket_bound();
989
990        recv!(s, []);
991        recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
992        recv!(s, time 999_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
993        recv!(s, time 1_000_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
994        match &s.state {
995            ClientState::Discovering(_) => {}
996            _ => panic!("Invalid state"),
997        }
998    }
999
1000    #[test]
1001    fn test_renew_nak() {
1002        let mut s = socket_bound();
1003
1004        recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]);
1005        send!(s, time 500_000, (IP_SERVER_BROADCAST, UDP_RECV, DHCP_NAK));
1006        recv!(s, time 500_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]);
1007    }
1008}