Skip to main content

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