About

The following guide demonstrates a way to use FreeRADIUS with OpenWRT in order to keep track of connecting wireless clients. Using FreeRADIUS will enable the user to accept or reject connecting clients based on their MAC address.

Even though it is common to say that MAC filtering does not provide any security, there is not much to that saying that would be rationally true. Considering the amount of bytes permitted in a MAC address, the likelihood of someone randomly guessing a full MAC address in order to connect to the network is negligible. Perhaps, the only validity to claims are due to the fact that hardware MAC addresses can be faked, but that has no bearing onto accessing a network illicitly without any knowledge of what MAC addresses are permitted within the network.

In order to perform MAC address accounting in OpenWRT, the only tool that OpenWRT provides is to add the MAC addresses to the available wireless radios using a drop-down list. With that being said, the FreeRADIUS setup described in this section should provide a robust and easy way to manage wireless connecting clients and to keep track of the clients separately rather than using the built-in OpenWRT features. Keeping the MAC accounting separate from OpenWRT makes sense, mainly in terms of separation of concerns and in particular because whilst the radios might change, the connecting clients will still be the same.

Setting up OpenWRT

On a vanilla installed, if the RADIUS options are used in hostapd, the following errors will be returned:

Line 46: unknown configuration item 'auth_server_addr'
Line 47: unknown configuration item 'auth_server_port'
Line 48: unknown configuration item 'auth_server_shared_secret'

In order to fix this issue, the full wpad package must be installed. On some platforms that use an embedded TLS library, the wpad-basic-mbedtls package should be removed in favor of the wpad-mbedtls package:

opkg update
opkg remove wpad-basic-mbedtls
opkg install wpad-mbedtls

To check which package is installed, the wpad package should be listed amongst the installed packages:

opkg list | grep wpad

Now, the RADIUS options can be added, either by using UCI or manually. The options can be set manually using the hostapd_options configuration key. Here is a full configuration from /etc/config/wireless:

config wifi-device 'radio1'
        option type 'mac80211'
        option band '2g'
#        ...
        list hostapd_options 'auth_server_addr=192.168.1.5'
        list hostapd_options 'auth_server_port=1812'
        list hostapd_options 'auth_server_shared_secret=testing123'
        list hostapd_options 'macaddr_acl=2'

where:

  • auth_server_addr, auth_server_port, auth_server_shared_secret are the RADIUS options

For the weary, hostapd_options is defined as a list of options, as can be observed above, such that the uci command-line tool has to be invoked multiple times in order for the options to be added to the OpenWRT configuration:

uci add_list wireless.radio0.hostapd_options='auth_server_addr=192.168.1.5'
uci add_list wireless.radio0.hostapd_options='auth_server_port=1812'
uci add_list wireless.radio0.hostapd_options='auth_server_shared_secret=testing123'
uci add_list wireless.radio0.hostapd_options='macaddr_acl=2'

With this set up, there is not more need to mess with OpenWRT and the rest of the configuration involves setting up FreeRADIUS.

FreeRADIUS (with Docker)

It is possible to install FreeRADIUS on a separate server as a self-standing process but if a Docker swarm is running on the local network, it is more interesting to run FreeRADIUS on the swarm. The tendency would be to run FreeRADIUS on OpenWRT itself, and packages are provided therefore, however FreeRADIUS can also be ran outside of OpenWRT as a self-standing service. One of the justifications regarding running FreeRADIUS on a Docker swarm is that FreeRADIUS on its own can be seen as a giant authentication, authorization and accounting service that has split responsibilities contrasted to wireless radio in general. In fact, FreeRADIUS is not exactly as a database, it can run as a makeshift database using flat-file storage but the data it stores are properties that do not involve wireless nor routers in particular.

For some reason, the developers of FreeRADIUS provide a Docker image that cooks-in the configuration instead of providing the configuration on a separate volume, which seems awkward. Similarly, no default configuration is provided at all and the user is expected to write it all themselves. Interestingly, the Docker image is based on Debian such that the Debian FreeRADIUS configuration can be transferred over as a starting point. Simply install the freeradius package on a different server and then transfer the configuration over from /etc/freeradius/3.0/ to the raddb folder that is then cooked inside the resulting image.

Here is the official filesystem layout recommended in order to build the Docker container:

.
├── Dockerfile
└── raddb

where:

  • raddb should contain the configuration from a fresh install of FreeRADIUS from a Debian machine.

With the configuration transferred, the next step is to change the FreeRADIUS configuration in order to allow MAC authentication and authorization. This is done preferrably by using the minimal amount of changes to the default FreeRADIUS configuration.

Here is a patch file between the original default Debian FreeRADIUS configuration and the changes made to accomodate for MAC-based authentication:

diff -Naur /root/raddb-orig/clients.conf raddb/clients.conf
--- /root/raddb-orig/clients.conf       2024-12-19 04:45:06.605883128 +0000
+++ raddb/clients.conf  2024-12-19 04:55:13.182944655 +0000
@@ -27,7 +27,7 @@
 #  the "ipaddr" or "ipv6addr" fields.  For compatibility, the 1.x
 #  format is still accepted.
 #
-client localhost {
+client all {
        #  Only *one* of ipaddr, ipv4addr, ipv6addr may be specified for
        #  a client.
        #
@@ -39,7 +39,8 @@
        #
        #  If both A and AAAA records are found, A records will be
        #  used in preference to AAAA.
-       ipaddr = 127.0.0.1
+#      ipaddr = 127.0.0.1
+       ipaddr = *

        #  Same as ipaddr but allows v4 addresses only. Requires A
        #  record for domain names.
@@ -118,6 +119,7 @@
        #  longer necessary in >= 2.0
        #
 #      shortname = localhost
+       shortname = docker

        #
        # the following three fields are optional, but may be used by
diff -Naur /root/raddb-orig/mods-available/files raddb/mods-available/files
--- /root/raddb-orig/mods-available/files       2024-12-19 04:45:07.013889208 +0000
+++ raddb/mods-available/files  2024-12-19 10:38:09.925597711 +0000
@@ -15,6 +15,7 @@
        # of this attribute is used to match the "name" of the
        # entry.
        #key = "%{%{Stripped-User-Name}:-%{User-Name}}"
+       key = %{Calling-Station-ID}

        #  The old "users" style file is now located here.
        filename = ${moddir}/authorize
@@ -22,6 +23,7 @@
        #  This is accepted for backwards compatibility
        #  It will be removed in a future release.
 #      usersfile = ${moddir}/authorize
+       usersfile = /data/authorized_macs

        #  These are accepted for backwards compatibility.
        #  They will be renamed in a future release.
diff -Naur /root/raddb-orig/mods-enabled/files raddb/mods-enabled/files
--- /root/raddb-orig/mods-enabled/files 2024-12-19 04:45:08.769915379 +0000
+++ raddb/mods-enabled/files    2024-12-19 10:38:09.925597711 +0000
@@ -15,6 +15,7 @@
        # of this attribute is used to match the "name" of the
        # entry.
        #key = "%{%{Stripped-User-Name}:-%{User-Name}}"
+       key = %{Calling-Station-ID}

        #  The old "users" style file is now located here.
        filename = ${moddir}/authorize
@@ -22,6 +23,7 @@
        #  This is accepted for backwards compatibility
        #  It will be removed in a future release.
 #      usersfile = ${moddir}/authorize
+       usersfile = /data/authorized_macs

        #  These are accepted for backwards compatibility.
        #  They will be renamed in a future release.
diff -Naur /root/raddb-orig/radiusd.conf raddb/radiusd.conf
--- /root/raddb-orig/radiusd.conf       2024-12-19 04:45:09.009918956 +0000
+++ raddb/radiusd.conf  2024-12-19 04:56:21.075960641 +0000
@@ -510,8 +510,10 @@
        #  member.  This can allow for some finer-grained access
        #  controls.
        #
-       user = freerad
-       group = freerad
+#      user = freerad
+#      group = freerad
+       user = root
+       group = root

        #  Core dumps are a bad thing.  This should only be set to
        #  'yes' if you're debugging a problem with the server.
diff -Naur /root/raddb-orig/sites-available/default raddb/sites-available/default
--- /root/raddb-orig/sites-available/default    2024-12-19 04:45:09.157921162 +0000
+++ raddb/sites-available/default       2024-12-19 10:32:20.012466692 +0000
@@ -311,6 +311,9 @@
        #  and the 'raddb/mods-config/preprocess/huntgroups' files.
        preprocess

+       # Normalize MAC
+       rewrite_calling_station_id
+
        #  If you intend to use CUI and you require that the Operator-Name
        #  be set for CUI generation and you want to generate CUI also
        #  for your local clients then uncomment the operator-name
@@ -420,6 +423,19 @@
        #  raddb/mods-config/files/authorize
        files

+        if (!ok) {
+                 # No match was found, so reject
+                 reject
+         }
+         else {
+                 # The MAC address was found, so update Auth-Type
+                 # to accept this auth.
+                 update control {
+                         Auth-Type := Accept
+                 }
+         }
+
+
        #
        #  Look in an SQL database.  The schema of the database
        #  is meant to mirror the "users" file.
@@ -610,6 +626,9 @@
 preacct {
        preprocess

+       # Normalize MAC
+       rewrite_calling_station_id
+
        #
        #  Merge Acct-[Input|Output]-Gigawords and Acct-[Input-Output]-Octets
        #  into a single 64bit counter Acct-[Input|Output]-Octets64.
diff -Naur /root/raddb-orig/sites-enabled/default raddb/sites-enabled/default
--- /root/raddb-orig/sites-enabled/default      2024-12-19 04:45:09.285923069 +0000
+++ raddb/sites-enabled/default 2024-12-19 10:32:20.012466692 +0000
@@ -311,6 +311,9 @@
        #  and the 'raddb/mods-config/preprocess/huntgroups' files.
        preprocess

+       # Normalize MAC
+       rewrite_calling_station_id
+
        #  If you intend to use CUI and you require that the Operator-Name
        #  be set for CUI generation and you want to generate CUI also
        #  for your local clients then uncomment the operator-name
@@ -420,6 +423,19 @@
        #  raddb/mods-config/files/authorize
        files

+        if (!ok) {
+                 # No match was found, so reject
+                 reject
+         }
+         else {
+                 # The MAC address was found, so update Auth-Type
+                 # to accept this auth.
+                 update control {
+                         Auth-Type := Accept
+                 }
+         }
+
+
        #
        #  Look in an SQL database.  The schema of the database
        #  is meant to mirror the "users" file.
@@ -610,6 +626,9 @@
 preacct {
        preprocess

+       # Normalize MAC
+       rewrite_calling_station_id
+
        #
        #  Merge Acct-[Input|Output]-Gigawords and Acct-[Input-Output]-Octets
        #  into a single 64bit counter Acct-[Input|Output]-Octets64.

Amongst the changes, the following should probably adapted to the local scenario:

  • shortname is the name of the FreeRADIUS server, here set to docker,
  • user and group are both set to root; this is meant as a shortcut in order to be able to access the mounted volume but a proper user and group should probably be used in production,
  • usersfile sets a path to a file, in this case, /data/authorized_macs that will contain a list of MAC addresses that will be allowed access and the path therefore could be adjusted to something else if need be, depending on how Docker will be invoked

Additionally, although not changed in this example configuration, the secret configuration key, to be found inside the sites-enabled/default file should be changed from testing123 to something else provided that the OpenWRT /etc/config/wireless file is updated to match.

Here is the Dockerfile that is used to build the FreeRADIUS image:

FROM freeradius/freeradius-server:latest

# update package manager
RUN  apt-get update -y && \
     apt-get upgrade -y && \
     apt-get dist-upgrade -y && \
     apt-get -y autoremove && \
     apt-get clean

# generate SSL snakeoil for freeradius
RUN apt-get install ssl-cert && \
    make-ssl-cert generate-default-snakeoil

COPY raddb/ /etc/raddb/
VOLUME /data
EXPOSE 1812-1813:1812-1813/udp
ENTRYPOINT [ "freeradius", "-x", "-X", "-f" ]

where:

  • some additional configuration is added in order to install the snakeoil Debian certificates that is used by FreeRADIUS

Next, the Docker image is built:

docker build -t registry/freeradius:latest .

where:

  • registry/freeradius is a repository URL

and then pushed to the registry:

docker push registry/freeradius:latest

Lastly, the image has to be run. For that purpose, here is a Docker compose file:

version: '3.9'
services:
  freeradius:
    image: registry/freeradius:latest
    user: root
    ports:
      - 1812:1812
      - 1813:1813
      - 1812:1812/udp
      - 1813:1813/udp
    volumes:
      - /mnt/docker/data/freeradius:/data
    deploy:
      labels:
        - shepherd.enable=true
        - shepherd.auth.config=local
      replicas: 1
      placement:
        max_replicas_per_node: 1

where:

  • /mnt/docker/data/freeradius is the local path to a directory that will contain the authorized_macs file with a list of MAC addresses that is mounted within the container and referenced by the FreeRADIUS configuration

Adding and Removing MAC Addresses

In this configuration, the file /data/authorized_macs is only read on startup, such that after adding or removing MAC addresses, the container must be restarted. In order to address this issue, such that the /data/authorized_macs can be changed without having to manually restart Docker, an INOTIFY-based watcher can be added to the FreeRADIUS container in order to deliver a HUP signal, thereby making FreeRADIUS reload the configuration.

The result is a pretty neat combo of FreeRADIUS configured for MAC-based authentication that can be very easily deployed and takes care of everything needing only a single file containing MAC addresses to be provided and one other file to provide the FreeRADIUS secret:

In order to use the container, ensure that the volume that is mapped to the /data inside the container contains the files secret and macs where secret should contain the following:

secret = testing123

with test123 being a user supplied string representing the default FreeRADIUS password and the macs file should contain MAC addresses in IEEE format (with a dash - instead of colon :) listed line-by line, as in:

bc-ad-95-d6-65-f4
c4-c9-c5-ab-11-a7
d3-b4-dc-55-87-4b
e5-9d-42-7d-21-fc
3e-41-af-19-d9-30
2b-1e-1f-dc-14-46
96-2b-22-fa-f0-29
d0-d9-12-6a-23-92
61-d3-1e-c1-02-7c
53-9c-2a-9d-07-e4

After starting the container, FreeRADIUS can be monitored by attaching to the console from Docker and then reading the messages.

Debugging and Clarifications

The Docker compose file supplied runs freeradius with both -x and -X options in order to increase debugging. The general idea is that whenever a request is received by FreeRADIUS, it is processed through all the available authorization methods defined in the sites-enabled/default file. In this case, the authorization request runs through all the authentication methods, such as pop, mschap and the rest, some of them having been used even in other guides, such as authentication for a PPTP dialin server:

...
suffix: Checking for suffix after "@"
suffix: No '@' in User-Name = "2addf183853a", looking up realm NULL
suffix: No such realm "NULL"
    modsingle[authorize]: returned from suffix (rlm_realm)
    [suffix] = noop
    modsingle[authorize]: calling eap (rlm_eap)
eap: No EAP-Message, not doing EAP
    modsingle[authorize]: returned from eap (rlm_eap)
    [eap] = noop
    modsingle[authorize]: calling files (rlm_files)
...

and then eventually ends up in the files authentication method section:

...
%{Calling-Station-ID}
Parsed xlat tree:
attribute --> Calling-Station-Id
files: EXPAND %{Calling-Station-ID}
files:    --> 6B-77-12-96-38-1A
files: users: Checking entry 6B-77-12-96-38-1A at line 15
files: users: Matched entry 6B-77-12-96-38-1A at line 15
    modsingle[authorize]: returned from files (rlm_files)
    [files] = ok
    if (!ok) {
    if (!ok)  -> FALSE
    else {
      update control {
        Auth-Type := Accept
      } # update control = noop
    } # else = noop
...

where the files authentication method finds a match in the list of MAC addresses and returns a positive match.

In that sense, FreeRADIUS takes a best effort approach to authentication, running through all the methods and accepting the request in case a match has been found.

One thing to watch out for that deviates slightly from the base configuration is that various devices send the MAC address with different casings. In other words, if 6B-77-12-96-38-1A is received via an authentication request, and the file containing MAC addresses containers the lowercase equivalent 6b-77-12-96-38-1a, then the MAC address will not match. This leads to massive confusion because FreeRADIUS will definitely not throw an error, given that the files authentication method is just a plain string match line-by-line. For that reason, the official guide mentions adding a policy file that will normalize MAC addresses received in various formats to the IEEE standard representation of MAC addresses. The configuration lifted from Debian needs to be changed as well, but the configuration already defines the regular expression and the MAC address transformation, such that the rewrite_called_station_id configuration is added both in the authorization and preacct sections of the FreeRADIUS configuration. This has already been done in the patch provided in the FreeRADIUS server section.


openwrt/hardware_address_accounting_for_wireless_clients_with_freeradius.txt · Last modified: 2024/12/19 11:37 by office

Wizardry and Steamworks

© 2025 Wizardry and Steamworks

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.