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:
-
Dictionaries Index of Keywords
-
dictionary(5)
manpage
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 |
|---|---|---|---|
|
Yes |
Yes |
Protocol attributes that NAS devices send/receive. |
|
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.
| Keyword | Syntax | Description |
|---|---|---|
|
|
Declares the vendor name and PEN. |
|
|
Opens the vendor namespace. |
|
|
Defines a VSA. |
|
|
Names an enumerated value. |
|
|
Closes the vendor namespace. |
| v4 type | Description | Value |
|---|---|---|
|
Unix timestamp displayed as a date |
|
|
UTF-8 string |
|
|
IPv4 address |
|
|
32-bit unsigned integer (with optional |
|
|
The v3 data type name |
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
-
What happens if two dictionary files define the same attribute number within the same vendor block?
-
Why do v4 attribute names use a hierarchical path
Vendor-Specific.Test.Test-What-To-Eatinstead of a flat nameTest-What-To-Eat? -
When should you use
DEFINEinstead of a VSA? -
Why do the shared dictionaries in
share/warn against editing? -
What is the purpose of the
VALUEkeyword, and what happens at the protocol level when you sendTest-What-To-Eat = Bread?