toe_beans/v4/server/
server.rs

1use super::config::*;
2use super::ip_pool::*;
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 IpPool 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 ip_pool: IpPool,
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 ip_pool = IpPool::restore_or_new(&config);
55
56        Self {
57            socket,
58            ip,
59            config,
60            ip_pool,
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.ip_pool.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!("Ignoring client broadcast of DHCP Request for {} which is not this server ({}).", server_identifier, self.ip);
132
133                            match self.ip_pool.release(mac_address) {
134                                Ok(_) => {
135                                    debug!("Removed IP address previously offered to this client")
136                                }
137                                Err(error) => error!("{}", error),
138                            }
139
140                            return;
141                        }
142
143                        debug!("client has selected this server for a lease");
144                        let ip = match self.ip_pool.ack(mac_address) {
145                            Ok(ip) => ip,
146                            Err(error) => {
147                                error!("{}", error);
148                                return;
149                            }
150                        };
151                        self.ack(decoded, ip)
152                    }
153                    _ => {
154                        if decoded.ciaddr.is_unspecified() {
155                            // no server identifier, and no ciaddr = init-reboot (verify)
156                            debug!("client is attempting to verify lease");
157
158                            let ip = match decoded.find_option(50) {
159                                Some(MessageOptions::RequestedIp(address_option)) => {
160                                    address_option.inner()
161                                }
162                                _ => {
163                                    // If a server receives a DHCPREQUEST message with an invalid 'requested IP address',
164                                    // the server SHOULD respond to the client with a DHCPNAK message
165                                    break 'request self.nak(decoded, "Request does not have required option 50 (requested ip address)".to_string());
166                                }
167                            };
168
169                            match self.ip_pool.verify_lease(mac_address, &ip) {
170                                Ok(_) => self.ack(decoded, ip),
171                                Err(error) => {
172                                    if error == "No IP address lease found with that owner" {
173                                        // If the DHCP server has no record of this client, then it MUST remain silent,
174                                        // This is necessary for peaceful coexistence of non-communicating DHCP servers on the same wire.
175                                        return;
176                                    } else {
177                                        // Client's notion of ip address is wrong or lease expired
178                                        self.nak(decoded, error.to_string())
179                                    }
180                                }
181                            }
182                        } else {
183                            // no server identifier, and a ciaddr = renew/rebind (extend)
184                            debug!("client is attempting to renew/rebind lease");
185                            match self.ip_pool.extend(mac_address) {
186                                Ok(_) => {
187                                    // TODO The server SHOULD return T1 and T2,
188                                    // and their values SHOULD be adjusted from their original values
189                                    // to take account of the time remaining on the lease.
190                                    let ip = decoded.ciaddr;
191                                    self.ack(decoded, ip)
192                                }
193                                Err(error) => self.nak(decoded, error.to_string()),
194                            }
195                        }
196                    }
197                }
198            }
199            MessageTypes::Release => {
200                debug!("Received Release message");
201                if let Err(error) = self.ip_pool.release(decoded.get_mac_address()) {
202                    error!("{}", error);
203                }
204                return;
205            }
206            MessageTypes::Decline => {
207                debug!("Received Decline message");
208
209                // RFC 2131 says to mark the network address as not available,
210                // but I'm concerned that could be a vector for easy ip address exhaustion.
211                // For now, I'll release the address as there is no sense in keeping it acked.
212                if let Err(error) = self.ip_pool.release(decoded.get_mac_address()) {
213                    error!("{}", error);
214                }
215                return;
216            }
217            MessageTypes::Inform => {
218                debug!("Received Inform message");
219                self.ack_inform(decoded)
220            }
221            MessageTypes::LeaseQuery
222            | MessageTypes::BulkLeaseQuery
223            | MessageTypes::ActiveLeaseQuery
224            | MessageTypes::Tls => {
225                warn!("Client sent a message type that is known but not yet handled");
226                return;
227            }
228            _ => {
229                error!("Client sent unknown message type");
230                return;
231            }
232        };
233
234        match self.socket.broadcast(&response, src.port()) {
235            Ok(_) => {}
236            Err(error) => {
237                error!("{error}");
238            }
239        };
240    }
241
242    /// Calls `listen_once` in a loop forever.
243    pub fn listen(&mut self) -> ! {
244        loop {
245            self.listen_once();
246        }
247    }
248
249    /// If a request message has a parameter request list,
250    /// each requested parameter is added to the response message
251    /// if the parameter's value is configured on the server.
252    fn handle_parameter_requests(&self, request_message: &Message) -> Vec<MessageOptions> {
253        match request_message.find_option(55) {
254            Some(MessageOptions::RequestedOptions(list)) => {
255                // list is a Vec<u8> of dhcp option numbers
256                list.iter()
257                    .filter_map(|parameter_request| {
258                        self.config
259                            .parameters
260                            .get(&parameter_request.to_string())
261                            .cloned()
262                    })
263                    .collect()
264            }
265            _ => vec![],
266        }
267    }
268
269    /// Sent from the server to client following a Discover message
270    pub fn offer(&mut self, message: Message) -> Result<Message> {
271        debug!("Sending Offer message");
272
273        // The client may suggest values for the network address
274        // and lease time in the DHCPDISCOVER message. The client may include
275        // the 'requested IP address' option to suggest that a particular IP
276        // address be assigned
277        let maybe_requested_ip = match message.find_option(50) {
278            Some(MessageOptions::RequestedIp(address_option)) => Some(address_option.inner()),
279            _ => None,
280        };
281        let mac_address = message.get_mac_address();
282        let yiaddr = self.ip_pool.offer(mac_address, maybe_requested_ip)?;
283
284        let mut parameters = self.handle_parameter_requests(&message);
285        parameters.push(MessageOptions::MessageType(MessageTypes::Offer)); // must
286        parameters.push(MessageOptions::LeaseTime(TimeOption::new(
287            self.config.lease_time.as_client(),
288        ))); // must
289        parameters.push(MessageOptions::ServerIdentifier(AddressOption::new(
290            self.ip,
291        ))); // must, rfc2132
292        parameters.push(MessageOptions::End); // must be last
293
294        Ok(Message {
295            op: Ops::Reply,
296            htype: HTypes::Ethernet,
297            hlen: 6,
298            hops: 0,
299            xid: message.xid,
300            secs: 0,
301            flags: message.flags,
302            ciaddr: Ipv4Addr::UNSPECIFIED, // field not used
303            yiaddr,
304            siaddr: self.ip,
305            giaddr: message.giaddr,
306            chaddr: message.chaddr,
307            sname: SName::EMPTY,
308            file: File::EMPTY,
309            magic: MAGIC,
310            options: parameters,
311        })
312    }
313
314    /// Sent from the server to client following a Request message
315    ///
316    /// Acks behave differently depending on if they are sent in response to a DHCP Request or DHCP Inform.
317    /// For example, a DHCP Request must send lease time and a DHCP Inform must not.
318    ///
319    /// Any configuration parameters in the DHCPACK message SHOULD NOT
320    /// conflict with those in the earlier DHCPOFFER message to which the
321    /// client is responding.
322    pub fn ack(&mut self, message: Message, yiaddr: Ipv4Addr) -> Message {
323        debug!("Sending Ack message");
324
325        // TODO If a server receives a request message with an invalid or unavailable 'requested IP address', the server should respond to the client with a NAK
326
327        let mut parameters = self.handle_parameter_requests(&message);
328        parameters.push(MessageOptions::MessageType(MessageTypes::Ack)); // must
329        parameters.push(MessageOptions::LeaseTime(TimeOption::new(
330            self.config.lease_time.as_client(),
331        ))); // must
332        parameters.push(MessageOptions::ServerIdentifier(AddressOption::new(
333            self.ip,
334        ))); // must
335        parameters.push(MessageOptions::End); // must be last
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        parameters.push(MessageOptions::End); // must be last
370
371        Message {
372            op: Ops::Reply,
373            htype: HTypes::Ethernet,
374            hlen: 6,
375            hops: 0,
376            xid: message.xid,
377            secs: 0,
378            flags: message.flags,
379            ciaddr: message.ciaddr,
380            yiaddr: Ipv4Addr::UNSPECIFIED,
381            siaddr: self.ip, // ip address of next bootstrap server
382            giaddr: message.giaddr,
383            chaddr: message.chaddr,
384            sname: SName::EMPTY,
385            file: File::EMPTY,
386            magic: MAGIC,
387            options: parameters,
388        }
389    }
390
391    /// Sent from the server to client following a Request message
392    pub fn nak(&self, message: Message, error: String) -> Message {
393        debug!("Sending Nak message");
394
395        let mut response = Message {
396            op: Ops::Reply,
397            htype: HTypes::Ethernet,
398            hlen: 6,
399            hops: 0,
400            xid: message.xid,
401            secs: 0,
402            flags: message.flags,
403            ciaddr: Ipv4Addr::UNSPECIFIED,
404            yiaddr: Ipv4Addr::UNSPECIFIED,
405            siaddr: Ipv4Addr::UNSPECIFIED,
406            giaddr: message.giaddr,
407            chaddr: message.chaddr,
408            sname: SName::EMPTY, // this field not used
409            file: File::EMPTY,   // this field not used
410            magic: MAGIC,
411            options: vec![
412                MessageOptions::MessageType(MessageTypes::Nak), // must
413                MessageOptions::ServerIdentifier(AddressOption::new(self.ip)), // must
414                MessageOptions::End,                            // must be last
415            ],
416        };
417
418        match StringOption::new(error) {
419            Ok(converted) => {
420                response.add_option(MessageOptions::Message(converted)); // should
421            }
422            Err(_) => {
423                error!("Sending Nak without error message option because option's value is empty");
424            }
425        };
426
427        response
428    }
429}