FreeRADIUS InkBridge

Dictionaries

Goal: To understand how dictionaries map protocol numbers to names and how these files are used by the server. You’ll create a local dictionary and a custom dictionary file with Vendor-Specific attributes. Then you will test the custom dictionary using a RADIUS test client. Finally, you’ll configure a dictionary definition within a virtual server.

Time: 30-35 minutes

Files:

  • raddb/dictionary

  • raddb/dictionary.d/

  • raddb/mods-config/files/authorize

  • ${prefix}/share/freeradius/

Documentation Pages:

The dictionary files used by FreeRADIUS work together to map protocol numbers to human-readable names, and link them to data types. Some dictionaries are taken from the relevant standards, while others are defined by vendors (i.e.g other companies). Entries in one dictionary can reference definitions from another, which allows for extensive customisations.

RADIUS packets carry attributes as type-length-value triples. The type field is a number (e.g. 1 for User-Name). Without a dictionary, a policy would have to say something like:

if attribute 1 == "bob" { ... }

With a dictionary entry that says "number 1 is called User-Name and holds a string", the policy can say:

if User-Name == "bob" { ... }

The dictionaries are local. i.e. They names are available only within the scope of the server which has loaded the dictionary. Renaming an attribute in a dictionary file does not change anything on the network. Clients and NAS devices never see dictionary names.

The share/ dictionaries are defined by the FreeRADIUS team, and are updated with every release. This means that theu are overwritten on every package update or upgrade. If you need to define your own dictionary entries, they must go into raddb/dictionary or into raddb/dictionary.d/.

In order to help you organize your own dictionaries, the raddb/dictionary file ends with:

$INCLUDE- dictionary.d/

The - suffix means the include is optional. The server starts normally even if the referenced directory is empty or missing.

The dictionaries in raddb are never changed or updated when FreeRADIUS is updated.

Hierarchical Names (v4 change from v3)

In v3, attribute names were global. Vendor-specific attributes were generally named with a vendor prefix, e.g. Cisco-AVPair.

In v4, names are hierarchical. The same Cisco-AVPair`attribute is now `Vendor-Specific.Cisco.AVPair. The full path makes the protocol structure explicit and removes name conflicts across vendors.

Old v3-style flat names are still available through alias dictionaries. See raddb/dictionary for the $INCLUDE directives that enable v3 compatibility names.

DEFINE versus ATTRIBUTE

Keyword Number required Goes on the wire Use for

ATTRIBUTE

Yes

Yes

Protocol attributes that NAS devices send/receive.

DEFINE

No

No

Internal server-side variables (policies, caching, etc.).

Use DEFINE for attributes that exist only inside the server. Use ATTRIBUTE (inside a vendor block) for attributes that must appear in real RADIUS packets.

Step 1: Local Attributes with DEFINE

Local attributes are the simplest way to add custom data to a policy. They live in raddb/dictionary, never go into a packet, and do not need a number assigned to them.

Open raddb/dictionary and add the following lines before the final $INCLUDE- dictionary.d/ line:

DEFINE  My-Local-String   string
DEFINE  My-Local-IPAddr   ipaddr
DEFINE  My-Local-Integer  uint32

Start the server in debug mode to confirm the definitions load without errors:

$ radiusd -X

You should see the server print Ready to process requests with no errors about unknown attributes. Stop the server with Ctrl-C.

Step 2: Vendor-Specific Dictionary

Vendor-specific attributes (VSAs) use an IANA-assigned Private Enterprise Number (PEN) to namespace the vendor’s attributes inside a RADIUS packet. For this exercise, use the example PEN 123456.

Create the dictionary file

Create the file raddb/dictionary.d/dictionary.test with the following content:

# -*- text -*-
#
# dictionary.test - Example vendor-specific dictionary for the tutorial
#
# Vendor PEN 123456 is used here as an example only.
# Real deployments require an IANA-assigned number.
#
VENDOR    Test    123456
BEGIN-VENDOR    Test
ATTRIBUTE   Test-Lunch-Time         1   date
ATTRIBUTE   Test-People-To-Eat-With 2   string
ATTRIBUTE   Test-Where-To-Eat       3   ipaddr
ATTRIBUTE   Test-What-To-Eat        4   uint32
VALUE   Test-What-To-Eat    Salad    1
VALUE   Test-What-To-Eat    Bread    2
VALUE   Test-What-To-Eat    Dessert  3
VALUE   Test-What-To-Eat    Beans    4
END-VENDOR  Test

The file is picked up automatically because the raddb/dictionary file includes all files in the dictionary.d/ directory.

Dictionary syntax reference

The following tables outline the type of keywords, data types, and related syntax and formats used in this tutorial.

Table 1. v4 Keywords
Keyword Syntax Description

VENDOR

VENDOR <name> <pen>

Declares the vendor name and PEN.

BEGIN-VENDOR

BEGIN-VENDOR <name>

Opens the vendor namespace.

ATTRIBUTE

ATTRIBUTE <name> <number> <type>

Defines a VSA.

VALUE

VALUE <attr> <name> <integer>

Names an enumerated value.

END-VENDOR

END-VENDOR <name>

Closes the vendor namespace.

Table 2. v4 Data types
v4 type Description Value

date

Unix timestamp displayed as a date

2026-06-01T09:00:00

string

UTF-8 string

"Alice"

ipaddr

IPv4 address

192.0.2.1

uint32

32-bit unsigned integer (with optional VALUE names)

Salad

The v3 data type name integer is still accepted, and is treated as an alias for uint32.

Verify that server will load the changed dictionary

Start the server in debug mode:

$ radiusd -X

With -X, the server logs one line for the root raddb/dictionary file (the leading path depends on your install prefix):

Including dictionary file ".../raddb/dictionary"

Files loaded via $INCLUDE- inside that file, including dictionary.d/dictionary.test, do not produce their own log line. A clean startup ends with Ready to process requests., which means that all dictionary files were loaded without errors.

If the server fails to start, it will write out one or more error messages which point to the exact file and line that caused the problem. In most cases, the error will be clear enough for you to find and correct the problem.

Otherwise, check for typos in attribute numbers or types. Each attribute number must be unique within the vendor block. Stop the server before continuing.

Step 3: Test the New Attributes

Add a test user

Edit raddb/mods-config/files/authorize and add the following entry for user bob:

bob     Password.Cleartext := "hello"
        Reply-Message := "Hello, bob",
        Vendor-Specific.Test.Test-Lunch-Time := "Jun  1 2026 12:00:00 UTC",
        Vendor-Specific.Test.Test-People-To-Eat-With := "Alice",
        Vendor-Specific.Test.Test-Where-To-Eat := "192.0.2.50",
        Vendor-Specific.Test.Test-What-To-Eat := Bread

The fully qualified name Vendor-Specific.Test.Test-Lunch-Time reflects the v4 hierarchical naming. The vendor name (Test) and the attribute name (Test-Lunch-Time) are both part of the path.

Start the server and send a test packet

In one terminal window, run the server:

$ radiusd -X

And in another terminal window, run radclient:

$ echo "User-Name = bob, User-Password = hello" | radclient 127.0.0.1 auth testing123

In the server debug output, look for the VSA reply attributes printed by name rather than as raw numbers:

(0)   files - Found match "bob" on line ...
(0)   files - Preparing attribute updates:
(0)     Password.Cleartext := "hello"
(0)     Reply-Message := "Hello, bob"
(0)     Vendor-Specific.Test.Test-Lunch-Time := "Jun  1 2026 12:00:00 UTC"
(0)     Vendor-Specific.Test.Test-People-To-Eat-With := "Alice"
(0)     Vendor-Specific.Test.Test-Where-To-Eat := "192.0.2.50"
(0)     Vendor-Specific.Test.Test-What-To-Eat := Bread

If the attributes were showing as raw numbers (e.g. Vendor-Specific.123456.4 := 0x00000002) instead of names, the dictionary file was not loaded or used.

Test multiple values for one attribute

Edit the bob entry to return four values for Test-People-To-Eat-With:

bob     Password.Cleartext := "hello"
        Reply-Message := "Hello, bob",
        Vendor-Specific.Test.Test-People-To-Eat-With := "Alice",
        Vendor-Specific.Test.Test-People-To-Eat-With += "Bob",
        Vendor-Specific.Test.Test-People-To-Eat-With += "Carol",
        Vendor-Specific.Test.Test-People-To-Eat-With += "Dave",
        Vendor-Specific.Test.Test-What-To-Eat := Salad

The += operator appends an additional instance of the attribute rather than replacing the first. Re-send the radclient request and verify all four names appear in the reply.

Step 4: Add Local Dictionary to a Virtual Server

In FreeRADIUS v4, there is now a dictionary { } subsection directly inside a virtual server. Attributes defined there are visible only within that virtual server and never go into a packet. They behave like DEFINE attributes, but are scoped to only that one server.

Open raddb/sites-available/default and look for the dictionary { } section. It already contains commented-out examples. Add a local attribute:

dictionary {
    uint32 My-Session-Counter
    values My-Session-Counter {
        None = 0
        Low  = 1
        High = 2
    }
}

This attribute can be set and tested in policies within the default virtual server without adding anything to raddb/dictionary or raddb/dictionary.d/.

Questions

  1. What happens if two dictionary files define the same attribute number within the same vendor block?

  2. Why do v4 attribute names use a hierarchical path Vendor-Specific.Test.Test-What-To-Eat instead of a flat name Test-What-To-Eat?

  3. When should you use DEFINE instead of a VSA?

  4. Why do the shared dictionaries in share/ warn against editing?

  5. What is the purpose of the VALUE keyword, and what happens at the protocol level when you send Test-What-To-Eat = Bread?