Wireless Lan with 802.1x

Share wlan with a single mac80211 capable nic and networkmanager

Example updated to use systemd interface names that embed the PCI and/or USB bus device path in the interface names

In this example we used wlp2s0 with networkmanager to connect to an upstream wlan service. We created a wlp2s0ap with bridge ext0 interface to host a copy for clients.

Networkmanager needs to be told to ignore interfaces used to provide the accesspoint, otherwise they may be used as secondary station interfaces, although thta can also be useful to join several AndroidAP at once. Insert to /etc/NetworkManager/NetworkManager.conf to do that. I also removed references to ifupdown


Hostapd and all androidap that need to share the same station, have to all be set to use the same channel, so if one of these is an upstream we don't control, such as some remote hotspot service, then script this to set all our devices to the same channel.

We added a small delay to limit the rate at which our AP stomps on traffic from our station side, since by necessity it is all on the same channel. This has the tendency to prioritise the ap-station's own traffic over its own clients.

D-Bus can be used to dump stations from wpa-supplicant without upsetting networkmanager.

Recent NetworkManager now accepts the idea of multiple interfaces with the same MAC address, so the only residual reason is some code in linux, since we like to use the proper MAC, apart from mathematical necessity.

A "phy" with an AP interface and any number of station interfaces could easily share the same MAC address, because:

Allow Identical mac addresses anyway.

It is still possible with hack of iface.c to disable identical_mac_addr_allowed, as an unoffical modification this would taint, although it would be nice if this was in the kernel, maybe as sysctl or similar so I don't need to EXTMOD it. It makes quite a few fun things possible.

  1. --- net/mac80211/iface.c
  2. +++ net/mac80211/iface.c
  3. @@ -305,3 +304,0 @@
  4. - if (!identical_mac_addr_allowed(iftype,
  5. - nsdata->vif.type))
  6. - return -ENOTUNIQ;
  1. make -C /usr/src/linux-headers-$(uname -r) KBUILD_EXTMOD=/usr/src/linux-source-*/net/mac80211/
  2. modprobe -r ath5k
  3. modprobe cfg80211
  4. dpkg-divert --add --rename /lib/modules/$(uname -r)/kernel/net/mac80211/mac80211.ko
  5. ln --symbolic /usr/src/linux-source-4.9/net/mac80211/mac80211.ko /lib/modules/$(uname -r)/kernel/net/mac80211/mac80211.ko
  6. depmod -a
  7. modprobe ath5k

Why I would want do allow this

  1. iface wlp2s0ap inet manual
  2. #pre-up /sbin/iw phy phy0 interface add wlp2s0ap type __ap
  3. pre-up /sbin/iw phy phy0 interface add wlp2s0ap type managed
  4. pre-up ifconfig wlp2s0ap hw ether $(eval `cat /sys/class/ieee80211/phy0/macaddress | sed -s "s/^/printf \"%02x%s\" \\$(( 2^0x/;s/:/)) :/;"`)
  5. pre-up iwconfig wlp2s0ap retry 16 power on txpower auto
  6. pre-up sed -si "s/^channel=.*$/channel="`iwgetid -r --channel`"/" /etc/hostapd/hostapd.conf
  7. pre-up sleep 1
  8. post-down /sbin/iw dev wlp2s0ap del
  9. hostapd /etc/hostapd/hostapd.conf
  1. iface ext0 inet static
  2. pre-up sysctl net.ipv6.conf.default.forwarding=1
  3. pre-up sleep 1
  4. up tc qdisc add dev ext0 root netem delay 250ms
  5. address
  6. bridge_ports wlp2s0ap
  7. bridge_stp off
  8. bridge_ageing 0
  9. bridge_bridgeprio 34000
  10. bridge_fd 0
  11. #bridge_gcint 0
  12. bridge_hello 0
  13. bridge_maxage 0
  14. bridge_maxwait 0

IPtables NAT masquerade is then setup with wlp2s0 as the external interface and ext0 as the internal, with dhcp and possibly DNS service.


Recent editions of network manager allow the instantiation of interfaces in ad-hoc and AP mode, using functionality embedded in wpa_supplicant.

This did not have quite the same feature level as hostapd.

Other Implementatiions

hostapd and random android macs

Previously hostapd needed a file that mapped mac addresses to vlan, called from hostapd.conf accept_mac_file

This matched hostapd.wpa_psk where PSK would be matched with a mac address. This of course breaks down when a client changes its mac address at random, although as a good feature to support it, hostapd needs to map psk directly to the vlan of admission, allowing them to be segregated by trust level.

It is actually good from a privacy viewpoint as the mac addresses is outside the WPA wrapper, and is possible to go all in with re-connection randomisation as well, although that may cause captive portal that tracks user by mac to re-register.

This feature has been spotted in hostapd.wpa_psk example file, verified in changelog as of 2019-04-21 - v2.8, and quickly enabled 😸

All that is needed is to combine the data from .accept with the .wpa_psk, and a mac address match is not required any longer. accept_mac_address is commented off although dynamic_vlan=2 remains. Since this change it may no longer necessary or even desirable to have guests specify IEEE assigned unique "device mac", as they can be identified by PSK. Whilst it will be desirable to have the choice to fix DHCPv4 and DHCPv6 to a PSK (ignoring the mac, it is likely a good first step, as that is maybe the one remaining usecase that needs the device mac.)

When the macs are random it means that the ap and thus everything beyond, can only tell stations apart based on the psk itself, so the keyid= parameter is introduced to give a persistent label to each psk, this can be viewid in hostapd_cli and likely scripted into use such as with dhcpd.

  1. LINE=0
  2. while read MAC PSK
  3. do
  4. LINE=$(( ${LINE} + 1))
  5. VLAN=$(grep "${MAC}" wlp4s0.accept | cut -d" " -f2)
  6. echo vlanid=${VLAN} keyid=example${LINE} "00:00:00:00:00:00" "${PSK}"
  7. done < wlp4s0.wpa_psk.old


UK free wlan operator www.thecloud.net used to have an Android application that enabled FastConnect, and gave an encrypted session between station and AP, a peek in settings revealed some details for creating a profile manually, on both Android and in Network Manager.

_The Cloud X
EAP method
Phase 2 authentication
PAP (MSCHAPv may also work)
Identity (when this was first seen, it was written MYCLOUD/)
mycloud/registered_e-mail for account
Anonymous Identitiy
Used the same as the Identity

Some newer Android request the Common Name and/or DNS field of server x.509 certificate, found it to be thecloud.net

Fairphone 2 and EAP

If user has a fairphone2 then it is more difficult to use connection security because wifi utility crashes when attempting to add an WPA-EAP connection, so setup a hostapd temporarily with "_The Cloud X" and user's own domain name instead of thecloud.net in "use system certificates"

The eap_user file contains an extra line

User then tests by attempting to connect to the fake AP, at least getting as far as attempting to get an IP address.

When it works, exchange for the thecloud.net, and try to connect again, which would be expected to fail, thus showing that there will be reasonably secure access when in range of a real thecloud AP.


This program creates the certificates used by FreeRADIUS and other programs.

Add object identifiers to tell Windows XP clients what the certificates are for: cat /usr/share/doc/freeradius/examples/scripts/xpextensions >> /etc/ssl/openssl.cnf

Then alter the other options in openssl.conf, especially paths for certificates in CA_default and defaults for new certiciates in req_distinguished_name

First, you probably want a root certificate if you dont already have one. This is used to sign the other certificates, so clients only need to trust it, so that trust is implied for the other certificates. I used this script from the Jabber server. It asks for a password, but the password is taken off when it is requested again.

## This generates the cert and key
## The key will be valid for 3650 days.
## Be sure to enter the FQDN of your Jabber
## server as the "Common Name".
# -x509 means make root selfsigned cert
openssl req -new -x509 -newkey rsa:1024 -days 3650 -keyout privkey.pem -out key.pem
## This will remove the passphrase
openssl rsa -in privkey.pem -out privkey.pem
## Put it all together
cat privkey.pem >> key.pem
## Cleanup
rm privkey.pem

I rename key.pem to root.pem and it goes in the /etc/ssl/certs/ directory. If other users on the system cannot be trusted, then the private key can be split off the certificate and kept in /etc/ssl/certs/private/ directory. Do remember to edit [ CA_default ] in /etc/ssl/openssl.conf to tell it where the root certificate is.

Now a server certificate for freeradius can be made, with a common root. Server certs for other SSL using things like imaps, https, jabber are made similarly.

## This generates the cert and key
## The key will be valid for 3650 days.
## Be sure to enter the FQDN of your Jabber
## server as the "Common Name".
# -x509 means make root selfsigned cert
openssl req -new -newkey rsa:1024 -days 3650 -keyout privkey.pem -out key.pem

## This will remove the passphrase
# if you bother keeping this ... it matches that specified in /etc/freeradius/eap.conf as "private_key_password ="
# however it also ask for a challenge which is what xsupplicant might ask about
openssl rsa -in privkey.pem -out privkey.pem

## Put it all together
cat privkey.pem >> key.pem

# You may need to create these files as so if this is the first cert.
#echo > /etc/ssl/index.txt
#echo 01 > /etc/ssl/serial

# -extensions read in from section in openssl.cnf
# -extensions xpclient_ext if generating for supplicant, -extensions xpserver_ext if generating for FreeRADIUS
# XP does not recognise Radius or choose its certificate otherwise.
openssl ca -extensions xpserver_ext -policy policy_anything -out signedkey.pem -infiles key.pem

cat privkey.pem >> signedkey.pem
rm privkey.pem
rm key.pem

-extensions xpserver_ext is only used when making the FreeRADIUS server certificate. It is needed for Windows XP clients to be able to use the access point.

Windows XP seems to like its certificates provided in p12 files though... In this case the passphrase has to be used to encrypt the private key, but windows lets the user remove it when this certificate/private-key is imported!


openssl req -new -keyout newreq.pem -out newreq.pem -days 3650
openssl ca -extensions xpclient_ext -policy policy_anything -out newcert.pem -infiles newreq.pem
openssl pkcs12 -export -in newcert.pem -inkey newreq.pem -out cert-clt.p12 -clcerts
openssl pkcs12 -in cert-clt.p12 -out cert-clt.pem

To complete the XP setup, the public portion of the root certificate is also imported to trust it, then Certificate authentication is set up in wireless lan settings, with My key is provided for me

Freeradius Setup

I have patched freeradius 1.5.0. We want EAP-TLS to be built.

I like to keep all my certificates together in OpenSSL, so in the tls section of eap.conf I write:

tls {
# password is not needed if the certificate does not have one
# private_key_password = cats
private_key_file = /etc/ssl/certs/radius.pem
certificate_file = /etc/ssl/certs/radius.pem
CA_file = /etc/ssl/certs/root.pem
# CA_path = /etc/ssl/certs/
dh_file = /dev/urandom
random_file = /dev/urandom
fragment_size = 1024
# include_length = yes
# check_crl = yes
check_cert_cn = %{User-Name}

Freeradius with SQL

Firstly it’s needed to give Freeradius access to an SQL database. I’m using MySQL, so to do that do like:

create database freerad;
GRANT ALL ON freerad.* TO freerad@localhost IDENTIFIED BY 'password';
use freerad;
\. /usr/share/freeradius-dialupadmin/sql/userinfo.sql
\. /usr/share/freeradius-dialupadmin/sql/badusers.sql
\. /usr/share/freeradius-dialupadmin/sql/totacct.sql
\. /usr/share/freeradius-dialupadmin/sql/mtotacct.sql
-- import the following, though it's needed to gunzip db_mysql.sql.gz and uncomment all the tables in that.
\. /usr/share/doc/freeradius/examples/db_mysql.sql.gz

The dbms login details, i.e. server, database, username, password are also specified in the /etc/freeradius/sql.conf file. Also, for security, I activate the default profile features, so usernames that are not authorised are rejected, rather than default accepting them.

default_user_profile = "DEFAULT"
query_on_not_found = yes

Add a 802.1x user (I use EAP) to a Freeradius SQL Database.

It can be tested by trying to auth the user User's Laptop. DELETE FROM radcheck; should result in the auth attempt being rejected instead. The =27 is a mime HEX-escape for a ', and they are only used to check the UserName column. Thereafter the PHP dialupadmin provided with freeradius can be used to admin which usernames are authorised to connect via 802.1x

-- Here is the first SQL that I had FreeRADIUS accepting iff a given username is mentioned in the table

DELETE FROM radcheck;
DELETE FROM radreply;
DELETE FROM usergroup;
DELETE FROM radgroupcheck;
DELETE FROM radgroupreply;

INSERT INTO radcheck (UserName, Attribute, op, Value) VALUES
        ('User=27s Laptop', 'User-Name', '==', 'User''s Laptop'),
        ('User=27s Laptop', 'Auth-Type', ':=', 'EAP');

INSERT INTO radreply (UserName, Attribute, op, Value) VALUES
        ('User=27s Laptop', 'Reply-Message', '=', 'Welcome user %u on the laptop');

INSERT INTO usergroup (UserName, GroupName) VALUES
        ('DEFAULT', 'DEFAULT');

INSERT INTO radgroupcheck (GroupName, Attribute, op, Value) VALUES
        ('DEFAULT', 'User-Name', '!=', 'DEFAULT'),
        ('DEFAULT', 'Auth-Type', ':=', 'Reject');

INSERT INTO radgroupreply (GroupName, Attribute, op, Value) VALUES
        ('DEFAULT', 'Reply-Message', '=', 'You have no account!');

Some more openssl commands

openssl x509 -inform PEM -in root.pem -outform DER -out /tmp/root.cer