Altering the Subnet Mask Option Based on giaddr

Introduction

Increasingly, we have been asked how to assign a /32 netmask in DHCPv4. This is, apparently, an address saving scheme. Kea automatically computes the appropriate value for option 1 or "subnet-mask" based on the content of the subnet statement. For example, if you have a subnet configured like the below.

{
  "Dhcp4": {
    "subnet4": [
      {
        "subnet": "192.0.2.0/24",
        "relay": {
          "ip-addresses": [ "10.1.2.6" ]
        },
        "id": 1,
        "pools": [
          {
            "pool": "192.0.2.2 - 192.0.2.254"
          }
        ],
        "option-data": [
          {
            "name": "routers",
            "data": "192.0.2.1"
          }
        ]
      }
    ]
  }
}

Then option 1 "subnet-mask" will be computed as 255.255.255.0. When a client requests an IP address, the subnet-mask option will be sent with that content as part of the lease allocation. Sometimes an ISP will want to alter this behavior and send a "subnet-mask" that represents a /32 which would be 255.255.255.255. As noted in this table in the ARM, option 1 "subnet-mask" is not configurable by the administrator. How can this be achieved?

Altering the subnet-mask

The solution is provided by the flex-option hook. This hook allows an administrator to alter virtually any option's content. An option that isn't present may be added. An option that is present may be removed. This can be done based on various criteria using a similar syntax to that of classification "tests" and the Forensic Log hook's *-parser-format.

Commonly, the need to set a /32 subnet mask is limited to one or more subnets or networks. Also commonly, ISPs will make use of a relay-agent for a particular network since the DHCP server is usually centrally located. Conveniently, we can use the relay-agent's IP address as a constraint to limit what packets will have the /32 subnet mask applied. The following example will illustrate how this might be done.

Example

In this example, the relay-agent is 10.1.2.6. We will use this as a constraint to match only the packets that should have the subnet-mask altered to a /32 or "255.255.255.255". Basically, if the giaddr field contains "10.1.2.6", then the subnet-mask will be altered from "255.255.255.0" to "255.255.255.255". The hook configuration to do so looks like this.

    "hooks-libraries": [
      {
        "library": "/path/libdhcp_flex_option.so",
        "parameters": {
          "options": [
            {
              "code": 1,
              "supersede": "ifelse(pkt4.giaddr==10.1.2.6,255.255.255.255,'')"
            }
          ]
        }
      }
    ],

The above will load the library with the "library": "/path/libdhcp_flex_option.so", portion and will override the content in the option with "code": 1, which is "subnet-mask". The "supersede": "ifelse(pkt4.giaddr==10.1.2.6,255.255.255.255,'')" directs Kea to "replace the content of option code 1 with "255.255.255.255" if pkt4.giaddr has the value "10.1.2.6". Else, it returns an empty string which causes the flex_option hook to do nothing.

Example with Multiple Relay-agents

What if we have multiple relay-agents that service subnets that have clients that should be using a /32 subnet-mask? Multiple tests can be performed as shown below.

    "hooks-libraries": [
      {
        "library": "/usr/local/kea/2.6.0/lib/kea/hooks/libdhcp_flex_option.so",
        "parameters": {
          "options": [
            {
              "code": 1,
              "supersede": "ifelse(pkt4.giaddr==10.1.2.7,255.255.255.255,ifelse(pkt4.giaddr==10.1.2.6,255.255.255.255,''))"
            }
          ]
        }
      }
    ],

What was done here was the return of an empty string was replaced with another ifelse(<condition>,<value>,<emtpy string>) statement. These can be further nested if more are needed. The most interior statement is the one that finally returns the empty string if none matched.

Conclusion

What does this configuration look like when put together?

{
  "Dhcp4": {
    "calculate-tee-times": true,
    "interfaces-config": {
      "interfaces": [ "*" ]
    },
    "lease-database": {
      "type": "memfile",
      "persist": true,
      "name": "/tmp/lease4.csv"
    },
    "hooks-libraries": [
      {
        "library": "/path/libdhcp_flex_option.so",
        "parameters": {
          "options": [
            {
              "code": 1,
              "supersede": "ifelse(pkt4.giaddr==10.1.2.7,255.255.255.255,ifelse(pkt4.giaddr==10.1.2.6,255.255.255.255,''))"
            }
          ]
        }
      }
    ],
    "subnet4": [
      {
        "subnet": "192.0.2.0/24",
        "relay": {
          "ip-addresses": [ "10.1.2.6" ]
        },
        "id": 1,
        "pools": [
          {
            "pool": "192.0.2.2 - 192.0.2.254"
          }
        ],
        "option-data": [
          {
            "name": "routers",
            "data": "192.0.2.1"
          }
        ]
      }
    ]
  }
}

The above is a complete configuration that Kea 2.6.1 will load and run. Of course, the /path/ would need to be altered to the actual path to your libdhcp_flex_option.so and the subnets would need to be altered to match your network. The relay-agent value(s) would need to be altered to match your configuration, as well.