Using Radiator to re-write VLAN assignment (RADIUS tunnel) attributes

VLANs are often used to ensure that traffic is segregated between user groups. Assigning users into VLANs based on their identity is an attractive way of enforcing user privilege--in fact, it was the main topic of the Interop Labs LAN Access Security class this year. VLAN assignment is accomplished by configuring a RADIUS server to pass back the VLAN identifier to a wireless access point or a switch. The precise method of passing VLAN assignment information was published last September as RFC 3580. Section 3.31 tells you clearly how to assign a VLAN to a user: in the final RADIUS Access-Accept message, put the VLAN ID in the Tunnel-Private-Group-ID attribute. Tunnel-Private-Group-ID was defined in RFC 2868 as a string, so RFC 3580 says that the numeric VLAN ID should be encoded a string and passed in the attribute.

Not all 802.1X authenticators follow RFC 3580, however. Some do VLAN assignment only through the use of vendor-specific RADIUS attributes. Others expect the Tunnel-Private-Group-ID attribute to contain an integer data type, not a string. Still others expect a string that corresponds to something else entirely. By far the most common non-compliant case, however, are the devices which expect to receive an integer data type instead of the string-encoded integer mandated by the standard.

The Demo at the Interop Labs

For our demonstration booth on the show floor at Interop, we had several RADIUS servers, all of which were configured with a set of users and attributes. We try to assess interoperability, so we had defined VLAN identifiers as strings, as required by RFC 3580. When I started working on our RADIUS proxy demonstration, however, I quickly determined that the wireless access point assigned to the demonstration was non-compliant. It required the VLAN identifier encoded as binary integer, not the string representation of that number. Fortunately, I was working with the Radiator RADIUS server, a powerful and flexible RADIUS server written entirely in Perl. Among many other capabilities, it has "hooks" to run arbitrary user-defined procedures at various check points in the authentication process. By writing my own hooks, I was able to selectively rewrite the VLAN identifiers for RADIUS clients that expected the identifier in the wrong format.

I had to use two different hooks, depending on whether the user was authenticated locally by Radiator or proxied to another RADIUS server. In the local authentication case, I used a PostAuthHook, which is run after the authentication transaction completes, but is not run when the request is proxied to another server. For the proxy authentications handled by external servers, I used a ReplyHook, which is run when the reply from the external RADIUS server is received. (Due to the arguments in the functions, I needed two separate hook procedures.)

Configuring the Hooks

The first thing I did was to define a "Identifier" string used by Radiator and passed along with authentication requests. I used the string "IntegerVLANTag" in radius.cfg, the main configuration file, to note which clients required me to rewrite the VLAN identifier from the standard string into a binary integer. Here's an example:

To call the hooks, I needed to configure them with the appropriate authentication handlers. One of the back-end servers I was using was a FreeRADIUS server. The handler is configured to route authentication request for a username of the form user@freeradius to the FreeRADIUS server. There is a simple configuration section for the external server to use, and the reply hook is defined there. In the following configuration, the external file vlan-ascii-to-binary-reply contains a Perl procedure that will be run against the reply packet received from 45.200.1.11.

For authentication requests which are handled locally, the configuration is a bit more complex because the authentication details are done locally. In this example, we are stripping the realm, so that user names of the form user@radiator become simply user. When authentication completes, the procedure in the file vlan-ascii-to-binary-postauth will be run to rewrite the VLAN identifier.

The PostAuthHook is run for locally-processed authentications. First, the hook pulls in the arguments passed to it: the request ($p), the reply ($rp), and the result of the authentication ($result). If the reply packet is an Access-Accept and the RADIUS client is defined to want binary integer VLAN identifiers, then the attribute rewriting takes place. I had to work around one additional wrinkle, which is that RADIUS servers may supply groups of tunnel attributes with a tag. My hook needed to preserve the tag on attributes, but rewrite the value from a string into a binary number.

The ReplyHook uses the same code for the guts, but it needs to pick up arguments that are passed in a slightly different order. Also, the ReplyHook is run for every response packet from the remote RADIUS server, so it may be run several times before it acts on an Access-Accept message. I didn't find a way around this when I was trying to get the demonstration running before the show floor opened.