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