Kea  1.9.9-git
cfg_subnets4.cc
Go to the documentation of this file.
1 // Copyright (C) 2014-2021 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 other_subnets = other.getAll();
97  for (auto other_subnet = other_subnets->begin();
98  other_subnet != other_subnets->end();
99  ++other_subnet) {
100 
101  // Check if there is a subnet with the same ID.
102  auto subnet_id_it = index_id.find((*other_subnet)->getID());
103  if (subnet_id_it != index_id.end()) {
104 
105  // Subnet found.
106  auto existing_subnet = *subnet_id_it;
107 
108  // If the existing subnet and other subnet
109  // are the same instance skip it.
110  if (existing_subnet == *other_subnet) {
111  continue;
112  }
113 
114  // Updating the prefix can lead to problems... e.g. pools
115  // and reservations going outside range.
116  // @todo: check prefix change.
117 
118  // We're going to replace the existing subnet with the other
119  // version. If it belongs to a shared network, we need
120  // remove it from that network.
121  SharedNetwork4Ptr network;
122  existing_subnet->getSharedNetwork(network);
123  if (network) {
124  network->del(existing_subnet->getID());
125  }
126 
127  // Now we remove the existing subnet.
128  index_id.erase(subnet_id_it);
129  }
130 
131  // Check if there is a subnet with the same prefix.
132  auto subnet_prefix_it = index_prefix.find((*other_subnet)->toText());
133  if (subnet_prefix_it != index_prefix.end()) {
134 
135  // Subnet found.
136  auto existing_subnet = *subnet_prefix_it;
137 
138  // Updating the id can lead to problems... e.g. reservation
139  // for the previous subnet ID.
140  // @todo: check reservations
141 
142  // We're going to replace the existing subnet with the other
143  // version. If it belongs to a shared network, we need
144  // remove it from that network.
145  SharedNetwork4Ptr network;
146  existing_subnet->getSharedNetwork(network);
147  if (network) {
148  network->del(existing_subnet->getID());
149  }
150 
151  // Now we remove the existing subnet.
152  index_prefix.erase(subnet_prefix_it);
153  }
154 
155  // Create the subnet's options based on the given definitions.
156  (*other_subnet)->getCfgOption()->createOptions(cfg_def);
157  for (auto 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 
256  for (Subnet4Collection::const_iterator subnet = subnets_.begin();
257  subnet != subnets_.end(); ++subnet) {
258  Cfg4o6& cfg4o6 = (*subnet)->get4o6();
259 
260  // Is this an 4o6 subnet at all?
261  if (!cfg4o6.enabled()) {
262  continue; // No? Let's try the next one.
263  }
264 
265  // First match criteria: check if we have a prefix/len defined.
266  std::pair<asiolink::IOAddress, uint8_t> pref = cfg4o6.getSubnet4o6();
267  if (!pref.first.isV6Zero()) {
268 
269  // Let's check if the IPv6 address is in range
270  IOAddress first = firstAddrInPrefix(pref.first, pref.second);
271  IOAddress last = lastAddrInPrefix(pref.first, pref.second);
272  if ((first <= selector.remote_address_) &&
273  (selector.remote_address_ <= last)) {
274  return (*subnet);
275  }
276  }
277 
278  // Second match criteria: check if the interface-id matches
279  if (cfg4o6.getInterfaceId() && selector.interface_id_ &&
280  cfg4o6.getInterfaceId()->equals(selector.interface_id_)) {
281  return (*subnet);
282  }
283 
284  // Third match criteria: check if the interface name matches
285  if (!cfg4o6.getIface4o6().empty() && !selector.iface_name_.empty()
286  && cfg4o6.getIface4o6() == selector.iface_name_) {
287  return (*subnet);
288  }
289  }
290 
291  // Ok, wasn't able to find any matching subnet.
292  return (Subnet4Ptr());
293 }
294 
296 CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
297 
298  // First use RAI link select sub-option or subnet select option
299  if (!selector.option_select_.isV4Zero()) {
300  return (selectSubnet(selector.option_select_,
301  selector.client_classes_));
302  }
303 
304  // If relayed message has been received, try to match the giaddr with the
305  // relay address specified for a subnet and/or shared network. It is also
306  // possible that the relay address will not match with any of the relay
307  // addresses across all subnets, but we need to verify that for all subnets
308  // before we can try to use the giaddr to match with the subnet prefix.
309  if (!selector.giaddr_.isV4Zero()) {
310  for (Subnet4Collection::const_iterator subnet = subnets_.begin();
311  subnet != subnets_.end(); ++subnet) {
312 
313  // If relay information is specified for this subnet, it must match.
314  // Otherwise, we ignore this subnet.
315  if ((*subnet)->hasRelays()) {
316  if (!(*subnet)->hasRelayAddress(selector.giaddr_)) {
317  continue;
318  }
319  } else {
320  // Relay information is not specified on the subnet level,
321  // so let's try matching on the shared network level.
322  SharedNetwork4Ptr network;
323  (*subnet)->getSharedNetwork(network);
324  if (!network || !(network->hasRelayAddress(selector.giaddr_))) {
325  continue;
326  }
327  }
328 
329  // If a subnet meets the client class criteria return it.
330  if ((*subnet)->clientSupported(selector.client_classes_)) {
331  return (*subnet);
332  }
333  }
334  }
335 
336  // If we got to this point it means that we were not able to match the
337  // giaddr with any of the addresses specified for subnets. Let's determine
338  // what address from the client's packet to use to match with the
339  // subnets' prefixes.
340 
342  // If there is a giaddr, use it for subnet selection.
343  if (!selector.giaddr_.isV4Zero()) {
344  address = selector.giaddr_;
345 
346  // If it is a Renew or Rebind, use the ciaddr.
347  } else if (!selector.ciaddr_.isV4Zero() &&
348  !selector.local_address_.isV4Bcast()) {
349  address = selector.ciaddr_;
350 
351  // If ciaddr is not specified, use the source address.
352  } else if (!selector.remote_address_.isV4Zero() &&
353  !selector.local_address_.isV4Bcast()) {
354  address = selector.remote_address_;
355 
356  // If local interface name is known, use the local address on this
357  // interface.
358  } else if (!selector.iface_name_.empty()) {
359  IfacePtr iface = IfaceMgr::instance().getIface(selector.iface_name_);
360  // This should never happen in the real life. Hence we throw an
361  // exception.
362  if (iface == NULL) {
363  isc_throw(isc::BadValue, "interface " << selector.iface_name_
364  << " doesn't exist and therefore it is impossible"
365  " to find a suitable subnet for its IPv4 address");
366  }
367 
368  // Attempt to select subnet based on the interface name.
369  Subnet4Ptr subnet = selectSubnet(selector.iface_name_,
370  selector.client_classes_);
371 
372  // If it matches - great. If not, we'll try to use a different
373  // selection criteria below.
374  if (subnet) {
375  return (subnet);
376  } else {
377  // Let's try to get an address from the local interface and
378  // try to match it to defined subnet.
379  iface->getAddress4(address);
380  }
381  }
382 
383  // Unable to find a suitable address to use for subnet selection.
384  if (address.isV4Zero()) {
385  return (Subnet4Ptr());
386  }
387 
388  // We have identified an address in the client's packet that can be
389  // used for subnet selection. Match this packet with the subnets.
390  return (selectSubnet(address, selector.client_classes_));
391 }
392 
394 CfgSubnets4::selectSubnet(const std::string& iface,
395  const ClientClasses& client_classes) const {
396  for (Subnet4Collection::const_iterator subnet = subnets_.begin();
397  subnet != subnets_.end(); ++subnet) {
398 
399  Subnet4Ptr subnet_selected;
400 
401  // First, try subnet specific interface name.
402  if (!(*subnet)->getIface(Network4::Inheritance::NONE).empty()) {
403  if ((*subnet)->getIface(Network4::Inheritance::NONE) == iface) {
404  subnet_selected = (*subnet);
405  }
406 
407  } else {
408  // Interface not specified for a subnet, so let's try if
409  // we can match with shared network specific setting of
410  // the interface.
411  SharedNetwork4Ptr network;
412  (*subnet)->getSharedNetwork(network);
413  if (network &&
414  (network->getIface(Network4::Inheritance::NONE) == iface)) {
415  subnet_selected = (*subnet);
416  }
417  }
418 
419  if (subnet_selected) {
420 
421  // If a subnet meets the client class criteria return it.
422  if (subnet_selected->clientSupported(client_classes)) {
425  .arg((*subnet)->toText())
426  .arg(iface);
427  return (subnet_selected);
428  }
429  }
430  }
431 
432  // Failed to find a subnet.
433  return (Subnet4Ptr());
434 }
435 
437 CfgSubnets4::getSubnet(const SubnetID id) const {
438 
441  for (auto subnet = subnets_.begin(); subnet != subnets_.end(); ++subnet) {
442  if ((*subnet)->getID() == id) {
443  return (*subnet);
444  }
445  }
446  return (Subnet4Ptr());
447 }
448 
450 CfgSubnets4::selectSubnet(const IOAddress& address,
451  const ClientClasses& client_classes) const {
452  for (Subnet4Collection::const_iterator subnet = subnets_.begin();
453  subnet != subnets_.end(); ++subnet) {
454 
455  // Address is in range for the subnet prefix, so return it.
456  if (!(*subnet)->inRange(address)) {
457  continue;
458  }
459 
460  // If a subnet meets the client class criteria return it.
461  if ((*subnet)->clientSupported(client_classes)) {
463  .arg((*subnet)->toText())
464  .arg(address.toText());
465  return (*subnet);
466  }
467  }
468 
469  // Failed to find a subnet.
470  return (Subnet4Ptr());
471 }
472 
473 void
474 CfgSubnets4::removeStatistics() {
475  using namespace isc::stats;
476 
477  // For each v4 subnet currently configured, remove the statistic.
478  StatsMgr& stats_mgr = StatsMgr::instance();
479  for (Subnet4Collection::const_iterator subnet4 = subnets_.begin();
480  subnet4 != subnets_.end(); ++subnet4) {
481  SubnetID subnet_id = (*subnet4)->getID();
482  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
483  "total-addresses"));
484 
485  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
486  "assigned-addresses"));
487 
488  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
489  "cumulative-assigned-addresses"));
490 
491  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
492  "declined-addresses"));
493 
494  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
495  "reclaimed-declined-addresses"));
496 
497  stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
498  "reclaimed-leases"));
499  }
500 }
501 
502 void
503 CfgSubnets4::updateStatistics() {
504  using namespace isc::stats;
505 
506  StatsMgr& stats_mgr = StatsMgr::instance();
507  for (Subnet4Collection::const_iterator subnet4 = subnets_.begin();
508  subnet4 != subnets_.end(); ++subnet4) {
509  SubnetID subnet_id = (*subnet4)->getID();
510 
511  stats_mgr.setValue(StatsMgr::
512  generateName("subnet", subnet_id, "total-addresses"),
513  static_cast<int64_t>
514  ((*subnet4)->getPoolCapacity(Lease::
515  TYPE_V4)));
516  const std::string& name =
517  StatsMgr::generateName("subnet", subnet_id, "cumulative-assigned-addresses");
518  if (!stats_mgr.getObservation(name)) {
519  stats_mgr.setValue(name, static_cast<int64_t>(0));
520  }
521  }
522 
523  // Only recount the stats if we have subnets.
524  if (subnets_.begin() != subnets_.end()) {
525  LeaseMgrFactory::instance().recountLeaseStats4();
526  }
527 }
528 
530 CfgSubnets4::toElement() const {
531  ElementPtr result = Element::createList();
532  // Iterate subnets
533  for (Subnet4Collection::const_iterator subnet = subnets_.cbegin();
534  subnet != subnets_.cend(); ++subnet) {
535  result->add((*subnet)->toElement());
536  }
537  return (result);
538 }
539 
540 } // end of namespace isc::dhcp
541 } // 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's message.
asiolink::IOAddress remote_address_
Source address of the message.
const Subnet4Collection * getAll() const
Returns pointer to the collection of all IPv4 subnets.
Definition: cfg_subnets4.h:119
boost::shared_ptr< OptionCustom > OptionCustomPtr
A pointer to the OptionCustom object.
Tag for the index for searching by subnet identifier.
Definition: subnet.h:802
const isc::log::MessageID DHCPSRV_CFGMGR_ADD_SUBNET4
OptionPtr getOption(uint16_t type) const
Returns shared_ptr to suboption of specific type.
Definition: option.cc:211
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:522
const isc::log::MessageID DHCPSRV_CFGMGR_UPDATE_SUBNET4
boost::shared_ptr< Element > ElementPtr
Definition: data.h:20
asiolink::IOAddress giaddr_
giaddr from the client's message.
Tag for the index for searching by subnet prefix.
Definition: subnet.h:805
bool enabled() const
Returns whether the DHCP4o6 is enabled or not.
Definition: cfg_4o6.h:33
boost::shared_ptr< CfgOptionDef > CfgOptionDefPtr
Non-const pointer.
ObservationPtr getObservation(const std::string &name) const
Returns an observation.
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.
util::Optional< std::pair< asiolink::IOAddress, uint8_t > > getSubnet4o6() const
Returns prefix/len for the IPv6 subnet.
Definition: cfg_4o6.h:58
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:808
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.
This structure contains information about DHCP4o6 (RFC7341)
Definition: cfg_4o6.h:22
bool del(const std::string &name)
Removes specified statistic.
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.
bool empty() const
Checks if the encapsulated value is empty.
Definition: optional.h:138
Defines the logger used by the top-level component of kea-dhcp-ddns.
boost::shared_ptr< const Subnet4 > ConstSubnet4Ptr
A const pointer to a Subnet4 object.
Definition: subnet.h:516
const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4_IFACE
void merge(ElementPtr element, ConstElementPtr other)
Merges the data from other into element.
Definition: data.cc:1079
a common structure for IPv4 and IPv6 leases
Definition: lease.h:35
OptionPtr getInterfaceId() const
Returns the interface-id.
Definition: cfg_4o6.h:72
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 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
Container for storing client class names.
Definition: classify.h:43
util::Optional< std::string > getIface4o6() const
Returns the DHCP4o6 interface.
Definition: cfg_4o6.h:45
void setValue(const std::string &name, const int64_t value)
Records absolute integer observation.
uint32_t SubnetID
Unique identifier for a subnet (both v4 and v6)
Definition: lease.h:24
const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4_ADDR