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