toe_beans/v4/server/
server.rs

1use super::config::*;
2use super::leases::*;
3use crate::v4::error::Result;
4use crate::v4::message::*;
5use log::{debug, error, warn};
6use std::net::Ipv4Addr;
7
8/// Ties together a Config, Socket, and Leases in order to handle incoming Client Messages,
9/// and construct server-specific response Messages.
10///
11/// Used by the server binary and integration tests.
12///
13/// Requires the `v4_server` feature, which is enabled by default.
14pub struct Server {
15    /// A UdpSocket that understands Deliverables to communicate with the Client
16    pub socket: Socket,
17    /// Direct access to the ipv4 address passed to the `UdpSocket`.
18    ///
19    /// A server with multiple network addresses may use any of its addresses.
20    pub ip: Ipv4Addr,
21    /// Configuration read from toe-beans.toml
22    pub config: Config,
23    /// Manages the leasing logic of a range of ip addresses
24    pub leases: Leases,
25}
26
27impl Server {
28    /// Setup everything necessary for a Server to listen for incoming Messages.
29    ///
30    /// This fn is relatively "slow" and may panic, but that is okay
31    /// because it is called before the `listen` or `listen_once` fns.
32    pub fn new(config: Config) -> Self {
33        let socket = Socket::new(config.listen_address, config.interface.as_ref());
34        let ip = config.server_address.unwrap_or_else(|| socket.get_ip());
35        let leases = Leases::restore_or_new(&config);
36
37        if config.rapid_commit {
38            debug!("Rapid commit is enabled on server");
39        }
40
41        Self {
42            socket,
43            ip,
44            config,
45            leases,
46        }
47    }
48
49    /// Will wait for one Message on the UdpSocket and send a response based on it.
50    ///
51    /// To handle more than one Message, use `listen`.
52    pub fn listen_once(&mut self) {
53        let (decoded, src) = match self.socket.receive::<Message>() {
54            Ok(values) => values,
55            Err(error) => {
56                error!("{error}");
57                return;
58            }
59        };
60
61        let message_type = match decoded.find_option(53) {
62            Some(MessageOptions::MessageType(message_type)) => message_type,
63            _ => {
64                error!("Request does not have option 53");
65                return;
66            }
67        };
68
69        let response = match message_type {
70            MessageTypes::Discover => 'discover: {
71                debug!("Received Discover message");
72                if self.config.rapid_commit {
73                    match decoded.find_option(80) {
74                        Some(MessageOptions::RapidCommit) => {
75                            debug!("Discover message has rapid commit option. Will send Ack instead of Offer");
76                            let mac_address = decoded.get_mac_address();
77
78                            let ip = match self.leases.ack(mac_address) {
79                                Ok(ip) => ip,
80                                Err(error) => {
81                                    error!("{}", error);
82                                    return;
83                                }
84                            };
85                            let mut message = self.ack(decoded, ip);
86                            message.add_option(MessageOptions::RapidCommit);
87                            break 'discover message;
88                        }
89                        _ => {
90                            // Discover message did not send a rapid commit option or it wasnt valid. Will send Offer
91                        }
92                    };
93                }
94
95                match self.offer(decoded) {
96                    Ok(message) => message,
97                    Err(error) => {
98                        error!("{}", error);
99                        return;
100                    }
101                }
102            }
103            MessageTypes::Request => 'request: {
104                debug!("Received Request message");
105                let mac_address = decoded.get_mac_address();
106
107                // "If the DHCPREQUEST message contains a 'server identifier' option, the message is in response to a DHCPOFFER message."
108                // "Otherwise, the message is a request to verify or extend an existing lease."
109                match decoded.find_option(54) {
110                    Some(MessageOptions::ServerIdentifier(address_option)) => {
111                        let server_identifier = address_option.inner();
112                        if server_identifier != self.ip {
113                            debug!(
114                                "Ignoring client broadcast of DHCP Request for {} which is not this server ({}).",
115                                server_identifier, self.ip
116                            );
117
118                            match self.leases.release(mac_address) {
119                                Ok(_) => {
120                                    debug!("Removed IP address previously offered to this client")
121                                }
122                                Err(error) => error!("{}", error),
123                            }
124
125                            return;
126                        }
127
128                        debug!("client has selected this server for a lease");
129                        let ip = match self.leases.ack(mac_address) {
130                            Ok(ip) => ip,
131                            Err(error) => {
132                                error!("{}", error);
133                                return;
134                            }
135                        };
136                        self.ack(decoded, ip)
137                    }
138                    _ => {
139                        if decoded.ciaddr.is_unspecified() {
140                            // no server identifier, and no ciaddr = init-reboot (verify)
141                            debug!("client is attempting to verify lease");
142
143                            let ip = match decoded.find_option(50) {
144                                Some(MessageOptions::RequestedIp(address_option)) => {
145                                    address_option.inner()
146                                }
147                                _ => {
148                                    // If a server receives a DHCPREQUEST message with an invalid 'requested IP address',
149                                    // the server SHOULD respond to the client with a DHCPNAK message
150                                    break 'request self.nak(decoded, "Request does not have required option 50 (requested ip address)".to_string());
151                                }
152                            };
153
154                            match self.leases.verify_lease(mac_address, &ip) {
155                                Ok(_) => self.ack(decoded, ip),
156                                Err(error) => {
157                                    if error == "No IP address lease found with that owner" {
158                                        // If the DHCP server has no record of this client, then it MUST remain silent,
159                                        // This is necessary for peaceful coexistence of non-communicating DHCP servers on the same wire.
160                                        debug!("{}. Not sending response", error);
161                                        return;
162                                    } else {
163                                        // Client's notion of ip address is wrong or lease expired
164                                        self.nak(decoded, error.to_string())
165                                    }
166                                }
167                            }
168                        } else {
169                            // no server identifier, and a ciaddr = renew/rebind (extend)
170                            debug!("client is attempting to renew/rebind lease");
171                            match self.leases.extend(mac_address) {
172                                Ok(_) => {
173                                    // TODO The server SHOULD return T1 and T2,
174                                    // and their values SHOULD be adjusted from their original values
175                                    // to take account of the time remaining on the lease.
176                                    let ip = decoded.ciaddr;
177                                    self.ack(decoded, ip)
178                                }
179                                Err(error) => self.nak(decoded, error.to_string()),
180                            }
181                        }
182                    }
183                }
184            }
185            MessageTypes::Release => {
186                debug!("Received Release message");
187                if let Err(error) = self.leases.release(decoded.get_mac_address()) {
188                    error!("{}", error);
189                }
190                return;
191            }
192            MessageTypes::Decline => {
193                debug!("Received Decline message");
194
195                // RFC 2131 says to mark the network address as not available,
196                // but I'm concerned that could be a vector for easy ip address exhaustion.
197                // For now, I'll release the address as there is no sense in keeping it acked.
198                if let Err(error) = self.leases.release(decoded.get_mac_address()) {
199                    error!("{}", error);
200                }
201                return;
202            }
203            MessageTypes::Inform => {
204                debug!("Received Inform message");
205                self.ack_inform(decoded)
206            }
207            MessageTypes::LeaseQuery
208            | MessageTypes::BulkLeaseQuery
209            | MessageTypes::ActiveLeaseQuery
210            | MessageTypes::Tls => {
211                warn!("Client sent a message type that is known but not yet handled");
212                return;
213            }
214            _ => {
215                error!("Client sent unknown message type");
216                return;
217            }
218        };
219
220        match self.socket.broadcast(&response, src.port()) {
221            Ok(_) => {}
222            Err(error) => {
223                error!("{error}");
224            }
225        };
226    }
227
228    /// Calls `listen_once` in a loop forever.
229    pub fn listen(&mut self) -> ! {
230        loop {
231            self.listen_once();
232        }
233    }
234
235    /// If a request message has a parameter request list,
236    /// each requested parameter is added to the response message
237    /// if the parameter's value is configured on the server.
238    fn handle_parameter_requests(&self, request_message: &Message) -> Vec<MessageOptions> {
239        match request_message.find_option(55) {
240            Some(MessageOptions::RequestedOptions(list)) => {
241                // list is a Vec<u8> of dhcp option numbers
242                list.iter()
243                    .filter_map(|parameter_request| {
244                        self.config
245                            .parameters
246                            .get(&parameter_request.to_string())
247                            .cloned()
248                    })
249                    .collect()
250            }
251            _ => vec![],
252        }
253    }
254
255    /// Sent from the server to client following a Discover message
256    pub fn offer(&mut self, message: Message) -> Result<Message> {
257        debug!("Sending Offer message");
258
259        // The client may suggest values for the network address
260        // and lease time in the DHCPDISCOVER message. The client may include
261        // the 'requested IP address' option to suggest that a particular IP
262        // address be assigned
263        let maybe_requested_ip = match message.find_option(50) {
264            Some(MessageOptions::RequestedIp(address_option)) => Some(address_option.inner()),
265            _ => None,
266        };
267        let mac_address = message.get_mac_address();
268        let yiaddr = self.leases.offer(mac_address, maybe_requested_ip)?;
269
270        let mut parameters = self.handle_parameter_requests(&message);
271        parameters.push(MessageOptions::MessageType(MessageTypes::Offer)); // must
272        parameters.push(MessageOptions::LeaseTime(TimeOption::new(
273            self.config.lease_time.as_client(),
274        ))); // must
275        parameters.push(MessageOptions::ServerIdentifier(AddressOption::new(
276            self.ip,
277        ))); // must, rfc2132
278
279        Ok(Message {
280            op: Ops::Reply,
281            htype: HTypes::Ethernet,
282            hlen: 6,
283            hops: 0,
284            xid: message.xid,
285            secs: 0,
286            flags: message.flags,
287            ciaddr: Ipv4Addr::UNSPECIFIED, // field not used
288            yiaddr,
289            siaddr: self.ip,
290            giaddr: message.giaddr,
291            chaddr: message.chaddr,
292            sname: SName::EMPTY,
293            file: File::EMPTY,
294            magic: MAGIC,
295            options: parameters,
296        })
297    }
298
299    /// Sent from the server to client following a Request message
300    ///
301    /// Acks behave differently depending on if they are sent in response to a DHCP Request or DHCP Inform.
302    /// For example, a DHCP Request must send lease time and a DHCP Inform must not.
303    ///
304    /// Any configuration parameters in the DHCPACK message SHOULD NOT
305    /// conflict with those in the earlier DHCPOFFER message to which the
306    /// client is responding.
307    pub fn ack(&mut self, message: Message, yiaddr: Ipv4Addr) -> Message {
308        debug!("Sending Ack message");
309
310        let mut parameters = self.handle_parameter_requests(&message);
311        parameters.push(MessageOptions::MessageType(MessageTypes::Ack)); // must
312        parameters.push(MessageOptions::LeaseTime(TimeOption::new(
313            self.config.lease_time.as_client(),
314        ))); // must
315        parameters.push(MessageOptions::ServerIdentifier(AddressOption::new(
316            self.ip,
317        ))); // must
318
319        Message {
320            op: Ops::Reply,
321            htype: HTypes::Ethernet,
322            hlen: 6,
323            hops: 0,
324            xid: message.xid,
325            secs: 0,
326            flags: message.flags,
327            ciaddr: message.ciaddr,
328            yiaddr,
329            siaddr: self.ip, // ip address of next bootstrap server
330            giaddr: message.giaddr,
331            chaddr: message.chaddr,
332            sname: SName::EMPTY,
333            file: File::EMPTY,
334            magic: MAGIC,
335            options: parameters,
336        }
337    }
338
339    /// Sent from server to client in response to an Inform message
340    ///
341    /// Acks behave differently depending on if they are sent in response to a DHCP Request or DHCP Inform.
342    /// For example, a DHCP Request must send lease time and a DHCP Inform must not.
343    pub fn ack_inform(&self, message: Message) -> Message {
344        debug!("Sending Ack message (in response to an inform)");
345
346        let mut parameters = self.handle_parameter_requests(&message);
347        parameters.push(MessageOptions::MessageType(MessageTypes::Ack)); // must
348        parameters.push(MessageOptions::ServerIdentifier(AddressOption::new(
349            self.ip,
350        ))); // must
351
352        Message {
353            op: Ops::Reply,
354            htype: HTypes::Ethernet,
355            hlen: 6,
356            hops: 0,
357            xid: message.xid,
358            secs: 0,
359            flags: message.flags,
360            ciaddr: message.ciaddr,
361            yiaddr: Ipv4Addr::UNSPECIFIED,
362            siaddr: self.ip, // ip address of next bootstrap server
363            giaddr: message.giaddr,
364            chaddr: message.chaddr,
365            sname: SName::EMPTY,
366            file: File::EMPTY,
367            magic: MAGIC,
368            options: parameters,
369        }
370    }
371
372    /// Sent from the server to client following a Request message
373    pub fn nak(&self, message: Message, error: String) -> Message {
374        debug!("Sending Nak message");
375
376        let mut response = Message {
377            op: Ops::Reply,
378            htype: HTypes::Ethernet,
379            hlen: 6,
380            hops: 0,
381            xid: message.xid,
382            secs: 0,
383            flags: message.flags,
384            ciaddr: Ipv4Addr::UNSPECIFIED,
385            yiaddr: Ipv4Addr::UNSPECIFIED,
386            siaddr: Ipv4Addr::UNSPECIFIED,
387            giaddr: message.giaddr,
388            chaddr: message.chaddr,
389            sname: SName::EMPTY, // this field not used
390            file: File::EMPTY,   // this field not used
391            magic: MAGIC,
392            options: vec![
393                MessageOptions::MessageType(MessageTypes::Nak), // must
394                MessageOptions::ServerIdentifier(AddressOption::new(self.ip)), // must
395            ],
396        };
397
398        match StringOption::new(error) {
399            Ok(converted) => {
400                response.add_option(MessageOptions::Message(converted)); // should
401            }
402            Err(_) => {
403                error!("Sending Nak without error message option because option's value is empty");
404            }
405        };
406
407        response
408    }
409}