SatCat5
ip_dhcp.cc
1 // Copyright 2023-2024 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/ip_dhcp.h>
7 #include <satcat5/eth_checksum.h>
8 #include <satcat5/log.h>
9 #include <satcat5/timeref.h>
10 #include <satcat5/udp_dispatch.h>
11 #include <satcat5/utils.h>
12 
15 using satcat5::eth::MACADDR_BROADCAST;
19 using satcat5::ip::Addr;
20 using satcat5::ip::ADDR_BROADCAST;
21 using satcat5::ip::ADDR_NONE;
22 using satcat5::ip::DEFAULT_ROUTE;
28 using satcat5::net::Type;
29 using satcat5::udp::PORT_DHCP_CLIENT;
30 using satcat5::udp::PORT_DHCP_SERVER;
32 namespace log = satcat5::log;
33 
34 // Enable additional logs for debugging? (Verbosity = 0/1/2)
35 static constexpr unsigned DEBUG_VERBOSE = 0;
36 
37 // Dispatch type codes.
38 static constexpr Type TYPE_CLIENT = Type(PORT_DHCP_CLIENT.value);
39 static constexpr Type TYPE_SERVER = Type(PORT_DHCP_SERVER.value);
40 
41 // Opcodes for the legacy "OP" field from BOOTP.
42 static constexpr u8 OP_REQUEST = 1;
43 static constexpr u8 OP_REPLY = 2;
44 
45 // DHCP "magic cookie" identifier.
46 static constexpr u32 DHCP_MAGIC = 0x63825363;
47 
48 // Request bits in the FLAGS header.
49 static constexpr u16 FLAG_BROADCAST = 0x8000;
50 
51 // Field lengths in the DHCP_HEADER
52 static constexpr unsigned MACADDR_LEN = 6;
53 static constexpr unsigned CHADDR_LEN = 16;
54 static constexpr unsigned LEGACY_BYTES = 192;
55 static constexpr unsigned LEGACY_WORDS = LEGACY_BYTES / 4;
56 
57 // DHCP message types for use with OPTION_MSG_TYPE (Section 3.1.2)
58 static constexpr u8 DHCP_DISCOVER = 1; // Client to server
59 static constexpr u8 DHCP_OFFER = 2; // Server to client
60 static constexpr u8 DHCP_REQUEST = 3; // Client to server
61 static constexpr u8 DHCP_DECLINE = 4; // Client to server
62 static constexpr u8 DHCP_ACK = 5; // Server to client
63 static constexpr u8 DHCP_NAK = 6; // Server to client
64 static constexpr u8 DHCP_RELEASE = 7; // Client to server
65 static constexpr u8 DHCP_INFORM = 8; // Client to server
66 
67 // Minimal subset of DHCP option codes. Type/length/value except as noted.
68 // See also: IETF RFC 2132: https://www.rfc-editor.org/rfc/rfc2132.html
69 static constexpr u8 OPTION_PAD = 0; // No length
70 static constexpr u8 OPTION_SUBNET_MASK = 1;
71 static constexpr u8 OPTION_ROUTER = 3;
72 static constexpr u8 OPTION_DNS_SERVER = 6;
73 static constexpr u8 OPTION_DOMAIN_NAME = 15;
74 static constexpr u8 OPTION_REQUEST_IP = 50;
75 static constexpr u8 OPTION_LEASE_TIME = 51;
76 static constexpr u8 OPTION_MSG_TYPE = 53;
77 static constexpr u8 OPTION_SERVER_IP = 54;
78 static constexpr u8 OPTION_CLIENT_ID = 61;
79 static constexpr u8 OPTION_END = 255; // No length
80 
81 // Most option headers consist of an option code plus a length.
82 // When the length is fixed, we can treat both as a single unit.
83 static constexpr u16 make_optlen(u8 opcode, u8 len)
84  { return u16(256*opcode + len); }
85 static constexpr u16 OPTLEN_SUBNET_MASK = make_optlen(OPTION_SUBNET_MASK, 4);
86 static constexpr u16 OPTLEN_ROUTER = make_optlen(OPTION_ROUTER, 4);
87 static constexpr u16 OPTLEN_REQUEST_IP = make_optlen(OPTION_REQUEST_IP, 4);
88 static constexpr u16 OPTLEN_LEASE_TIME = make_optlen(OPTION_LEASE_TIME, 4);
89 static constexpr u16 OPTLEN_MSG_TYPE = make_optlen(OPTION_MSG_TYPE, 1);
90 static constexpr u16 OPTLEN_SERVER_IP = make_optlen(OPTION_SERVER_IP, 4);
91 
92 // Various time-related constants, always in seconds.
93 static constexpr u32 TIME_INIT_FIRST = 3;
94 static constexpr u32 TIME_INIT_RETRY = 5;
95 static constexpr u32 TIME_LEASE_DEFAULT = 24 * 60 * 60;
96 static constexpr u32 TIME_LEASE_OFFER = 30;
97 static constexpr u32 TIME_WAIT_ARP = 3;
98 static constexpr u32 TIME_WAIT_OFFER = 5;
99 static constexpr u32 TIME_WAIT_RENEW = 30;
100 static constexpr u32 TIME_WAIT_REBIND = 30;
101 static constexpr u32 TIME_WAIT_REQUEST = 5;
102 
103 // Reserved client-IDs.
104 static constexpr u32 CLIENT_NONE = 0;
105 static constexpr u32 CLIENT_RSVD = 1;
106 
107 // Shortcut for expired or inactive leases.
108 static constexpr DhcpAddress LEASE_NONE = {CLIENT_NONE, 0};
109 
110 // Is a given lease expired or otherwise available?
111 inline bool lease_expired(DhcpAddress* meta, u32 tref)
112 {
113  // If no Client-ID exists, then the lease is ready for use.
114  // Otherwise, check remaining time. (Difference of two u32
115  // timestamps is guaranteed to wrap correctly.)
116  if (meta->client != CLIENT_NONE) {
117  s32 rem = (s32)(meta->timeout - tref);
118  return rem < 0; // Timeout elapsed?
119  } else {
120  return true; // Empty lease ignores time
121  }
122 }
123 
124 // Consolidate common log functions for reduced code-size.
125 static void log_dhcp_info(const char* msg, u32 yiaddr)
126  { log::Log(log::INFO, "DHCP client", msg).write(Addr(yiaddr)); }
127 
128 DhcpClient::DhcpClient(satcat5::udp::Dispatch* iface)
129  : satcat5::net::Protocol(TYPE_CLIENT)
130  , m_iface(iface)
131  , m_client_id(0)
132  , m_server(iface)
133  // Does the interface have a static IP-address?
134  , m_state(iface->ipaddr().value ? DhcpState::STOPPED : DhcpState::INIT)
135  , m_ipaddr(ADDR_NONE)
136  , m_server_id(0)
137  // Wait a few seconds before first DHCP_DISCOVER attempt.
138  , m_timeout(TIME_INIT_FIRST)
139  // RFC2131 requires XID to be "random". Local MAC address should
140  // be unique, so use CRC32 as a crude psuedorandom hash.
141  , m_xid(crc32(MACADDR_LEN, iface->macaddr().addr))
142 {
143  // Additional entropy for XID.
144  m_xid += SATCAT5_CLOCK->raw();
145 
146  // Call frame_rcvd() for incoming packets.
147  m_iface->add(this);
148 
149  // Call timer_event() once per second.
150  timer_every(1000);
151 }
152 
153 #if SATCAT5_ALLOW_DELETION
154 DhcpClient::~DhcpClient() {
155  // Unlink incoming message handler.
156  m_iface->remove(this);
157 
158  // Release the currently-held lease, if any.
159  send_message(DHCP_RELEASE);
160 }
161 #endif
162 
163 void DhcpClient::inform(const Addr& new_addr) {
164  if (DEBUG_VERBOSE > 1)
165  log::Log(log::DEBUG, "DHCP client", "User inform");
166 
167  // Release lease if held, and set the new address.
168  send_message(DHCP_RELEASE);
169  m_iface->iface()->set_addr(new_addr);
170 
171  // If possible, request subnet parameters after a short delay.
172  if (new_addr != ADDR_NONE) {
173  m_state = DhcpState::INFORMING;
174  m_timeout = 1;
175  }
176 }
177 
178 void DhcpClient::release(const Addr& new_addr) {
179  if (DEBUG_VERBOSE > 1)
180  log::Log(log::DEBUG, "DHCP client", "User release");
181 
182  send_message(DHCP_RELEASE);
183  m_iface->iface()->set_addr(new_addr);
184 }
185 
187  if (DEBUG_VERBOSE > 1)
188  log::Log(log::DEBUG, "DHCP client", "User renew");
189 
190  // Do we currently hold a lease?
191  if (status() > 0) {
192  // Lease held -> Send REQUEST and attempt reuse.
193  send_message(DHCP_REQUEST);
194  } else {
195  // No lease or tentative lease -> Start over.
196  send_message(DHCP_DISCOVER);
197  }
198 }
199 
200 u32 DhcpClient::status() const {
201  // Do we currently hold a lease?
202  if (m_state == DhcpState::BOUND) {
203  return m_timeout + TIME_WAIT_REBIND + TIME_WAIT_RENEW;
204  } else if (m_state == DhcpState::RENEWING) {
205  return m_timeout + TIME_WAIT_REBIND;
206  } else if (m_state == DhcpState::REBINDING) {
207  return m_timeout;
208  } else {
209  return 0;
210  }
211 }
212 
213 void DhcpClient::arp_event(const MacAddr& mac, const Addr& ip) {
214  if (DEBUG_VERBOSE > 1)
215  log::Log(log::DEBUG, "DHCP client", "arp_event");
216 
217  // When we get a tentative IP through DHCPOFFER, send an ARP request to
218  // test if it's already taken. If there's a response, decline the offer.
219  if (ip == m_ipaddr && mac != m_iface->macaddr()) {
220  // Unregister ARP callbacks and notify server.
221  log::Log(log::WARNING, "DHCP client", "Address already claimed");
222  m_iface->arp()->remove(this);
223  send_message(DHCP_DECLINE);
224  }
225 }
226 
228  if (DEBUG_VERBOSE > 1)
229  log::Log(log::DEBUG, "DHCP client", "frame_rcvd");
230 
231  // Shortcut if we're not listening for DHCP messages.
232  if (m_state != DhcpState::SELECTING &&
233  m_state != DhcpState::REQUESTING &&
234  m_state != DhcpState::RENEWING &&
235  m_state != DhcpState::REBINDING &&
236  m_state != DhcpState::INFORMING) return;
237 
238  // Read the BOOTP/DHCP message header.
239  u8 op = src.read_u8();
240  u8 htype = src.read_u8();
241  u8 hlen = src.read_u8();
242  src.read_u8(); // hops
243  u32 xid = src.read_u32();
244  src.read_consume(8); // secs + flags + ciaddr
245  u32 yiaddr = src.read_u32();
246  src.read_consume(8 + CHADDR_LEN + LEGACY_BYTES);
247  u32 magic = src.read_u32();
248 
249  // Sanity check before proceeding.
250  if (!src.get_read_ready()) return; // Incomplete header
251  if (op != OP_REPLY) return; // Not a server-to-client message
252  if (htype != 1 || hlen != 6) return; // Not an IPv4 / Ethernet request
253  if (xid != m_xid) return; // Transaction-ID mismatch
254  if (magic != DHCP_MAGIC) return; // Invalid "magic cookie" value
255 
256  // Scan through options for information of interest.
257  // (Options in any order, so we need to parse the whole thing.)
258  // TODO: Do something with DNS server and domain name options?
259  u8 opcode = 0;
260  u32 lease_time = 0;
261  u32 server = 0;
262  u32 subnet = 0;
263  u32 router = 0;
264  while (src.get_read_ready()) {
265  // Read option type and handle no-length options.
266  u8 typ = src.read_u8();
267  if (typ == OPTION_PAD) continue;
268  if (typ == OPTION_END) break;
269  // Read option length and contents.
270  u8 len = src.read_u8();
271  u16 optlen = make_optlen(typ, len);
272  if (optlen == OPTLEN_SUBNET_MASK) {
273  subnet = src.read_u32();
274  } else if (optlen == OPTLEN_ROUTER) {
275  router = src.read_u32();
276  } else if (optlen == OPTLEN_LEASE_TIME) {
277  lease_time = src.read_u32();
278  } else if (optlen == OPTLEN_MSG_TYPE) {
279  opcode = src.read_u8();
280  } else if (optlen == OPTLEN_SERVER_IP) {
281  server = src.read_u32();
282  } else {
283  src.read_consume(len); // Discard unsupported options
284  }
285  }
286 
287  // Log this event if applicable.
288  if (DEBUG_VERBOSE > 0)
289  log::Log(log::DEBUG, "DHCP client", "Received").write(opcode);
290 
291  // Update internal state and take further action.
292  if (opcode == DHCP_OFFER && m_state == DhcpState::SELECTING) {
293  // In the SELECTING state, tentatively accept the first OFFER.
294  // Test if assigned IP is occupied before setting up the IP stack.
295  log::Log(log::INFO, "DHCP client", "Offer received").write(Addr(yiaddr));
296  // TODO: For some reason, calling "log_dhcp_info" here crashes the XSDK linker.
297  m_iface->iface()->set_addr(ADDR_NONE);
298  m_ipaddr = Addr(yiaddr);
299  m_state = DhcpState::TESTING;
300  m_timeout = TIME_WAIT_ARP;
301  // Register for callbacks and send an ARP probe (RFC5227)
302  m_iface->arp()->add(this);
303  m_iface->arp()->send_probe(yiaddr);
304  } else if (opcode == DHCP_ACK && m_state == DhcpState::INFORMING) {
305  // Information only, set up the local IP stack.
306  log_dhcp_info("Information", yiaddr);
307  m_state = DhcpState::STOPPED;
308  m_timeout = 0;
309  if (router && subnet)
310  m_iface->iface()->route_simple(Addr(router), Addr(subnet));
311  } else if (opcode == DHCP_ACK && server == m_server_id) {
312  // Lease granted. Can we accept it?
313  if (lease_time > TIME_WAIT_RENEW + TIME_WAIT_REBIND
314  && yiaddr == m_ipaddr.value && m_ipaddr.is_unicast()) {
315  log_dhcp_info("Lease granted", yiaddr);
316  // Move to the BOUND state.
317  m_state = DhcpState::BOUND;
318  m_timeout = lease_time - TIME_WAIT_RENEW - TIME_WAIT_REBIND;
319  // Set up the local IP stack.
320  m_iface->iface()->set_addr(m_ipaddr);
321  if (router && subnet)
322  m_iface->iface()->route_simple(Addr(router), Addr(subnet));
323  } else {
324  // Reject the assigned lease.
325  log_dhcp_info("Lease invalid", yiaddr);
326  send_message(DHCP_RELEASE);
327  }
328  } else if (opcode == DHCP_NAK && server == m_server_id) {
329  // Lease denied -> Shut down and start over.
330  log::Log(log::WARNING, "DHCP client", "Request refused").write(yiaddr);
331  m_ipaddr = ADDR_NONE;
332  m_state = DhcpState::INIT;
333  m_timeout = TIME_INIT_RETRY;
334  m_iface->iface()->set_addr(ADDR_NONE);
335  }
336 
337  // Bind the server address for later messages?
338  if (m_state == DhcpState::BOUND || m_state == DhcpState::TESTING) {
339  m_server_id = server;
341  m_iface->reply_ip(), m_iface->reply_mac(),
342  PORT_DHCP_SERVER, PORT_DHCP_CLIENT);
343  }
344 }
345 
347  ++m_seconds;
348  if (m_timeout == 1) {
349  // Execute next scheduled action.
350  --m_timeout;
351  next_timer();
352  } else if (m_timeout > 0) {
353  // Countdown to next scheduled action.
354  --m_timeout;
355  }
356 }
357 
358 void DhcpClient::next_timer() {
359  if (DEBUG_VERBOSE > 1)
360  log::Log(log::DEBUG, "DHCP client", "next_timer");
361 
362  switch (m_state) {
363  case DhcpState::INIT: // Initial state, ready for operation.
364  case DhcpState::SELECTING: // Timeout waiting for DHCPOFFER, start over.
365  case DhcpState::REBINDING: // Timeout waiting for DHCPOFFER, start over.
366  case DhcpState::REQUESTING: // Timeout waiting for DHCPACK, start over.
367  // Send a DISCOVER message and wait for OFFER.
368  send_message(DHCP_DISCOVER);
369  break;
370  case DhcpState::TESTING: // Timeout waiting for ARP reply.
371  // Proceed with REQUEST and wait for ACK or NAK.
372  m_iface->arp()->remove(this);
373  send_message(DHCP_REQUEST);
374  break;
375  case DhcpState::BOUND: // Lease expiring soon, attempt unicast renew.
376  case DhcpState::RENEWING: // Unable to unicast renew, attempt broadcast renew.
377  // Send a REQUEST message and wait for ACK or NAK.
378  send_message(DHCP_REQUEST);
379  break;
380  case DhcpState::INFORMING: // Timeout waiting for DHCPACK, retry.
381  send_message(DHCP_INFORM);
382  break;
383  default: break; // LCOV_EXCL_LINE (Unreachable but harmless.)
384  }
385 }
386 
387 void DhcpClient::send_message(u8 opcode) {
388  if (DEBUG_VERBOSE > 1)
389  log::Log(log::DEBUG, "DHCP client", "Sending opcode").write(opcode);
390 
391  // How does sending this message change the current state?
392  if (opcode == DHCP_DISCOVER) {
393  m_state = DhcpState::SELECTING;
394  m_timeout = TIME_WAIT_OFFER;
395  } else if (opcode == DHCP_REQUEST && m_state == DhcpState::TESTING) {
396  m_state = DhcpState::REQUESTING;
397  m_timeout = TIME_WAIT_REQUEST;
398  } else if (opcode == DHCP_REQUEST && m_state == DhcpState::BOUND) {
399  m_state = DhcpState::RENEWING;
400  m_timeout = TIME_WAIT_RENEW;
401  } else if (opcode == DHCP_REQUEST) {
402  m_state = DhcpState::REBINDING;
403  m_timeout = TIME_WAIT_REBIND;
404  } else if (opcode == DHCP_DECLINE) {
405  m_state = DhcpState::INIT;
406  m_timeout = TIME_INIT_RETRY;
407  } else if (opcode == DHCP_RELEASE) {
408  m_state = DhcpState::STOPPED;
409  m_timeout = 0;
410  } else if (opcode == DHCP_INFORM) {
411  m_state = DhcpState::INFORMING;
412  m_timeout = TIME_INIT_RETRY;
413  } else if (DEBUG_VERBOSE > 0) {
414  log::Log(log::ERROR, "DHCP client", "Unexpected command");
415  }
416 
417  // Restart the elapsed-time counter? (Table 5)
418  if (opcode == DHCP_DISCOVER || opcode == DHCP_INFORM ||
419  opcode == DHCP_DECLINE || opcode == DHCP_RELEASE)
420  m_seconds = 0;
421 
422  // Release commands without a lease are ignored.
423  if (opcode == DHCP_RELEASE && m_ipaddr == ADDR_NONE) return;
424 
425  // Should this message be a broadcast?
426  bool bcast = !m_server.ready();
427  if (opcode == DHCP_REQUEST && !status())
428  bcast = true; // Initial bindings are always broadcast
429  if (opcode == DHCP_DISCOVER || opcode == DHCP_INFORM)
430  bcast = true; // Certain opcodes are always broadcast
431  if (m_state == DhcpState::REBINDING)
432  bcast = true; // Unicast renew failed, try broadcast
433  if (bcast) {
435  ADDR_BROADCAST, MACADDR_BROADCAST,
436  PORT_DHCP_SERVER, PORT_DHCP_CLIENT);
437  }
438 
439  // Client hardware address.
440  MacAddr macaddr = m_iface->macaddr();
441  u8 chaddr[CHADDR_LEN];
442  for (unsigned a = 0 ; a < CHADDR_LEN ; ++a)
443  chaddr[a] = (a < MACADDR_LEN) ? macaddr.addr[a] : 0;
444 
445  // Put IP address in ciaddr or an option? (Never both.)
446  // See RFC2131 Table 5 for MUST/MAY/MUST-NOT rules.
447  u32 ciaddr = 0, reqaddr = 0;
448  if ((opcode == DHCP_RELEASE) || (opcode == DHCP_REQUEST && status() > 0)) {
449  // Client holds a valid lease.
450  ciaddr = m_ipaddr.value;
451  reqaddr = 0;
452  } else if (opcode == DHCP_INFORM) {
453  // Client holds a static address.
454  ciaddr = m_iface->ipaddr().value;
455  reqaddr = 0;
456  } else {
457  // No lease or tentative lease.
458  ciaddr = 0;
459  reqaddr = m_ipaddr.value;
460  }
461 
462  // Include server address field?
463  u32 server = 0;
464  if (opcode == DHCP_REQUEST && m_state == DhcpState::REQUESTING)
465  server = m_server_id; // Required per RFC2131 Table 5, Col 2
466  else if (opcode == DHCP_DECLINE || opcode == DHCP_RELEASE)
467  server = m_server_id; // Required per RFC2131 Table 5, Col 3
468 
469  // Write out options to determine total length.
470  // See RFC2131 Table 5 for MUST/MAY/MUST-NOT rules.
471  static constexpr unsigned BUFF_SIZE = 64 + SATCAT5_DHCP_MAX_ID_LEN;
473  // Message type is always required.
474  opt.write_u16(OPTLEN_MSG_TYPE);
475  opt.write_u8(opcode);
476  // Requested IP address.
477  if (reqaddr) {
478  opt.write_u16(OPTLEN_REQUEST_IP);
479  opt.write_u32(reqaddr);
480  }
481  // Requested lease time.
482  if (opcode == DHCP_DISCOVER || opcode == DHCP_REQUEST) {
483  opt.write_u16(OPTLEN_LEASE_TIME);
484  opt.write_u32(TIME_LEASE_DEFAULT);
485  }
486  // Server identifier.
487  if (server) {
488  opt.write_u16(OPTLEN_SERVER_IP);
489  opt.write_u32(server);
490  }
491  // Client identifier.
492  if (SATCAT5_DHCP_MAX_ID_LEN >= 1 &&
493  SATCAT5_DHCP_MAX_ID_LEN <= 254 &&
494  m_client_id && m_client_id->id_len &&
495  m_client_id->id_len <= SATCAT5_DHCP_MAX_ID_LEN) {
496  opt.write_u16(make_optlen(OPTION_CLIENT_ID, m_client_id->id_len + 1));
497  opt.write_u8(m_client_id->type);
498  opt.write_bytes(m_client_id->id_len, m_client_id->id);
499  }
500 
501  // End-of-options marker.
502  opt.write_u8(OPTION_END);
503  opt.write_finalize();
504 
505  // Prepare to send a new UDP packet...
506  unsigned msg_len = 240 + opt.written_len();
507  Writeable* dst = m_server.open_write(msg_len);
508  if (dst) {
509  // Write the basic DHCP message header.
510  dst->write_u32(0x01010600); // OP, HTYPE, HLEN, HOPS
511  dst->write_u32(m_xid); // xid
512  dst->write_u16(m_seconds); // secs
513  dst->write_u16(0); // flags = 0
514  dst->write_u32(ciaddr); // ciaddr (see above)
515  dst->write_u32(0); // yiaddr = 0
516  dst->write_u32(0); // siaddr = 0
517  dst->write_u32(0); // giaddr = 0
518  dst->write_bytes(CHADDR_LEN, chaddr);
519  for (unsigned a = 0 ; a < LEGACY_WORDS ; ++a)
520  dst->write_u32(0); // 192 bytes of zeros
521  dst->write_u32(DHCP_MAGIC); // Magic cookie
522  // Write options and send the message.
523  dst->write_bytes(opt.written_len(), opt.buffer());
524  dst->write_finalize();
525  }
526 }
527 
529  : satcat5::net::Protocol(TYPE_SERVER)
530  , m_iface(iface)
531  , m_pool(pool)
532  , m_time(0)
533  , m_max_lease(TIME_LEASE_DEFAULT)
534  , m_next_lease(0)
535  , m_next_timer(0)
536  , m_dns(ADDR_NONE)
537  , m_domain(0)
538  , m_gateway(DEFAULT_ROUTE)
539 {
540  // Mark the entire lease pool as available.
541  unsigned idx = 0;
542  while (1) {
543  DhcpAddress* next = m_pool->idx2meta(idx++);
544  if (next) *next = LEASE_NONE;
545  else break;
546  }
547 
548  // Call frame_rcvd() for incoming packets.
549  m_iface->add(this);
550 
551  // Call timer_event() once per second.
552  timer_every(1000);
553 }
554 
555 #if SATCAT5_ALLOW_DELETION
556 DhcpServer::~DhcpServer() {
557  // Unlink incoming message handler.
558  m_iface->remove(this);
559 }
560 #endif
561 
562 void DhcpServer::count_leases(unsigned& free, unsigned& taken) const {
563  // Reset output counters.
564  free = 0; taken = 0;
565 
566  // Iterate over the entire lease pool.
567  unsigned idx = 0;
568  while (1) {
569  DhcpAddress* next = m_pool->idx2meta(idx++);
570  if (!next)
571  return; // Reached end of list
572  else if (next->timeout > 0)
573  ++taken; // Lease has been claimed
574  else
575  ++free; // Lease is available
576  }
577 }
578 
579 Addr DhcpServer::request(u32 lease_seconds, const Addr& addr) {
580  log_dhcp_info("Local request", addr.value);
581  if (addr == ADDR_NONE) // First available
582  return offer(CLIENT_RSVD, addr.value, lease_seconds);
583  else // Specific address
584  return reserve(CLIENT_RSVD, addr.value, lease_seconds);
585 }
586 
588  if (DEBUG_VERBOSE > 1)
589  log::Log(log::DEBUG, "DHCP server", "frame_rcvd");
590 
591  // Define some working buffers for later use...
592  static constexpr unsigned MAX_OPTION = 255;
593  u8 chaddr[CHADDR_LEN]; // Header field CHADDR
594  u8 buffer[MAX_OPTION]; // Temp buffer for one option
595 
596  // Read the BOOTP/DHCP message header.
597  u8 op = src.read_u8();
598  u8 htype = src.read_u8();
599  u8 hlen = src.read_u8();
600  src.read_u8(); // hops
601  u32 xid = src.read_u32();
602  src.read_u16(); // secs
603  u16 flags = src.read_u16();
604  u32 ciaddr = src.read_u32();
605  src.read_consume(8); // yiaddr + siaddr
606  u32 giaddr = src.read_u32();
607  src.read_bytes(CHADDR_LEN, chaddr);
608  src.read_consume(LEGACY_BYTES);
609  u32 magic = src.read_u32();
610 
611  // Sanity check before proceeding.
612  if (!src.get_read_ready()) return; // Incomplete header
613  if (op != OP_REQUEST) return; // Not a client-to-server message
614  if (htype != 1 || hlen != 6) return; // Not an IPv4 / Ethernet request
615  if (magic != DHCP_MAGIC) return; // Invalid "magic cookie" value
616 
617  // To save memory, we use a hash to identify clients. Calculate
618  // a hash of CHADDR now; replace it later if Client-ID is provided.
619  // (Likelihood and consequence of a collision are both low, and even a
620  // CRC32 hash is hardly the weakest link in DHCP's security.)
621  u32 client = crc32(CHADDR_LEN, chaddr);
622 
623  // Scan through options for information of interest.
624  // (Options in any order, so we need to parse the whole thing.)
625  bool opt_complete = false;
626  u8 opcode = 0;
627  u32 lease_time = TIME_LEASE_DEFAULT;
628  while (src.get_read_ready()) {
629  // Read option type and handle no-length options.
630  u8 typ = src.read_u8();
631  if (typ == OPTION_PAD) continue;
632  if (typ == OPTION_END) {opt_complete = true; break;}
633  // Read option length and confirm it is valid.
634  u8 len = src.read_u8();
635  if (src.get_read_ready() < len) break;
636  // Read option contents.
637  u16 optlen = make_optlen(typ, len);
638  if (optlen == OPTLEN_REQUEST_IP) {
639  // Client has a specific IP they'd like to reuse.
640  ciaddr = src.read_u32();
641  } else if (optlen == OPTLEN_LEASE_TIME) {
642  // Update the requested lease duration.
643  lease_time = min_u32(src.read_u32(), m_max_lease);
644  } else if (optlen == OPTLEN_MSG_TYPE) {
645  // Message type (required, but not necessarily first option).
646  opcode = src.read_u8();
647  } else if (typ == OPTION_CLIENT_ID) {
648  // Update client hash using Client-ID field.
649  src.read_bytes(len, buffer);
650  client = crc32(len, buffer);
651  } else {
652  // Unsupported options are discarded.
653  src.read_consume(len);
654  }
655  }
656 
657  // Silently discard messages with an incomplete options field.
658  if (!opt_complete) return;
659 
660  // Optional diagnostic logging.
661  if (DEBUG_VERBOSE > 1)
662  log::Log(log::DEBUG, "DHCP server", "Received opcode").write(opcode);
663 
664  // Force client hash out of the reserved range.
665  if (client == CLIENT_NONE || client == CLIENT_RSVD) client = ~client;
666 
667  // Update internal state and decide how to reply.
668  const char* log_msg = "Message ignored";
669  s8 log_typ = log::INFO;
670  u8 reply_type = 0;
671  Addr reply_addr(0), yiaddr(ciaddr);
672  if (opcode == DHCP_DISCOVER) {
673  // If we have an open slot, grant a tentative lease.
674  log_msg = "Discover";
675  yiaddr = offer(client, ciaddr, TIME_LEASE_OFFER);
676  if (yiaddr != ADDR_NONE) reply_type = DHCP_OFFER;
677  } else if (opcode == DHCP_REQUEST) {
678  // Request from client -> Accept, reject, or ignore?
679  yiaddr = reserve(client, ciaddr, lease_time);
680  if (yiaddr != ADDR_NONE) {
681  log_msg = "Request granted";
682  reply_addr = yiaddr;
683  reply_type = DHCP_ACK;
684  } else if (m_pool->contains(ciaddr)) {
685  log_msg = "Request refused";
686  log_typ = log::WARNING;
687  reply_addr = m_iface->reply_ip();
688  reply_type = DHCP_NAK;
689  }
690  } else if (opcode == DHCP_DECLINE) {
691  // We assigned client an IP, but it's already taken!?
692  // If it's one of ours, mark it so we don't reassign it.
693  if (m_pool->contains(ciaddr)) {
694  log_msg = "Lease declined";
695  log_typ = log::WARNING;
696  reserve(CLIENT_RSVD, ciaddr, m_max_lease);
697  }
698  } else if (opcode == DHCP_RELEASE) {
699  // Client is giving up an assigned lease.
700  DhcpAddress* meta = m_pool->addr2meta(ciaddr);
701  if (meta && client == meta->client) {
702  log_msg = "Release granted";
703  *meta = LEASE_NONE;
704  }
705  } else if (opcode == DHCP_INFORM) {
706  // Client has an address but needs gateway, etc.
707  log_msg = "Information request";
708  reply_addr = m_iface->reply_ip();
709  reply_type = DHCP_ACK;
710  }
711 
712  // Always write something to the event log.
713  log::Log(log_typ, "DHCP server", log_msg)
714  .write(ciaddr | yiaddr.value).write(client);
715 
716  // Skip the rest if no reply is needed.
717  if (reply_type == 0) return;
718  if (DEBUG_VERBOSE > 1)
719  log::Log(log::DEBUG, "DHCP server", "Sending opcode").write(reply_type);
720 
721  // Write outgoing options into the working buffer.
722  // (Do this up front to ensure an accurate length estimate.)
724  // Message type is always required.
725  opt.write_u8(OPTION_MSG_TYPE);
726  opt.write_u8(1);
727  opt.write_u8(reply_type);
728  // Include setup and lease parameters?
729  if (reply_type == DHCP_OFFER || reply_type == DHCP_ACK) {
730  // Option: Gateway and subnet mask and gateway
731  if (m_gateway != DEFAULT_ROUTE) {
732  opt.write_u8(OPTION_SUBNET_MASK);
733  opt.write_u8(4);
734  opt.write_u32(m_gateway.mask.value);
735  opt.write_u8(OPTION_ROUTER);
736  opt.write_u8(4);
737  opt.write_u32(m_gateway.addr.value);
738  }
739  // Option: DNS server
740  if (m_dns.value) {
741  opt.write_u8(OPTION_DNS_SERVER);
742  opt.write_u8(4);
743  opt.write_u32(m_dns.value);
744  }
745  // Option: Domain name.
746  if (m_domain) {
747  u8 dlen = (u8)min_u32(32, strlen(m_domain));
748  opt.write_u8(OPTION_DOMAIN_NAME);
749  opt.write_u8(dlen);
750  opt.write_bytes(dlen, m_domain);
751  }
752  // Option: Lease time
753  opt.write_u8(OPTION_LEASE_TIME);
754  opt.write_u8(4);
755  opt.write_u32(lease_time);
756  }
757  // Option: DHCP server IP
758  opt.write_u8(OPTION_SERVER_IP);
759  opt.write_u8(4);
760  opt.write_u32(m_iface->ipaddr().value);
761  // End-of-options marker.
762  opt.write_u8(OPTION_END);
763  opt.write_finalize();
764 
765  // Unicast or broadcast reply? (RFC2131 Section 4.1)
766  satcat5::udp::Address dstaddr(m_iface);
767  dstaddr.connect(
768  (flags & FLAG_BROADCAST) ? ADDR_BROADCAST : reply_addr,
769  (flags & FLAG_BROADCAST) ? MACADDR_BROADCAST : m_iface->reply_mac(),
770  PORT_DHCP_CLIENT, PORT_DHCP_SERVER);
771 
772  // Calculate reply length and formulate response.
773  // See also: RFC2131 Table 3
774  unsigned reply_len = 240 + opt.written_len();
775  satcat5::io::Writeable* dst = dstaddr.open_write(reply_len);
776  if (dst) {
777  // Write the basic DHCP message header.
778  dst->write_u32(0x02010600); // OP, HTYPE, HLEN, HOPS
779  dst->write_u32(xid); // xid = Echo
780  dst->write_u16(0); // secs = 0
781  dst->write_u16(flags); // flags = Echo
782  dst->write_u32(0); // ciaddr = 0
783  dst->write_u32(yiaddr.value); // Offered address (see above)
784  dst->write_u32(0); // siaddr = None
785  dst->write_u32(giaddr); // giaddr = Echo
786  dst->write_bytes(CHADDR_LEN, chaddr); // chaddr = Echo
787  for (unsigned a = 0 ; a < LEGACY_WORDS ; ++a)
788  dst->write_u32(0); // 192 bytes of zeros
789  dst->write_u32(DHCP_MAGIC); // Magic cookie
790  // Write options and send the message.
791  dst->write_bytes(opt.written_len(), opt.buffer());
792  dst->write_finalize();
793  }
794 }
795 
797  // Increment the reference time.
798  ++m_time;
799 
800  // Check ONE address to see if its lease has expired.
801  // No need to check the entire pool; as long as we touch everything
802  // within 2^31 seconds then we'll avoid overflow/wraparound glitches.
803  DhcpAddress* meta = m_pool->idx2meta(m_next_timer++);
804  if (meta) {
805  // Has this lease expired? If so, reset it.
806  if (lease_expired(meta, m_time))
807  *meta = LEASE_NONE;
808  } else {
809  // End of pool, wrap around to the beginning.
810  m_next_timer = 0;
811  }
812 }
813 
814 // Reuse an existing address, or find the next free address.
815 Addr DhcpServer::offer(u32 client_id, u32 req_ipaddr, u32 req_lease) {
816  // Did the client request a preferred IP address?
817  if (req_ipaddr) {
818  // Claim the address if it's available (reuse or expired).
819  Addr tmp = reserve(client_id, req_ipaddr, req_lease);
820  if (tmp != ADDR_NONE) return tmp;
821  }
822 
823  // Confirm we're starting from a valid initial index.
824  // (Certain edge-cases can leave it out-of-bounds.)
825  if (!m_pool->idx2meta(m_next_lease)) m_next_lease = 0;
826 
827  // Find the next open address...
828  unsigned wrap = m_next_lease;
829  do {
830  // Check the next address in the pool...
831  DhcpAddress* meta = m_pool->idx2meta(m_next_lease);
832  if (!meta) {
833  // Reached end of pool -> Wrap to beginning.
834  m_next_lease = 0;
835  } else if (lease_expired(meta, m_time)) {
836  // Found an open lease -> Assign it.
837  *meta = {client_id, m_time + req_lease};
838  return m_pool->idx2addr(m_next_lease++);
839  } else {
840  // Try the next address in the pool.
841  ++m_next_lease;
842  }
843  } while (m_next_lease != wrap);
844 
845  // If we've reached this point, there are no vacancies.
846  return ADDR_NONE;
847 }
848 
849 // Attempt to reserve the designated address for the designated client.
850 Addr DhcpServer::reserve(u32 client_id, u32 req_ipaddr, u32 req_lease) {
851  // Lookup the requested address.
852  DhcpAddress* meta = m_pool->addr2meta(req_ipaddr);
853 
854  // If it's the same client or unclaimed, assign it.
855  if (!meta) {
856  return ADDR_NONE; // No such address
857  } else if (client_id == CLIENT_RSVD) {
858  *meta = {client_id, m_time + req_lease};
859  return Addr(req_ipaddr); // Forced-reserve
860  } else if (client_id == meta->client || lease_expired(meta, m_time)) {
861  *meta = {client_id, m_time + req_lease};
862  return Addr(req_ipaddr); // Requested IP is OK!
863  } else {
864  return ADDR_NONE; // Already in use
865  }
866 }
void add(satcat5::eth::ArpListener *evt)
Register an event-listener.
Definition: eth_arp.h:76
void remove(satcat5::eth::ArpListener *evt)
Unregister an event-listener.
Definition: eth_arp.h:79
bool send_probe(const satcat5::ip::Addr &target, const satcat5::eth::VlanTag &vtag=satcat5::eth::VTAG_NONE)
Send a probe to test if a given address is occupied.
Definition: eth_arp.cc:98
unsigned written_len() const
Report total length after write_finalize() is called.
Definition: io_writeable.h:151
bool write_finalize() override
Mark end of frame and release temporary working data.
const u8 * buffer() const
Read-only access to the working buffer.
Definition: io_writeable.h:147
Thin wrapper for ArrayWrite with a built-in buffer.
Definition: io_writeable.h:169
Limited read of next N bytes.
Definition: io_readable.h:255
bool read_consume(unsigned nbytes) override
Read and discard 0 or more bytes.
Definition: io_readable.cc:307
bool read_bytes(unsigned nbytes, void *dst) override
Read 0 or more bytes into a buffer.
Definition: io_readable.cc:296
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Definition: io_readable.cc:293
u8 read_u8()
One of many functions for reading integer/floating point values, see details.
Definition: io_readable.cc:44
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
virtual void write_bytes(unsigned nbytes, const void *src)
Write 0 or more bytes from a buffer.
void write_u8(u8 data)
One of many functions for writing integer/floating point values, see details.
Definition: io_writeable.cc:20
virtual bool write_finalize()
Mark end of frame and release temporary working data.
DHCP Client for leasing an IP address from a server.
Definition: ip_dhcp.h:75
void inform(const satcat5::ip::Addr &new_addr)
Set a static IP and fetch other parameters from the server.
Definition: ip_dhcp.cc:163
u32 m_timeout
Time to next action, in seconds.
Definition: ip_dhcp.h:126
void arp_event(const satcat5::eth::MacAddr &mac, const satcat5::ip::Addr &ip) override
Callback for any announced MAC/IP address pair.
Definition: ip_dhcp.cc:213
void renew()
Request extension of current lease if held, otherwise request a new lease.
Definition: ip_dhcp.cc:186
satcat5::ip::DhcpState m_state
Client state.
Definition: ip_dhcp.h:122
u32 m_server_id
Server-ID (may not match IP-addr)
Definition: ip_dhcp.h:125
satcat5::udp::Address m_server
Server IP+MAC address.
Definition: ip_dhcp.h:121
void timer_event() override
Child class MUST override this method.
Definition: ip_dhcp.cc:346
void frame_rcvd(satcat5::io::LimitedRead &src) override
Dispatch calls frame_rcvd(...) for each incoming frame with with a matching net::Type value.
Definition: ip_dhcp.cc:227
satcat5::ip::Addr m_ipaddr
Assigned IP address, if any.
Definition: ip_dhcp.h:123
void release(const satcat5::ip::Addr &new_addr=satcat5::ip::ADDR_NONE)
Relinquish the currently held lease, if one exists.
Definition: ip_dhcp.cc:178
u32 m_xid
Client/server transaction-ID.
Definition: ip_dhcp.h:127
u32 status() const
Report remaining lease time, or zero if none is held.
Definition: ip_dhcp.cc:200
u16 m_seconds
Seconds since start of process.
Definition: ip_dhcp.h:124
Generic container for a group of DhcpAddress objects.
Definition: ip_dhcp.h:144
virtual satcat5::ip::Addr idx2addr(unsigned idx) const =0
Fetch IP-address for Nth object in the pool.
virtual satcat5::ip::DhcpAddress * idx2meta(unsigned idx)=0
Fetch metadata for Nth object in the pool.or Returns NULL if the index is out of bounds.
satcat5::ip::DhcpAddress * addr2meta(const satcat5::ip::Addr &addr)
Two-step lookup of metadata from address.
Definition: ip_dhcp.h:166
bool contains(const satcat5::ip::Addr &addr)
Does this pool contain the designated address?
Definition: ip_dhcp.h:162
DHCP Server for managing leases to other clients.
Definition: ip_dhcp.h:197
unsigned m_next_timer
Next timer event.
Definition: ip_dhcp.h:249
u32 m_max_lease
Maximum lease in seconds.
Definition: ip_dhcp.h:245
unsigned m_next_lease
Next lease request.
Definition: ip_dhcp.h:248
u32 m_time
Arbitrary timescale (+1/sec)
Definition: ip_dhcp.h:244
satcat5::ip::Subnet m_gateway
Default gateway and subnet mask.
Definition: ip_dhcp.h:254
DhcpServer(satcat5::udp::Dispatch *iface, satcat5::ip::DhcpPool *pool)
Attach this DHCP server to a UDP-dispatch object and address pool.
Definition: ip_dhcp.cc:528
satcat5::ip::Addr m_dns
DNS server, if one is available.
Definition: ip_dhcp.h:252
const char * m_domain
Domain name (human-readable)
Definition: ip_dhcp.h:253
void timer_event() override
Child class MUST override this method.
Definition: ip_dhcp.cc:796
satcat5::ip::Addr request(u32 lease_seconds, const satcat5::ip::Addr &addr=satcat5::ip::ADDR_NONE)
Manually request/reserve an IP address for the next N seconds.
Definition: ip_dhcp.cc:579
void frame_rcvd(satcat5::io::LimitedRead &src) override
Dispatch calls frame_rcvd(...) for each incoming frame with with a matching net::Type value.
Definition: ip_dhcp.cc:587
void count_leases(unsigned &free, unsigned &taken) const
Report the number of active or open leases.
Definition: ip_dhcp.cc:562
void set_addr(const satcat5::ip::Addr &addr)
Set the local IP-address.
Definition: ip_dispatch.cc:74
bool route_simple(const satcat5::ip::Addr &gateway, const satcat5::ip::Mask &subnet=satcat5::ip::MASK_24)
Routing table shortcuts.
Definition: ip_dispatch.h:95
The Log class creates and formats one log message.
Definition: log.h:195
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
void add(satcat5::net::Protocol *proto)
Register a Protocol object.
Definition: net_dispatch.h:55
void remove(satcat5::net::Protocol *proto)
Unregister a Protocol object.
Definition: net_dispatch.h:59
void timer_every(unsigned msec)
Configure a repeating notification every X milliseconds.
Definition: polling.cc:321
Implementation of "net::Address" for UDP Dispatch.
Definition: udp_core.h:72
bool ready() const override
Is this address object ready for use? Child MUST override this method.
Definition: udp_core.h:103
satcat5::io::Writeable * open_write(unsigned len) override
Open a new frame to the designated address and type.
Definition: udp_core.cc:71
void connect(const satcat5::udp::Addr &dstaddr, const satcat5::eth::MacAddr &dstmac, const satcat5::udp::Port &dstport, const satcat5::udp::Port &srcport=satcat5::udp::PORT_NONE, const satcat5::eth::VlanTag &vtag=satcat5::eth::VTAG_NONE)
Manual address resolution (user supplies IP + MAC).
Definition: udp_core.cc:30
Dispatcher sorts incoming UDP messages by port index.
Definition: udp_dispatch.h:20
Inline Ethernet Checksum insertion and verification.
u32 crc32(unsigned nbytes, const void *data)
Directly calculate CRC32 on a block of data.
Dynamic Host Configuration Protocol (DHCP) client and server.
DhcpState
DHCP protocol state.
Definition: ip_dhcp.h:57
Diagnostic logging to UART and/or Ethernet ports.
An Ethernet MAC address (with serializable interface).
Definition: eth_header.h:29
u8 addr[6]
Byte array in network order (Index 0 = MSB)
Definition: eth_header.h:31
IPv4 address is a 32-bit unsigned integer.
Definition: ip_core.h:15
u32 value
Raw access to the underlying representation.
Definition: ip_core.h:17
bool is_unicast() const
Any normal single-destination address.
Definition: ip_core.cc:59
One address in the pool allocated to a DhcpServer.
Definition: ip_dhcp.h:131
u32 timeout
Lease expiration time.
Definition: ip_dhcp.h:133
u32 client
Hash of client-ID.
Definition: ip_dhcp.h:132
u16 value
Raw access to the underlying representation.
Definition: ip_core.h:121
satcat5::ip::Mask mask
Subnet mask.
Definition: ip_core.h:90
satcat5::ip::Addr addr
Base address.
Definition: ip_core.h:89
Multipurpose filter for matching fields in network packets.
Definition: net_type.h:38
TimeRef and TimeVal define the API for monotonic timers.
Miscellaneous mathematical utility functions.
constexpr u32 min_u32(u32 a, u32 b)
Min and max functions.
Definition: utils.h:103