toe_beans/v4/server/
server.rs

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