Skip to main content

toe_beans/v4/server/
mod.rs

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