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, info, 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::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!(
76                                "Discover message has rapid commit option. Will send Ack instead of Offer"
77                            );
78                            let mac_address = decoded.get_mac_address();
79
80                            let ip = match self.leases.ack(mac_address) {
81                                Ok(ip) => ip,
82                                Err(error) => {
83                                    error!("{}", error);
84                                    return;
85                                }
86                            };
87                            let mut message = self.ack(decoded, ip);
88                            message.add_option(MessageOptions::RapidCommit);
89                            break 'discover message;
90                        }
91                        _ => {
92                            // Discover message did not send a rapid commit option or it wasnt valid. Will send Offer
93                        }
94                    };
95                }
96
97                match self.offer(decoded) {
98                    Ok(message) => message,
99                    Err(error) => {
100                        error!("{}", error);
101                        return;
102                    }
103                }
104            }
105            MessageTypes::Request => 'request: {
106                debug!("Received Request message");
107                let mac_address = decoded.get_mac_address();
108
109                // "If the DHCPREQUEST message contains a 'server identifier' option, the message is in response to a DHCPOFFER message."
110                // "Otherwise, the message is a request to verify or extend an existing lease."
111                match decoded.find_option(54) {
112                    Some(MessageOptions::ServerIdentifier(address_option)) => {
113                        let server_identifier = address_option.inner();
114                        if server_identifier != self.ip {
115                            debug!(
116                                "Ignoring client broadcast of DHCP Request for {} which is not this server ({}).",
117                                server_identifier, self.ip
118                            );
119
120                            match self.leases.release(mac_address) {
121                                Ok(_) => {
122                                    debug!("Removed IP address previously offered to this client")
123                                }
124                                Err(error) => error!("{}", error),
125                            }
126
127                            return;
128                        }
129
130                        debug!("client has selected this server for a lease");
131                        let ip = match self.leases.ack(mac_address) {
132                            Ok(ip) => ip,
133                            Err(error) => {
134                                error!("{}", error);
135                                return;
136                            }
137                        };
138                        self.ack(decoded, ip)
139                    }
140                    _ => {
141                        if decoded.ciaddr.is_unspecified() {
142                            // no server identifier, and no ciaddr = init-reboot (verify)
143                            debug!("client is attempting to verify lease");
144
145                            let ip = match decoded.find_option(50) {
146                                Some(MessageOptions::RequestedIp(address_option)) => {
147                                    address_option.inner()
148                                }
149                                _ => {
150                                    // If a server receives a DHCPREQUEST message with an invalid 'requested IP address',
151                                    // the server SHOULD respond to the client with a DHCPNAK message
152                                    break 'request self.nak(decoded, "Request does not have required option 50 (requested ip address)".to_string());
153                                }
154                            };
155
156                            match self.leases.verify_lease(mac_address, &ip) {
157                                Ok(_) => self.ack(decoded, ip),
158                                Err(error) => {
159                                    if error == "No IP address lease found with that owner" {
160                                        // If the DHCP server has no record of this client, then it MUST remain silent,
161                                        // This is necessary for peaceful coexistence of non-communicating DHCP servers on the same wire.
162                                        debug!("{}. Not sending response", error);
163                                        return;
164                                    } else {
165                                        // Client's notion of ip address is wrong or lease expired
166                                        debug!("Lease not verified: {}", error);
167                                        self.nak(decoded, error.to_string())
168                                    }
169                                }
170                            }
171                        } else {
172                            // no server identifier, and a ciaddr = renew/rebind (extend)
173                            debug!("client is attempting to renew/rebind lease");
174                            match self.leases.extend(mac_address) {
175                                Ok(_) => {
176                                    // TODO The server SHOULD return T1 and T2,
177                                    // and their values SHOULD be adjusted from their original values
178                                    // to take account of the time remaining on the lease.
179                                    let ip = decoded.ciaddr;
180                                    self.ack(decoded, ip)
181                                }
182                                Err(error) => {
183                                    debug!("Lease not extended: {}", error);
184                                    self.nak(decoded, error.to_string())
185                                }
186                            }
187                        }
188                    }
189                }
190            }
191            MessageTypes::Release => {
192                debug!("Received Release message");
193                if let Err(error) = self.leases.release(decoded.get_mac_address()) {
194                    error!("{}", error);
195                }
196                return;
197            }
198            MessageTypes::Decline => {
199                debug!("Received Decline message");
200
201                // RFC 2131 says to mark the network address as not available,
202                // but I'm concerned that could be a vector for easy ip address exhaustion.
203                // For now, I'll release the address as there is no sense in keeping it acked.
204                if let Err(error) = self.leases.release(decoded.get_mac_address()) {
205                    error!("{}", error);
206                }
207                return;
208            }
209            MessageTypes::Inform => {
210                debug!("Received Inform message");
211                self.ack_inform(decoded)
212            }
213            MessageTypes::LeaseQuery
214            | MessageTypes::BulkLeaseQuery
215            | MessageTypes::ActiveLeaseQuery
216            | MessageTypes::Tls => {
217                warn!("Client sent a message type that is known but not yet handled");
218                return;
219            }
220            _ => {
221                error!("Client sent unknown message type");
222                return;
223            }
224        };
225
226        match self.socket.broadcast(&response, src.port()) {
227            Ok(_) => {}
228            Err(error) => {
229                error!("{error}");
230            }
231        };
232    }
233
234    /// Calls `listen_once` in a loop forever.
235    pub fn listen(&mut self) -> ! {
236        info!("Listening for DHCP requests");
237        loop {
238            self.listen_once();
239        }
240    }
241
242    /// If a request message has a parameter request list,
243    /// each requested parameter is added to the response message
244    /// if the parameter's value is configured on the server.
245    fn handle_parameter_requests(&self, request_message: &Message) -> Vec<MessageOptions> {
246        match request_message.find_option(55) {
247            Some(MessageOptions::RequestedOptions(list)) => {
248                // list is a Vec<u8> of dhcp option numbers
249                list.iter()
250                    .filter_map(|parameter_request| {
251                        self.config
252                            .parameters
253                            .get(&parameter_request.to_string())
254                            .cloned()
255                    })
256                    .collect()
257            }
258            _ => vec![],
259        }
260    }
261
262    /// Sent from the server to client following a Discover message
263    pub fn offer(&mut self, message: Message) -> Result<Message> {
264        debug!("Sending Offer message");
265
266        // The client may suggest values for the network address
267        // and lease time in the DHCPDISCOVER message. The client may include
268        // the 'requested IP address' option to suggest that a particular IP
269        // address be assigned
270        let maybe_requested_ip = match message.find_option(50) {
271            Some(MessageOptions::RequestedIp(address_option)) => Some(address_option.inner()),
272            _ => None,
273        };
274        let mac_address = message.get_mac_address();
275        let yiaddr = self.leases.offer(mac_address, maybe_requested_ip)?;
276
277        let mut parameters = self.handle_parameter_requests(&message);
278        parameters.push(MessageOptions::MessageType(MessageTypes::Offer)); // must
279        parameters.push(MessageOptions::LeaseTime(TimeOption::new(
280            self.config.lease_time.as_client(),
281        ))); // must
282        parameters.push(MessageOptions::ServerIdentifier(AddressOption::new(
283            self.ip,
284        ))); // must, rfc2132
285
286        Ok(Message {
287            op: Ops::Reply,
288            htype: HTypes::Ethernet,
289            hlen: 6,
290            hops: 0,
291            xid: message.xid,
292            secs: 0,
293            flags: message.flags,
294            ciaddr: Ipv4Addr::UNSPECIFIED, // field not used
295            yiaddr,
296            siaddr: self.ip,
297            giaddr: message.giaddr,
298            chaddr: message.chaddr,
299            sname: SName::EMPTY,
300            file: File::EMPTY,
301            magic: MAGIC,
302            options: parameters,
303        })
304    }
305
306    /// Sent from the server to client following a Request message
307    ///
308    /// Acks behave differently depending on if they are sent in response to a DHCP Request or DHCP Inform.
309    /// For example, a DHCP Request must send lease time and a DHCP Inform must not.
310    ///
311    /// Any configuration parameters in the DHCPACK message SHOULD NOT
312    /// conflict with those in the earlier DHCPOFFER message to which the
313    /// client is responding.
314    pub fn ack(&mut self, message: Message, yiaddr: Ipv4Addr) -> Message {
315        debug!("Sending Ack message");
316
317        let mut parameters = self.handle_parameter_requests(&message);
318        parameters.push(MessageOptions::MessageType(MessageTypes::Ack)); // must
319        parameters.push(MessageOptions::LeaseTime(TimeOption::new(
320            self.config.lease_time.as_client(),
321        ))); // must
322        parameters.push(MessageOptions::ServerIdentifier(AddressOption::new(
323            self.ip,
324        ))); // must
325
326        Message {
327            op: Ops::Reply,
328            htype: HTypes::Ethernet,
329            hlen: 6,
330            hops: 0,
331            xid: message.xid,
332            secs: 0,
333            flags: message.flags,
334            ciaddr: message.ciaddr,
335            yiaddr,
336            siaddr: self.ip, // ip address of next bootstrap server
337            giaddr: message.giaddr,
338            chaddr: message.chaddr,
339            sname: SName::EMPTY,
340            file: File::EMPTY,
341            magic: MAGIC,
342            options: parameters,
343        }
344    }
345
346    /// Sent from server to client in response to an Inform message
347    ///
348    /// Acks behave differently depending on if they are sent in response to a DHCP Request or DHCP Inform.
349    /// For example, a DHCP Request must send lease time and a DHCP Inform must not.
350    pub fn ack_inform(&self, message: Message) -> Message {
351        debug!("Sending Ack message (in response to an inform)");
352
353        let mut parameters = self.handle_parameter_requests(&message);
354        parameters.push(MessageOptions::MessageType(MessageTypes::Ack)); // must
355        parameters.push(MessageOptions::ServerIdentifier(AddressOption::new(
356            self.ip,
357        ))); // must
358
359        Message {
360            op: Ops::Reply,
361            htype: HTypes::Ethernet,
362            hlen: 6,
363            hops: 0,
364            xid: message.xid,
365            secs: 0,
366            flags: message.flags,
367            ciaddr: message.ciaddr,
368            yiaddr: Ipv4Addr::UNSPECIFIED,
369            siaddr: self.ip, // ip address of next bootstrap server
370            giaddr: message.giaddr,
371            chaddr: message.chaddr,
372            sname: SName::EMPTY,
373            file: File::EMPTY,
374            magic: MAGIC,
375            options: parameters,
376        }
377    }
378
379    /// Sent from the server to client following a Request message
380    pub fn nak(&self, message: Message, error: String) -> Message {
381        debug!("Sending Nak message");
382
383        let mut response = Message {
384            op: Ops::Reply,
385            htype: HTypes::Ethernet,
386            hlen: 6,
387            hops: 0,
388            xid: message.xid,
389            secs: 0,
390            flags: message.flags,
391            ciaddr: Ipv4Addr::UNSPECIFIED,
392            yiaddr: Ipv4Addr::UNSPECIFIED,
393            siaddr: Ipv4Addr::UNSPECIFIED,
394            giaddr: message.giaddr,
395            chaddr: message.chaddr,
396            sname: SName::EMPTY, // this field not used
397            file: File::EMPTY,   // this field not used
398            magic: MAGIC,
399            options: vec![
400                MessageOptions::MessageType(MessageTypes::Nak), // must
401                MessageOptions::ServerIdentifier(AddressOption::new(self.ip)), // must
402            ],
403        };
404
405        match StringOption::new(error) {
406            Ok(converted) => {
407                response.add_option(MessageOptions::Message(converted)); // should
408            }
409            Err(_) => {
410                error!("Sending Nak without error message option because option's value is empty");
411            }
412        };
413
414        response
415    }
416}