wiki:DhcpOptionDefinition

Defining New Options in BIND 10 DHCP

Introduction

The current version of ISC DHCP provides the capability to define new options without writing additional C code (referred to as user-defined options in this document). These are defined in the configuration database and are read when the DHCP server (or client) starts up. When interpreting a received packet, the definition allows the server to interpret the user-defined options and take action based on them. If a user-defined option is included in the server configuration, the definition allows the information to be correctly encoded in the packet sent by the server.

A similar capability will exist in BIND 10 DHCP. This document describes how the options will be configured, and how the options will be interpreted with the code: it is based on the Option definition Requirements document.

Note: the current version of the document only applies to the DHCP server. A similar document will be written for the client closer to the time that component is scheduled for implementation.

Basic Ideas

DHCP options will be configured using the standard BIND 10 configuration mechanism. This is a database of configuration parameters accessed through the bindctl program. Each process in BIND 10 has a set of configuration parameters associated with it, these parameters residing in part of the database namespace rooted at the name of the program. (For example, parameters associated with the "xfrin" process have full names starting Xfrin, e.g. Xfrin/zones[0]/name.) The relevant process names for BIND 10 DHCP are Dhcp4 and Dhcp6, and the option definitions reside below those names.

Each configuration parameter has a data type based on those available in JSON. In particular, data values can be one of:

  • String
  • Integer (of varying sizes)
  • Boolean
  • List
  • Map

A list is an ordered list of elements (of any type). A map is effectively an associative array: each element of the map has a name (a string) and a value (which may be of any type). This combination of data types can allow for quite complex nesting, e.g. a list of maps, each element of the map being itself a list of maps.

DHCP Option Definition

User Configuration

Introduction

The option definition capability is similar to that in current version of ISC DHCP. This is deliberate because (a) the functionality required is similar and (b) retaining similarity to the ISC DHCP definitions allows a simpler porting of existing configurations to BIND 10 DHCP.

The main difference between the options for IPv4 and IPv6 is the field length for option codes and option lengths: one byte for IPv4 and two bytes for IPv6. Other than that, the concepts are sufficiently similar to allow for identical syntax in option definition, although the interpretation of vendor-defined options is slightly different. The following sections outline a uniform way of defining both types of user options.

Basic Definition

The definition of the set of user-defined options is a list of maps, with each map defining the characteristics of one option. Following list defines length of the options *data*. Note that each option also contains option header, consisting of option type and length fields. That header takes two or four additional bytes. For example int8 data sent as DHCPv6 option will require 5 bytes (4 bytes header + 1 byte for the actual data). Each map has the following elements:

  • name: this is the name of the option, and is a string. It must not include a period character.
  • code: the code of the option. For IPv4 site-local options, this should be an integer between 224 and 254 (see RFC 3942). For IPv6 options, it can be any value between 0 and 65,535, although IANA-defined option codes should not be used.
  • type: the type of the option. This is a string and is one of:
    • "empty": empty option. That indicates that the option does not contain any data. DHCPv6 defines several options (see Rapid-commit RFC3315, Section 22.14 for example) that convey the information by its presence (or lack of thereof).
    • "boolean": a boolean value. When specified within the server configuration, it takes a value of true or false. It occupies a single byte on the wire, taking a value of 0 for false and 1 for true.
    • "int8": a signed 8-bit integer. When specified within the server configuration, it is a number between -128 and 127. It occupies a single byte on the wire.
    • "int16": a signed 16-bit integer. When specified within the server configuration, it is a number between -32,768 and 32,767. It occupies two bytes on the wire.
    • "int32": a signed 32-bit integer. When specified within the server configuration, it is a number between -2,147,483,648 and 2,147,483,647. It occupies four bytes on the wire..
    • "uint8": an unsigned 8-bit integer. When specified within the server configuration, it is a number between 0 and 255. It occupies a single byte on the wire.
    • "uint16": an unsigned 16-bit integer. When specified within the server configuration, it is a number between 0 and 65,535. It occupies a two bytes on the wire.
    • "uint32": an unsigned 32-bit integer. When specified within the server configuration, it is a number between 0 and 4,294,967,295. It occupies a four bytes on the wire.
    • "ip4-address": an IPv4 address. When specified in the server configuration, it is a string representing the IPv4 address in dotted-decimal format (e.g. "192.0.2.16"). It occupies a four bytes on the wire.
    • "ip6-address": an IPv6 address. When specified in the server configuration, it is any string representing a valid IPv6 address (e.g. "::", "2001:db8::" etc.). On the wire it occupies 16 bytes.
    • "string": the value of the option is a sequence of bytes. When specified in the server configuration it is specified as any other text string, with non-printing characters specified using standard "C" escape codes (e.g. \xff). On the wire it is a sequence of bytes. Note that the wire format of the string does not have a trailing null character, the length of the string being determined by the length field of the option. (For this reason, it is not possible to have an option that includes multiple text strings, or in a record (see below) containing a string in in any place other than as the last element. Only with these restrictions can the length of the text string be calculated.)
    • "fqdn": When specified in the server configuration, it is a string representing a domain name. It is a collection of one or more labels (each up to 63 bytes long) separated with periods. This data will be encoded on wire as specified in RFC3315, Section 8. Each label begins with one byte long length field followed by specified number of bytes. Each domain name ends with empty label (i.e. a single length field with value of 0). Completely encoded domain name must not exceed length of 255 bytes. fqdn can be used in arrays.
    • "record": the option is a list of differing data types. The number and type of values are defined in the "record_types" list element of the definition.
  • array: a boolean flag stating whether the option is a single value or an array. (This is optional in the specification of the option and defaults to false if not specified.) The number of elements in the array is determined by the total option length. As noted above, arrays of strings are not supported.
  • record_types: only valid if the type is "record" and ignored otherwise, this comprises a list of valid data types. Note that "string" can only appear in the list once, and then as the last element. Record types are discussed further below.
  • vendor: this is an optional element and is a string determining the vendor space to which this option belongs. This is discussed further below.
  • suboptions: a type that specifies that suboptions will appear in this option. It requires one parameter of a custom defined space. Options defined from that space may appear here. suboptions type may not appear in arrays.

TBD: Array of booleans does not make much sense as each value (single bit) is coded on whole byte. I can't speak for DHCPv4, but in DHCPv6 there are no options that use just a single bit. If that is

The data for each defined option can be set by the user and it is held in the different data structure. This data structure groups the elements by subnet. The same option may appear in this structure multiple times, one time for each subnet. Within the subnet the data for the particular option has the following elements:

  • name: this is the name of the option, and is a string.
  • code: this is the code of the option.
  • tokens: list of strings, each giving the value of subsequent data fields in the order they appear in the option.

Record Types

Where an option is a record, the components of the element have to be defined. This is achieved by defining the record_types field, which is a list of map elements, each map defining one sub-option. The components of the map are:

  • name: name of the sub-option
  • type: type of the sub-option. This can be any one of the type fields defined above, with the exception of record.

TBD: is there a need for more complicated structures? Do we want to allow records to contain other records?

Note:

  • The position of the definition in the list gives the position of the sub-option within the option.
  • There is no array element within a record definition - if a record comprises a repetition of elements, the element will have to be defined multiple times.

Vendor Spaces and Vendor Encapsulated Options

IPv4 Vendor Extensions

In IPv4, the vendor option code is 43, the format of the option being described in RFC 2132, section 8.4. The data carried in this option is interpreted in one of two ways. Either:

  • it is interpreted as a set of data bytes
  • it is interpreted as a series of sub-options with vendor-specific codes. (This is "vendor encapsulation".) In other words, excluding the enterprise number the data comprises a sequence of data records, each record comprising a one-byte code field, a one-byte length field, and a data field equal in length to that specified in the sub-option length field. The number of such sub-options is determined by the length field of the vendor option.

The actual interpretation of the vendor option data is determined in the server configuration itself. At various points in the configuration, the "vendor space" can be specified. Within the scope of the vendor space declaration, the interpretation of option code 43 is this it carries a set of vendor-encapsulated sub-options defined for the vendor space. If no vendor space is in effect, option 43 data is interpreted as an array of bytes.

IPv6 Vendor Extensions

In IPv6, the vendor option code is 17, the format of the option field being described in RFC 3315, section 22.17. The data carried in the option comprises an "enterprise number" followed by a number of sub-options, each identified by a vendor-specific code. Each sub-option comprises a two-byte code field, a two-byte length field, and a data field equal in length to that specified in the sub-option length field. The number of such sub-options is determined by the vendor option's length field.

Unlike the IPv4 vendor option, the "vendor space" to which a sub-option belongs is carried within the vendor option itself in the form of the enterprise number; therefore there is only one way of interpreting the sub-option data. For this reason, there is no need specify a "vendor space" in the configuration, although for symmetry with IPv4 configuration such a specification will be allowed (but ignored).

Specification of Vendor Space and Scope

Vendor options are specified in exactly the same way as the standard option definitions, with the exception that the vendor_space indicator is non-null. This is set to the name of the vendor space for which the option applies. As this is a free-form text field, the possibility of a misspelling is significant. To guard against this, the vendor spaces have to be specified within the "vendor" area of the DHCP configuration database. Such a requirement also allows a single place in which enterprise numbers can be defined.

The scope is similar to vendor space - it allows to group options that are encapsulated by other options. For the IPv4 The data structure holding scope/vendor spaces will comprise the following fields:

  • name: the name of the vendor space. This must be unique within the list.
  • vendor_space: boolean value that indicates that the element describes vendor space.

For IPv6 the structure will be extended with enterprise_number field:

  • name: the name of the vendor space. This must be unique within the list.
  • vendor_space: boolean value that indicates that the element describes vendor space.
  • enterprise_number: The enterprise number associated with the vendor space. This must also be unique.

When reading the configuration, the DHCP server will process scopes before processing the user option definitions. It is an error for a user-defined option to be associated a scope not appearing in the list of vendor spaces.

Referring to User-Defined Options

Within the configuration of the server, a particular data element within an option is referred to as:

[vendor_space.]option-name[[array-index]][.record-element-name]

For example, the fictitious option

isc.dns-name[0].server

...would refer to the vendor space isc. Within this space the sub-option dns-name has been defined as an array of records. dns-name[0] refers to the first element of the array. Within that record, the element server has been defined.

There is an element of ambiguity in the specification: for example, does the option name isc.local-relay refer to the local-relay field of the globally-defined isc option, or does it mean the local-relay option within the isc vendor space? The rule here is that when a name containing periods is encountered, a check is made to see if any vendor space has the same name as the first component of the option name. If so, the vendor space is searched and an error returned if the option is not found. If not, the global option space is checked.

Examples

Vendor Space Definitions

config set Dhcp4/scope ["company1", "isc"]

Defines two IPv4 vendor spaces, "company1" and "isc".


config set Dhcp6/scope[0]/name "company1"
config set Dhcp6/scope[0]/enterprise_number 2887
config set Dhcp6/scope[0]/vendor_space true
config set Dhcp6/scope[1]/name "isc"
config set Dhcp6/scope[1]/enterprise_number 42
config set Dhcp6/scope[1]/vendor_space true

Defines two IPv6 vendor spaces, "company1" with an enterprise number of 2997, and and "isc" with an enterprise number of 42. Note that bindctl also allows their definition in a single statement:

config set Dhcp6/scope [{"name": "company1", "enterprise_number": 2887, "vendor_space": true },
                        {"name": "isc",  "enterprise_number": 42, "vendor_space": true }]

Option Definitions

In the following examples, "DhcpX" should be interpreted as "Dhcp4" for IPv4 options and "Dhcp6" for IPv6 options. Although the option codes in these examples are in the IPv4 range of 224 and 254, bear in mind that for IPv6 definitions, the option codes can be anything in the range 0 to 65,535 (although lower-numbers may conflict with IANA-defined codes).


config set DhcpX/options[0]/name "use-beta"
config set DhcpX/options[0]/code 231
config set DhcpX/options[0]/type "boolean"
config set DhcpX/options[0]/array true

Defines an option named use-beta that is interpreted as a an array of boolean values. The length of the array is determined by the length field of the option.


config set DhcpX/options[0]/name "widget-server"
config set DhcpX/options[0]/code 242
config set DhcpX/options[0]/type "record"
config set DhcpX/options[0]/record_types [{"name": "address", "type": "ip4-address"},
                                          {"name": "port",    "type": "uint16"}]

Defines an option named widget-server which is a record containing two elements: four bytes that represent an IPV4 address, and two bytes that represent an unsigned integer. Within the configuration, the data elements can be referred to as widget-server.address and widget-server.port.


config set DhcpX/options[0]/name "use-gamma"
config set DhcpX/options[0]/code 229
config set DhcpX/options[0]/type "boolean"

config set DhcpX/options[1]/name "use-delta"
config set DhcpX/options[1]/code 231
config set DhcpX/options[1]/type "boolean"

config set DhcpX/options[2]/name "server-address"
config set DhcpX/options[2]/code 229
config set DhcpX/options[2]/type "ip4-address"
config set DhcpX/options[2]/vendor_space "isc"

config set DhcpX/options[3]/name "source-port"
config set DhcpX/options[3]/code 231
config set DhcpX/options[3]/type "uint16"
config set DhcpX/options[3]/vendor_space "isc"

A more complicated setup. Two user-defined options have been defined, use-gamma and use-delta with option codes 229 and 231. These can be used anywhere in the configuration. In addition, two vendor options have been defined within the isc vendor space.

For the IPv4 server, when the vendor space isc is in effect, the contents of option 43 are interpreted as vendor encapsulated options. The codes recognised here are 229 - interpreted as an IPv4 address and identified by the name isc.server-address - and 231 - interpreted a single unsigned 16-bit integer identified by the name isc.source-port.

The interpretation of the vendor options is the same for IPv6. What is different is that there is no need for a vendor space to be in effect within the configuration. Assuming the vendor space definitions given in an earlier example, these option definitions will be in effect if a vendor option is received in a DHCP packet with an enterprise number of 42.

Design

Introduction

The design is based around the idea of an OptionDefinition object. The configuration is first read for user-defined options, and for each option an OptionDefinition object is created. A user-defined option overrides standard option if its vendor space is undefined and its code belongs to the set of IANA-defined options. In order to allow the overriding of standard options, the program will create OptionDefinition objects only for those standard options which are not already defined in user options space. On receipt of the option in a DHCP message, all OptionDefinition objects are inspected. Should local match be found, the OptionDefinition object is queried for corresponding option factory function to parse the received option.

Option Definition (outline design)

The OptionDefinition object is be created from the definition in the configuration for user configurable definitions. For standard option the definition is not configurable thus OptionDefinition object is created using hard coded data. This object has the following data structure:

  • Name
  • Code
  • Type - enum value indicating option type, e.g. int8, record etc.
  • Record fields - a vector of enums representing data types of fields that belong to the record. This is unused if option type is other than 'record'.
  • Array indication - if this value is set to true than multiple occurrences of the record are allowed
  • Suboptions - holds the list of suboptions being encapsulated by this option. This list is obtained by querying the list of all options belonging to the custom defined space specified in the Configuration Manager.

When option (not a sub-option) has been received in the packet its code is used to identify appropriate OptionDefinition object. This object can be queried for Namespace object it encapsulates. This object can be then queried for the list of sub-options (actually their OptionDefinition objects). The received sub-option code can be then matched with one of these OptionDefinition object's codes, which is used to unpack the sub-option. Such design implies that additional information (Namespace) has to be passed to the function which unpacks encapsulated options. Obviously this parameter may be optional.

TBD: What should we do if the received sub-option code is not found among encapsulated options?

When appropriate OptionDefinition object has been identified for option or sub-option it is used to get the corresponding factory function. The array of bytes representing the option is passed to the factory function, which creates the the object of the type Option or of the type derived from Option. The actual type of the object being created depends on the structure of the option described by the OptionDefinition. For typical option formats there are dedicated classes derived from Option class (e.g. Option6IA, Option6IAAddr etc). These objects expose accessors and mutators to access and modify data in the option. If the option format does not match any typical format that has specialized factory function associated with it, the OptionDefinition returns a generic factory function. When the buffer containing received option is passed to this factory function, it iterates through the vector of codes and uses each code to identify the meaning of the next set of bytes; the array indication is used to determine whether it should repeatedly run through the code vector until the byte stream is exhausted. An error indication is returned if the length of the data does not match the length inferred from the option definition. As the result, the CustomOption object is created with the following data structure:

  • Vector with the array of data records. This array always contains one element if "array indicator" is set to false. Otherwise it may contain one or more elements. Each data record is represented as:
    • vector of structures and each structure represents single field within the record. This vector may contain one or more elements.
      • Name - field name, if record field is to be accessed by its name
      • Type - data type, e.g. string, uint8 etc.
      • Data - vector of bytes (buffer) representing field data.

The 'Type' and 'Data' fields provide sufficient information to cast the information in 'Data' to appropriate type, bool, uint8_t, IOAddress. Also CustomOption has all required utility functions to cast the data behind the scenes.

The CustomOption object has a generic structure that can be used to store any option format. Also, the CustomOption organizes data in the structure that reflects its definition in the configuration .spec file so it is possible to access data fields as shown above:

isc.dns-name[0].server

However, this generic approach to storing options has performance implications and should not be used for all options. For some standard options, there are dedicated classes derived from Option class that have specialized pack()/unpack() functions, and keep data in the specialized structures to optimize performance. Such options do not provide an interface to access the data fields by their name right now, but they may be extended to support it.

If the server has to add user-defined options to its responses, it is assumed that elsewhere in the configuration will be the values of the parameters to be used. These values can be passed to an OptionDefinition object as a set of tokens. The OptionDefinition object is validates if the provided set of tokens matches the specified option format. The stream of tokens should be validated every time the option data set is added or changed in the Configuration Manager.

When an OptionDefinition object is created, it tries to match the specified format with the common factory functions implemented in it. These functions are those used to create instances of typical options, e.g. carrying list of addresses, single string value etc. The factory function instance returned by the OptionDefinition object can be "registered" and used when the option associated with it is to be created. In the current implementation of libdhcp++, factory functions are sought using the option code only. This is not sufficient because option codes may overlap: vendor options may have the same codes as standard options. For this reason there is a need to extend the factory function search mechanism to allow searching for factory functions using combination of option codes and fully qualified option names. It is possible that std::multimap or boost::multi_index_container could be used to achieve it.

Once the factory functions are registered and are accessible using the option code and name, they are used to create instances of all options configured to be sent by the server. Options instances are grouped by subnets. There may be multiple instances of the same option, as each instance can be associated with different subnet and may convey different data. Each subnet definition has an underlying options container. This container should be indexed by the option name and option code. It can be implemented with one map (to search by option names) and one multimap (to search by option codes). Alternatively, it can be implemented as multi_index_container with these two indexes. The Option pointers are directly used to form the DHCP packets being sent to the client.

Option Data (outline design)

Many DHCP options, either standard or custom-defined, can be configured to carry predefined data. This data is selected on per-subnet basis. The configuration structures for subnets have been created as part of ticket #2269. It is planned to use these data structures hold the options for different subnets. The Subnet class will be extended with an OptionCollection, which will be the container for all options that can be sent for the particular subnet. These options are represented by Option (or derived from Option) objects and can be directly added to the outgoing DHCP packet. It is desired that elements of this collection can be searched using option name (alternatively, using scope) or option code. Each option instance is created using the appropriate factory function returned by OptionDefinition object and data "tokens" from the configuration database.

TBD: should the predefined options in OptionCollection have sub-options included or sub-options should be added on case by case basis? It should be easy to get the list of sub-options using an OptionDefinition object, as it has the list of sub-options belonging to the scope that option encapsulates.

Open Questions

  • Do we want the user to have ability to redefine options?
  • We need a way to specify which options (and when) server is supposed to send them? The default should be "send them when client asks for them", but that works only for custom options. We also need some way to say "send option X regardless if client asks for it or not". There should be also a way to say "send all options from namespace X, if [condition here]". That is not strictly a matter of option definition, but rather a policy regarding how to use defined options.
    • Marcin: this is beyond the scope of this document.
  • We should emphasize somehow that vendor space definitions are not strictly for vendor specific options (option 43 in DHCPv4 and option 17 in DHCPv6), but also for defining custom options that may appear in suboptions.
    • Marcin: Vendor space is similar to the scope. There will be one data structure that holds scopes and vendor spaces. For each element in this data structure there will be a boolean field indicating that the element is a vendor space or scope.
  • Currently this proposal defines a way to define custom suboptions. However, it does not define a way to convey them, i.e. there is no way to define that custom option should appear as sub-option within other option. Here's a life example: there is route option draft that defines NEXT_HOP option that contains one or more RTPREFIX option. We need a way to define that.
    • Marcin: Each option definition comprises the optional suboptions which takes string parameter indicating the sub-options scope. This scope aggregates options that the particular option encapsulates. In the first stage of implementation we may use the scope to get the list of sub-options to be included in the particular option. In the future we may want to create the conditional decisions whether to include the particular option or not and possibly some extra configuration will be required through the .spec file.
  • Do we want to control cardinality in any way? "at most one option may appear", "at least one must appear", "there may be one or more of such options", etc. Again, that is more of a policy discussion, rather than option format definition.
Last modified 5 years ago Last modified on Oct 17, 2012, 1:41:33 PM