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