FreeRADIUS InkBridge

Configure "device", "class" and "group" options

Beyond the global, network and subnet options already described, most sites will have a number of group or class based options, and have a requirement for setting reply parameters against individual devices.

In general, FreeRADIUS does not differentiate between "classes" (memberships defined by some attribute of the DHCP request) and "groups" (memberships defined by some manually aggregation related devices, typically based on lists of MAC address).

The sample DHCP configuration provided with FreeRADIUS makes use of an internal attribute Group-Name to support the setting of different options for different groups of devices.

In general the groups to which a device belongs is determined during the processing of a request and these are added as instances of the Group-Name attribute. This may be by performing a test on one or more request parameters (akin to a "class"), hash-based lookup of up all of part of an attribute in a local list (akin to a "subclass"), or doing the same using a remote datastore (SQL, LDAP, REST API, etc).

FreeRADIUS can then iterate over Group-Name to set group-specific options.

We describe some of these options in more detail.

Directly in Policy

Simple class options can be written directly into policy. This is most suited to those options that rarely change and are based on attributes in the request such as the User-Class.

Consider the ISC DHCP configuration snippet:

filename "undionly.kpxe";
class "pxeclient" {
    match option substring(user-class,0,4);
}
subclass "pxeclient" "iPXE" {
    filename "http://my.web.server/boot_script.php";
}

See also the isc_dhcp module, which can read a subset of the ISC DHCP configuration files.

Or the equivalent Kea configuration:

"Dhcp4": {
    "option-data": [
        { "name": "boot-file-name", "data": "undionly.kpxe" }
    ],
    "client-classes": [
        {
            "name": "pxeclient",
            "test": "substring(option[77],0,4) == 'iPXE'",
            "option-data": [
               {
                   "name": "boot-file-name",
                   "data": "http://my.web.server/boot_script.php"
               }
            ]
        }
    ]
    ...
}

These define the "filename" DHCP option differently based on whether or not the supplied "user-class" option begins with "iPXE".

FreeRADIUS provides multiple ways for this to be configured.

For example, the following "unlang" policy implements the class options defined above:

if (User-Class && %str.subst(User-Class, 0, 4) == 'iPXE') {
    reply.Boot-Filename := 'http://my.web.server/boot_script.php'
} else {
    reply.Boot-Filename := 'undionly.kpxe'
}

Policy-based configuration of DHCP options is also useful for complex matching. For example, the following Unlang sets the Boot-Filename parameter based on the request’s Client-Identifier using regular expression captures, provided that it matches the given format:

if (Client-Identifier && \
        "%{(string)Client-Identifier}" =~ /^RAS([0-9])-site([A-Z])$/) {
    reply.Boot-Filename := "rasboot-%regex.match(1)-%regex.match(2).kpxe"
}

In Text Files

The files module that has already been described for global, network and subnet options can also be used to apply options to groups of clients.

Firstly we must defined a mapping from a set of clients clients to their respective groups. One option for this is to use the passwd module.

Create <raddb>/mods-enabled/dhcp_groups:

passwd dhcp_groups {
	filename = ${modconfdir}/files/dhcp_groups
	format = "~Group-Name:*,Client-Hardware-Address"
	hash_size = 100
	allow_multiple_keys = yes
	delimiter = '|'
}

Then create <raddb>/mods-config/files/dhcp_groups

<group1 name>|<hardware address>,<hardware address>,<hardware address>
<group2 name>|<hardware address>,<hardware address>

i.e. one line for each group starting with the group name followed by a pipe character and then a comma-separated list of hardware addresses.

The allow_multiple_keys option allows for a host to be a member of more than one group.

Having mapped the client to a group, an instance of files can be used to add reply options.

Create an instance of files - <raddb>/mods-enabled/dhcp_group_options:

files dhcp_group_options {
	filename = ${modconfdir}/files/dhcp_group_options
	key = Group-Name
}

Then create the data file '<raddb>/mods-config/files/dhcp_group_options' containing entries with the group name as the key such as:

group1
       Log-Server := 10.10.0.100,
       LPR-Server := 10.10.0.200

group2
       LPR-Server := 192.168.20.200

In the SQL Database

Policy and files are both read during startup and editing them while FreeRADIUS is running will not result in any changes in behaviour. If you require regular changes to DHCP options, then storing them in an SQL database provides greater flexibility since the queries will be run in response to each DHCP packet rather than requiring the server to be restarted.

DHCP reply options for devices (including network-specific options) can be fetched from SQL (or other data stores such as LDAP or redis) using an arbitrary lookup key. This can be performed multiple times as necessary using different contexts, for example to first set subnet-specific options and then to set group-specific options.

There is no set way to perform this but one method would be to create an SQL table such as

CREATE TABLE dhcpoptions (
	context		VARCHAR(30) NOT NULL,
	identifier	VARCHAR(255) NOT NULL,
	option		VARCHAR(511) NOT NULL,
);

Then populate "dhcpoptions" with reply options for a given identifier (e.g. MAC Address):

Table 1. dhcpoptions table
context identifier option

by-mac

02:01:aa:bb:cc:dd

reply.Log-Server := 192.0.2.10

by-mac

02:01:aa:bb:cc:dd

reply.LPR-Server := 192.0.2.11

Additionally records can be added to map identifiers to a group of options that can be shared:

Table 2. dhcpoptions table
context identifier option

by-mac

02:01:aa:bb:cc:dd

control.Group-Name := 'salesdept'

And then group reply options can be added to the "dhcpoptions" table:

Table 3. dhcpoptions table
context identifier option

by-group

salesdept

reply.NTP-Servers := 192.0.2.20

by-group

salesdept

reply.Log-Server += 192.0.2.21

by-group

salesdept

reply.LPR-Server ^= 192.0.2.22

Within the context of assigning options directly to devices, as well as to manually-curated groups of devices keyed by their MAC address:

  • Place device-specific options in the "dhcpoptions" table, with the option column having the option name prefixed with reply..

  • Place entries in the "dhcpoptions" which set the control.Group-Name attribute to assign the device to one or more groups.

  • Place the grouped options in the "dhcpoptions" table with the context being by-group.

The following unlang will then allow retrieval of options from the database

%map(%sql("SELECT option FROM dhcpoptions WHERE context = 'by-mac' AND identifier = '%{Client-Hardware-Address}'"))

foreach dhcpgroup (control.Group-Name[*]) {
	%map(%sql("SELECT option FROM dhcpoptions WHERE context = 'by-group' AND identifier = '%{dhcpgroup}'"))
}

In the above, the DHCP reply options would be assigned to a device with MAC address 02:01:aa:bb:cc:dd as follows:

  • Firstly, the Log-Server option would be set to 192.0.2.10 and the LPR-Server option set to 192.0.2.11.

  • The Group-Name option in the control list gets set to salesdept.

  • Finally, the options for the salesdept group are now merged, setting a NTP-Servers option to 192.0.2.20, appending an additional Log-Server option set to 192.0.2.21, and prepending an additional LPR-Server option set to 192.0.2.22.

If instead you wanted to perform a "subclass" lookup based on the first three octets of the device’s MAC address then with tables containing the following sample data you could invoke an SQL lookup as shown:

Table 4. "dhcpoptions" table:
context identifier option

class-vendor

000393

control.Group-Name := apple

class-vendor

000a27

control.Group-Name := apple

class-vendor

f40304

control.Group-Name := google

by-group

apple

reply.Boot-Filename := 'apple.efi'

by-group

google

reply.Boot-Filename := 'google.efi'

%map(%sql("SELECT option FROM dhcpoptions WHERE context = 'class-vendor' AND identifier = '%substring(%{Client-Hardware-Address}, 0, 6)'"))

foreach dhcpgroup (control.Group-Name[*]) {
	%map(%sql("SELECT option FROM dhcpoptions WHERE context = 'by-group' AND identifier = '%{dhcpgroup}'"))
}

This is just one approach which can be taken to populate DHCP reply options from an SQL database.

Test "device", "class" and "group" options

You should now test that any device-related options that you have configured using the various methods available are applied successfully by generating packets containing those parameters based upon which the reply options are set.

For example, to test the iPXE user class example above you might want to generate a request as follows:

cat <<EOF > dhcp-packet-ipxe-boot.txt
Message-Type := Discover
Client-Hardware-Address := 02:01:aa:bb:cc:dd
User-Class := "iPXE-class-abc"
EOF

To which you would expect to see a response such as:

Example 1. Example output from dhcpclient
dhcpclient: ...
----------------------------------------------------------------------
Waiting for DHCP replies for: 5.000000
----------------------------------------------------------------------
...
Message-Type = ::Offer
Your-IP-Address = 1.2.3.4
Boot-Filename := "http://my.web.server/boot_script.php"
...