wiki:DhcpHooks

DHCP Hooks

0. Disclaimer

This page documents contains ideas about DHCP (v4 and v6) hooks that may be implemented. As of 2011-08, it is in early definition phase and significant changes (up to complete rewrite) may happen. Your comments and feedback is more than welcome.

1. Goal

The primary goal of hooks is the ability to customize Kea to specific environment. They will allow executing external code, ranging from simple logging scripts up to complex network management enterprise solutions.

2. Language interface

see Bind10HooksFramework

2.1 Data type definitions

See DhcpLibDesign

3. Basic Ideas

3.1 Callback Chain

It is envisaged that add-ons - in the form of libraries containing callback functions - could be supplied by many different authors. To allow these to inter-operate, each callback point does not define a single function; instead, it defines a potential list of functions that are called in sequence.

Each library includes one or more callback functions. If two libraries define a function for the same hook, the functions are chained: when the hook is called, the first function is executed and, depending on the returned status, the second is called. (The order in which the functions are called depends on the order the libraries are specified in the configuration.)

The implications of this are:

  • Functions should be able to control whether functions later in the call chain are called. To achieve this, each function returns an int status which is one of:
    • SUCCESS (= 0) - call succeeded, call the next function in the chain. If there isn't one, return to DHCP.
    • COMPLETE (= -1) - call succeeded, but return immediately to DHCP; don't call any more functions in the chain.
    • Other (positive value) - call failed, return immediately to DHCP, don't call other functions in the chain.
  • A function may modify the data passed to it. This updated data is passed to functions later in the call chain, and the aggregate result is passed back to the calling program.

3.2 Packet Context

Between the receipt of a incoming packet and the sending of the associated response, one or more callback hooks will be invoked. To allow information to be passed between associated functions, callback libraries may supply a function to create a "context" object. This object (actually, a void* pointer to library-allocated memory) is passed between hooks for the duration of processing. After processing for the packet is complete and the response sent, a library function is called to destroy the context.

The context is created on a per-library basis. If multiple libraries are specified, multiple contexts will be created on each call. Each library will be passed its own context structure.

3.3 Pre-Action and Post-Action Hooks

Where relevant, each point at which a significant action takes place has two hooks, a pre-action hook and a post-action hook.

The pre-action hook, as the name implies, is called before the program is about to take some action. Typically a failure status returned from the hook would cause the program not to take the intended action.

The post-action hook is called after the action has been taken. A typical use for a post-action hook is to record that the action has been taken.

Note: the post-action hook is not called if the action fails. If the pre-action hook allocates any resources that must be released after the action has taken place, it can will have the chance to do so when the context destruction hook is called at the end of packet processing.

4. Hook definitions

4.1 DHCPv6

Hook point name: pkt6_receive

Arguments:

  • name: pkt6, type: Pkt6Ptr, direction: [in/out]

Description: this callout is executed when incoming DHCPv6 packet is received and its content is parsed. The sole argument - pkt6 - contains a pointer to Pkt6 class that contains all informations regarding incoming packet, including its source and destination addresses, interface over which it was received, list of all options present within and relay information. See Pkt6 class definition for details. All fields of the Pkt6 class can be modified at this time, except data_ (which contains incoming packet as raw buffer, but that information was already parsed, so it doesn't make sense to modify it at this time). If any callout sets skip flag, the server will drop the packet and will not do anything with it, except logging a drop reason as a callout action.

Hook point name: subnet6_select

Arguments:

  • name: pkt6, type: Pkt6Ptr, direction [in/out]
  • name: subnet6, type: Subnet6Ptr, direction [in/out]
  • name: subnet6collection, type: const Subnet6Collection&, direction [in]

Description: this callout is executed when a subnet is being selected for incoming packet. All parameters, addresses and prefixes will be assigned from that subnet. Callout can select a different subnet if it wishes so. The list of all subnets currently configured is provided as 'subnet6collection'. The list itself must not be modified. If any callout installed on 'subnet6_select' sets a flag skip, then the server will not select any subnet. Packet processing will continue, but it will be severely limited (i.e. only global options will be assigned).

Hook point name: lease6_select

Arguments:

  • name: subnet6, type: Subnet6Ptr, direction: [in]
  • name: fake_allocation, type: bool, direction: [in]
  • name: lease6, type: Lease6Ptr, direction: [in/out]

Description: this callout is executed after the server engine has selected a lease for client's request, and before the lease has been inserted into the database. Any modifications made to the Lease6 object will be directly inserted into database. Make sure that any modifications the callout does are sanity checked as server will use that data as is. Server processes lease requests for SOLICIT and REQUEST in a very similar way. The only major difference is that for SOLICIT the lease is just selected to be offered, but it is not inserted into database. You can distinguish between SOLICIT and REQUEST by checking value of fake_allocation flag: true means that the lease won't be interested into database (SOLICIT), false means that it will (REQUEST).

Hook point name: pkt6_send

Arguments:

  • name: pkt6, type: Pkt6Ptr, direction: [in/out]

Description: this callout is executed when server's response DHCP is about to be send back to clients. The sole argument - pkt6 - contains a pointer to Pkt6 class that contains packet, with set source and destination addresses, interface over which it will be send, list of all options and relay information. See Pkt6 class definition for details. All fields of the Pkt6 class can be modified at this time, except bufferOut_ (packet will be constructed there after pkt6_send callouts are complete), so any changes to that field will be overwritten. If any callout sets skip flag, the server will drop the packet and will not do anything with it, except logging a drop reason as a callout action.


The text below contains previous proposal. It will be converted to the new Bind10HooksFramework format as the respective hook points are implemented.

Context Allocation and Deallocation

void* context_create()
Called when a packet has been received (before the call to buffer_rcvd), this gives the library a chance to allocate context for the processing.

void context_destroy(void* context)
Called when a packet has been sent (after buffer_send), this gives the library a chance to deallocate context allocated by the call to context_create. This function is also called whenever processing of a packet is terminated for whatever reason (e.g. a callback function returns an error code).

Packet Reception Callbacks

int buffer_rcvd(void* context, Pkt6& pkt)
Called when packet is received on wire. Packet is not parsed yet. msg_type_, transid_, options_ and relays_ fields are not set yet. Message modification is not recommended at this phase. This callback is executed immediately after packet is received. Return values are:

  • SUCCESS or COMPLETE - continue packet handling
  • Other - drop the packet and abort any further processing (except logging).

int pkt_rcvd(void* context, Pkt6& pkt)
Called when received packet is parsed. msg_type_, transid_, options_ and relays_ fields are set. This is recommended callback for introducing any modifications. Return values are:

  • SUCCESS or COMPLETE - continue packet handling
  • Other - drop the packet and abort any further processing (except logging).

Packet Processing Callbacks

int ia_lease_assign_pre(void* context, Pkt6& query, Pkt6& reply, OptionIA_NA& hint, OptionIA_NA& assigned)
Called when non-temporary lease is about to be assigned. context is the context created in the call to context_create, and will be NULL if no context was created. query contains a pointer to the whole client SOLICIT (with rapid-commit) or REQUEST message. reply contains a pointer to ADVERTISE or REPLY message to be sent back. hint is a IA_NA sent by client (it may contain addresses - hints that client sent). assigned is an IA_NA that is about to be assigned. It contains addresses to be assigned. Note: DHCPv6 protocol allows including more than one address in IA_NA (there are implementations that leverage that), so both hint and assigned may contain more than one address. They will usually contain only one address, though. The callback may modify contents of the assigned structure if necessary (e.g. to assign a different address or modify assigned address parameters). Return value:

  • SUCCESS or COMPLETE - continue packet handling and assign addresses.
  • Other - do not assign addresses

void ia_lease_assign_post(void* context, const Pkt6& query, const Pkt6& reply, const OptionIA_NA& hint, const OptionIA_NA& assigned)
Called when a non-temporary lease has been assigned. Parameters are the same as those in the call to ia_lease_assign_pre although they may not be modified (except for the context).

int ta_lease_assign_pre(void* context, Pkt6& query, Pkt6& reply, OptionIA_TA& hint, OptionIA_TA& assigned)
Called when temporary lease is about to be assigned. context is the context created in the call to context_create, and will be NULL if no context was created. query contains a pointer to whole client SOLICIT (with rapid-commit) or REQUEST message. reply contains a pointer to ADVERTISE or REPLY message to be sent back. hint is a IA_TA sent by client (it may contain addresses - hints that client sent). assigned is an IA_TA that is about to be assigned. It contains addresses to be assigned. Note: DHCPv6 protocol allows including more than one address in IA_TA (there are implementations that leverage that), so both hint and assigned may contain more than one address. They will usually contain only one address, though. Callback may modify contents of the assigned structure if necessary (e.g. to assign a different address or modify assigned address parameters). Return value:

  • SUCCESS or COMPLETE - continue packet handling and assign addresses.
  • Other - do not assign addresses

void ta_lease_assign_post(void* context, const Pkt6& query, const Pkt6& reply, const OptionIA_TA& hint, const OptionIA_TA& assigned)
Called when a temporary lease has been assigned. Parameters are the same as those in the call to ta_lease_assign_pre although they may not be modified (except for the context).

int pd_lease_assign_pre(void* context, Pkt6& query, Pkt6& reply, OptionIA_PD& hint, OptionIA_PD& assigned)
Called when prefix is about to be assigned. context is the context created in the call to context_create, and will be NULL if no context was created. query contains a pointer to whole client SOLICIT (with rapid-commit) or REQUEST message. reply contains a pointer to ADVERTISE or REPLY message to be sent back. hint is a IA_PD sent by client (it may contain addresses - hints that client sent). assigned is an IA_TA that is about to be assigned. It contains addresses to be assigned. Note: DHCPv6 protocol allows including more than one address in IA_PD (there are implementations that leverage that), so both hint and assigned may contain more than one prefix. They will usually contain only one prefix, though. Callback may modify contents of the assigned structure if necessary (e.g. to assign a different prefix or modify assigned prefix parameters). Return value:

  • SUCCESS or COMPLETE - continue packet handling and assign prefix(es).
  • Other - do not assign prefix(es).

void pd_lease_assign_post(void* context, const Pkt6& query, const Pkt6& reply, const OptionIA_PD& hint, const OptionIA_PD& assigned)
Called when a prefix has been assigned. Parameters are the same as those in the call to pd_lease_assign_pre although they may not be modified (except for the context).

int ia_lease_renew_pre(void* context, Pkt6& renew, Pkt6& reply, int iaid, Addr6[]& hint, Addr6[]& assigned)
context is the context created in the call to context_create, and will be NULL if no context was created. renew is a pointer to client's message. May be SOLICIT (after rebooting client that still has a lease), REQUEST, RENEW, REBIND or CONFIRM. Return value:

  • SUCCESS or COMPLETE - continue packet handling and renew lease.
  • Other - reject lease renewal.

void ia_lease_renew_post(void* context, const Pkt6& renew, const Pkt6& reply, int iaid, const Addr6[]& hint, const Addr6[]& assigned)
Called when a non-temporary lease has been renewed. Parameters are the same as those in the call to ia_lease_renew_pre although they may not be modified (except for the context).

int ta_lease_renew_pre(void* context, Pkt6& renew, Pkt6& reply, int iaid, Addr6[]& hint, Addr6[]& assigned)
context is the context created in the call to context_create, and will be NULL if no context was created. renew is a pointer to client's message. May be SOLICIT (after rebooting client that still has a lease), REQUEST, RENEW, REBIND or CONFIRM. Return value:

  • SUCCESS or COMPLETE - continue packet handling and renew lease.
  • Other - reject lease renewal.

void ta_lease_renew_post(void* context, const Pkt6& renew, const Pkt6& reply, int iaid, const Addr6[]& hint, const Addr6[]& assigned)
Called when a non-temporary lease has been renewed. Parameters are the same as those in the call to ta_lease_renew_pre although they may not be modified (except for the context).

int pd_lease_renew_pre(void* context, Pkt6& renew, Pkt6& reply, int iaid, Addr6[]& hint, Addr6[]& assigned)
context is the context created in the call to context_create, and will be NULL if no context was created. renew is a pointer to client's message. May be SOLICIT (after rebooting client that still has a lease), REQUEST, RENEW, REBIND or CONFIRM. Return value:

  • SUCCESS or COMPLETE - continue packet handling and renew lease.
  • Other - reject lease renewal.

void pd_lease_renew_post(void* context, const Pkt6& renew, const Pkt6& reply, int iaid, const Addr6[]& hint, const Addr6[]& assigned)
Called when a prefix has been renewed. Parameters are the same as those in the call to pd_lease_renew_pre although they may not be modified (except for the context).

int ia_lease_release_pre(void* context, Pkt6& release, Pkt6& reply, Addr6& release)
Called when a non-temporary lease (given by release) is about to be released. Return value:

  • SUCCESS or COMPLETE - continue packet handling and release lease.
  • Other - do not release the lease.

void ia_lease_release_post(void* context, const Pkt6& release, const Pkt6& reply, const Addr6& release)
Called when a non-temporary lease has been released. Parameters are the same as those in the call to ia_lease_release_pre although they may not be modified (except for the context).

int ta_lease_release_pre(void* context, Pkt6& release, Pkt6& reply, Addr6& release)
Called when a temporary lease (given by release) is about to be released. Return value:

  • SUCCESS or COMPLETE - continue packet handling and release lease.
  • Other - do not release the lease.

void ta_lease_release_post(void* context, const Pkt6& release, const Pkt6& reply, const Addr6& release)
Called when a temporary lease has been released. Parameters are the same as those in the call to ta_lease_release_pre although they may not be modified (except for the context).

int pd_lease_release_pre(void* context, Pkt6& release, Pkt6& reply, Addr6& release)
Called when a prefix (given by release) is about to be released. Return value:

  • SUCCESS or COMPLETE - continue packet handling and release prefix.
  • Other - do not release the prefix.

void pd_lease_release_post(void* context, const Pkt6& release, const Pkt6& reply, const Addr6& release)
Called when a tprefix has been released. Parameters are the same as those in the call to pd_lease_release_pre although they may not be modified (except for the context).

Transmission Phase Callbacks

int pkt_send(void* context, Pkt6& pkt)
Called when packet is almost ready to be sent, before binary form is prepared. This is the last chance to modify options. All options are set. Callback may modify options_, relays_, msg_type_ and transid_ fields. Callback may not modify binary form as it is not yet available. That is the penultimate callback before packet is sent. Return values:

  • SUCCESS/COMPLETE - continue building packet
  • Other - drop the packet, abort any further processing (except logging)

int buffer_send(void* context, Pkt6& pkt)
Called when packet is built and is available as a byte buffer only. Options_, relays_, msg_type_ and transid_ fields cannot be modified any more. Raw content of the packet may be modified (e.g. to update checksums). That is the ultimate callback before packet is sent. Return values:

  • SUCCESS/COMPLETE - transmit packet
  • Other - drop the packet

Lease Expiration Callbacks

Following callbacks are not related to any transmission, but are time triggered:

void ia_lease_expired(const Addr6& expired, const string& fqdn)
Called when a non-temporary lease has expired.

void ta_lease_expired(const Addr6& expired, const string& fqdn)
Called when a temporary lease has expired.

void pd_lease_expired(const Addr6& expired, const string& fqdn)
Called when a prefix has expired.

In all cases, "expired" is the lease/prefix that has expired, and fqdn the DNS fully-qualified domain name associated with it.

Notes

For {ia|ta|pd}_lease_assign_pre() callbacks, the developer is expected to find appropriate IA/TA/PD and modify its contents if different values should be assigned. Although it may be tempting to just have expected address/prefix returned as a value, that would be too great simplification. There are many other parameters that may be adjusted, e.g. prefered/valid lifetimes, T1, T2 timer, prefix length, possible suboptions like PD_EXCLUDE etc.

TODO: Need DNS Update related callbacks. FQDN must be passed in some usable way to existing callbacks as well.

NOTE: Failover callbacks are not defined at this time. They won't be until v6 failover design is defined.

NOTE: For relayed messages, buffer_rcvd is called. All following calls use encapsulated message with relays_[] array set. For example, when RELAY-FORW(REQUEST) message is received, following series of calls will be made:

  1. context_create()
  2. buffer_rcvd(RELAY-FORW)
  3. pkt_rcvd(REQUEST with relay_[0] set)
  4. ia_lease_assign_pre(REQUEST with relay_[0] set)
  5. ia_lease_assign_post(const REQUEST with relay_[0] set)
  6. pkt_send(REPLY with relay_[0] set)
  7. buffer_send(RELAY-REPL)
  8. context_destroy()

4.2 DHCPv4

Note: This section is work in progress and has not been reviewed or approved yet.

For DHCPv4 we shall use the same basic plan as we use in DHCPv6, as a packet is received a call is made to create a context and then a series of calls as the packet is processed until finally processing completes and the context is freed.

Currently there is a major difference in the style of the hooks - instead of having separate hooks in similar places for different items (for example for temporary vs non-temporary addresses) there will be fewer DHCPv4 hooks with the expectation that the routine will examine the proper data to determine the appropriate processing for that hook.

Context Allocation and Deallocation

void* context_create()
Called when a packet has been received (before the call to buffer_rcvd), this gives the library a chance to allocate context for the processing.

void context_destroy(void* context)
Called when a packet has been sent (after buffer_send), this gives the library a chance to deallocate context allocated by the call to context_create. This function is also called whenever processing of a packet is terminated for whatever reason (e.g. a callback function returns an error code).

Packet Reception Callbacks

int buffer_rcvd(void* context, Pkt4& pkt)
Called when packet is received on wire. Packet is not parsed yet. msg_type_, transid_, options_ and relays_ fields are not set yet. Message modification is not recommended at this phase. This callback is executed immediately after packet is received. Return values are:

  • SUCCESS or COMPLETE - continue packet handling
  • Other - drop the packet and abort any further processing (except logging).

int pkt_rcvd(void* context, Pkt4& pkt)
Called when received packet is parsed. msg_type_, transid_, options_ and relays_ fields are set. This is recommended callback for introducing any modifications. Return values are:

  • SUCCESS or COMPLETE - continue packet handling
  • Other - drop the packet and abort any further processing (except logging).

Packet Processing Callbacks

A DHCPv4 server can process the following packets: discover, request, inform, query, bulk query, release and decline. It must also handle processing leases that expire as part of DHCP. Within these paths we have the following major events: address assignment, options assignment and writing the lease to storage (or expiring it). Each code path may include only a subset of the events.

This proposal calls for defining hooks for the three events and requiring the invoked code to examine the packet to determine the packet type.

int v4_address_assign_pre(void* context, Pkt4& query, Pkt4& reply)
This hook is called when the server has selected an address and is preparing to assign it to a client. context is the context created in the call to context_create, and will be NULL if no context was created. query contains a pointer to the whole client DISCOVER or REQUEST message. reply contains a pointer to the OFFER or ACKNOWLEDGEMENT message to be sent back. The callback may modify the address information in the packet structure if desired.

Return value:

  • SUCCESS or COMPLETE - continue packet handling and assign addresses.
  • Other - do not assign addresses

TBD: I prefer leaving the address information in the packets and providing a library routine for the hook to use to get or set the address or do we want to go with the v6 style and include the address information explicitly: Do we also need to include information about fixed vs dynamic vs reserved? What should we do if the callback changes the address? Should the server attempt to change the lease it is updating or should we simply leave any lease database maintenance to the user? If we do not find an appropriate lease should we still call the callback? One potential method of lease assignment I could imagine a user using is to provide the DHCP server itself with no valid addresses and instead do the address assignment and lease database via the callback routines.

void v4_address_assign_post(void* context, const Pkt4& query, const Pkt4& reply)
This hook is called when the address assignment step is complete. At this point the server and the callbacks agree on the address to send to the client but the server needs to do more work before sending a response to the client. The callback function is not allowed to modify "query" or "reply" but can update "context".

int v4_options_assign_pre(void* context, Pkt4& query, Pkt4& reply)
This hook is called when the server has selected the options to send to a client. context is the context created in the call to context_create, and will be NULL if no context was created. query contains a pointer to the whole client DISCOVER, REQUEST, INFORM, QUERY or BULK QUERY message. reply contains a pointer to the response message to be sent back. The callback may modify the option information in the packet structure if desired.

In the case of a BULK query this callback may be invoked multiple times as the server walks through the desired information.

Return value:

  • SUCCESS or COMPLETE - continue packet handling and assign addresses.
  • Other - do not assign addresses

TBD:

void v4_options_assign_post(void* context, const Pkt4& query, const Pkt4& reply)
This hook is called when the option assignment step is complete. At this point the server and the callbacks agree on the options to send to the client but the server needs to do more work before sending a response to the client. The callback function is not allowed to modify "query" or "reply" but can update "context".

int v4_lease_write_pre(void* context, Pkt4& query, Pkt4& reply)
This hook is called when the server is about to write the lease information to the database. context is the context created in the call to context_create, and will be NULL if no context was created. query contains a pointer to the whole client DISCOVER, REQUEST, RELEASE or DECLINE message. reply contains a pointer to the response message to be sent back if one exists or NULL otherwise. The callback may modify the information in the "reply" structure if desired.

Return value:

  • SUCCESS or COMPLETE - continue packet handling and assign addresses.
  • Other - do not assign addresses

TBD: This function is primarily for the use of the RELEASE and DECLINE packet processing. I've included it int eh DISCOVER and REQUEST processing for completeness but it is not actually necessary.

void v4_Lease_write_post(void* context, const Pkt4& query, const Pkt4& reply)
This hook is called when the lease writing step is complete. At this point the server and the callbacks agree on the information to be written and to send to the client but the server needs to do more work before sending a response to the client. The callback function is not allowed to modify "query" or "reply" but can update "context".

int v4_expire_pre(void* context, TBD)
This hook is called when the server is about to expire a lease. context is the context created in the call to context_create, and will be NULL if no context was created.

Return value:

  • SUCCESS or COMPLETE - continue packet handling and assign addresses.
  • Other - do not assign addresses

TBD: What arguments should we include here? The v6 call back use address and fqdn or we could use some sort of pointer to the lease structure itself.

void v4_expire_post(void* context, TBD)
This hook is called when the expiration step is complete. The callback function is not allowed to modify TBD but can update "context".

Transmission Phase Callbacks

int pkt_send(void* context, Pkt4& pkt)
Called when packet is almost ready to be sent, before binary form is prepared. This is the last chance to modify options. All options are set. Callback may modify options_, relays_, msg_type_ and transid_ fields. Callback may not modify binary form as it is not yet available. That is the penultimate callback before packet is sent. Return values:

  • SUCCESS/COMPLETE - continue building packet
  • Other - drop the packet, abort any further processing (except logging)

int buffer_send(void* context, Pkt4& pkt)
Called when packet is built and is available as a byte buffer only. Options_, relays_, msg_type_ and transid_ fields cannot be modified any more. Raw content of the packet may be modified (e.g. to update checksums). That is the ultimate callback before packet is sent. Return values:

  • SUCCESS/COMPLETE - transmit packet
  • Other - drop the packet

5. Open questions

  1. Options structure and options collection storage - for performance reasons (access complexity O(1)) it would be better to store options in associative array of some kind (e.g. a hash). That is not perfect, as more than one option of the same type may be present in DHCPv6 (e.g. several IA_NA options). Hash table would be somewhat difficult to export to external scripts. Benefits of having hash table would be negated if it has to be converted to plain vector every time external script is called. The alternative approach is to have options stored in an array (e.g. vector or list). This is worse from performance perspective (access complexity O(n)), but has benefit of being easily exported to external scripts. Tomek: it is very unlikely that CPU will ever be a performance bottleneck in DHCP, so we probably should choose simple array (vector, deque or list). Although O(1) algorithms are in theory better than O(n), but in DHCP number of options rarely extends beyond 10, so it is not a big deal. See DhcpLibDesign
  2. {ia|ta|pd}_lease_assigned could return a status code (defined in RFC3315), instead of just a boolean. That would be easier to explain why address was not assigned. On the other hand, STATUS_CODE option has 2 fields: status code and text field that explains reasons, so callback would have to return a tuple (or a structure).
  3. Do we need extra set of calls for ADVERTISE?
  4. Should the order of calling the libraries be configurable on a per-hook basis? For example, we might need a plugin always needing to be on the "edge" - so on hookpoints for the ingress of the packet it should be the first plugin to run, but on hookpoints for the packet's egress it should be the last one to run.
  5. Hooks should be able to control allocation and classification, so we need some way of accessing structures representing pools, classes etc.
  6. We need to add pool selection hook.
Last modified 4 years ago Last modified on Jun 21, 2013, 3:02:41 PM