I've got a PCEngines APU running Debian Jessie.
I'm trying to configure hostapd to perform wired 802.1x authentication against a remote domain controller running NPS on 2008 R2.
I would like 2 of it's network ports to join a bridge interface once authenticated that will have our subnet configured, DHCP relay running & router IP our clients can talk to.
After some digging, the "bridge" config option in hostapd seems to only be applicable for certain WiFi drivers, not the wired driver.
If I add the ports to the bridge on boot, but have hostapd running on each interface, the users can pass traffic and use the interfaces without authentication.
Without doing that, clients can connect and authenticate properly, but there's obviously no network for them to talk to ( I have tried to bridge config option, hoping it would automatically join to the bridge, but it doesn't ).
My hostapd config is as follows:
interface=eth1
driver=wired
ieee8021x=1
use_pae_group_addr=1
eap_reauth_period=3600
eapol_version=2
# RADIUS authentication server
auth_server_addr=**secret**
auth_server_port=1812
auth_server_shared_secret=**secret**
# RADIUS accounting server
acct_server_addr=**secret**
acct_server_port=1813
acct_server_shared_secret=**secret**
logger_syslog=-1
logger_syslog_level=2
Does anyone know if what I am after is possible, or what I'm doing wrong?
Edit:
I read that hostapd doesn't implement the full authenticator stack for the "wired" driver, so can't be used to secure a port with 802.1x out of the box.
I also read it should be possible to use the control interface to listen for events, then use an external program to control the bridge.
I did this and it works initially, but after about 3 mins the clients always get disconnected. It is logged in syslog as due to inactivity.
Even so, I'll include my CGo code below:
package main
/*
#cgo CFLAGS: -DCONFIG_CTRL_IFACE -DCONFIG_CTRL_IFACE_UNIX
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "libbridge.h"
#include "wpa_ctrl.h"
*/
import "C"
import "unsafe"
import "fmt"
import "time"
import "strings"
var briface string = "br0"
var auiface string = "eth1"
var hostapd_path string = "/var/run/hostapd/"
var connected bool
var current_mac string
var wpa_ctl *C.struct_wpa_ctrl
func main() {
br_del()
hostapd_connect()
defer func(){
if wpa_ctl != nil {
C.wpa_ctrl_detach(wpa_ctl)
C.wpa_ctrl_close(wpa_ctl)
}
}()
for {
for C.wpa_ctrl_pending(wpa_ctl) > 0 {
log(fmt.Sprintf("Reading message from hostapd..."))
var buf [256]C.char
var llen C.size_t = C.size_t(unsafe.Sizeof(buf) - 1)
if C.wpa_ctrl_recv(wpa_ctl, &buf[0], &llen) == 0 {
null := C.CString("\000")
buf[llen] = *null
C.free(unsafe.Pointer(null))
//fmt.Printf("%s\n", C.GoString(&buf[0]))
msg := C.GoString(&buf[0])
mData := strings.Split(msg, " ")
if len(mData) < 2 {
log(fmt.Sprintf("Event data too short when processing message: %s", msg))
continue
}
switch (mData[0]){
case "<3>AP-STA-CONNECTED":
log(fmt.Sprintf("Got Device Authentication, adding to bridge..."))
br_add()
connected = true
current_mac = mData[1]
case "<3>AP-STA-DISCONNECTED":
log(fmt.Sprintf("Got Device Disconnect, removing from bridge..."))
br_del()
connected = false
case "<3>CTRL-EVENT-EAP-STARTED":
if connected {
if mData[1] != current_mac {
log(fmt.Sprintf("Got active MAC different from current MAC. %s vs %s - Disconnecting current.", mData[1], current_mac))
hostapd_disconnect(current_mac)
}
}
}
} else {
break
}
}
if ! hostapd_ping() {
C.wpa_ctrl_detach(wpa_ctl)
C.wpa_ctrl_close(wpa_ctl)
log(fmt.Sprintf("Lost connection to hostapd, reconnecting..."))
hostapd_connect()
}
time.Sleep(time.Millisecond * 100)
}
}
func hostapd_connect(){
ci := C.CString(hostapd_path + auiface)
defer C.free(unsafe.Pointer(ci))
for {
wpa_ctl = C.wpa_ctrl_open(ci)
if wpa_ctl != nil {
log(fmt.Sprintf("Connected to hostapd OK, attach..."))
if C.wpa_ctrl_attach(wpa_ctl) == 0 {
log(fmt.Sprintf("Attached event listener..."))
break
} else {
fmt.Printf("Failed to attach to event listener.")
C.wpa_ctrl_close(wpa_ctl)
}
} else {
log(fmt.Sprintf("Failed to connect to hostapd control socket, waiting for retry..."))
}
time.Sleep(time.Millisecond * 100)
}
}
func hostapd_disconnect(mac string){
ci := C.CString(hostapd_path + auiface)
defer C.free(unsafe.Pointer(ci))
dc := C.wpa_ctrl_open(ci)
if dc == nil {
log(fmt.Sprintf("Error opening connection to disconnect current station."))
return
}
defer C.wpa_ctrl_close(dc)
var buf [4096]C.char
var len C.size_t = C.size_t(unsafe.Sizeof(buf) - 1)
cping := C.CString("deauthenticate " + mac)
defer C.free(unsafe.Pointer(cping))
ret := C.wpa_ctrl_request(dc, cping, C.strlen(cping), &buf[0], &len, nil)
if (ret == -2) {
log(fmt.Sprintf("Station disconnect failed with timeout..."))
} else if (ret < 0) {
log(fmt.Sprintf("Station disconnect failed..."))
}
log(fmt.Sprintf("Station disconnect requested."))
}
func hostapd_ping() (bool) {
var buf [4096]C.char
var len C.size_t = C.size_t(unsafe.Sizeof(buf) - 1)
cping := C.CString("PING")
defer C.free(unsafe.Pointer(cping))
ret := C.wpa_ctrl_request(wpa_ctl, cping, C.strlen(cping), &buf[0], &len, nil)
if (ret == -2) {
log(fmt.Sprintf("PING failed with timeout..."))
return false
} else if (ret < 0) {
log(fmt.Sprintf("PING failed..."))
return false
}
return true
}
func br_add(){
br := C.CString(briface)
defer C.free(unsafe.Pointer(br))
ifa := C.CString(auiface)
defer C.free(unsafe.Pointer(ifa))
if ( C.br_init() > 0 ) {
log(fmt.Sprintf("Can't setup bridge control in br_add. Failed to add interface to bridge."))
return
}
ret := C.br_add_interface(br, ifa)
if ret > 0 {
log(fmt.Sprintf("Failed to add interface to bridge, error code: %d", ret))
return
}
}
func br_del(){
br := C.CString(briface)
defer C.free(unsafe.Pointer(br))
ifa := C.CString(auiface)
defer C.free(unsafe.Pointer(ifa))
if ( C.br_init() > 0 ) {
log(fmt.Sprintf("Can't setup bridge control in br_del. Failed to remove interface from bridge."))
return
}
ret := C.br_del_interface(br, ifa)
if ret > 0 {
log(fmt.Sprintf("Failed to remove interface from bridge, error code: %d", ret))
return
}
}
func log(data string) {
fmt.Printf("%s\n", data)
}