+1666. [func] marcin
+ Client classes specified within host reservations can be used
+ to influence subnet selection within a shared network and pool
+ selection within a subnet.
+ (Gitlab #1155)
+
Kea 1.6.2 released on Feb 19, 2020
1665. [bug] tmark
the server to assign classes to a client, based on the content of the
options that this client sends to the server. Host reservations
mechanisms also allow for the static assignment of classes to clients.
-The definitions of these classes are placed in the Kea configuration.
-The following configuration snippet shows how to specify that a client
-belongs to classes ``reserved-class1`` and ``reserved-class2``. Those
-classes are associated with specific options that are sent to the clients
-which belong to them.
+The definitions of these classes are placed in the Kea configuration or
+a database. The following configuration snippet shows how to specify that
+a client belongs to classes ``reserved-class1`` and ``reserved-class2``. Those
+classes are associated with specific options sent to the clients which belong
+to them.
::
} ]
}
-Static class assignments, as shown above, can be used in conjunction
-with classification, using expressions. The "KNOWN" or "UNKNOWN" built-in
-class is added to the packet and any class depending on it (directly or
-indirectly) and not only-if-required is evaluated.
+In some cases the host reservations can be used in conjuction with client
+classes specified within the Kea configuration. In particular, when a
+host reservation exists for a client within a given subnet, the "KNOWN"
+built-in class is assigned to the client. Conversely, when there is no
+static assignment for the client, the "UNKNOWN" class is assigned to the
+client. Class expressions within the Kea configuration file can
+refer to "KNOWN" or "UNKNOWN" classes using using the "member" operator.
+For example:
-.. note::
+::
+
+ {
+ "client-classes": [
+ {
+ "name": "dependent-class",
+ "test": "member('KNOWN')",
+ "only-if-required": true
+ }
+ ]
+ }
- To force the evaluation of a class expression after the
- host reservation lookup, for instance because of a dependency on
- "reserved-class1" from the previous example, add a
- "member('KNOWN')" statement in the expression.
+Note that the ``only-if-required`` parameter is needed here to force
+evaluation of the class after the lease has been allocated and thus the
+reserved class has been also assigned.
+
+.. note::
+ Be aware that the classes specified in non global host reservations
+ are assigned to the processed packet after all classes with the
+ ``only-if-required`` parameter set to ``false`` have been evaluated.
+ This has an implication that these classes must not depend on the
+ statically assigned classes from the host reservations. If there
+ is a need to create such dependency, the ``only-if-required`` must
+ be set to ``true`` for the dependent classes. Such classes are
+ evaluated after the static classes have been assigned to the packet.
+ This, however, imposes additional configuration overhead, because
+ all classes marked as ``only-if-required`` must be listed in the
+ ``require-client-classes`` list for every subnet where they are used.
.. note::
- Beware that the reserved classes are assigned to the processed
- packet after all classes with the ``only-if-required`` parameter
- set to ``false`` have been evaluated. This has an implication that
- these classes must not depend on the statically assigned classes
- from the host reservations. If there is a need to create such
- dependency, the ``only-if-required`` must be set to ``true`` for
- the dependent classes. Such classes are evaluated after the static
- classes have been assigned to the packet. This, however, imposes
- additional configuration overhead, because all classes marked as
- ``only-if-required`` must be listed in the ``require-client-classes``
- list for every subnet where they are used.
+ Client classes specified within the Kea configuration file may
+ depend on the classes specified within the global host reservations.
+ In such case the ``only-if-required`` parameter is not needed.
+ Refer to the :ref:`pool-selection-with-class-reservations4` and
+ :ref:`subnet-selection-with-class-reservations4`
+ for the specific use cases.
+
.. _reservations4-mysql-pgsql-cql:
distinguished from regular reservations by using a subnet-id value of
zero.
+.. _pool-selection-with-class-reservations4:
+
+Pool Selection with Client Class Reservations
+---------------------------------------------
+
+Client classes can be specified both in the Kea configuration file and/or
+host reservations. The classes specified in the Kea configuration file are
+evaluated immediately after receiving the DHCP packet and therefore can be
+used to influence subnet selection using the ``client-class`` parameter
+specified in the subnet scope. The classes specified within the host
+reservations are fetched and assigned to the packet after the server has
+already selected a subnet for the client. This means that the client
+class specified within a host reservation cannot be used to influence
+subnet assignment for this client, unless the subnet belongs to a
+shared network. If the subnet belongs to a shared network, the server may
+dynamically change the subnet assignment while trying to allocate a lease.
+If the subnet does not belong to a shared network, once selected, the subnet
+is not changed.
+
+If the subnet does not belong to a shared network, it is possible to
+use host reservation based client classification to select an address pool
+within the subnet as follows:
+
+::
+
+ "Dhcp4": {
+ "client-classes": [
+ {
+ "name": "reserved_class"
+ },
+ {
+ "name": "unreserved_class",
+ "test": "not member('reserved_class')"
+ }
+ ],
+ "subnet4": [
+ {
+ "subnet": "192.0.2.0/24",
+ "reservations": [{"
+ "hw-address": "aa:bb:cc:dd:ee:fe",
+ "client-classes": [ "reserved_class" ]
+ }],
+ "pools": [
+ {
+ "pool": "192.0.2.10-192.0.2.20",
+ "client-class": "reserved_class"
+ },
+ {
+ "pool": "192.0.2.30-192.0.2.40",
+ "client-class": "unreserved_class"
+ }
+ ]
+ }
+ ]
+ }
+
+The ``reserved_class`` is declared without the ``test`` parameter because
+it may be only assigned to the client via host reservation mechanism. The
+second class, ``unreserved_class``, is assigned to the clients which do not
+belong to the ``reserved_class``. The first pool within the subnet is only
+used for the clients having a reservation for the ``reserved_class``. The
+second pool is used for the clients not having such reservation. The
+configuration snippet includes one host reservation which causes the client
+having the MAC address of aa:bb:cc:dd:ee:fe to be assigned to the
+``reserved_class``. Thus, this client will be given an IP address from the
+first address pool.
+
+.. _subnet-selection-with-class-reservations4:
+
+Subnet Selection with Client Class Reservations
+-----------------------------------------------
+
+There is one specific use case when subnet selection may be influenced by
+client classes specified within host reservations. This is the case when the
+client belongs to a shared network. In such case it is possible to use
+classification to select a subnet within this shared network. Consider the
+following example:
+
+::
+
+ "Dhcp4": {
+ "client-classes": [
+ {
+ "name": "reserved_class"
+ },
+ {
+ "name: "unreserved_class",
+ "test": "not member('reserved_class")
+ }
+ ],
+ "reservations": [{"
+ "hw-address": "aa:bb:cc:dd:ee:fe",
+ "client-classes": [ "reserved_class" ]
+ }],
+ "reservation-mode": "global",
+ "shared-networks": [{
+ "subnet4": [
+ {
+ "subnet": "192.0.2.0/24",
+ "pools": [
+ {
+ "pool": "192.0.2.10-192.0.2.20",
+ "client-class": "reserved_class"
+ }
+ ]
+ },
+ {
+ "subnet": "192.0.3.0/24",
+ "pools": [
+ {
+ "pool": "192.0.3.10-192.0.3.20",
+ "client-class": "unreserved_class"
+ }
+ ]
+ }
+ ]
+ }]
+ }
+
+This is similar to the example described in the
+:ref:`pool-selection-with-class-reservations4`. This time, however, there
+are two subnets, each of them having a pool associated with a different
+class. The clients which don't have a reservation for the ``reserved_class``
+will be assigned an address from the subnet 192.0.3.0/24. Clients having
+a reservation for the ``reserved_class`` will be assigned an address from
+the subnet 192.0.2.0/24. The subnets must belong to the same shared network.
+In addition, the reservation for the client class must be specified at the
+global scope (global reservation) and the ``reservation-mode`` must be
+set to ``global``.
+
+In the example above the ``client-class`` could also be specified at the
+subnet level rather than pool level yielding the same effect.
+
+
.. _shared-network4:
Shared Networks in DHCPv4
the server to assign classes to a client, based on the content of the
options that this client sends to the server. Host reservations
mechanisms also allow for the static assignment of classes to clients.
-The definitions of these classes are placed in the Kea configuration.
-The following configuration snippet shows how to specify that the client
-belongs to classes ``reserved-class1`` and ``reserved-class2``. Those
-classes are associated with specific options that are sent to the clients
-which belong to them.
+The definitions of these classes are placed in the Kea configuration or
+a database. The following configuration snippet shows how to specify that
+a client belongs to classes ``reserved-class1`` and ``reserved-class2``. Those
+classes are associated with specific options sent to the clients which belong
+to them.
::
} ]
}
-Static class assignments, as shown above, can be used in conjunction
-with classification, using expressions. The "KNOWN" or "UNKNOWN" built-in
-class is added to the packet and any class depending on it (directly or
-indirectly) and not only-if-required is evaluated.
+In some cases the host reservations can be used in conjuction with client
+classes specified within the Kea configuration. In particular, when a
+host reservation exists for a client within a given subnet, the "KNOWN"
+built-in class is assigned to the client. Conversely, when there is no
+static assignment for the client, the "UNKNOWN" class is assigned to the
+client. Class expressions within the Kea configuration file can
+refer to "KNOWN" or "UNKNOWN" classes using using the "member" operator.
+For example:
-.. note::
+::
+
+ {
+ "client-classes": [
+ {
+ "name": "dependent-class",
+ "test": "member('KNOWN')",
+ "only-if-required": true
+ }
+ ]
+ }
- To force the evaluation of a class expression after the
- host reservation lookup, for instance because of a dependency on
- "reserved-class1" from the previous example, add a
- "member('KNOWN')" statement in the expression.
+Note that the ``only-if-required`` parameter is needed here to force
+evaluation of the class after the lease has been allocated and thus the
+reserved class has been also assigned.
.. note::
- Beware that the reserved classes are assigned to the processed
- packet after all classes with the ``only-if-required`` parameter
- set to ``false`` have been evaluated. This has an implication that
- these classes must not depend on the statically assigned classes
- from the host reservations. If there is a need to create such
- dependency, the ``only-if-required`` must be set to ``true`` for
- the dependent classes. Such classes are evaluated after the static
- classes have been assigned to the packet. This, however, imposes
- additional configuration overhead, because all classes marked as
- ``only-if-required`` must be listed in the ``require-client-classes``
- list for every subnet where they are used.
+ Be aware that the classes specified in non global host reservations
+ are assigned to the processed packet after all classes with the
+ ``only-if-required`` parameter set to ``false`` have been evaluated.
+ This has an implication that these classes must not depend on the
+ statically assigned classes from the host reservations. If there
+ is a need to create such dependency, the ``only-if-required`` must
+ be set to ``true`` for the dependent classes. Such classes are
+ evaluated after the static classes have been assigned to the packet.
+ This, however, imposes additional configuration overhead, because
+ all classes marked as ``only-if-required`` must be listed in the
+ ``require-client-classes`` list for every subnet where they are used.
+
+.. note::
+ Client classes specified within the Kea configuration file may
+ depend on the classes specified within the global host reservations.
+ In such case the ``only-if-required`` parameter is not needed.
+ Refer to the :ref:`pool-selection-with-class-reservations6` and
+ :ref:`subnet-selection-with-class-reservations6`
+ for the specific use cases.
.. _reservations6-mysql-pgsql-cql:
distinguished from regular reservations by using subnet-id value of
zero.
+.. _pool-selection-with-class-reservations6:
+
+Pool Selection with Client Class Reservations
+---------------------------------------------
+
+Client classes can be specified both in the Kea configuration file and/or
+host reservations. The classes specified in the Kea configuration file are
+evaluated immediately after receiving the DHCP packet and therefore can be
+used to influence subnet selection using the ``client-class`` parameter
+specified in the subnet scope. The classes specified within the host
+reservations are fetched and assigned to the packet after the server has
+already selected a subnet for the client. This means that the client
+class specified within a host reservation cannot be used to influence
+subnet assignment for this client, unless the subnet belongs to a
+shared network. If the subnet belongs to a shared network, the server may
+dynamically change the subnet assignment while trying to allocate a lease.
+If the subnet does not belong to a shared network, once selected, the subnet
+is not changed.
+
+If the subnet does not belong to a shared network, it is possible to
+use host reservation based client classification to select an address pool
+within the subnet as follows:
+
+::
+
+ "Dhcp6": {
+ "client-classes": [
+ {
+ "name": "reserved_class"
+ },
+ {
+ "name": "unreserved_class",
+ "test": "not member('reserved_class')"
+ }
+ ],
+ "subnet6": [
+ {
+ "subnet": "2001:db8:1::/64",
+ "reservations": [{"
+ "hw-address": "aa:bb:cc:dd:ee:fe",
+ "client-classes": [ "reserved_class" ]
+ }],
+ "pools": [
+ {
+ "pool": "2001:db8:1::10-2001:db8:1::20",
+ "client-class": "reserved_class"
+ },
+ {
+ "pool": "2001:db8:1::30-2001:db8:1::40",
+ "client-class": "unreserved_class"
+ }
+ ]
+ }
+ ]
+ }
+
+The ``reserved_class`` is declared without the ``test`` parameter because
+it may be only assigned to the client via host reservation mechanism. The
+second class, ``unreserved_class``, is assigned to the clients which do not
+belong to the ``reserved_class``. The first pool within the subnet is only
+used for the clients having a reservation for the ``reserved_class``. The
+second pool is used for the clients not having such reservation. The
+configuration snippet includes one host reservation which causes the client
+having the MAC address of aa:bb:cc:dd:ee:fe to be assigned to the
+``reserved_class``. Thus, this client will be given an IP address from the
+first address pool.
+
+.. _subnet-selection-with-class-reservations6:
+
+Subnet Selection with Client Class Reservations
+-----------------------------------------------
+
+There is one specific use case when subnet selection may be influenced by
+client classes specified within host reservations. This is the case when the
+client belongs to a shared network. In such case it is possible to use
+classification to select a subnet within this shared network. Consider the
+following example:
+
+::
+
+ "Dhcp6": {
+ "client-classes": [
+ {
+ "name": "reserved_class"
+ },
+ {
+ "name: "unreserved_class",
+ "test": "not member('reserved_class")
+ }
+ ],
+ "reservations": [{"
+ "hw-address": "aa:bb:cc:dd:ee:fe",
+ "client-classes": [ "reserved_class" ]
+ }],
+ "reservation-mode": "global",
+ "shared-networks": [{
+ "subnet6": [
+ {
+ "subnet": "2001:db8:1::/64",
+ "pools": [
+ {
+ "pool": "2001:db8:1::10-2001:db8:1::20",
+ "client-class": "reserved_class"
+ }
+ ]
+ },
+ {
+ "subnet": "2001:db8:2::/64",
+ "pools": [
+ {
+ "pool": "2001:db8:2::10-2001:db8:2::20",
+ "client-class": "unreserved_class"
+ }
+ ]
+ }
+ ]
+ }]
+ }
+
+This is similar to the example described in the
+:ref:`pool-selection-with-class-reservations6`. This time, however, there
+are two subnets, each of them having a pool associated with a different
+class. The clients which don't have a reservation for the ``reserved_class``
+will be assigned an address from the subnet 2001:db8:2::/64. Clients having
+a reservation for the ``reserved_class`` will be assigned an address from
+the subnet 2001:db8:1::/64. The subnets must belong to the same shared network.
+In addition, the reservation for the client class must be specified at the
+global scope (global reservation) and the ``reservation-mode`` must be
+set to ``global``.
+
+In the example above the ``client-class`` could also be specified at the
+subnet level rather than pool level yielding the same effect.
+
+
.. _shared-network6:
Shared Networks in DHCPv6
// If subnet found, retrieve client identifier which will be needed
// for allocations and search for reservations associated with a
// subnet/shared network.
+ SharedNetwork4Ptr sn;
if (subnet) {
OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
if (opt_clientid) {
// Check for static reservations.
alloc_engine->findReservation(*context_);
+
+ // Get shared network to see if it is set for a subnet.
+ subnet->getSharedNetwork(sn);
+ }
+ }
+
+ // Global host reservations are independent of a selected subnet. If the
+ // global reservations contain client classes we should use them in case
+ // they are meant to affect pool selection. Also, if the subnet does not
+ // belong to a shared network we can use the reserved client classes
+ // because there is no way our subnet could change. Such classes may
+ // affect selection of a pool within the selected subnet.
+ auto global_host = context_->globalHost();
+ auto current_host = context_->currentHost();
+ if ((global_host && !global_host->getClientClasses4().empty()) ||
+ (!sn && current_host && !current_host->getClientClasses4().empty())) {
+ // We have already evaluated client classes and some of them may
+ // be in conflict with the reserved classes. Suppose there are
+ // two classes defined in the server configuration: first_class
+ // and second_class and the test for the second_class it looks
+ // like this: "not member('first_class')". If the first_class
+ // initially evaluates to false, the second_class evaluates to
+ // true. If the first_class is now set within the hosts reservations
+ // and we don't remove the previously evaluated second_class we'd
+ // end up with both first_class and second_class evaluated to
+ // true. In order to avoid that, we have to remove the classes
+ // evaluated in the first pass and evaluate them again. As
+ // a result, the first_class set via the host reservation will
+ // replace the second_class because the second_class will this
+ // time evaluate to false as desired.
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ const ClientClassDefListPtr& defs_ptr = dict->getClasses();
+ for (auto def : *defs_ptr) {
+ context_->query_->classes_.erase(def->getName());
}
+ setReservedClientClasses(context_);
+ evaluateClasses(context_->query_, false);
}
// Set KNOWN builtin class if something was found, UNKNOWN if not.
}
// Perform second pass of classification.
- Dhcpv4Srv::evaluateClasses(query, true);
+ evaluateClasses(query, true);
const ClientClasses& classes = query_->getClasses();
if (!classes.empty()) {
}
void
-Dhcpv4Exchange::setReservedClientClasses() {
- if (context_->currentHost() && query_) {
- const ClientClasses& classes = context_->currentHost()->getClientClasses4();
+Dhcpv4Exchange::setReservedClientClasses(AllocEngine::ClientContext4Ptr context) {
+ if (context->currentHost() && context->query_) {
+ const ClientClasses& classes = context->currentHost()->getClientClasses4();
for (ClientClasses::const_iterator cclass = classes.cbegin();
cclass != classes.cend(); ++cclass) {
- query_->addClass(*cclass);
+ context->query_->addClass(*cclass);
+ }
+ }
+}
+
+void
+Dhcpv4Exchange::conditionallySetReservedClientClasses() {
+ if (context_->subnet_) {
+ SharedNetwork4Ptr shared_network;
+ context_->subnet_->getSharedNetwork(shared_network);
+ if (shared_network && !context_->globalHost()) {
+ setReservedClientClasses(context_);
}
}
}
}
}
+void Dhcpv4Exchange::classifyByVendor(const Pkt4Ptr& pkt) {
+ // Built-in vendor class processing
+ boost::shared_ptr<OptionString> vendor_class =
+ boost::dynamic_pointer_cast<OptionString>(pkt->getOption(DHO_VENDOR_CLASS_IDENTIFIER));
+
+ if (!vendor_class) {
+ return;
+ }
+
+ pkt->addClass(Dhcpv4Srv::VENDOR_CLASS_PREFIX + vendor_class->getValue());
+}
+
+void Dhcpv4Exchange::classifyPacket(const Pkt4Ptr& pkt) {
+ // All packets belongs to ALL.
+ pkt->addClass("ALL");
+
+ // First: built-in vendor class processing.
+ classifyByVendor(pkt);
+
+ // Run match expressions on classes not depending on KNOWN/UNKNOWN.
+ evaluateClasses(pkt, false);
+}
+
+void Dhcpv4Exchange::evaluateClasses(const Pkt4Ptr& pkt, bool depend_on_known) {
+ // Note getClientClassDictionary() cannot be null
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ const ClientClassDefListPtr& defs_ptr = dict->getClasses();
+ for (ClientClassDefList::const_iterator it = defs_ptr->cbegin();
+ it != defs_ptr->cend(); ++it) {
+ // Note second cannot be null
+ const ExpressionPtr& expr_ptr = (*it)->getMatchExpr();
+ // Nothing to do without an expression to evaluate
+ if (!expr_ptr) {
+ continue;
+ }
+ // Not the right time if only when required
+ if ((*it)->getRequired()) {
+ continue;
+ }
+ // Not the right pass.
+ if ((*it)->getDependOnKnown() != depend_on_known) {
+ continue;
+ }
+ // Evaluate the expression which can return false (no match),
+ // true (match) or raise an exception (error)
+ try {
+ bool status = evaluateBool(*expr_ptr, *pkt);
+ if (status) {
+ LOG_INFO(options4_logger, EVAL_RESULT)
+ .arg((*it)->getName())
+ .arg(status);
+ // Matching: add the class
+ pkt->addClass((*it)->getName());
+ } else {
+ LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, EVAL_RESULT)
+ .arg((*it)->getName())
+ .arg(status);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(options4_logger, EVAL_RESULT)
+ .arg((*it)->getName())
+ .arg(ex.what());
+ } catch (...) {
+ LOG_ERROR(options4_logger, EVAL_RESULT)
+ .arg((*it)->getName())
+ .arg("get exception?");
+ }
+ }
+}
+
+
const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
Dhcpv4Srv::Dhcpv4Srv(uint16_t server_port, uint16_t client_port,
// Adding any other options makes sense only when we got the lease.
if (!ex.getResponse()->getYiaddr().isV4Zero()) {
- // Assign reserved classes.
- ex.setReservedClientClasses();
+ // If this is global reservation or the subnet doesn't belong to a shared
+ // network we have already fetched it and evaluated the classes.
+ ex.conditionallySetReservedClientClasses();
+
// Required classification
requiredClassify(ex);
// Adding any other options makes sense only when we got the lease.
if (!ex.getResponse()->getYiaddr().isV4Zero()) {
- // Assign reserved classes.
- ex.setReservedClientClasses();
+ // If this is global reservation or the subnet doesn't belong to a shared
+ // network we have already fetched it and evaluated the classes.
+ ex.conditionallySetReservedClientClasses();
+
// Required classification
requiredClassify(ex);
Pkt4Ptr ack = ex.getResponse();
- ex.setReservedClientClasses();
+ // If this is global reservation or the subnet doesn't belong to a shared
+ // network we have already fetched it and evaluated the classes.
+ ex.conditionallySetReservedClientClasses();
+
requiredClassify(ex);
buildCfgOptionList(ex);
}
}
-void Dhcpv4Srv::classifyByVendor(const Pkt4Ptr& pkt) {
- // Built-in vendor class processing
- boost::shared_ptr<OptionString> vendor_class =
- boost::dynamic_pointer_cast<OptionString>(pkt->getOption(DHO_VENDOR_CLASS_IDENTIFIER));
-
- if (!vendor_class) {
- return;
- }
-
- pkt->addClass(VENDOR_CLASS_PREFIX + vendor_class->getValue());
-}
-
void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
- // All packets belongs to ALL.
- pkt->addClass("ALL");
-
- // First: built-in vendor class processing.
- classifyByVendor(pkt);
-
- // Run match expressions on classes not depending on KNOWN/UNKNOWN.
- evaluateClasses(pkt, false);
-}
-
-void Dhcpv4Srv::evaluateClasses(const Pkt4Ptr& pkt, bool depend_on_known) {
- // Note getClientClassDictionary() cannot be null
- const ClientClassDictionaryPtr& dict =
- CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
- const ClientClassDefListPtr& defs_ptr = dict->getClasses();
- for (ClientClassDefList::const_iterator it = defs_ptr->cbegin();
- it != defs_ptr->cend(); ++it) {
- // Note second cannot be null
- const ExpressionPtr& expr_ptr = (*it)->getMatchExpr();
- // Nothing to do without an expression to evaluate
- if (!expr_ptr) {
- continue;
- }
- // Not the right time if only when required
- if ((*it)->getRequired()) {
- continue;
- }
- // Not the right pass.
- if ((*it)->getDependOnKnown() != depend_on_known) {
- continue;
- }
- // Evaluate the expression which can return false (no match),
- // true (match) or raise an exception (error)
- try {
- bool status = evaluateBool(*expr_ptr, *pkt);
- if (status) {
- LOG_INFO(options4_logger, EVAL_RESULT)
- .arg((*it)->getName())
- .arg(status);
- // Matching: add the class
- pkt->addClass((*it)->getName());
- } else {
- LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, EVAL_RESULT)
- .arg((*it)->getName())
- .arg(status);
- }
- } catch (const Exception& ex) {
- LOG_ERROR(options4_logger, EVAL_RESULT)
- .arg((*it)->getName())
- .arg(ex.what());
- } catch (...) {
- LOG_ERROR(options4_logger, EVAL_RESULT)
- .arg((*it)->getName())
- .arg("get exception?");
- }
- }
+ Dhcpv4Exchange::classifyPacket(pkt);
}
void Dhcpv4Srv::requiredClassify(Dhcpv4Exchange& ex) {
void setReservedMessageFields();
/// @brief Assigns classes retrieved from host reservation database.
- void setReservedClientClasses();
+ ///
+ /// @param context pointer to the context.
+ static void setReservedClientClasses(AllocEngine::ClientContext4Ptr context);
+
+ /// @brief Assigns classes retrieved from host reservation database
+ /// if they haven't been yet set.
+ ///
+ /// This function sets reserved client classes in case they haven't
+ /// been set after fetching host reservations from the database.
+ /// This is the case when the client has non-global host reservation
+ /// and the selected subnet belongs to a shared network.
+ void conditionallySetReservedClientClasses();
+
+ /// @brief Assigns incoming packet to zero or more classes.
+ ///
+ /// @note This is done in two phases: first the content of the
+ /// vendor-class-identifier option is used as a class, by
+ /// calling @ref classifyByVendor(). Second, the classification match
+ /// expressions are evaluated. The resulting classes will be stored
+ /// in the packet (see @ref isc::dhcp::Pkt4::classes_ and
+ /// @ref isc::dhcp::Pkt4::inClass).
+ ///
+ /// @param pkt packet to be classified
+ static void classifyPacket(const Pkt4Ptr& pkt);
private:
+ /// @brief Assign class using vendor-class-identifier option
+ ///
+ /// @note This is the first part of @ref classifyPacket
+ ///
+ /// @param pkt packet to be classified
+ static void classifyByVendor(const Pkt4Ptr& pkt);
+
+ /// @brief Evaluate classes.
+ ///
+ /// @note Second part of the classification.
+ ///
+ /// Evaluate expressions of client classes: if it returns true the class
+ /// is added to the incoming packet.
+ ///
+ /// @param pkt packet to be classified.
+ /// @param depend_on_known if false classes depending on the KNOWN or
+ /// UNKNOWN classes are skipped, if true only these classes are evaluated.
+ static void evaluateClasses(const Pkt4Ptr& pkt, bool depend_on_known);
+
/// @brief Copies default parameters from client's to server's message
///
/// Some fields are copied from client's message into server's response,
/// server's response.
void processClientName(Dhcpv4Exchange& ex);
+public:
+
/// @brief this is a prefix added to the content of vendor-class option
///
/// If incoming packet has a vendor class option, its content is
/// @param pkt packet to be classified
void classifyPacket(const Pkt4Ptr& pkt);
-public:
-
- /// @brief Evaluate classes.
- ///
- /// @note Second part of the classification.
- ///
- /// Evaluate expressions of client classes: if it returns true the class
- /// is added to the incoming packet.
- ///
- /// @param pkt packet to be classified.
- /// @param depend_on_known if false classes depending on the KNOWN or
- /// UNKNOWN classes are skipped, if true only these classes are evaluated.
- static void evaluateClasses(const Pkt4Ptr& pkt, bool depend_on_known);
-
protected:
/// @brief Assigns incoming packet to zero or more classes (required pass).
private:
- /// @public
/// @brief Assign class using vendor-class-identifier option
///
/// @note This is the first part of @ref classifyPacket
-// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
" }\n"
"]\n"
"}\n"
+ ,
+
+ // Configuration 4 client-class reservation in global, shared network
+ // and client-class guarded pools.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"reserved_class\""
+ "},"
+ "{"
+ " \"name\": \"unreserved_class\","
+ " \"test\": \"not member('reserved_class')\""
+ "}"
+ "],\n"
+ "\"reservation-mode\": \"global\","
+ "\"valid-lifetime\": 600,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:fe\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ "}\n"
+ "],\n"
+ "\"shared-networks\": [{"
+ " \"name\": \"frog\",\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"10.0.0.0/24\", \n"
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.10-10.0.0.11\","
+ " \"client-class\": \"reserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " },\n"
+ " {\n"
+ " \"subnet\": \"192.0.3.0/24\", \n"
+ " \"id\": 11,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.3.10-192.0.3.11\","
+ " \"client-class\": \"unreserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ " ]\n"
+ "}]\n"
+ "}",
+
+ // Configuration 5 client-class reservation in global, shared network
+ // and client-class guarded subnets.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"reserved_class\""
+ "},"
+ "{"
+ " \"name\": \"unreserved_class\","
+ " \"test\": \"not member('reserved_class')\""
+ "}"
+ "],\n"
+ "\"reservation-mode\": \"global\","
+ "\"valid-lifetime\": 600,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:fe\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ "}\n"
+ "],\n"
+ "\"shared-networks\": [{"
+ " \"name\": \"frog\",\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"10.0.0.0/24\", \n"
+ " \"id\": 10,"
+ " \"client-class\": \"reserved_class\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.10-10.0.0.10\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " },\n"
+ " {\n"
+ " \"subnet\": \"192.0.3.0/24\", \n"
+ " \"id\": 11,"
+ " \"client-class\": \"unreserved_class\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.3.10-192.0.3.10\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ " ]\n"
+ "}]\n"
+ "}",
+
+ // Configuration 6 client-class reservation and client-class guarded pools.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"reserved_class\""
+ "},"
+ "{"
+ " \"name\": \"unreserved_class\","
+ " \"test\": \"not member('reserved_class')\""
+ "}"
+ "],\n"
+ "\"valid-lifetime\": 600,\n"
+ "\"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"10.0.0.0/24\", \n"
+ " \"id\": 10,"
+ " \"reservations\": [{ \n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:fe\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ " }],\n"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.10-10.0.0.11\","
+ " \"client-class\": \"reserved_class\""
+ " },"
+ " {"
+ " \"pool\": \"10.0.0.20-10.0.0.21\","
+ " \"client-class\": \"unreserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ "]\n"
+ "}"
};
/// @brief Test fixture class for testing global v4 reservations.
/// @param expected_addr expected address to be assigned
void runDoraTest(const std::string& config, Dhcp4Client& client,
const std::string& expected_host,
- const std::string& expected_addr) {
+ const std::string& expected_addr,
+ const std::string& requested_addr = "") {
// Configure DHCP server.
ASSERT_NO_FATAL_FAILURE(configure(config, *client.getServer()));
// Perform 4-way exchange with the server but to not request any
// specific address in the DHCPDISCOVER message.
- ASSERT_NO_THROW(client.doDORA());
+ boost::shared_ptr<IOAddress> hint;
+ if (!requested_addr.empty()) {
+ hint = boost::make_shared<IOAddress>(requested_addr);
+ }
+ ASSERT_NO_THROW(client.doDORA(hint));
// Make sure that the server responded.
ASSERT_TRUE(client.getContext().response_);
EXPECT_EQ(client.config_.lease_.addr_.toText(), expected_addr);
}
-
+ /// @brief Test pool or subnet selection using global class reservation.
+ ///
+ /// Verifies that client class specified in the global reservation
+ /// may be used to influence pool or subnet selection.
+ ///
+ /// @param config_idx Index of the server configuration from the
+ /// @c CONFIGS array.
+ /// @param first_address Address to be allocated from the pool having
+ /// a reservation.
+ /// @param second_address Address to be allocated from the pool not
+ /// having a reservation.
+ void testGlobalClassSubnetPoolSelection(const int config_idx,
+ const std::string& first_address = "10.0.0.10",
+ const std::string& second_address = "192.0.3.10") {
+ Dhcp4Client client_resrv(Dhcp4Client::SELECTING);
+
+ // Use HW address for which we have host reservation including
+ // client class.
+ client_resrv.setHWAddress("aa:bb:cc:dd:ee:fe");
+ client_resrv.setIfaceName("eth0");
+
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[config_idx], *client_resrv.getServer()));
+
+ // This client should be given an address from the 10.0.0.0/24 pool.
+ // Let's use the 192.0.3.10 as a hint to make sure that the server
+ // refuses allocating it and uses the sole pool available for this
+ // client.
+ ASSERT_NO_THROW(client_resrv.doDORA(boost::make_shared<IOAddress>(second_address)));
+ ASSERT_TRUE(client_resrv.getContext().response_);
+ auto resp = client_resrv.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_EQ(first_address, resp->getYiaddr().toText());
+
+ // This client has no reservation and therefore should be
+ // assigned to the unreserved_class and be given an address
+ // from the other pool.
+ Dhcp4Client client_no_resrv(client_resrv.getServer(), Dhcp4Client::SELECTING);
+ client_no_resrv.setHWAddress("aa:bb:cc:dd:ee:ff");
+ client_no_resrv.setIfaceName("eth0");
+
+ // Let's use the address of 10.0.0.10 as a hint to make sure that the
+ // server refuses it in favor of the 192.0.3.10.
+ ASSERT_NO_THROW(client_no_resrv.doDORA(boost::make_shared<IOAddress>(first_address)));
+ ASSERT_TRUE(client_no_resrv.getContext().response_);
+ resp = client_no_resrv.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_EQ(second_address, resp->getYiaddr().toText());
+ }
};
// Verifies that a client, which fails to match to a global
runDoraTest(CONFIGS[3], client, "subnet-10-host", "192.0.5.10");
}
+// Verifies that client class specified in the global reservation
+// may be used to influence pool selection.
+TEST_F(HostTest, clientClassGlobalPoolSelection) {
+ ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(4));
+}
+
+// Verifies that client class specified in the global reservation
+// may be used to influence subnet selection within shared network.
+TEST_F(HostTest, clientClassGlobalSubnetSelection) {
+ ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(5));
+}
+
+// Verifies that client class specified in the reservation may be
+// used to influence pool selection within a subnet.
+TEST_F(HostTest, clientClassPoolSelection) {
+ ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(6, "10.0.0.10", "10.0.0.20"));
+}
+
} // end of anonymous namespace
// Collect host identifiers if host reservations enabled. The identifiers
// are stored in order of preference. The server will use them in that
// order to search for host reservations.
+ SharedNetwork6Ptr sn;
if (ctx.subnet_) {
const ConstCfgHostOperationsPtr cfg =
CfgMgr::instance().getCurrentCfg()->getCfgHostOperations6();
// Find host reservations using specified identifiers.
alloc_engine_->findReservation(ctx);
+
+ // Get shared network to see if it is set for a subnet.
+ ctx.subnet_->getSharedNetwork(sn);
+ }
+
+ // Global host reservations are independent of a selected subnet. If the
+ // global reservations contain client classes we should use them in case
+ // they are meant to affect pool selection. Also, if the subnet does not
+ // belong to a shared network we can use the reserved client classes
+ // because there is no way our subnet could change. Such classes may
+ // affect selection of a pool within the selected subnet.
+ auto global_host = ctx.globalHost();
+ auto current_host = ctx.currentHost();
+ if ((global_host && !global_host->getClientClasses6().empty()) ||
+ (!sn && current_host && !current_host->getClientClasses6().empty())) {
+ // We have already evaluated client classes and some of them may
+ // be in conflict with the reserved classes. Suppose there are
+ // two classes defined in the server configuration: first_class
+ // and second_class and the test for the second_class it looks
+ // like this: "not member('first_class')". If the first_class
+ // initially evaluates to false, the second_class evaluates to
+ // true. If the first_class is now set within the hosts reservations
+ // and we don't remove the previously evaluated second_class we'd
+ // end up with both first_class and second_class evaluated to
+ // true. In order to avoid that, we have to remove the classes
+ // evaluated in the first pass and evaluate them again. As
+ // a result, the first_class set via the host reservation will
+ // replace the second_class because the second_class will this
+ // time evaluate to false as desired.
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ const ClientClassDefListPtr& defs_ptr = dict->getClasses();
+ for (auto def : *defs_ptr) {
+ ctx.query_->classes_.erase(def->getName());
+ }
+ setReservedClientClasses(pkt, ctx);
+ evaluateClasses(pkt, false);
}
// Set KNOWN builtin class if something was found, UNKNOWN if not.
processClientFqdn(solicit, response, ctx);
assignLeases(solicit, response, ctx);
- setReservedClientClasses(solicit, ctx);
+ conditionallySetReservedClientClasses(solicit, ctx);
requiredClassify(solicit, ctx);
copyClientOptions(solicit, response);
processClientFqdn(request, reply, ctx);
assignLeases(request, reply, ctx);
- setReservedClientClasses(request, ctx);
+ conditionallySetReservedClientClasses(request, ctx);
requiredClassify(request, ctx);
copyClientOptions(request, reply);
processClientFqdn(renew, reply, ctx);
extendLeases(renew, reply, ctx);
- setReservedClientClasses(renew, ctx);
+ conditionallySetReservedClientClasses(renew, ctx);
requiredClassify(renew, ctx);
copyClientOptions(renew, reply);
processClientFqdn(rebind, reply, ctx);
extendLeases(rebind, reply, ctx);
- setReservedClientClasses(rebind, ctx);
+ conditionallySetReservedClientClasses(rebind, ctx);
requiredClassify(rebind, ctx);
copyClientOptions(rebind, reply);
Dhcpv6Srv::processConfirm(AllocEngine::ClientContext6& ctx) {
Pkt6Ptr confirm = ctx.query_;
- setReservedClientClasses(confirm, ctx);
+ conditionallySetReservedClientClasses(confirm, ctx);
requiredClassify(confirm, ctx);
// Get IA_NAs from the Confirm. If there are none, the message is
Dhcpv6Srv::processRelease(AllocEngine::ClientContext6& ctx) {
Pkt6Ptr release = ctx.query_;
- setReservedClientClasses(release, ctx);
+ conditionallySetReservedClientClasses(release, ctx);
requiredClassify(release, ctx);
// Create an empty Reply message.
Dhcpv6Srv::processDecline(AllocEngine::ClientContext6& ctx) {
Pkt6Ptr decline = ctx.query_;
- setReservedClientClasses(decline, ctx);
+ conditionallySetReservedClientClasses(decline, ctx);
requiredClassify(decline, ctx);
// Create an empty Reply message.
Dhcpv6Srv::processInfRequest(AllocEngine::ClientContext6& ctx) {
Pkt6Ptr inf_request = ctx.query_;
- setReservedClientClasses(inf_request, ctx);
+ conditionallySetReservedClientClasses(inf_request, ctx);
requiredClassify(inf_request, ctx);
// Create a Reply packet, with the same trans-id as the client's.
}
}
+void
+Dhcpv6Srv::conditionallySetReservedClientClasses(const Pkt6Ptr& pkt,
+ const AllocEngine::ClientContext6& ctx) {
+ if (ctx.subnet_) {
+ SharedNetwork6Ptr shared_network;
+ ctx.subnet_->getSharedNetwork(shared_network);
+ if (shared_network && !ctx.globalHost()) {
+ setReservedClientClasses(pkt, ctx);
+ }
+ }
+}
+
void
Dhcpv6Srv::requiredClassify(const Pkt6Ptr& pkt, AllocEngine::ClientContext6& ctx) {
// First collect required classes
///
/// @note This is done in two phases: first the content of the
/// vendor-class-identifier option is used as a class, by
- /// calling @ref classifyByVendor(). Second classification match
+ /// calling @ref classifyByVendor(). Second, the classification match
/// expressions are evaluated. The resulting classes will be stored
/// in the packet (see @ref isc::dhcp::Pkt6::classes_ and
/// @ref isc::dhcp::Pkt6::inClass).
void setReservedClientClasses(const Pkt6Ptr& pkt,
const AllocEngine::ClientContext6& ctx);
+ /// @brief Assigns classes retrieved from host reservation database
+ /// if they haven't been yet set.
+ ///
+ /// This function sets reserved client classes in case they haven't
+ /// been set after fetching host reservations from the database.
+ /// This is the case when the client has non-global host reservation
+ /// and the selected subnet belongs to a shared network.
+ ///
+ /// @param pkt Pointer to the packet to which classes will be assigned.
+ /// @param ctx Reference to the client context.
+ void conditionallySetReservedClientClasses(const Pkt6Ptr& pkt,
+ const AllocEngine::ClientContext6& ctx);
+
/// @brief Assigns incoming packet to zero or more classes (required pass).
///
/// @note This required classification evaluates all classes which
-// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2020 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
" }] \n"
" }"
" ] \n"
- "} \n"
+ "} \n",
+
+ // Configuration 10: client-class reservation in global, shared network
+ // and client-class guarded pools.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"host-reservation-identifiers\": [ \"duid\", \"hw-address\" ], \n"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"reserved_class\""
+ "},"
+ "{"
+ " \"name\": \"unreserved_class\","
+ " \"test\": \"not member('reserved_class')\""
+ "}"
+ "],\n"
+ "\"reservation-mode\": \"global\","
+ "\"valid-lifetime\": 4000,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"duid\": \"01:02:03:05\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ "}\n"
+ "],\n"
+ "\"shared-networks\": [{"
+ " \"name\": \"frog\",\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::10-2001:db8:1::11\","
+ " \"client-class\": \"reserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " },\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:2::/64\", \n"
+ " \"id\": 11,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::10-2001:db8:2::11\","
+ " \"client-class\": \"unreserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ " ]\n"
+ "}]\n"
+ "}",
+
+ // Configuration 11: client-class reservation in global, shared network
+ // and client-class guarded subnets.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"host-reservation-identifiers\": [ \"duid\", \"hw-address\" ], \n"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"reserved_class\""
+ "},"
+ "{"
+ " \"name\": \"unreserved_class\","
+ " \"test\": \"not member('reserved_class')\""
+ "}"
+ "],\n"
+ "\"reservation-mode\": \"global\","
+ "\"valid-lifetime\": 4000,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"duid\": \"01:02:03:05\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ "}\n"
+ "],\n"
+ "\"shared-networks\": [{"
+ " \"name\": \"frog\",\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"client-class\": \"reserved_class\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::10-2001:db8:1::11\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " },\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:2::/64\", \n"
+ " \"client-class\": \"unreserved_class\","
+ " \"id\": 11,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::10-2001:db8:2::11\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ " ]\n"
+ "}]\n"
+ "}",
+
+ // Configuration 12 client-class reservation and client-class guarded pools.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"reserved_class\""
+ "},"
+ "{"
+ " \"name\": \"unreserved_class\","
+ " \"test\": \"not member('reserved_class')\""
+ "}"
+ "],\n"
+ "\"valid-lifetime\": 4000,\n"
+ "\"subnet6\": [\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"id\": 10,"
+ " \"reservations\": [{ \n"
+ " \"duid\": \"01:02:03:05\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ " }],\n"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::10-2001:db8:1::11\","
+ " \"client-class\": \"reserved_class\""
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:1::20-2001:db8:1::21\","
+ " \"client-class\": \"unreserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ "]\n"
+ "}"
};
/// @brief Base class representing leases and hints conveyed within IAs.
/// @param hint Const reference to an object holding the hint.
static void requestIA(Dhcp6Client& client, const Hint& hint);
+ /// @brief Test pool or subnet selection using global class reservation.
+ ///
+ /// Verifies that client class specified in the global reservation
+ /// may be used to influence pool or subnet selection.
+ ///
+ /// @param config_idx Index of the server configuration from the
+ /// @c CONFIGS array.
+ /// @param first_address Address to be allocated from the pool having
+ /// a reservation.
+ /// @param second_address Address to be allocated from the pool not
+ /// having a reservation.
+ void testGlobalClassSubnetPoolSelection(const int config_idx,
+ const std::string& first_address = "2001:db8:1::10",
+ const std::string& second_address = "2001:db8:2::10");
+
/// @brief Configures client to include 6 IAs without hints.
///
/// This method configures the client to include 3 IA_NAs and
EXPECT_EQ("3000:1::234", addrs[0].toText());
}
+void
+HostTest::testGlobalClassSubnetPoolSelection(const int config_idx,
+ const std::string& first_address,
+ const std::string& second_address) {
+ Dhcp6Client client_resrv;
+
+ // Use DUID for which we have host reservation including client class.
+ client_resrv.setDUID("01:02:03:05");
+
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[config_idx], *client_resrv.getServer()));
+
+ // This client should be given an address from the 2001:db8:1::/64 subnet.
+ // Let's use the 2001:db8:2::10 as a hint to make sure that the server
+ // refuses allocating it and uses the sole pool available for this
+ // client.
+ client_resrv.requestAddress(1, IOAddress(second_address));
+ ASSERT_NO_THROW(client_resrv.doSARR());
+ ASSERT_EQ(1, client_resrv.getLeaseNum());
+ Lease6 lease_client = client_resrv.getLease(0);
+ EXPECT_EQ(first_address, lease_client.addr_.toText());
+
+ // This client has no reservation and therefore should be
+ // assigned to the unreserved_class and be given an address
+ // from the other pool.
+ Dhcp6Client client_no_resrv(client_resrv.getServer());
+ client_no_resrv.setDUID("01:02:03:04");
+
+ // Let's use the address of 2001:db8:1::10 as a hint to make sure that the
+ // server refuses it in favor of the 2001:db8:2::10.
+ client_no_resrv.requestAddress(1, IOAddress(first_address));
+ ASSERT_NO_THROW(client_no_resrv.doSARR());
+ ASSERT_EQ(1, client_no_resrv.getLeaseNum());
+ lease_client = client_no_resrv.getLease(0);
+ EXPECT_EQ(second_address, lease_client.addr_.toText());
+}
+
void
HostTest::requestEmptyIAs(Dhcp6Client& client) {
// Create IAs with IAIDs between 1 and 6.
}
}
+// Verifies that client class specified in the global reservation
+// may be used to influence pool selection.
+TEST_F(HostTest, clientClassGlobalPoolSelection) {
+ ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(10));
+}
+
+// Verifies that client class specified in the global reservation
+// may be used to influence subnet selection within shared network.
+TEST_F(HostTest, clientClassGlobalSubnetSelection) {
+ ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(11));
+}
+
+// Verifies that client class specified in the reservation may be
+// used to influence pool selection within a subnet.
+TEST_F(HostTest, clientClassPoolSelection) {
+ ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(12, "2001:db8:1::10",
+ "2001:db8:1::20"));
+}
+
} // end of anonymous namespace
-// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
}
}
+void
+ClientClasses::erase(const ClientClass& class_name) {
+ list_.remove(class_name);
+ static_cast<void>(set_.erase(class_name));
+}
+
std::string
ClientClasses::toText(const std::string& separator) const {
std::stringstream s;
-// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
set_.insert(class_name);
}
+ /// @brief Erase element by name.
+ ///
+ /// @param class_name The name of the class to erase.
+ void erase(const ClientClass& class_name);
+
/// @brief Check if classes is empty.
bool empty() const {
return (list_.empty());
-// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2020 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// Check non-standard separator.
EXPECT_EQ("alpha.gamma.beta", classes.toText("."));
}
+
+// Check that selected class can be erased.
+TEST(ClassifyTest, Erase) {
+ ClientClasses classes;
+
+ classes.insert("alpha");
+ classes.insert("beta");
+ EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_TRUE(classes.contains("beta"));
+
+ classes.erase("beta");
+ EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_FALSE(classes.contains("beta"));
+
+ classes.erase("alpha");
+ EXPECT_FALSE(classes.contains("alpha"));
+ EXPECT_FALSE(classes.contains("beta"));
+}
return (ConstHostPtr());
}
+ConstHostPtr
+AllocEngine::ClientContext4::globalHost() const {
+ if (subnet_ && subnet_->getHostReservationMode() == Network::HR_GLOBAL) {
+ auto host = hosts_.find(SUBNET_ID_GLOBAL);
+ if (host != hosts_.cend()) {
+ return (host->second);
+ }
+ }
+
+ return (ConstHostPtr());
+}
+
Lease4Ptr
AllocEngine::allocateLease4(ClientContext4& ctx) {
// The NULL pointer indicates that the old lease didn't exist. It may
/// @return Pointer to the host object.
ConstHostPtr currentHost() const;
+ /// @brief Returns global host reservation if there is one
+ ///
+ /// If the current subnet's reservation mode is global and
+ /// there is a global host (i.e. reservation belonging to
+ /// the global subnet), return it. Otherwise return an
+ /// empty pointer.
+ ///
+ /// @return Pointer to the host object.
+ ConstHostPtr globalHost() const;
+
/// @brief Default constructor.
ClientContext4();