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