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}