Kea  2.1.7-git
cfg_subnets4.cc
Go to the documentation of this file.
1 // Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 #include <dhcp/iface_mgr.h>
9 #include <dhcp/option_custom.h>
10 #include <dhcpsrv/cfg_subnets4.h>
11 #include <dhcpsrv/dhcpsrv_log.h>
13 #include <dhcpsrv/shared_network.h>
14 #include <dhcpsrv/subnet_id.h>
15 #include <asiolink/io_address.h>
17 #include <stats/stats_mgr.h>
18 #include <sstream>
19 
20 using namespace isc::asiolink;
21 using namespace isc::data;
22 
23 namespace isc {
24 namespace dhcp {
25 
26 void
27 CfgSubnets4::add(const Subnet4Ptr& subnet) {
28  if (getBySubnetId(subnet->getID())) {
29  isc_throw(isc::dhcp::DuplicateSubnetID, "ID of the new IPv4 subnet '"
30  << subnet->getID() << "' is already in use");
31 
32  } else if (getByPrefix(subnet->toText())) {
35  isc_throw(isc::dhcp::DuplicateSubnetID, "subnet with the prefix of '"
36  << subnet->toText() << "' already exists");
37  }
38 
40  .arg(subnet->toText());
41  static_cast<void>(subnets_.insert(subnet));
42 }
43 
45 CfgSubnets4::replace(const Subnet4Ptr& subnet) {
46  // Get the subnet with the same ID.
47  const SubnetID& subnet_id = subnet->getID();
48  auto& index = subnets_.template get<SubnetSubnetIdIndexTag>();
49  auto subnet_it = index.find(subnet_id);
50  if (subnet_it == index.end()) {
51  isc_throw(BadValue, "There is no IPv4 subnet with ID " <<subnet_id);
52  }
53  Subnet4Ptr old = *subnet_it;
54  bool ret = index.replace(subnet_it, subnet);
55 
57  .arg(subnet_id).arg(ret);
58  if (ret) {
59  return (old);
60  } else {
61  return (Subnet4Ptr());
62  }
63 }
64 
65 void
66 CfgSubnets4::del(const ConstSubnet4Ptr& subnet) {
67  del(subnet->getID());
68 }
69 
70 void
71 CfgSubnets4::del(const SubnetID& subnet_id) {
72  auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
73  auto subnet_it = index.find(subnet_id);
74  if (subnet_it == index.end()) {
75  isc_throw(BadValue, "no subnet with ID of '" << subnet_id
76  << "' found");
77  }
78 
79  Subnet4Ptr subnet = *subnet_it;
80 
81  index.erase(subnet_it);
82 
84  .arg(subnet->toText());
85 }
86 
87 void
89  CfgSubnets4& other) {
90  auto& index_id = subnets_.get<SubnetSubnetIdIndexTag>();
91  auto& index_prefix = subnets_.get<SubnetPrefixIndexTag>();
92 
93  // Iterate over the subnets to be merged. They will replace the existing
94  // subnets with the same id. All new subnets will be inserted into the
95  // configuration into which we're merging.
96  auto const& other_subnets = other.getAll();
97  for (auto const& other_subnet : (*other_subnets)) {
98 
99  // Check if there is a subnet with the same ID.
100  auto subnet_id_it = index_id.find(other_subnet->getID());
101  if (subnet_id_it != index_id.end()) {
102 
103  // Subnet found.
104  auto existing_subnet = *subnet_id_it;
105 
106  // If the existing subnet and other subnet
107  // are the same instance skip it.
108  if (existing_subnet == other_subnet) {
109  continue;
110  }
111 
112  // Updating the prefix can lead to problems... e.g. pools
113  // and reservations going outside range.
114  // @todo: check prefix change.
115 
116  // We're going to replace the existing subnet with the other
117  // version. If it belongs to a shared network, we need
118  // remove it from that network.
119  SharedNetwork4Ptr network;
120  existing_subnet->getSharedNetwork(network);
121  if (network) {
122  network->del(existing_subnet->getID());
123  }
124 
125  // Now we remove the existing subnet.
126  index_id.erase(subnet_id_it);
127  }
128 
129  // Check if there is a subnet with the same prefix.
130  auto subnet_prefix_it = index_prefix.find(other_subnet->toText());
131  if (subnet_prefix_it != index_prefix.end()) {
132 
133  // Subnet found.
134  auto existing_subnet = *subnet_prefix_it;
135 
136  // Updating the id can lead to problems... e.g. reservation
137  // for the previous subnet ID.
138  // @todo: check reservations
139 
140  // We're going to replace the existing subnet with the other
141  // version. If it belongs to a shared network, we need
142  // remove it from that network.
143  SharedNetwork4Ptr network;
144  existing_subnet->getSharedNetwork(network);
145  if (network) {
146  network->del(existing_subnet->getID());
147  }
148 
149  // Now we remove the existing subnet.
150  index_prefix.erase(subnet_prefix_it);
151  }
152 
153  // Create the subnet's options based on the given definitions.
154  other_subnet->getCfgOption()->createOptions(cfg_def);
155 
156  // Create the options for pool based on the given definitions.
157  for (auto const& pool : other_subnet->getPoolsWritable(Lease::TYPE_V4)) {
158  pool->getCfgOption()->createOptions(cfg_def);
159  }
160 
161  // Add the "other" subnet to the our collection of subnets.
162  static_cast<void>(subnets_.insert(other_subnet));
163 
164  // If it belongs to a shared network, find the network and
165  // add the subnet to it
166  std::string network_name = other_subnet->getSharedNetworkName();
167  if (!network_name.empty()) {
168  SharedNetwork4Ptr network = networks->getByName(network_name);
169  if (network) {
170  network->add(other_subnet);
171  } else {
172  // This implies the shared-network collection we were given
173  // is out of sync with the subnets we were given.
174  isc_throw(InvalidOperation, "Cannot assign subnet ID of "
175  << other_subnet->getID()
176  << " to shared network: " << network_name
177  << ", network does not exist");
178  }
179  }
180  }
181 }
182 
184 CfgSubnets4::getBySubnetId(const SubnetID& subnet_id) const {
185  const auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
186  auto subnet_it = index.find(subnet_id);
187  return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr());
188 }
189 
191 CfgSubnets4::getByPrefix(const std::string& subnet_text) const {
192  const auto& index = subnets_.get<SubnetPrefixIndexTag>();
193  auto subnet_it = index.find(subnet_text);
194  return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr());
195 }
196 
197 bool
198 CfgSubnets4::hasSubnetWithServerId(const asiolink::IOAddress& server_id) const {
199  const auto& index = subnets_.get<SubnetServerIdIndexTag>();
200  auto subnet_it = index.find(server_id);
201  return (subnet_it != index.cend());
202 }
203 
205 CfgSubnets4::initSelector(const Pkt4Ptr& query) {
206  SubnetSelector selector;
207  selector.ciaddr_ = query->getCiaddr();
208  selector.giaddr_ = query->getGiaddr();
209  selector.local_address_ = query->getLocalAddr();
210  selector.remote_address_ = query->getRemoteAddr();
211  selector.client_classes_ = query->classes_;
212  selector.iface_name_ = query->getIface();
213 
214  // If the link-selection sub-option is present, extract its value.
215  // "The link-selection sub-option is used by any DHCP relay agent
216  // that desires to specify a subnet/link for a DHCP client request
217  // that it is relaying but needs the subnet/link specification to
218  // be different from the IP address the DHCP server should use
219  // when communicating with the relay agent." (RFC 3527)
220  //
221  // Try first Relay Agent Link Selection sub-option
222  OptionPtr rai = query->getOption(DHO_DHCP_AGENT_OPTIONS);
223  if (rai) {
224  OptionCustomPtr rai_custom =
225  boost::dynamic_pointer_cast<OptionCustom>(rai);
226  if (rai_custom) {
227  OptionPtr link_select =
228  rai_custom->getOption(RAI_OPTION_LINK_SELECTION);
229  if (link_select) {
230  OptionBuffer link_select_buf = link_select->getData();
231  if (link_select_buf.size() == sizeof(uint32_t)) {
232  selector.option_select_ =
233  IOAddress::fromBytes(AF_INET, &link_select_buf[0]);
234  return (selector);
235  }
236  }
237  }
238  }
239  // The query does not include a RAI option or that option does
240  // not contain the link-selection sub-option. Try subnet-selection
241  // option.
242  OptionPtr sbnsel = query->getOption(DHO_SUBNET_SELECTION);
243  if (sbnsel) {
244  OptionCustomPtr oc =
245  boost::dynamic_pointer_cast<OptionCustom>(sbnsel);
246  if (oc) {
247  selector.option_select_ = oc->readAddress();
248  }
249  }
250  return (selector);
251 }
252 
254 CfgSubnets4::selectSubnet4o6(const SubnetSelector& selector) const {
255  for (auto const& subnet : subnets_) {
256  Cfg4o6& cfg4o6 = subnet->get4o6();
257 
258  // Is this an 4o6 subnet at all?
259  if (!cfg4o6.enabled()) {
260  continue; // No? Let's try the next one.
261  }
262 
263  // First match criteria: check if we have a prefix/len defined.
264  std::pair<asiolink::IOAddress, uint8_t> pref = cfg4o6.getSubnet4o6();
265  if (!pref.first.isV6Zero()) {
266 
267  // Let's check if the IPv6 address is in range
268  IOAddress first = firstAddrInPrefix(pref.first, pref.second);
269  IOAddress last = lastAddrInPrefix(pref.first, pref.second);
270  if ((first <= selector.remote_address_) &&
271  (selector.remote_address_ <= last)) {
272  return (subnet);
273  }
274  }
275 
276  // Second match criteria: check if the interface-id matches
277  if (cfg4o6.getInterfaceId() && selector.interface_id_ &&
278  cfg4o6.getInterfaceId()->equals(selector.interface_id_)) {
279  return (subnet);
280  }
281 
282  // Third match criteria: check if the interface name matches
283  if (!cfg4o6.getIface4o6().empty() && !selector.iface_name_.empty()
284  && cfg4o6.getIface4o6() == selector.iface_name_) {
285  return (subnet);
286  }
287  }
288 
290 
291  // Ok, wasn't able to find any matching subnet.
292  return (Subnet4Ptr());
293 }
294 
296 CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
297  // First use RAI link select sub-option or subnet select option
298  if (!selector.option_select_.isV4Zero()) {
299  return (selectSubnet(selector.option_select_,
300  selector.client_classes_));
301  } else {
304  }
305 
306  // If relayed message has been received, try to match the giaddr with the
307  // relay address specified for a subnet and/or shared network. It is also
308  // possible that the relay address will not match with any of the relay
309  // addresses across all subnets, but we need to verify that for all subnets
310  // before we can try to use the giaddr to match with the subnet prefix.
311  if (!selector.giaddr_.isV4Zero()) {
312  for (auto const& subnet : subnets_) {
313 
314  // If relay information is specified for this subnet, it must match.
315  // Otherwise, we ignore this subnet.
316  if (subnet->hasRelays()) {
317  if (!subnet->hasRelayAddress(selector.giaddr_)) {
318  continue;
319  }
320  } else {
321  // Relay information is not specified on the subnet level,
322  // so let's try matching on the shared network level.
323  SharedNetwork4Ptr network;
324  subnet->getSharedNetwork(network);
325  if (!network || !(network->hasRelayAddress(selector.giaddr_))) {
326  continue;
327  }
328  }
329 
330  // If a subnet meets the client class criteria return it.
331  if (subnet->clientSupported(selector.client_classes_)) {
334  .arg(subnet->toText())
335  .arg(selector.giaddr_.toText());
336  return (subnet);
337  }
338  }
341  .arg(selector.giaddr_.toText());
342  } else {
345  }
346 
347  // If we got to this point it means that we were not able to match the
348  // giaddr with any of the addresses specified for subnets. Let's determine
349  // what address from the client's packet to use to match with the
350  // subnets' prefixes.
351 
353  // If there is a giaddr, use it for subnet selection.
354  if (!selector.giaddr_.isV4Zero()) {
355  address = selector.giaddr_;
356 
357  // If it is a Renew or Rebind, use the ciaddr.
358  } else if (!selector.ciaddr_.isV4Zero() &&
359  !selector.local_address_.isV4Bcast()) {
360  address = selector.ciaddr_;
361 
362  // If ciaddr is not specified, use the source address.
363  } else if (!selector.remote_address_.isV4Zero() &&
364  !selector.local_address_.isV4Bcast()) {
365  address = selector.remote_address_;
366 
367  // If local interface name is known, use the local address on this
368  // interface.
369  } else if (!selector.iface_name_.empty()) {
370  IfacePtr iface = IfaceMgr::instance().getIface(selector.iface_name_);
371  // This should never happen in the real life. Hence we throw an
372  // exception.
373  if (iface == NULL) {
374  isc_throw(isc::BadValue, "interface " << selector.iface_name_
375  << " doesn't exist and therefore it is impossible"
376  " to find a suitable subnet for its IPv4 address");
377  }
378 
379  // Attempt to select subnet based on the interface name.
380  Subnet4Ptr subnet = selectSubnet(selector.iface_name_,
381  selector.client_classes_);
382 
383  // If it matches - great. If not, we'll try to use a different
384  // selection criteria below.
385  if (subnet) {
386  return (subnet);
387  } else {
388  // Let's try to get an address from the local interface and
389  // try to match it to defined subnet.
390  iface->getAddress4(address);
391  }
392  }
393 
394  // Unable to find a suitable address to use for subnet selection.
395  if (address.isV4Zero()) {
398 
399  return (Subnet4Ptr());
400  }
401 
402  // We have identified an address in the client's packet that can be
403  // used for subnet selection. Match this packet with the subnets.
404  return (selectSubnet(address, selector.client_classes_));
405 }
406 
408 CfgSubnets4::selectSubnet(const std::string& iface,
409  const ClientClasses& client_classes) const {
410  for (auto const& subnet : subnets_) {
411  Subnet4Ptr subnet_selected;
412 
413  // First, try subnet specific interface name.
414  if (!subnet->getIface(Network4::Inheritance::NONE).empty()) {
415  if (subnet->getIface(Network4::Inheritance::NONE) == iface) {
416  subnet_selected = subnet;
417  }
418 
419  } else {
420  // Interface not specified for a subnet, so let's try if
421  // we can match with shared network specific setting of
422  // the interface.
423  SharedNetwork4Ptr network;
424  subnet->getSharedNetwork(network);
425  if (network &&
426  (network->getIface(Network4::Inheritance::NONE) == iface)) {
427  subnet_selected = subnet;
428  }
429  }
430 
431  if (subnet_selected) {
432  // If a subnet meets the client class criteria return it.
433  if (subnet_selected->clientSupported(client_classes)) {
436  .arg(subnet->toText())
437  .arg(iface);
438  return (subnet_selected);
439  }
440  }
441  }
442 
445  .arg(iface);
446 
447  // Failed to find a subnet.
448  return (Subnet4Ptr());
449 }
450 
452 CfgSubnets4::getSubnet(const SubnetID id) const {
455  for (auto const& subnet : subnets_) {
456  if (subnet->getID() == id) {
457  return (subnet);
458  }
459  }
460  return (Subnet4Ptr());
461 }
462 
464 CfgSubnets4::selectSubnet(const IOAddress& address,
465  const ClientClasses& client_classes) const {
466  for (auto const& subnet : subnets_) {
467 
468  // Address is in range for the subnet prefix, so return it.
469  if (!subnet->inRange(address)) {
470  continue;
471  }
472 
473  // If a subnet meets the client class criteria return it.
474  if (subnet->clientSupported(client_classes)) {
476  .arg(subnet->toText())
477  .arg(address.toText());
478  return (subnet);
479  }
480  }
481 
484  .arg(address.toText());
485 
486  // Failed to find a subnet.
487  return (Subnet4Ptr());
488 }
489 
490 void
491 CfgSubnets4::removeStatistics() {
492  using namespace isc::stats;
493 
494  // For each v4 subnet currently configured, remove the statistic.
495  StatsMgr& stats_mgr = StatsMgr::instance();
496  for (auto const& subnet4 : subnets_) {
497  SubnetID subnet_id = subnet4->getID();
498  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
499  "total-addresses"));
500 
501  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
502  "assigned-addresses"));
503 
504  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
505  "cumulative-assigned-addresses"));
506 
507  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
508  "declined-addresses"));
509 
510  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
511  "reclaimed-declined-addresses"));
512 
513  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
514  "reclaimed-leases"));
515  }
516 }
517 
518 void
519 CfgSubnets4::updateStatistics() {
520  using namespace isc::stats;
521 
522  StatsMgr& stats_mgr = StatsMgr::instance();
523  for (auto const& subnet4 : subnets_) {
524  SubnetID subnet_id = subnet4->getID();
525 
526  stats_mgr.setValue(StatsMgr::
527  generateName("subnet", subnet_id, "total-addresses"),
528  static_cast<int64_t>
529  (subnet4->getPoolCapacity(Lease::TYPE_V4)));
530  const std::string& name =
531  StatsMgr::generateName("subnet", subnet_id, "cumulative-assigned-addresses");
532  if (!stats_mgr.getObservation(name)) {
533  stats_mgr.setValue(name, static_cast<int64_t>(0));
534  }
535  }
536 
537  // Only recount the stats if we have subnets.
538  if (subnets_.begin() != subnets_.end()) {
539  LeaseMgrFactory::instance().recountLeaseStats4();
540  }
541 }
542 
544 CfgSubnets4::toElement() const {
545  ElementPtr result = Element::createList();
546  // Iterate subnets
547  for (auto const& subnet : subnets_) {
548  result->add(subnet->toElement());
549  }
550  return (result);
551 }
552 
553 } // end of namespace isc::dhcp
554 } // end of namespace isc
Exception thrown upon attempt to add subnet with an ID that belongs to the subnet that already exists...
Definition: subnet_id.h:35
asiolink::IOAddress ciaddr_
ciaddr from the client&#39;s message.
const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_BY_ADDRESS_FAILED
asiolink::IOAddress remote_address_
Source address of the message.
ObservationPtr getObservation(const std::string &name) const
Returns an observation.
util::Optional< std::string > getIface4o6() const
Returns the DHCP4o6 interface.
Definition: cfg_4o6.h:45
bool enabled() const
Returns whether the DHCP4o6 is enabled or not.
Definition: cfg_4o6.h:33
boost::shared_ptr< OptionCustom > OptionCustomPtr
A pointer to the OptionCustom object.
const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_BY_RELAY_ADDRESS_FAILED
Tag for the index for searching by subnet identifier.
Definition: subnet.h:804
const isc::log::MessageID DHCPSRV_CFGMGR_ADD_SUBNET4
const isc::log::MessageID DHCPSRV_SUBNET4O6_SELECT_FAILED
boost::shared_ptr< Iface > IfacePtr
Type definition for the pointer to an Iface object.
Definition: iface_mgr.h:463
asiolink::IOAddress option_select_
RAI link select or subnet select option.
OptionPtr interface_id_
Interface id option.
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
boost::shared_ptr< Subnet4 > Subnet4Ptr
A pointer to a Subnet4 object.
Definition: subnet.h:524
const isc::log::MessageID DHCPSRV_CFGMGR_UPDATE_SUBNET4
boost::shared_ptr< Element > ElementPtr
Definition: data.h:24
const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4_RELAY
asiolink::IOAddress giaddr_
giaddr from the client&#39;s message.
Tag for the index for searching by subnet prefix.
Definition: subnet.h:807
OptionPtr getInterfaceId() const
Returns the interface-id.
Definition: cfg_4o6.h:72
boost::shared_ptr< CfgOptionDef > CfgOptionDefPtr
Non-const pointer.
util::Optional< std::pair< asiolink::IOAddress, uint8_t > > getSubnet4o6() const
Returns prefix/len for the IPv6 subnet.
Definition: cfg_4o6.h:58
std::vector< uint8_t > OptionBuffer
buffer types used in DHCP code.
Definition: option.h:24
Statistics Manager class.
ClientClasses client_classes_
Classes that the client belongs to.
Subnet selector used to specify parameters used to select a subnet.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Tag for the index for searching by server identifier.
Definition: subnet.h:810
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
boost::shared_ptr< CfgSharedNetworks4 > CfgSharedNetworks4Ptr
Pointer to the configuration of IPv4 shared networks.
const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_FAILED_NO_RELAY_ADDRESS
const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_FAILED_NO_RAI_OPTIONS_ADDRESS
This structure contains information about DHCP4o6 (RFC7341)
Definition: cfg_4o6.h:22
bool del(const std::string &name)
Removes specified statistic.
OptionPtr getOption(uint16_t type) const
Returns shared_ptr to suboption of specific type.
Definition: option.cc:199
asiolink::IOAddress local_address_
Address on which the message was received.
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:544
const isc::log::MessageID DHCPSRV_CFGMGR_DEL_SUBNET4
Holds subnets configured for the DHCPv4 server.
Definition: cfg_subnets4.h:33
boost::shared_ptr< SharedNetwork4 > SharedNetwork4Ptr
Pointer to SharedNetwork4 object.
Defines the logger used by the top-level component of kea-lfc.
boost::shared_ptr< const Subnet4 > ConstSubnet4Ptr
A const pointer to a Subnet4 object.
Definition: subnet.h:518
const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4_IFACE
void merge(ElementPtr element, ConstElementPtr other)
Merges the data from other into element.
Definition: data.cc:1134
const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_FAILED_NO_ADDRESS
A generic exception that is thrown if a function is called in a prohibited way.
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
std::string iface_name_
Name of the interface on which the message was received.
const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_BY_INTERFACE_FAILED
const int DHCPSRV_DBG_TRACE
DHCP server library logging levels.
Definition: dhcpsrv_log.h:26
isc::log::Logger dhcpsrv_logger("dhcpsrv")
DHCP server library Logger.
Definition: dhcpsrv_log.h:56
Option with defined data fields represented as buffers that can be accessed using data field index...
Definition: option_custom.h:32
bool empty() const
Checks if the encapsulated value is empty.
Definition: optional.h:153
const Subnet4Collection * getAll() const
Returns pointer to the collection of all IPv4 subnets.
Definition: cfg_subnets4.h:119
Container for storing client class names.
Definition: classify.h:68
void setValue(const std::string &name, const int64_t value)
Records absolute integer observation.
uint32_t SubnetID
Defines unique IPv4 or IPv6 subnet identifier.
Definition: subnet_id.h:24
const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4_ADDR