diff --git a/doc/sphinx/arm/dhcp6-srv.rst b/doc/sphinx/arm/dhcp6-srv.rst index f86fe26c10..4a9b2dadbb 100644 --- a/doc/sphinx/arm/dhcp6-srv.rst +++ b/doc/sphinx/arm/dhcp6-srv.rst @@ -6846,6 +6846,29 @@ confusion, ISC decided not to decrease ``assigned-nas`` immediately after receiving DHCPDECLINE, but to do it later when Kea recovers the address back to the available pool. +.. _dhcp6-address-registration: + +Address Registration (RFC 9686 Support) +======================================= + +Kea version 2.7.7 introduces the support of self-generated address registration +as defined in `RFC 9686 `__ i.e. +when a valid ADDR-REG-INFORM (36) message in received a registered lease is +added or updated and a ADDR-REG-REPLY (37) is sent back to the client. + +.. note:: + + Even if they share a common lease database with leases in other states, + registered leases are independent: when a lease in another state already + exists for an address this address in considered as in use and can't be + registered. In the other way a registered lease can't change to another + state, e.g. reclaimation of expired registered leases removes them. + +.. note:: + + Kea accepts and handles the Option Request option in the ADDR-REG-INFORM + message (the RFC specifies the client MUST NOT put such option in it). + .. _dhcp6-stats: Statistics in the DHCPv6 Server @@ -7011,6 +7034,26 @@ The DHCPv6 server supports the following statistics: | | | the server rather than back to the | | | | clients. | +---------------------------------------------------+----------------+------------------------------------+ + | pkt6-addr-reg-inform-received | integer | Number of ADDR-REG-INFORM packets | + | | | received. This statistic is | + | | | expected to grow if there are | + | | | devices that are using address | + | | | registration. | + +---------------------------------------------------+----------------+------------------------------------+ + | pkt6-addr-reg-reply-received | integer | Number of ADDR-REG-REPLY packets | + | | | received. This statistic is | + | | | expected to remain zero at all | + | | | times, as ADDR-REG-REPLY packets | + | | | are sent by the server and the | + | | | server is never expected to | + | | | receive them. A non-zero value | + | | | indicates an error. One likely | + | | | cause would be a misbehaving relay | + | | | agent that incorrectly forwards | + | | | ADDR-REG-REPLY message towards the | + | | | server rather than back to the | + | | | clients. | + +---------------------------------------------------+----------------+------------------------------------+ | pkt6-unknown-received | integer | Number of packets received of an | | | | unknown type. A non-zero value of | | | | this statistic indicates that the | @@ -7058,6 +7101,13 @@ The DHCPv6 server supports the following statistics: | | | are certain cases where there is | | | | no response. | +---------------------------------------------------+----------------+------------------------------------+ + | pkt6-addr-reg-reply-sent | integer | Number of ADDR-REG-REPLY packets | + | | | sent. This statistic is expected | + | | | to grow in most cases after a | + | | | ADDR-REG-INFORM is processed. | + | | | There are certain cases where | + | | | there is n response. | + +---------------------------------------------------+----------------+------------------------------------+ | subnet[id].total-nas | big integer | Total number of NA addresses | | | | available for DHCPv6 management | | | | for a given subnet; in other | @@ -7527,6 +7577,38 @@ The DHCPv6 server supports the following statistics: | | | basis. The *id* is the subnet ID | | | | of a given subnet. | +---------------------------------------------------+----------------+------------------------------------+ + | cumulative-registered | integer | Cumulative number of NA addresses | + | | | that have been registered since | + | | | server startup. It is incremented | + | | | each time a NA address is | + | | | registered and is not reset when | + | | | the server is reconfigured. | + +---------------------------------------------------+----------------+------------------------------------+ + | subnet[id].cumulative-registered | integer | Cumulative number of NA addresses | + | | | in a given subnet that were | + | | | registered. It increases every | + | | | a new address is registered (as a | + | | | result of receiving an | + | | | ADDR-REG-INFORM message) and is | + | | | never decreased. The *id* is the | + | | | subnet ID of a given subnet. This | + | | | statistic is exposed for each | + | | | subnet separately, and is reset | + | | | during a reconfiguration event. | + +---------------------------------------------------+----------------+------------------------------------+ + | subnet[id].registered | integer | Number of NA addresses in a given | + | | | subnet that are registered. It | + | | | increases every time a new address | + | | | registered (as a result of | + | | | receiving an ADDR-REG-INFORM | + | | | message) and is decreased every | + | | | time a registration expires. The | + | | | *id* is the the subnet ID of a | + | | | given subnet. This statistic is | + | | | exposed for each subnet | + | | | separately, and is reset during a | + | | | reconfiguration event. | + +---------------------------------------------------+----------------+------------------------------------+ .. note:: @@ -8015,6 +8097,9 @@ The following standards are currently supported in Kea: Resolvers (DNR)*, `RFC 9463 `__. The Kea server supports the DNR option. +- *Registering Self-Generated IPv6 Addresses Using DHCPv6*, `RFC 9686 `__. The Kea server supports self-generated address registration. See :ref:`dhcp6-address-registration` for details. + + .. _dhcp6-limit: DHCPv6 Server Limitations diff --git a/doc/sphinx/arm/hooks-run-script.rst b/doc/sphinx/arm/hooks-run-script.rst index d1b424a7b8..fddfa49138 100644 --- a/doc/sphinx/arm/hooks-run-script.rst +++ b/doc/sphinx/arm/hooks-run-script.rst @@ -74,6 +74,7 @@ The dhcpv6 hook points: leases6_committed lease6_release lease6_decline + addr6_register Each hook point extracts the Kea internal data and exports it as string environment variables. These parameters are shared with the target script @@ -145,6 +146,10 @@ An example of a script implementing all hook points is presented below: ... } + addr6_register () { + ... + } + case "$1" in "lease4_renew") lease4_renew @@ -185,6 +190,9 @@ An example of a script implementing all hook points is presented below: "lease6_decline") lease6_decline ;; + "addr6_register") + addr6_register + ;; *) unknown_handle "${@}" ;; @@ -626,6 +634,52 @@ at 0. LEASE6_PREFIX_LEN LEASE6_TYPE +``addr6_register`` + +:: + + QUERY6_TYPE + QUERY6_TXID + QUERY6_LOCAL_ADDR + QUERY6_LOCAL_PORT + QUERY6_REMOTE_ADDR + QUERY6_REMOTE_PORT + QUERY6_IFACE_INDEX + QUERY6_IFACE_NAME + QUERY6_REMOTE_HWADDR + QUERY6_REMOTE_HWADDR_TYPE + QUERY6_PROTO + QUERY6_CLIENT_ID + ADDRESS6 + OLD_LEASE6_ADDRESS + OLD_LEASE6_CLTT + OLD_LEASE6_HOSTNAME + OLD_LEASE6_HWADDR + OLD_LEASE6_HWADDR_TYPE + OLD_LEASE6_STATE + OLD_LEASE6_SUBNET_ID + OLD_LEASE6_VALID_LIFETIME + OLD_LEASE6_DUID + OLD_LEASE6_IAID + OLD_LEASE6_PREFERRED_LIFETIME + OLD_LEASE6_PREFIX_LEN + OLD_LEASE6_TYPE + NEW_LEASE6_ADDRESS + NEW_LEASE6_CLTT + NEW_LEASE6_HOSTNAME + NEW_LEASE6_HWADDR + NEW_LEASE6_HWADDR_TYPE + NEW_LEASE6_STATE + NEW_LEASE6_SUBNET_ID + NEW_LEASE6_VALID_LIFETIME + NEW_LEASE6_DUID + NEW_LEASE6_IAID + NEW_LEASE6_PREFERRED_LIFETIME + NEW_LEASE6_PREFIX_LEN + NEW_LEASE6_TYPE + +The OLD_LEASE6 do not always exists. + The leases4_committed hook point needs for loops to handle the list of addresses. This can be achived in the following way: @@ -660,4 +714,4 @@ This can be achived in the following way: ... done exit 0 - } \ No newline at end of file + } diff --git a/doc/sphinx/arm/stats.rst b/doc/sphinx/arm/stats.rst index 0490a4af5b..c2a641688a 100644 --- a/doc/sphinx/arm/stats.rst +++ b/doc/sphinx/arm/stats.rst @@ -499,12 +499,36 @@ or "2023-06-13 21:28:57.196758" ] ], + "cumulative-registered": [ + [ + 0, + "2023-06-13 21:28:57.196758" + ] + ], "declined-addresses": [ [ 0, "2023-06-13 21:28:57.196754" ] ], + "pkt6-addr-reg-inform-received": [ + [ + 0, + "2023-06-13 21:28:57.177731" + ] + ], + "pkt6-addr-reg-reply-received": [ + [ + 0, + "2023-06-13 21:28:57.177731" + ] + ], + "pkt6-addr-reg-reply-sent": [ + [ + 0, + "2023-06-13 21:28:57.177731" + ] + ], "pkt6-advertise-received": [ [ 0, @@ -655,6 +679,12 @@ or "2023-06-13 21:28:57.196729" ] ], + "subnet[1].cumulative-registered": [ + [ + 0, + "2023-06-13 21:28:57.196727" + ] + ], "subnet[1].declined-addresses": [ [ 0, @@ -733,6 +763,12 @@ or "2023-06-13 21:28:57.196770" ] ], + "subnet[1].registered": [ + [ + 0, + "2023-06-13 21:28:57.196727" + ] + ], "subnet[1].total-nas": [ [ 281474976710656, diff --git a/src/bin/dhcp6/dhcp6_hooks.dox b/src/bin/dhcp6/dhcp6_hooks.dox index c660305f6e..2e45e75273 100644 --- a/src/bin/dhcp6/dhcp6_hooks.dox +++ b/src/bin/dhcp6/dhcp6_hooks.dox @@ -345,7 +345,7 @@ called before "subnet6_select". - Next step status: Not applicable, its value will be ignored. -@subsection dhcpv6HookRegister6 register6 +@subsection dhcpv6HookAddr6Register addr6_register - @b Arguments: - name: @b query6, type: isc::dhcp::Pkt6Ptr, direction: in diff --git a/src/bin/dhcp6/dhcp6_messages.cc b/src/bin/dhcp6/dhcp6_messages.cc index d883dd70bd..4744e8cf00 100644 --- a/src/bin/dhcp6/dhcp6_messages.cc +++ b/src/bin/dhcp6/dhcp6_messages.cc @@ -12,9 +12,9 @@ extern const isc::log::MessageID DHCP6_ADDITIONAL_CLASS_EVAL_ERROR = "DHCP6_ADDI extern const isc::log::MessageID DHCP6_ADDITIONAL_CLASS_EVAL_RESULT = "DHCP6_ADDITIONAL_CLASS_EVAL_RESULT"; extern const isc::log::MessageID DHCP6_ADDITIONAL_CLASS_NO_TEST = "DHCP6_ADDITIONAL_CLASS_NO_TEST"; extern const isc::log::MessageID DHCP6_ADDITIONAL_CLASS_UNDEFINED = "DHCP6_ADDITIONAL_CLASS_UNDEFINED"; +extern const isc::log::MessageID DHCP6_ADDR_REG_INFORM_CLIENT_CHANGE = "DHCP6_ADDR_REG_INFORM_CLIENT_CHANGE"; +extern const isc::log::MessageID DHCP6_ADDR_REG_INFORM_FAIL = "DHCP6_ADDR_REG_INFORM_FAIL"; extern const isc::log::MessageID DHCP6_ADD_GLOBAL_STATUS_CODE = "DHCP6_ADD_GLOBAL_STATUS_CODE"; -extern const isc::log::MessageID DHCP6_ADD_REG_INFORM_CLIENT_CHANGE = "DHCP6_ADD_REG_INFORM_CLIENT_CHANGE"; -extern const isc::log::MessageID DHCP6_ADD_REG_INFORM_FAIL = "DHCP6_ADD_REG_INFORM_FAIL"; extern const isc::log::MessageID DHCP6_ADD_STATUS_CODE_FOR_IA = "DHCP6_ADD_STATUS_CODE_FOR_IA"; extern const isc::log::MessageID DHCP6_ALREADY_RUNNING = "DHCP6_ALREADY_RUNNING"; extern const isc::log::MessageID DHCP6_BUFFER_RECEIVED = "DHCP6_BUFFER_RECEIVED"; @@ -65,6 +65,7 @@ extern const isc::log::MessageID DHCP6_DYNAMIC_RECONFIGURATION = "DHCP6_DYNAMIC_ extern const isc::log::MessageID DHCP6_DYNAMIC_RECONFIGURATION_FAIL = "DHCP6_DYNAMIC_RECONFIGURATION_FAIL"; extern const isc::log::MessageID DHCP6_DYNAMIC_RECONFIGURATION_SUCCESS = "DHCP6_DYNAMIC_RECONFIGURATION_SUCCESS"; extern const isc::log::MessageID DHCP6_FLEX_ID = "DHCP6_FLEX_ID"; +extern const isc::log::MessageID DHCP6_HOOK_ADDR6_REGISTER_SKIP = "DHCP6_HOOK_ADDR6_REGISTER_SKIP"; extern const isc::log::MessageID DHCP6_HOOK_BUFFER_RCVD_DROP = "DHCP6_HOOK_BUFFER_RCVD_DROP"; extern const isc::log::MessageID DHCP6_HOOK_BUFFER_RCVD_SKIP = "DHCP6_HOOK_BUFFER_RCVD_SKIP"; extern const isc::log::MessageID DHCP6_HOOK_BUFFER_SEND_SKIP = "DHCP6_HOOK_BUFFER_SEND_SKIP"; @@ -79,7 +80,6 @@ extern const isc::log::MessageID DHCP6_HOOK_LEASES6_PARKING_LOT_FULL = "DHCP6_HO extern const isc::log::MessageID DHCP6_HOOK_PACKET_RCVD_SKIP = "DHCP6_HOOK_PACKET_RCVD_SKIP"; extern const isc::log::MessageID DHCP6_HOOK_PACKET_SEND_DROP = "DHCP6_HOOK_PACKET_SEND_DROP"; extern const isc::log::MessageID DHCP6_HOOK_PACKET_SEND_SKIP = "DHCP6_HOOK_PACKET_SEND_SKIP"; -extern const isc::log::MessageID DHCP6_HOOK_REGISTER6_SKIP = "DHCP6_HOOK_REGISTER6_SKIP"; extern const isc::log::MessageID DHCP6_HOOK_SUBNET6_SELECT_DROP = "DHCP6_HOOK_SUBNET6_SELECT_DROP"; extern const isc::log::MessageID DHCP6_HOOK_SUBNET6_SELECT_PARK = "DHCP6_HOOK_SUBNET6_SELECT_PARK"; extern const isc::log::MessageID DHCP6_HOOK_SUBNET6_SELECT_SKIP = "DHCP6_HOOK_SUBNET6_SELECT_SKIP"; @@ -140,6 +140,8 @@ extern const isc::log::MessageID DHCP6_QUERY_DATA = "DHCP6_QUERY_DATA"; extern const isc::log::MessageID DHCP6_QUERY_LABEL = "DHCP6_QUERY_LABEL"; extern const isc::log::MessageID DHCP6_RAPID_COMMIT = "DHCP6_RAPID_COMMIT"; extern const isc::log::MessageID DHCP6_RECLAIM_EXPIRED_LEASES_FAIL = "DHCP6_RECLAIM_EXPIRED_LEASES_FAIL"; +extern const isc::log::MessageID DHCP6_REGISTERED_LEASE_ADD_FAIL = "DHCP6_REGISTERED_LEASE_ADD_FAIL"; +extern const isc::log::MessageID DHCP6_REGISTERED_LEASE_UPDATE_FAIL = "DHCP6_REGISTERED_LEASE_UPDATE_FAIL"; extern const isc::log::MessageID DHCP6_RELEASE_NA = "DHCP6_RELEASE_NA"; extern const isc::log::MessageID DHCP6_RELEASE_NA_DELETED = "DHCP6_RELEASE_NA_DELETED"; extern const isc::log::MessageID DHCP6_RELEASE_NA_EXPIRED = "DHCP6_RELEASE_NA_EXPIRED"; @@ -182,9 +184,9 @@ const char* values[] = { "DHCP6_ADDITIONAL_CLASS_EVAL_RESULT", "%1: Expression '%2' evaluated to %3", "DHCP6_ADDITIONAL_CLASS_NO_TEST", "additional class %1 has no test expression, adding it to client's classes unconditionally", "DHCP6_ADDITIONAL_CLASS_UNDEFINED", "additional class %1 has no definition", + "DHCP6_ADDR_REG_INFORM_CLIENT_CHANGE", "received an addr-reg-inform for %1 from client '%2' but the address was registered by another client '%3'", + "DHCP6_ADDR_REG_INFORM_FAIL", "error on addr-reg-inform from client %1: %2", "DHCP6_ADD_GLOBAL_STATUS_CODE", "%1: adding Status Code to DHCPv6 packet: %2", - "DHCP6_ADD_REG_INFORM_CLIENT_CHANGE", "received an add-reg-inform for %1 from client '%2' but the address was registered by another client '%3'", - "DHCP6_ADD_REG_INFORM_FAIL", "error on add-reg-inform from client %1: %2", "DHCP6_ADD_STATUS_CODE_FOR_IA", "%1: adding Status Code to IA with iaid=%2: %3", "DHCP6_ALREADY_RUNNING", "%1 already running? %2", "DHCP6_BUFFER_RECEIVED", "received buffer from %1:%2 to %3:%4 over interface %5", @@ -235,6 +237,7 @@ const char* values[] = { "DHCP6_DYNAMIC_RECONFIGURATION_FAIL", "dynamic server reconfiguration failed with file: %1", "DHCP6_DYNAMIC_RECONFIGURATION_SUCCESS", "dynamic server reconfiguration succeeded with file: %1", "DHCP6_FLEX_ID", "%1: flexible identifier generated for incoming packet: %2", + "DHCP6_HOOK_ADDR6_REGISTER_SKIP", "%1: addr-reg-inform for %2 is dropped, because a callout set the next step to SKIP", "DHCP6_HOOK_BUFFER_RCVD_DROP", "received buffer from %1 to %2 over interface %3 was dropped because a callout set the drop flag", "DHCP6_HOOK_BUFFER_RCVD_SKIP", "received buffer from %1 to %2 over interface %3 is not parsed because a callout set the next step to SKIP", "DHCP6_HOOK_BUFFER_SEND_SKIP", "%1: prepared DHCPv6 response was dropped because a callout set the next step to SKIP", @@ -249,7 +252,6 @@ const char* values[] = { "DHCP6_HOOK_PACKET_RCVD_SKIP", "%1: packet is dropped, because a callout set the next step to SKIP", "DHCP6_HOOK_PACKET_SEND_DROP", "%1: prepared DHCPv6 response was not sent because a callout set the next ste to DROP", "DHCP6_HOOK_PACKET_SEND_SKIP", "%1: prepared DHCPv6 response is not built because a callout set the next step to SKIP", - "DHCP6_HOOK_REGISTER6_SKIP", "%1: add-reg-inform for %2 is dropped, because a callout set the next step to SKIP", "DHCP6_HOOK_SUBNET6_SELECT_DROP", "%1: packet was dropped because a callout set the drop flag", "DHCP6_HOOK_SUBNET6_SELECT_PARK", "%1: packet was parked", "DHCP6_HOOK_SUBNET6_SELECT_SKIP", "%1: no subnet was selected because a callout set the next step to SKIP", @@ -310,6 +312,8 @@ const char* values[] = { "DHCP6_QUERY_LABEL", "received query: %1", "DHCP6_RAPID_COMMIT", "%1: Rapid Commit option received, following 2-way exchange", "DHCP6_RECLAIM_EXPIRED_LEASES_FAIL", "failed to reclaim expired leases: %1", + "DHCP6_REGISTERED_LEASE_ADD_FAIL", "error in registered lease add for %1", + "DHCP6_REGISTERED_LEASE_UPDATE_FAIL", "error in registered lease update for %1: %2", "DHCP6_RELEASE_NA", "%1: binding for address %2 and iaid=%3 was released properly", "DHCP6_RELEASE_NA_DELETED", "%1: binding for address %2 and iaid=%3 was deleted on release", "DHCP6_RELEASE_NA_EXPIRED", "%1: binding for address %2 and iaid=%3 expired on release", diff --git a/src/bin/dhcp6/dhcp6_messages.h b/src/bin/dhcp6/dhcp6_messages.h index 127ebbf9f8..da7b114cc4 100644 --- a/src/bin/dhcp6/dhcp6_messages.h +++ b/src/bin/dhcp6/dhcp6_messages.h @@ -13,9 +13,9 @@ extern const isc::log::MessageID DHCP6_ADDITIONAL_CLASS_EVAL_ERROR; extern const isc::log::MessageID DHCP6_ADDITIONAL_CLASS_EVAL_RESULT; extern const isc::log::MessageID DHCP6_ADDITIONAL_CLASS_NO_TEST; extern const isc::log::MessageID DHCP6_ADDITIONAL_CLASS_UNDEFINED; +extern const isc::log::MessageID DHCP6_ADDR_REG_INFORM_CLIENT_CHANGE; +extern const isc::log::MessageID DHCP6_ADDR_REG_INFORM_FAIL; extern const isc::log::MessageID DHCP6_ADD_GLOBAL_STATUS_CODE; -extern const isc::log::MessageID DHCP6_ADD_REG_INFORM_CLIENT_CHANGE; -extern const isc::log::MessageID DHCP6_ADD_REG_INFORM_FAIL; extern const isc::log::MessageID DHCP6_ADD_STATUS_CODE_FOR_IA; extern const isc::log::MessageID DHCP6_ALREADY_RUNNING; extern const isc::log::MessageID DHCP6_BUFFER_RECEIVED; @@ -66,6 +66,7 @@ extern const isc::log::MessageID DHCP6_DYNAMIC_RECONFIGURATION; extern const isc::log::MessageID DHCP6_DYNAMIC_RECONFIGURATION_FAIL; extern const isc::log::MessageID DHCP6_DYNAMIC_RECONFIGURATION_SUCCESS; extern const isc::log::MessageID DHCP6_FLEX_ID; +extern const isc::log::MessageID DHCP6_HOOK_ADDR6_REGISTER_SKIP; extern const isc::log::MessageID DHCP6_HOOK_BUFFER_RCVD_DROP; extern const isc::log::MessageID DHCP6_HOOK_BUFFER_RCVD_SKIP; extern const isc::log::MessageID DHCP6_HOOK_BUFFER_SEND_SKIP; @@ -80,7 +81,6 @@ extern const isc::log::MessageID DHCP6_HOOK_LEASES6_PARKING_LOT_FULL; extern const isc::log::MessageID DHCP6_HOOK_PACKET_RCVD_SKIP; extern const isc::log::MessageID DHCP6_HOOK_PACKET_SEND_DROP; extern const isc::log::MessageID DHCP6_HOOK_PACKET_SEND_SKIP; -extern const isc::log::MessageID DHCP6_HOOK_REGISTER6_SKIP; extern const isc::log::MessageID DHCP6_HOOK_SUBNET6_SELECT_DROP; extern const isc::log::MessageID DHCP6_HOOK_SUBNET6_SELECT_PARK; extern const isc::log::MessageID DHCP6_HOOK_SUBNET6_SELECT_SKIP; @@ -141,6 +141,8 @@ extern const isc::log::MessageID DHCP6_QUERY_DATA; extern const isc::log::MessageID DHCP6_QUERY_LABEL; extern const isc::log::MessageID DHCP6_RAPID_COMMIT; extern const isc::log::MessageID DHCP6_RECLAIM_EXPIRED_LEASES_FAIL; +extern const isc::log::MessageID DHCP6_REGISTERED_LEASE_ADD_FAIL; +extern const isc::log::MessageID DHCP6_REGISTERED_LEASE_UPDATE_FAIL; extern const isc::log::MessageID DHCP6_RELEASE_NA; extern const isc::log::MessageID DHCP6_RELEASE_NA_DELETED; extern const isc::log::MessageID DHCP6_RELEASE_NA_EXPIRED; diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes index caa1fd16b6..aa362c709e 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -44,16 +44,6 @@ Status Code option. The first argument includes the client and the transaction identification information. The second argument includes the details of the status code. -% DHCP6_ADD_REG_INFORM_FAIL error on add-reg-inform from client %1: %2 -This information message is issued when the processing of an add-reg-inform -message failed. The address of the client, usually also the address to -register, and the description of the problem are printed. - -% DHCP6_ADD_REG_INFORM_CLIENT_CHANGE received an add-reg-inform for %1 from client '%2' but the address was registered by another client '%3' -This information message is issued when a lease for another client already -exists for an address being registered. The address, the new client and -previous client identifiers are printed. - % DHCP6_ADD_STATUS_CODE_FOR_IA %1: adding Status Code to IA with iaid=%2: %3 Logged at debug log level 50. This message is logged when the server is adding the Status Code @@ -61,6 +51,16 @@ option to an IA. The first argument includes the client and the transaction identification information. The second argument specifies the IAID. The third argument includes the details of the status code. +% DHCP6_ADDR_REG_INFORM_FAIL error on addr-reg-inform from client %1: %2 +This information message is issued when the processing of an addr-reg-inform +message failed. The address of the client, usually also the address to +register, and the description of the problem are printed. + +% DHCP6_ADDR_REG_INFORM_CLIENT_CHANGE received an addr-reg-inform for %1 from client '%2' but the address was registered by another client '%3' +This information message is issued when a lease for another client already +exists for an address being registered. The address, the new client and +previous client identifiers are printed. + % DHCP6_ALREADY_RUNNING %1 already running? %2 This is an error message that occurs when the DHCPv6 server encounters a pre-existing PID file which contains the PID of a running process. @@ -380,6 +380,13 @@ and the expression specified in its configuration generated (was evaluated to) an identifier for incoming packet. This debug message is mainly intended as a debugging assistance for flexible identifier. +% DHCP6_HOOK_ADDR6_REGISTER_SKIP %1: addr-reg-inform for %2 is dropped, because a callout set the next step to SKIP +Logged at debug log level 40. +This debug message is printed when a callout installed on the addr6_register +hook point sets the next step to SKIP. For this particular hook point, the +value setting instructs the server to cancel the address registration and +drop the packet. + % DHCP6_HOOK_BUFFER_RCVD_DROP received buffer from %1 to %2 over interface %3 was dropped because a callout set the drop flag Logged at debug log level 15. This debug message is printed when a callout installed on buffer6_receive @@ -494,13 +501,6 @@ not build the wire data (pack) because it was already done by the book. The argument specifies the client and transaction identification information. -% DHCP6_HOOK_REGISTER6_SKIP %1: add-reg-inform for %2 is dropped, because a callout set the next step to SKIP -Logged at debug log level 40. -This debug message is printed when a callout installed on the register6 -hook point sets the next step to SKIP. For this particular hook point, the -value setting instructs the server to cancel the address registration and -drop the packet. - % DHCP6_HOOK_SUBNET6_SELECT_DROP %1: packet was dropped because a callout set the drop flag Logged at debug log level 40. This debug message is printed when a callout installed on the @@ -921,6 +921,14 @@ specifies the client and transaction identification information. This error message indicates that the reclaim expired leases operation failed and provides the cause of failure. +% DHCP6_REGISTERED_LEASE_ADD_FAIL error in registered lease add for %1 +This error message indicates that the registered lease add failed and +provides the address being registered. + +% DHCP6_REGISTERED_LEASE_UPDATE_FAIL error in registered lease update for %1: %2 +This error message indicates that the registered lease update failed and +provides the registered address and the cause of failure. + % DHCP6_RELEASE_NA %1: binding for address %2 and iaid=%3 was released properly This informational message indicates that an address was released properly. It is a normal operation during client shutdown. The first argument includes diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 324579e5a1..47b0336f2f 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -145,7 +145,7 @@ struct Dhcp6Hooks { int hook_index_lease6_decline_; ///< index for "lease6_decline" hook point int hook_index_host6_identifier_; ///< index for "host6_identifier" hook point int hook_index_ddns6_update_; ///< index for "ddns6_update" hook point - int hook_index_register6_; ///< index for "register6" hook point + int hook_index_addr6_register_; ///< index for "addr6_register" hook point /// Constructor that registers hook points for DHCPv6 engine Dhcp6Hooks() { @@ -159,7 +159,7 @@ struct Dhcp6Hooks { hook_index_lease6_decline_ = HooksManager::registerHook("lease6_decline"); hook_index_host6_identifier_ = HooksManager::registerHook("host6_identifier"); hook_index_ddns6_update_ = HooksManager::registerHook("ddns6_update"); - hook_index_register6_ = HooksManager::registerHook("register6"); + hook_index_addr6_register_ = HooksManager::registerHook("addr6_register"); } }; @@ -1035,7 +1035,7 @@ Dhcpv6Srv::processDhcp6Query(Pkt6Ptr query) { (query->getType() == DHCPV6_REBIND) || (query->getType() == DHCPV6_RELEASE) || (query->getType() == DHCPV6_DECLINE) || - (query->getType() == DHCPV6_ADD_REG_INFORM))) { + (query->getType() == DHCPV6_ADDR_REG_INFORM))) { ContinuationPtr cont = makeContinuation(std::bind(&Dhcpv6Srv::processDhcp6QueryAndSendResponse, this, query)); @@ -1152,8 +1152,8 @@ Dhcpv6Srv::processLocalizedQuery6(AllocEngine::ClientContext6& ctx) { rsp = processInfRequest(ctx); break; - case DHCPV6_ADD_REG_INFORM: - rsp = processAddRegInform(ctx); + case DHCPV6_ADDR_REG_INFORM: + rsp = processAddrRegInform(ctx); break; default: @@ -1985,7 +1985,7 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt) { case DHCPV6_SOLICIT: case DHCPV6_REBIND: case DHCPV6_CONFIRM: - case DHCPV6_ADD_REG_INFORM: + case DHCPV6_ADDR_REG_INFORM: sanityCheck(pkt, MANDATORY, FORBIDDEN); return (true); @@ -4474,7 +4474,7 @@ Dhcpv6Srv::processDhcp4Query(const Pkt6Ptr& dhcp4_query) { } Pkt6Ptr -Dhcpv6Srv::processAddRegInform(AllocEngine::ClientContext6& ctx) { +Dhcpv6Srv::processAddrRegInform(AllocEngine::ClientContext6& ctx) { ConstSubnetPtr subnet = ctx.subnet_; // Silently ignore message which can't be localized @@ -4482,15 +4482,15 @@ Dhcpv6Srv::processAddRegInform(AllocEngine::ClientContext6& ctx) { return (Pkt6Ptr()); } - Pkt6Ptr add_reg_inf = ctx.query_; + Pkt6Ptr addr_reg_inf = ctx.query_; // Get the client source address. - IOAddress addr = add_reg_inf->getRemoteAddr(); + IOAddress addr = addr_reg_inf->getRemoteAddr(); // If there are some relays get the peer address of the closest relay // to the client. - size_t relay_level = add_reg_inf->relay_info_.size(); + size_t relay_level = addr_reg_inf->relay_info_.size(); if (relay_level > 0) { - addr = add_reg_inf->getRelay6LinkAddress(relay_level - 1); + addr = addr_reg_inf->getRelay6LinkAddress(relay_level - 1); } Option6IAPtr ia; @@ -4501,7 +4501,7 @@ Dhcpv6Srv::processAddRegInform(AllocEngine::ClientContext6& ctx) { try { // Get IA_NA from the Address registration inform. // There must be one. - OptionCollection ias = add_reg_inf->getOptions(D6O_IA_NA); + OptionCollection ias = addr_reg_inf->getOptions(D6O_IA_NA); if (ias.size() != 1) { isc_throw(RFCViolation, "Exactly 1 IA_NA option expected, but " << ias.size() << " received"); @@ -4557,35 +4557,29 @@ Dhcpv6Srv::processAddRegInform(AllocEngine::ClientContext6& ctx) { } } catch (const std::exception &ex) { // Incoming processing failed. - LOG_INFO(packet6_logger, DHCP6_ADD_REG_INFORM_FAIL) + LOG_INFO(packet6_logger, DHCP6_ADDR_REG_INFORM_FAIL) .arg(addr) .arg(ex.what()); return (Pkt6Ptr()); } - // Record if it is a registration renewal. - bool renewal = !!old_lease; - // Check if the client is the same. if (old_lease) { if (old_lease->duid_ && (*ctx.duid_ != *(old_lease->duid_))) { - LOG_INFO(packet6_logger, DHCP6_ADD_REG_INFORM_CLIENT_CHANGE) + LOG_INFO(packet6_logger, DHCP6_ADDR_REG_INFORM_CLIENT_CHANGE) .arg(addr) .arg(ctx.duid_->toText()) .arg(old_lease->duid_->toText()); } - if (old_lease->subnet_id_ != subnet->getID()) { - renewal = false; - } } // Build response. - Pkt6Ptr add_reg_rep(new Pkt6(DHCPV6_ADD_REG_REPLY, - add_reg_inf->getTransid())); - add_reg_rep->addOption(ia); + Pkt6Ptr addr_reg_rep(new Pkt6(DHCPV6_ADDR_REG_REPLY, + addr_reg_inf->getTransid())); + addr_reg_rep->addOption(ia); // Process FQDN. - processClientFqdn(add_reg_inf, add_reg_rep, ctx); + processClientFqdn(addr_reg_inf, addr_reg_rep, ctx); Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, ctx.duid_, ia->getIAID(), iaaddr->getPreferred(), @@ -4596,35 +4590,35 @@ Dhcpv6Srv::processAddRegInform(AllocEngine::ClientContext6& ctx) { lease->fqdn_rev_ = ctx.rev_dns_update_; lease->hostname_ = ctx.hostname_; - conditionallySetReservedClientClasses(add_reg_inf, ctx); + conditionallySetReservedClientClasses(addr_reg_inf, ctx); // Evaluate additional classes. - evaluateAdditionalClasses(add_reg_inf, ctx); + evaluateAdditionalClasses(addr_reg_inf, ctx); LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASSES_ASSIGNED) - .arg(add_reg_inf->getLabel()) - .arg(add_reg_inf->getName()) - .arg(add_reg_inf->getClasses().toText()); + .arg(addr_reg_inf->getLabel()) + .arg(addr_reg_inf->getName()) + .arg(addr_reg_inf->getClasses().toText()); - copyClientOptions(add_reg_inf, add_reg_rep); + copyClientOptions(addr_reg_inf, addr_reg_rep); CfgOptionList co_list; - buildCfgOptionList(add_reg_inf, ctx, co_list); + buildCfgOptionList(addr_reg_inf, ctx, co_list); // The RFC says to not do that... - appendDefaultOptions(add_reg_inf, add_reg_rep, co_list); - appendRequestedOptions(add_reg_inf, add_reg_rep, co_list); - appendRequestedVendorOptions(add_reg_inf, add_reg_rep, ctx, co_list); + appendDefaultOptions(addr_reg_inf, addr_reg_rep, co_list); + appendRequestedOptions(addr_reg_inf, addr_reg_rep, co_list); + appendRequestedVendorOptions(addr_reg_inf, addr_reg_rep, ctx, co_list); - // Handle the "register6" callout point. - if (HooksManager::calloutsPresent(Hooks.hook_index_register6_)) { - CalloutHandlePtr callout_handle = getCalloutHandle(add_reg_inf); + // Handle the "addr6_register" callout point. + if (HooksManager::calloutsPresent(Hooks.hook_index_addr6_register_)) { + CalloutHandlePtr callout_handle = getCalloutHandle(addr_reg_inf); ScopedCalloutHandleState callout_handle_state(callout_handle); // Pass the query6 argument. - ScopedEnableOptionsCopy query6_options_copy(add_reg_inf); - callout_handle->setArgument("query6", add_reg_inf); + ScopedEnableOptionsCopy query6_options_copy(addr_reg_inf); + callout_handle->setArgument("query6", addr_reg_inf); // Pass the response6 argument. - ScopedEnableOptionsCopy rsp6_options_copy(add_reg_rep); - callout_handle->setArgument("response6", add_reg_rep); + ScopedEnableOptionsCopy rsp6_options_copy(addr_reg_rep); + callout_handle->setArgument("response6", addr_reg_rep); // Pass the address6 argument. callout_handle->setArgument("address6", addr); @@ -4636,14 +4630,15 @@ Dhcpv6Srv::processAddRegInform(AllocEngine::ClientContext6& ctx) { callout_handle->setArgument("new_lease6", lease); // Call callouts - HooksManager::callCallouts(Hooks.hook_index_register6_, *callout_handle); + HooksManager::callCallouts(Hooks.hook_index_addr6_register_, *callout_handle); // Callouts decided to skip the next processing step. This means // cancel processing so drop. if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) || (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) { - LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_REGISTER6_SKIP) - .arg(add_reg_inf->getLabel()) + LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS, + DHCP6_HOOK_ADDR6_REGISTER_SKIP) + .arg(addr_reg_inf->getLabel()) .arg(addr); return (Pkt6Ptr()); } @@ -4661,37 +4656,61 @@ Dhcpv6Srv::processAddRegInform(AllocEngine::ClientContext6& ctx) { if (old_lease) { try { LeaseMgrFactory::instance().updateLease6(lease); - } catch (const std::exception&) { + } catch (const std::exception& ex) { // Assume that stats and DNS were handled by someone else. - return (add_reg_rep); + LOG_ERROR(dhcp6_logger, DHCP6_REGISTERED_LEASE_UPDATE_FAIL) + .arg(addr) + .arg(ex.what()); + return (Pkt6Ptr()); } // Save the old lease for the lease6_committed callout. ctx.currentIA().old_leases_.push_back(old_lease); - if (!renewal) { - // -1 on stats. - } else { - // Save the old lease for the DNS update. - ctx.currentIA().changed_leases_.push_back(old_lease); + // Save the old lease for the DNS update. + ctx.currentIA().changed_leases_.push_back(old_lease); + // Update stats when the subnet changed. + if (old_lease->subnet_id_ != lease->subnet_id_) { + StatsMgr::instance().addValue( + StatsMgr::generateName("subnet", old_lease->subnet_id_, + "registered-nas"), + static_cast(-1)); + StatsMgr::instance().addValue( + StatsMgr::generateName("subnet", lease->subnet_id_, + "registered-nas"), + static_cast(1)); + StatsMgr::instance().addValue( + StatsMgr::generateName("subnet", lease->subnet_id_, + "cumulative-registered-nas"), + static_cast(1)); } } else { if (!LeaseMgrFactory::instance().addLease(lease)) { // Assume that stats and DNS were handled by someone else. - return (add_reg_rep); + LOG_ERROR(dhcp6_logger, DHCP6_REGISTERED_LEASE_ADD_FAIL) + .arg(addr); + return (Pkt6Ptr()); } + // Update stats. + StatsMgr::instance().addValue( + StatsMgr::generateName("subnet", lease->subnet_id_, + "registered-nas"), + static_cast(1)); + StatsMgr::instance().addValue( + StatsMgr::generateName("subnet", lease->subnet_id_, + "cumulative-registered-nas"), + static_cast(1)); + StatsMgr::instance().addValue("cumulative-registered-nas", + static_cast(1)); } // Save the new lease for the lease6_committed callout. ctx.new_leases_.push_back(lease); - if (!renewal) { - // +1 on stats - } } // Deal with FQDN. - updateReservedFqdn(ctx, add_reg_rep); - generateFqdn(add_reg_rep, ctx); - createNameChangeRequests(add_reg_rep, ctx); + updateReservedFqdn(ctx, addr_reg_rep); + generateFqdn(addr_reg_rep, ctx); + createNameChangeRequests(addr_reg_rep, ctx); - return (add_reg_rep); + return (addr_reg_rep); } void Dhcpv6Srv::classifyByVendor(const Pkt6Ptr& pkt) { @@ -5158,12 +5177,12 @@ void Dhcpv6Srv::processStatsReceived(const Pkt6Ptr& query) { // Should not happen, but let's keep a counter for it stat_name = "pkt6-dhcpv4-response-received"; break; - case DHCPV6_ADD_REG_INFORM: - stat_name = "pkt6-add-reg-inform-received"; + case DHCPV6_ADDR_REG_INFORM: + stat_name = "pkt6-addr-reg-inform-received"; break; - case DHCPV6_ADD_REG_REPLY: + case DHCPV6_ADDR_REG_REPLY: // Should not happen, but let's keep a counter for it - stat_name = "pkt6-add-reg-reply-received"; + stat_name = "pkt6-addr-reg-reply-received"; break; default: ; // do nothing @@ -5188,8 +5207,8 @@ void Dhcpv6Srv::processStatsSent(const Pkt6Ptr& response) { case DHCPV6_DHCPV4_RESPONSE: stat_name = "pkt6-dhcpv4-response-sent"; break; - case DHCPV6_ADD_REG_REPLY: - stat_name = "pkt6-add-reg-reply-sent"; + case DHCPV6_ADDR_REG_REPLY: + stat_name = "pkt6-addr-reg-reply-sent"; break; default: // That should never happen diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 83ac85abec..9ab9af583e 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -480,12 +480,12 @@ protected: /// Does not throw void processDhcp4Query(const Pkt6Ptr& dhcp4_query); - /// @brief Processes incoming Add-reg-inform message. + /// @brief Processes incoming Addr-reg-inform message. /// /// @param ctx Reference to client context /// - /// @return Add-reg-reply message to be sent to the client. - Pkt6Ptr processAddRegInform(AllocEngine::ClientContext6& ctx); + /// @return Addr-reg-reply message to be sent to the client. + Pkt6Ptr processAddrRegInform(AllocEngine::ClientContext6& ctx); /// @brief Selects a subnet for a given client's packet. /// diff --git a/src/hooks/dhcp/lease_cmds/lease_cmds.cc b/src/hooks/dhcp/lease_cmds/lease_cmds.cc index 72f93761a8..9079f61c4d 100644 --- a/src/hooks/dhcp/lease_cmds/lease_cmds.cc +++ b/src/hooks/dhcp/lease_cmds/lease_cmds.cc @@ -560,7 +560,11 @@ LeaseCmdsImpl::updateStatsOnAdd(const Lease4Ptr& lease) { void LeaseCmdsImpl::updateStatsOnAdd(const Lease6Ptr& lease) { - if (!lease->stateExpiredReclaimed()) { + if (lease->stateRegistered()) { + StatsMgr::instance().addValue( + StatsMgr::generateName("subnet", lease->subnet_id_, "registered-nas"), + static_cast(1)); + } else if (!lease->stateExpiredReclaimed()) { StatsMgr::instance().addValue( StatsMgr::generateName("subnet", lease->subnet_id_, lease->type_ == Lease::TYPE_NA ? @@ -733,7 +737,19 @@ LeaseCmdsImpl::updateStatsOnUpdate(const Lease4Ptr& existing, void LeaseCmdsImpl::updateStatsOnUpdate(const Lease6Ptr& existing, const Lease6Ptr& lease) { - if (!existing->stateExpiredReclaimed()) { + // Does not cover registered <-> not registered transition. + if (existing->stateRegistered()) { + if (existing->subnet_id_ != lease->subnet_id_) { + StatsMgr::instance().addValue( + StatsMgr::generateName("subnet", existing->subnet_id_, + "registered-nas"), + static_cast(-1)); + StatsMgr::instance().addValue( + StatsMgr::generateName("subnet", lease->subnet_id_, + "registered-nas"), + static_cast(1)); + } + } else if (!existing->stateExpiredReclaimed()) { ConstSubnet6Ptr subnet; PoolPtr pool; @@ -910,7 +926,12 @@ LeaseCmdsImpl::updateStatsOnDelete(const Lease4Ptr& lease) { void LeaseCmdsImpl::updateStatsOnDelete(const Lease6Ptr& lease) { - if (!lease->stateExpiredReclaimed()) { + if (lease->stateRegistered()) { + StatsMgr::instance().addValue( + StatsMgr::generateName("subnet", lease->subnet_id_, + "registered-nas"), + static_cast(-1)); + } else if (!lease->stateExpiredReclaimed()) { StatsMgr::instance().addValue( StatsMgr::generateName("subnet", lease->subnet_id_, lease->type_ == Lease::TYPE_NA ? @@ -996,6 +1017,15 @@ LeaseCmdsImpl::addOrUpdate6(Lease6Ptr lease, bool force_create) { return (true); } if (existing) { + // Refuse used <-> registered transitions. + if (existing->stateRegistered() && !lease->stateRegistered()) { + isc_throw(BadValue, "illegal reuse of registered address " + << lease->addr_); + } else if (!existing->stateRegistered() && lease->stateRegistered()) { + isc_throw(BadValue, "address in use: " << lease->addr_ + << " can't be registered"); + } + // Update lease current expiration time with value received from the // database. Some database backends reject operations on the lease if // the current expiration time value does not match what is stored. @@ -2410,6 +2440,10 @@ LeaseCmdsImpl::lease6WipeHandler(CalloutHandle& handle) { StatsMgr::generateName("subnet", id, "declined-addresses"), static_cast(0)); + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", id, "registered-nas"), + static_cast(0)); + auto const& sub = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(id); if (sub) { for (auto const& pool : sub->getPools(Lease::TYPE_NA)) { @@ -2461,6 +2495,10 @@ LeaseCmdsImpl::lease6WipeHandler(CalloutHandle& handle) { StatsMgr::generateName("subnet", sub->getID(), "declined-addresses"), static_cast(0)); + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", sub->getID(), "registered-nas"), + static_cast(0)); + for (auto const& pool : sub->getPools(Lease::TYPE_NA)) { const std::string& name_anas(StatsMgr::generateName("subnet", sub->getID(), StatsMgr::generateName("pool", pool->getID(), diff --git a/src/hooks/dhcp/run_script/run_script.dox b/src/hooks/dhcp/run_script/run_script.dox index 63690b1136..8a91da0daf 100644 --- a/src/hooks/dhcp/run_script/run_script.dox +++ b/src/hooks/dhcp/run_script/run_script.dox @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2024 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2021-2025 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 @@ -89,8 +89,8 @@ point names. This library has several such functions: @ref lease4_renew, @ref lease4_expire, @ref lease4_recover, @ref leases4_committed, @ref lease4_release, @ref lease4_decline, @ref lease6_renew, @ref lease6_rebind, @ref lease6_expire, @ref lease6_recover, -@ref leases6_committed, @ref lease6_release, @ref lease6_decline located -in run_script_callouts.cc. +@ref leases6_committed, @ref lease6_release, @ref lease6_decline, +@ref addr6_register located in run_script_callouts.cc. Each hook point extracts the Kea internal data and exports it as string environment variables. These parameters are shared with the target script @@ -160,6 +160,10 @@ lease6_decline () { ... } +addr6_register() { + ... +} + case "$1" in "lease4_renew") lease4_renew @@ -200,6 +204,9 @@ case "$1" in "lease6_decline") lease6_decline ;; + "addr6_register") + addr6_register + ;; *) unknown_handle "${@}" ;; @@ -666,6 +673,52 @@ LEASE6_TYPE @endcode +addr6_register + +@code + +QUERY6_TYPE +QUERY6_TXID +QUERY6_LOCAL_ADDR +QUERY6_LOCAL_PORT +QUERY6_REMOTE_ADDR +QUERY6_REMOTE_PORT +QUERY6_IFACE_INDEX +QUERY6_IFACE_NAME +QUERY6_REMOTE_HWADDR +QUERY6_REMOTE_HWADDR_TYPE +QUERY6_PROTO +QUERY6_CLIENT_ID +ADDRESS6 +OLD_LEASE6_ADDRESS +OLD_LEASE6_CLTT +OLD_LEASE6_HOSTNAME +OLD_LEASE6_HWADDR +OLD_LEASE6_HWADDR_TYPE +OLD_LEASE6_STATE +OLD_LEASE6_SUBNET_ID +OLD_LEASE6_VALID_LIFETIME +OLD_LEASE6_DUID +OLD_LEASE6_IAID +OLD_LEASE6_PREFERRED_LIFETIME +OLD_LEASE6_PREFIX_LEN +OLD_LEASE6_TYPE +NEW_LEASE6_ADDRESS +NEW_LEASE6_CLTT +NEW_LEASE6_HOSTNAME +NEW_LEASE6_HWADDR +NEW_LEASE6_HWADDR_TYPE +NEW_LEASE6_STATE +NEW_LEASE6_SUBNET_ID +NEW_LEASE6_VALID_LIFETIME +NEW_LEASE6_DUID +NEW_LEASE6_IAID +NEW_LEASE6_PREFERRED_LIFETIME +NEW_LEASE6_PREFIX_LEN +NEW_LEASE6_TYPE + +@endcode + The leases4_committed hook point needs for loops to handle the list of addresses. This can be achived in the following way: diff --git a/src/hooks/dhcp/run_script/run_script_callouts.cc b/src/hooks/dhcp/run_script/run_script_callouts.cc index fc365f2140..9515735075 100644 --- a/src/hooks/dhcp/run_script/run_script_callouts.cc +++ b/src/hooks/dhcp/run_script/run_script_callouts.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2024 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2021-2025 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 @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -408,6 +409,36 @@ int lease6_decline(CalloutHandle& handle) { return (0); } +/// @brief handle @ref addr6_register hook and set environment parameters for +/// the script. +/// IN: query6 address6 old_lease6 new_lease6 +/// OUT: next_step +int addr6_register(CalloutHandle& handle) { + CalloutHandle::CalloutNextStep status = handle.getStatus(); + if (status == CalloutHandle::NEXT_STEP_DROP || + status == CalloutHandle::NEXT_STEP_SKIP) { + return (0); + } + ProcessEnvVars vars; + Pkt6Ptr pkt6; + handle.getArgument("query6", pkt6); + RunScriptImpl::extractPkt6(vars, pkt6, "QUERY6"); + IOAddress addr = IOAddress::IPV6_ZERO_ADDRESS(); + handle.getArgument("address6", addr); + RunScriptImpl::extractString(vars, addr.toText(), "ADDRESS6"); + Lease6Ptr lease6; + handle.getArgument("old_lease6", lease6); + if (lease6) { + RunScriptImpl::extractLease6(vars, lease6, "OLD_LEASE6"); + } + handle.getArgument("new_lease6", lease6); + RunScriptImpl::extractLease6(vars, lease6, "NEW_LEASE6"); + ProcessArgs args; + args.push_back("addr6_register"); + impl->runScript(args, vars); + return (0); +} + /// @brief This function is called to retrieve the multi-threading compatibility. /// /// @return 1 which means compatible with multi-threading. diff --git a/src/lib/dhcp/dhcp6.h b/src/lib/dhcp/dhcp6.h index 1109f38589..83dc427da7 100644 --- a/src/lib/dhcp/dhcp6.h +++ b/src/lib/dhcp/dhcp6.h @@ -246,8 +246,8 @@ enum DHCPv6MessageType { DHCPV6_DISCONNECT = 33, DHCPV6_STATE = 34, DHCPV6_CONTACT = 35, - DHCPV6_ADD_REG_INFORM = 36, - DHCPV6_ADD_REG_REPLY = 37, + DHCPV6_ADDR_REG_INFORM = 36, + DHCPV6_ADDR_REG_REPLY = 37, DHCPV6_TYPES_EOF }; diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc index 31549ec3a7..286a27405d 100644 --- a/src/lib/dhcp/pkt6.cc +++ b/src/lib/dhcp/pkt6.cc @@ -488,8 +488,8 @@ Pkt6::unpackUDP() { case DHCPV6_INFORMATION_REQUEST: case DHCPV6_DHCPV4_QUERY: case DHCPV6_DHCPV4_RESPONSE: - case DHCPV6_ADD_REG_INFORM: - case DHCPV6_ADD_REG_REPLY: + case DHCPV6_ADDR_REG_INFORM: + case DHCPV6_ADDR_REG_REPLY: default: // assume that unknown messages are not using relay format { return (unpackMsg(data_.begin(), data_.end())); @@ -801,8 +801,8 @@ Pkt6::getName(const uint8_t type) { static const char* SOLICIT = "SOLICIT"; static const char* DHCPV4_QUERY = "DHCPV4_QUERY"; static const char* DHCPV4_RESPONSE = "DHCPV4_RESPONSE"; - static const char* ADD_REG_INFORM = "ADD_REG_INFORM"; - static const char* ADD_REG_REPLY = "ADD_REG_REPLY"; + static const char* ADDR_REG_INFORM = "ADDR_REG_INFORM"; + static const char* ADDR_REG_REPLY = "ADDR_REG_REPLY"; static const char* UNKNOWN = "UNKNOWN"; switch (type) { @@ -863,11 +863,11 @@ Pkt6::getName(const uint8_t type) { case DHCPV6_DHCPV4_RESPONSE: return (DHCPV4_RESPONSE); - case DHCPV6_ADD_REG_INFORM: - return (ADD_REG_INFORM); + case DHCPV6_ADDR_REG_INFORM: + return (ADDR_REG_INFORM); - case DHCPV6_ADD_REG_REPLY: - return (ADD_REG_REPLY); + case DHCPV6_ADDR_REG_REPLY: + return (ADDR_REG_REPLY); default: ; diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc index 4af51a83f6..6013b7fa2a 100644 --- a/src/lib/dhcp/tests/pkt6_unittest.cc +++ b/src/lib/dhcp/tests/pkt6_unittest.cc @@ -682,12 +682,12 @@ TEST_F(Pkt6Test, getName) { EXPECT_STREQ("SOLICIT", Pkt6::getName(type)); break; - case DHCPV6_ADD_REG_INFORM: - EXPECT_STREQ("ADD_REG_INFORM", Pkt6::getName(type)); + case DHCPV6_ADDR_REG_INFORM: + EXPECT_STREQ("ADDR_REG_INFORM", Pkt6::getName(type)); break; - case DHCPV6_ADD_REG_REPLY: - EXPECT_STREQ("ADD_REG_REPLY", Pkt6::getName(type)); + case DHCPV6_ADDR_REG_REPLY: + EXPECT_STREQ("ADDR_REG_REPLY", Pkt6::getName(type)); break; default: diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index b783a2397e..ed5d80ced9 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2012-2025 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 @@ -904,7 +904,8 @@ AllocEngine::allocateBestMatch(ClientContext6& ctx, .arg(hint.toText()); } - } else if (usable_hint_lease->expired()) { + } else if (usable_hint_lease->expired() && + (usable_hint_lease->state_ != Lease::STATE_REGISTERED)) { // If the lease is expired, we may likely reuse it, but... ConstHostCollection hosts; @@ -1103,7 +1104,8 @@ AllocEngine::allocateBestMatch(ClientContext6& ctx, // Although the address was free just microseconds ago, it may have // been taken just now. If the lease insertion fails, we continue // allocation attempts. - } else if (existing->expired()) { + } else if (existing->expired() && + (existing->state_ != Lease::STATE_REGISTERED)) { // Make sure it's not reserved. if (!check_reservation_first && in_subnet && !out_of_pool) { auto hosts = getIPv6Resrv(subnet->getID(), candidate); @@ -1733,6 +1735,10 @@ AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx, isc_throw(BadValue, "Attempt to recycle lease that is still valid"); } + if (expired->state_ == Lease::STATE_REGISTERED) { + isc_throw(BadValue, "Attempt to recycle registered address"); + } + if (expired->type_ != Lease::TYPE_PD) { prefix_len = 128; // non-PD lease types must be always /128 } @@ -2903,6 +2909,12 @@ AllocEngine::reclaimExpiredLease(const Lease6Ptr& lease, // identifying information anymore. So we'll flag it for // removal unless the hook has set the skip flag. remove_lease = reclaimDeclined(lease); + } else if (lease->state_ == Lease::STATE_REGISTERED) { + if (reclaim_mode == DB_RECLAIM_LEAVE_UNCHANGED) { + isc_throw(Unexpected, "attempt to reuse a registered lease"); + } + // Remove (vs reclaim) expired registered leases. + remove_lease = true; } if (reclaim_mode != DB_RECLAIM_LEAVE_UNCHANGED) { @@ -2931,13 +2943,18 @@ AllocEngine::reclaimExpiredLease(const Lease6Ptr& lease, return; } - // Decrease number of assigned leases. - if (lease->type_ == Lease::TYPE_NA) { + // Decrease number of registered or assigned leases. + if (lease->state_ == Lease::STATE_REGISTERED) { + StatsMgr::instance().addValue(StatsMgr::generateName("subnet", + lease->subnet_id_, + "registered-nas"), + static_cast(-1)); + } else if (lease->type_ == Lease::TYPE_NA) { // IA_NA StatsMgr::instance().addValue(StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-nas"), - static_cast(-1)); + static_cast(-1)); auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_); if (subnet) { diff --git a/src/lib/dhcpsrv/cfg_subnets6.cc b/src/lib/dhcpsrv/cfg_subnets6.cc index 726139c85f..fc9f8dbfb5 100644 --- a/src/lib/dhcpsrv/cfg_subnets6.cc +++ b/src/lib/dhcpsrv/cfg_subnets6.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2024 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2014-2025 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 @@ -427,6 +427,12 @@ CfgSubnets6::removeStatistics() { stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, "reclaimed-leases")); + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "cumulative-registered-nas")); + + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "registered-nas")); + for (auto const& pool : subnet6->getPools(Lease::TYPE_NA)) { stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, StatsMgr::generateName("pool", pool->getID(), @@ -514,6 +520,13 @@ CfgSubnets6::updateStatistics() { stats_mgr.setValue(name_ia_pd_reuses, int64_t(0)); } + string const& name_registered(StatsMgr::generateName("subnet", subnet_id, + "cumulative-registered-nas")); + + if (!stats_mgr.getObservation(name_registered)) { + stats_mgr.setValue(name_registered, static_cast(0)); + } + for (auto const& pool : subnet6->getPools(Lease::TYPE_NA)) { const std::string& name_total_nas(StatsMgr::generateName("subnet", subnet_id, StatsMgr::generateName("pool", pool->getID(), diff --git a/src/lib/dhcpsrv/lease.cc b/src/lib/dhcpsrv/lease.cc index 905ee59c64..798173aee9 100644 --- a/src/lib/dhcpsrv/lease.cc +++ b/src/lib/dhcpsrv/lease.cc @@ -127,6 +127,11 @@ Lease::stateDeclined() const { return (state_ == STATE_DECLINED); } +bool +Lease::stateRegistered() const { + return (state_ == STATE_REGISTERED); +} + int64_t Lease::getExpirationTime() const { return (static_cast(cltt_) + valid_lft_); diff --git a/src/lib/dhcpsrv/lease.h b/src/lib/dhcpsrv/lease.h index f22ff0eeab..479f1c5e99 100644 --- a/src/lib/dhcpsrv/lease.h +++ b/src/lib/dhcpsrv/lease.h @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2024 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2025 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 @@ -215,6 +215,11 @@ struct Lease : public isc::data::UserContext, public isc::data::CfgToElement { /// @return true if the lease is in the "declined" state, false otherwise. bool stateDeclined() const; + /// @brief Indicates if the lease is in the "registered" state. + /// + /// @return true if the lease is in the "registered" state, false otherwise. + bool stateRegistered() const; + /// @brief Returns true if the other lease has equal FQDN data. /// /// The comparison of the hostname is case insensitive. diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc index bb34c1dd63..63284c47f9 100644 --- a/src/lib/dhcpsrv/lease_mgr.cc +++ b/src/lib/dhcpsrv/lease_mgr.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2012-2025 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 @@ -292,7 +292,8 @@ LeaseMgr::recountLeaseStats6() { // Zero out the global stats. // Cumulative counters ("reclaimed-declined-addresses", "reclaimed-leases", - // "cumulative-assigned-nas", "cumulative-assigned-pds") never get zeroed. + // "cumulative-assigned-nas", "cumulative-assigned-pds", + // "cumulative-registered-nas")) never get zeroed. int64_t zero = 0; stats_mgr.setValue("declined-addresses", zero); @@ -314,6 +315,11 @@ LeaseMgr::recountLeaseStats6() { stats_mgr.setValue("cumulative-assigned-pds", zero); } + // Create if it does not exit cumulative registered nas global stats. + if (!stats_mgr.getObservation("cumulative-registered-nas")) { + stats_mgr.setValue("cumulative-registered-nas", zero); + } + // Clear subnet level stats. This ensures we don't end up with corner // cases that leave stale values in place. const Subnet6Collection* subnets = @@ -351,6 +357,15 @@ LeaseMgr::recountLeaseStats6() { zero); } + if (!stats_mgr.getObservation( + StatsMgr::generateName("subnet", subnet_id, + "registered-nas"))) { + stats_mgr.setValue( + StatsMgr::generateName("subnet", subnet_id, + "registered-nas"), + zero); + } + for (auto const& pool : subnet->getPools(Lease::TYPE_NA)) { const std::string& name_anas(StatsMgr::generateName("subnet", subnet_id, StatsMgr::generateName("pool", pool->getID(), @@ -423,6 +438,11 @@ LeaseMgr::recountLeaseStats6() { stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_, "assigned-nas"), row.state_count_); + } else if (row.lease_state_ == Lease::STATE_REGISTERED) { + // Add to subnet level value + stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_, + "registered-nas"), + row.state_count_); } break; diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h index 57c91fbb36..c16d995a02 100644 --- a/src/lib/dhcpsrv/lease_mgr.h +++ b/src/lib/dhcpsrv/lease_mgr.h @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2012-2025 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 @@ -681,10 +681,12 @@ public: /// - assigned-nas /// - declined-addresses /// - assigned-pds + /// - registered /// global: /// - assigned-nas /// - declined-addresses /// - assigned-pds + /// - registered /// /// It invokes the virtual method, startLeaseStatsQuery6(), which /// returns an instance of an LeaseStatsQuery. The query contains