esp_hal_dhcp_server/
server.rs

1use edge_dhcp::{server::Action, DhcpOption, Ipv4Addr, MessageType, Options, Packet};
2use embassy_net::udp::{BindError, UdpSocket};
3use embassy_time::Instant;
4
5use crate::structs::{
6    DhcpLeaser, DhcpServerConfig, DHCP_BROADCAST, DHCP_BUFFER_SIZE, DHCP_SERVER_ENDPOINT,
7};
8
9pub struct DhcpServer<'a> {
10    pub config: DhcpServerConfig<'a>,
11
12    leaser: &'a mut dyn DhcpLeaser,
13    sock: UdpSocket<'a>,
14}
15
16impl<'a> DhcpServer<'a> {
17    pub fn new(
18        config: DhcpServerConfig<'a>,
19        leaser: &'a mut dyn DhcpLeaser,
20        mut sock: UdpSocket<'a>,
21    ) -> Result<Self, BindError> {
22        sock.bind(DHCP_SERVER_ENDPOINT)?;
23
24        Ok(Self {
25            config,
26            leaser,
27            sock,
28        })
29    }
30
31    pub async fn run(&mut self) {
32        let mut buf = [0; DHCP_BUFFER_SIZE];
33        loop {
34            let res = self.sock.recv_from(&mut buf).await;
35            if let Ok((n, _addr)) = res {
36                #[cfg(feature = "log")]
37                log::info!("received {n} from {_addr:?}");
38
39                let res = Packet::decode(&buf[..n]);
40                if let Ok(packet) = res {
41                    self.process_packet(packet).await;
42                }
43            }
44        }
45    }
46
47    async fn process_packet(&mut self, packet: Packet<'_>) {
48        let Some(action) = self.get_packet_action(&packet) else {
49            #[cfg(feature = "log")]
50            log::warn!("Skipping process_packet because packet action was None");
51            return;
52        };
53
54        match action {
55            Action::Discover(requested_ip, mac) => {
56                let ip = requested_ip
57                    .and_then(|ip| {
58                        let mac_lease = self.leaser.get_lease(*mac);
59                        let available = mac_lease
60                            .map(|d| d.ip == ip || Instant::now() > d.expires)
61                            .unwrap_or(true);
62
63                        available.then_some(ip)
64                    })
65                    .or_else(|| self.leaser.get_lease(*mac).map(|l| l.ip))
66                    .or_else(|| self.leaser.next_lease());
67
68                if ip.is_some() {
69                    self.send_reply(packet, edge_dhcp::MessageType::Offer, ip)
70                        .await;
71                }
72            }
73            Action::Request(ip, mac) => {
74                let mac_lease = self.leaser.get_lease(*mac);
75                let available = mac_lease
76                    .map(|d| d.ip == ip || Instant::now() > d.expires)
77                    .unwrap_or(true);
78
79                let ip = (available
80                    && self
81                        .leaser
82                        .add_lease(ip, *mac, Instant::now() + self.config.lease_time))
83                .then_some(ip);
84
85                let msg_type = match ip {
86                    Some(_) => MessageType::Ack,
87                    None => MessageType::Nak,
88                };
89
90                self.send_reply(packet, msg_type, ip).await;
91            }
92            Action::Release(_ip, mac) | Action::Decline(_ip, mac) => {
93                self.leaser.remove_lease(*mac);
94            }
95        }
96    }
97
98    async fn send_reply(&mut self, packet: Packet<'_>, mt: MessageType, ip: Option<Ipv4Addr>) {
99        let mut opt_buf = Options::buf();
100
101        let mut captive_portal: heapless::String<64> = heapless::String::new();
102        let captive_portal = if self.config.use_captive_portal {
103            match core::fmt::write(
104                &mut captive_portal,
105                format_args!("http://{}", self.config.ip),
106            ) {
107                Ok(_) => Some(captive_portal.as_str()),
108                Err(_) => None,
109            }
110        } else {
111            None
112        };
113
114        let reply = packet.new_reply(
115            ip,
116            packet.options.reply(
117                mt,
118                self.config.ip,
119                self.config.lease_time.as_secs() as u32,
120                self.config.gateways,
121                self.config.subnet,
122                self.config.dns,
123                captive_portal,
124                &mut opt_buf,
125            ),
126        );
127
128        let mut buf = [0; DHCP_BUFFER_SIZE];
129        let bytes_res = reply.encode(&mut buf);
130        match bytes_res {
131            Ok(bytes) => {
132                let res = self.sock.send_to(bytes, DHCP_BROADCAST).await;
133                if let Err(_e) = res {
134                    #[cfg(feature = "log")]
135                    log::error!("Dhcp sock send error: {_e:?}");
136                }
137            }
138            Err(_e) => {
139                #[cfg(feature = "log")]
140                log::error!("Dhcp encode error: {_e:?}");
141            }
142        }
143    }
144
145    fn get_packet_action<'b>(&self, packet: &'b Packet<'b>) -> Option<Action<'b>> {
146        if packet.reply {
147            return None;
148        }
149
150        let message_type = packet.options.iter().find_map(|option| match option {
151            DhcpOption::MessageType(msg_type) => Some(msg_type),
152            _ => None,
153        });
154
155        let message_type = message_type.or_else(|| {
156            #[cfg(feature = "log")]
157            log::warn!("Ignoring DHCP request, no message type found: {packet:?}");
158            None
159        })?;
160
161        let server_identifier = packet.options.iter().find_map(|option| match option {
162            DhcpOption::ServerIdentifier(ip) => Some(ip),
163            _ => None,
164        });
165
166        if server_identifier.is_some() && server_identifier != Some(self.config.ip) {
167            #[cfg(feature = "log")]
168            log::warn!("Ignoring {message_type} request, not addressed to this server: {packet:?}");
169            return None;
170        }
171
172        match message_type {
173            MessageType::Discover => Some(Action::Discover(
174                Self::get_requested_ip(&packet.options),
175                &packet.chaddr,
176            )),
177            MessageType::Request => {
178                let requested_ip =
179                    Self::get_requested_ip(&packet.options).or_else(|| {
180                        match packet.ciaddr.is_unspecified() {
181                            true => None,
182                            false => Some(packet.ciaddr),
183                        }
184                    })?;
185
186                Some(Action::Request(requested_ip, &packet.chaddr))
187            }
188            MessageType::Release if server_identifier == Some(self.config.ip) => {
189                Some(Action::Release(packet.yiaddr, &packet.chaddr))
190            }
191            MessageType::Decline if server_identifier == Some(self.config.ip) => {
192                Some(Action::Decline(packet.yiaddr, &packet.chaddr))
193            }
194            _ => None,
195        }
196    }
197
198    fn get_requested_ip<'b>(options: &'b Options<'b>) -> Option<Ipv4Addr> {
199        options.iter().find_map(|option| {
200            if let DhcpOption::RequestedIpAddress(ip) = option {
201                Some(ip)
202            } else {
203                None
204            }
205        })
206    }
207}