== 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 `DHCP-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 `DHCP-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 `DHCP-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: [source,iscdhcp] ---- filename "undionly.kpxe"; class "pxeclient" { match option substring(user-class,0,4); } subclass "pxeclient" "iPXE" { filename "http://my.web.server/boot_script.php"; } ---- Or the equivalent Kea configuration: [source,isckea] ---- "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: [source,unlang] ---- if (&DHCP-User-Class && "%{substring:&DHCP-User-Class 0 4}" == "iPXE") { update reply { &DHCP-Boot-Filename := "http://my.web.server/boot_script.php" } } else { update reply { &DHCP-Boot-Filename := "undionly.kpxe" } } ---- Policy-based configuration of DHCP options is also useful for complex matching. For example, the following Unlang sets the DHCP-Boot-Filename parameter based on the request's DHCP-Client-Identifier using regular expression captures, provided that it matches the given format: [source,unlang] ---- if (&DHCP-Client-Identifier && \ "%{string:DHCP-Client-Identifier}" =~ /^RAS([0-9])-site([A-Z])$/) { update reply { &DHCP-Boot-Filename := "rasboot-%{1}-%{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, for which a sample configuration is included. Firstly symlink or copy the module configuration `/mods-available/dhcp_passwd` into `/mods-enabled/`. The suggested configuration expects the group membership file to be in `/mods-config/files/dhcp_groups` and take the form of: [source,config] ---- |,, |, ---- 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. Sample configuration for looking up group options is contained in `/policy.d/dhcp` in the `dhcp_group_options` policy and in `/mods-available/dhcp_files` as the `dhcp_set_group_options` instance. The same data file `/mods-config/files/dhcp` is used to lookup group options as was used for global and network options. In this instance, add entries with the group name as the key such as: [source,config] ---- group1 DHCP-Log-Server := 10.10.0.100, DHCP-LPR-Server := 10.10.0.200 group2 DHCP-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 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. The default schema contains three tables to support this: "dhcpreply" contains reply options for a given identifier (e.g. MAC Address): .dhcpreply table |=== |Identifier |Attribute |Op |Value |Context |`02:01:aa:bb:cc:dd` |`DHCP-Log-Server` |`:=` |`192.0.2.10` |`by-mac` |`02:01:aa:bb:cc:dd` |`DHCP-LPR-Server` |`:=` |`192.0.2.11` |`by-mac` |`02:01:aa:bb:cc:dd` |`Fall-Through` |`:=` |`Yes` |`by-mac` |=== "dhcpgroup" maps identifiers to a group of options that can be shared: .dhcpgroup table |=== |Identifier |GroupName |Priority |Context |`02:01:aa:bb:cc:dd` |`salesdept` |`10` |`by-mac` |=== "dhcpgroupreply" contains reply options for each group: .dhcpgroupreply table |=== |GroupName |Attribute |Op |Value |Context |`salesdept` |`DHCP-NTP-Servers` |`:=` |`192.0.2.20` |`by-mac` |`salesdept` |`DHCP-Log-Server` |`+=` |`192.0.2.21` |`by-mac` |`salesdept` |`DHCP-LPR-Server` |`^=` |`192.0.2.22` |`by-mac` |=== 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 "dhcpreply" table. - Add `Fall-Through := Yes` to the options in the "dhcpreply" table in order to trigger group lookups, which are disabled by default. - Place entries in the "dhcpgroup" `identifier = , groupname = , priority = ` in the "dhcpgroup" table to map a device to its groups by priority. - Place the grouped options in the "dhcpgroupreply" table. - For each of the above, set `Context` to something by which the option lookup is referred to in the policy, for example `Context = 'by-mac'`. For the above example you would add the following to the DHCP virtual server to perform reply option lookup using the device's MAC address against the `by-mac` context: [source,unlang] ---- update control { &DHCP-SQL-Option-Context := "by-mac" &DHCP-SQL-Option-Identifier := &request:DHCP-Client-Hardware-Address } dhcp_sql.authorize ---- 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 `DHCP-Log-Server` option would be set to `192.0.2.10` and the `DHCP-LPR-Server` option set to `192.0.2.11`. - `Fall-Through` is set, so the group mapping is then queried which determines that the device belongs to a single `salesdept` group. - Finally, the options for the `salesdept` group are now merged, setting a `DHCP-NTP-Servers` option to `192.0.2.20`, appending an additional `DHCP-Log-Server` option set to `192.0.2.21`, and prepending an additional `DHCP-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: ."dhcpreply" table: |=== |Identifier |Attribute |Op |Value |Context |`000393` |`Fall-Through` |`:=` |`Yes` |`class-vendor` |`000a27` |`Fall-Through` |`:=` |`Yes` |`class-vendor` |`f40304` |`Fall-Through` |`:=` |`Yes` |`class-vendor` |=== ."dhcpgroup" table: |=== |Identifier |GroupName |Priority |Context |`000393` |`apple` |`10` |`class-vendor` |`000a27` |`apple` |`10` |`class-vendor` |`f40304` |`google` |`10` |`class-vendor` |=== ."dhcpgroupreply" table: |=== |GroupName |Attribute |Op |Value |Context |`apple` |`DHCP-Boot-Filename` |`:=` |`apple.efi` |`class-vendor` |`google` |`DHCP-Boot-Filename` |`:=` |`google.efi` |`class-vendor` |=== [source,unlang] ---- update control { &DHCP-SQL-Option-Context := "class-vendor" &DHCP-SQL-Option-Identifier := \ "%{substring:%{hex:&DHCP-Client-Hardware-Address} 0 6}" } dhcp_sql.authorize ---- The file `policy.d/dhcp` contains a policy named `dhcp_policy_sql` which provides further worked examples for different types of option lookups. === Testing "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: [source,shell] ---- cat < dhcp-packet-ipxe-boot.txt DHCP-Message-Type := DHCP-Discover DHCP-Client-Hardware-Address := 02:01:aa:bb:cc:dd DHCP-User-Class := "iPXE-class-abc" EOF ---- To which you would expect to see a response such as: .Example output from dhcpclient =============================== dhcpclient: ... ---------------------------------------------------------------------- Waiting for DHCP replies for: 5.000000 ---------------------------------------------------------------------- ... DHCP-Message-Type = DHCP-Offer DHCP-Your-IP-Address = 1.2.3.4 DHCP-Boot-Filename := "http://my.web.server/boot_script.php" ... ===============================