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