radius_gw_proxy: add support for a radius/gateway proxy

This will allow the AP to send radius requests via the websocket
to the gateway for routing to the correct AAA.

Fixes: WIFI-7328
Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
John Crispin
2022-05-24 09:17:47 +02:00
parent 85af9d7e0b
commit d4a14106b7
8 changed files with 517 additions and 5 deletions

View File

@@ -0,0 +1,25 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=radius-gw-proxy
PKG_RELEASE:=1
PKG_LICENSE:=BSD-3-Clause
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
define Package/radius-gw-proxy
SECTION:=ucentral
CATEGORY:=uCentral
TITLE:=uCentral Gateway radius-gw-proxy
DEPENDS:=+libubox +libubus
endef
define Package/radius-gw-proxy/install
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/radius-gw-proxy $(1)/usr/sbin/
$(CP) ./files/* $(1)
endef
$(eval $(call BuildPackage,radius-gw-proxy))

View File

@@ -0,0 +1,11 @@
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
start_service() {
procd_open_instance
procd_set_param command "/usr/sbin/radius-gw-proxy"
procd_close_instance
}

View File

@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 2.6)
PROJECT(radius-gw-proxy C)
ADD_DEFINITIONS(-Wall -Werror)
IF(CMAKE_C_COMPILER_VERSION VERSION_GREATER 6)
ADD_DEFINITIONS(-Wextra -Werror=implicit-function-declaration)
ADD_DEFINITIONS(-Wformat -Werror=format-security -Werror=format-nonliteral)
ENDIF()
ADD_DEFINITIONS(-Os -std=gnu99 -g3 -Wmissing-declarations -Wno-unused-parameter)
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
SET(SOURCES main.c)
FIND_LIBRARY(ubus NAMES ubus)
FIND_LIBRARY(ubox NAMES ubox)
FIND_PATH(ubox_include_dir libubox/uloop.h)
FIND_PATH(ubus_include_dir NAMES libubus.h)
INCLUDE_DIRECTORIES(${ubox_include_dir} ${ubus_include_dir})
ADD_EXECUTABLE(radius-gw-proxy ${SOURCES})
TARGET_LINK_LIBRARIES(radius-gw-proxy ${ubox} ${ubus})
INSTALL(TARGETS radius-gw-proxy
RUNTIME DESTINATION sbin
)

View File

@@ -0,0 +1,356 @@
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libubox/uloop.h>
#include <libubox/usock.h>
#include <libubox/avl.h>
#include <libubox/avl-cmp.h>
#include <libubus.h>
#define RAD_PROX_BUFLEN (4 * 1024)
#define TLV_STATION_ID 30
#define TLV_VENDOR 26
#define TLV_TIP_SERVER_V4 2
#define TIP_VENDOR 58888
enum socket_type {
RADIUS_AUTH = 0,
RADIUS_ACCT,
RADIUS_DAS
};
struct radius_socket {
struct uloop_fd fd;
enum socket_type type;
};
struct radius_header {
uint8_t code;
uint8_t id;
uint16_t len;
char auth[16];
char avp[];
};
struct radius_tlv {
uint8_t id;
uint8_t len;
char data[];
};
struct radius_station_id {
char id[256];
enum socket_type type;
};
struct radius_station {
struct avl_node avl;
struct radius_station_id key;
int port;
};
static struct ubus_auto_conn conn;
static uint32_t ucentral;
static struct blob_buf b;
static int
avl_memcmp(const void *k1, const void *k2, void *ptr)
{
return memcmp(k1, k2, sizeof(struct radius_station_id));
}
static AVL_TREE(radius_stations, avl_memcmp, false, NULL);
static void
radius_station_add(char *id, int port, enum socket_type type)
{
struct radius_station *station;
struct radius_station_id key = { .type = type };
strcpy(key.id, id);
station = avl_find_element(&radius_stations, &key, station, avl);
if (!station) {
printf("new station/port, adding to avl tree\n");
station = malloc(sizeof(*station));
memset(station, 0, sizeof(*station));
strcpy(station->key.id, id);
station->key.type = type;
station->avl.key = &station->key;
avl_insert(&radius_stations, &station->avl);
}
station->port = port;
}
static char *
b64(char *src, int len)
{
char *dst;
int ret;
if (!src)
return NULL;
dst = malloc(len * 4);
ret = b64_encode(src, len, dst, len * 4);
if (ret < 1) {
free(dst);
return NULL;
}
return dst;
}
static void
radius_forward(char *buf, char *server, enum socket_type type)
{
struct radius_header *hdr = (struct radius_header *) buf;
struct ubus_request async = { };
char *data = b64(buf, hdr->len);
if (!data)
return;
blob_buf_init(&b, 0);
switch (type) {
case RADIUS_AUTH:
blobmsg_add_string(&b, "radius", "auth");
break;
case RADIUS_ACCT:
blobmsg_add_string(&b, "radius", "acct");
break;
default:
return;
}
blobmsg_add_string(&b, "data", data);
blobmsg_add_string(&b, "dst", server);
ubus_invoke_async(&conn.ctx, ucentral, "radius", b.head, &async);
ubus_abort_request(&conn.ctx, &async);
free(data);
}
static int
radius_parse(char *buf, int len, int port, enum socket_type type)
{
struct radius_header *hdr = (struct radius_header *) buf;
struct radius_tlv *station_id = NULL;
char station_id_str[256] = {};
char server_ip_str[256] = {};
void *avp = hdr->avp;
hdr->len = ntohs(hdr->len);
if (hdr->len != len) {
printf("invalid header length\n");
return -1;
}
printf("\tcode:%d, id:%d, len:%d\n", hdr->code, hdr->id, hdr->len);
len -= sizeof(*hdr);
while (len > 0) {
struct radius_tlv *tlv = (struct radius_tlv *)avp;
if (len < tlv->len) {
printf("invalid TLV length\n");
return -1;
}
if (tlv->id == TLV_STATION_ID)
station_id = tlv;
if (tlv->id == TLV_VENDOR && ntohl(*(uint32_t *) tlv->data) == TIP_VENDOR) {
struct radius_tlv *vendor = (struct radius_tlv *) &tlv->data[6];
if (vendor->id == TLV_TIP_SERVER_V4)
strncpy(server_ip_str, vendor->data, vendor->len - 2);
}
printf("\tID:%d, len:%d\n", tlv->id, tlv->len);
avp += tlv->len;
len -= tlv->len;
}
if (!station_id) {
printf("no station ID found\n");
return -1;
}
if (!*server_ip_str) {
printf("no server ip found\n");
return -1;
}
memcpy(station_id_str, station_id->data, station_id->len - 2);
printf("\tcalling station id:%s, server ip:%s\n", station_id_str, server_ip_str);
radius_station_add(station_id_str, port, type);
radius_forward(buf, server_ip_str, type);
return 0;
}
static void
sock_recv(struct uloop_fd *u, unsigned int events)
{
static char buf[RAD_PROX_BUFLEN];
static char cmsg_buf[( CMSG_SPACE(sizeof(struct in_pktinfo)) + sizeof(int)) + 1];
static struct sockaddr_in sin;
char addr_str[INET_ADDRSTRLEN];
static struct iovec iov = {
.iov_base = buf,
.iov_len = sizeof(buf)
};
static struct msghdr msg = {
.msg_name = &sin,
.msg_namelen = sizeof(sin),
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsg_buf,
.msg_controllen = sizeof(cmsg_buf),
};
struct cmsghdr *cmsg;
struct radius_socket *sock = container_of(u, struct radius_socket, fd);
int len;
do {
struct in_pktinfo *pkti = NULL;
len = recvmsg(u->fd, &msg, 0);
if (len < 0) {
switch (errno) {
case EAGAIN:
return;
case EINTR:
continue;
default:
perror("recvmsg");
uloop_fd_delete(u);
return;
}
}
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_type != IP_PKTINFO)
continue;
pkti = (struct in_pktinfo *) CMSG_DATA(cmsg);
}
if (!pkti) {
printf("Received packet without ifindex\n");
continue;
}
inet_ntop(AF_INET, &sin.sin_addr, addr_str, sizeof(addr_str));
printf("RX: src:%s:%d, len=%d\n", addr_str, sin.sin_port, len);
radius_parse(buf, len, sin.sin_port, sock->type);
} while (1);
}
static struct radius_socket *
sock_open(char *port, enum socket_type type)
{
struct radius_socket *sock = malloc(sizeof(*sock));
int yes = 1;
if (!sock)
return NULL;
memset(sock, 0, sizeof(*sock));
sock->fd.fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK |
USOCK_NUMERIC | USOCK_IPV4ONLY,
"127.0.0.1", port);
if (sock->fd.fd < 0) {
perror("usock");
free(sock);
return NULL;
}
if (setsockopt(sock->fd.fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0)
perror("setsockopt(IP_PKTINFO)");
sock->type = type;
sock->fd.cb = sock_recv;
uloop_fd_add(&sock->fd, ULOOP_READ);
return sock;
}
static void
ubus_event_handler_cb(struct ubus_context *ctx, struct ubus_event_handler *ev,
const char *type, struct blob_attr *msg)
{
enum {
EVENT_ID,
EVENT_PATH,
__EVENT_MAX
};
static const struct blobmsg_policy status_policy[__EVENT_MAX] = {
[EVENT_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 },
[EVENT_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
};
struct blob_attr *tb[__EVENT_MAX];
char *path;
uint32_t id;
blobmsg_parse(status_policy, __EVENT_MAX, tb, blob_data(msg), blob_len(msg));
if (!tb[EVENT_ID] || !tb[EVENT_PATH])
return;
path = blobmsg_get_string(tb[EVENT_PATH]);
id = blobmsg_get_u32(tb[EVENT_ID]);
if (strcmp(path, "ucentral"))
return;
if (!strcmp("ubus.object.remove", type))
ucentral = 0;
else
ucentral = id;
}
static struct ubus_event_handler ubus_event_handler = { .cb = ubus_event_handler_cb };
static void
ubus_connect_handler(struct ubus_context *ctx)
{
ubus_register_event_handler(ctx, &ubus_event_handler, "ubus.object.add");
ubus_register_event_handler(ctx, &ubus_event_handler, "ubus.object.remove");
ubus_lookup_id(ctx, "ucentral", &ucentral);
}
int main(int argc, char **argv)
{
uloop_init();
conn.cb = ubus_connect_handler;
ubus_auto_connect(&conn);
sock_open("1812", RADIUS_AUTH);
sock_open("1813", RADIUS_ACCT);
uloop_run();
uloop_end();
return 0;
}

View File

@@ -4,10 +4,10 @@ PKG_NAME:=ucentral-client
PKG_RELEASE:=1
PKG_SOURCE_URL=https://github.com/blogic/ucentral-client.git
PKG_MIRROR_HASH:=37a1b7393cf5d15dbcd4840d9ffb2b16bf5b43cdd5c0da955c744bfc10211cbc
PKG_MIRROR_HASH:=ca93c95eaec7c4db45de3972db537ca9f022b00801d5df9813f135d9583d3f98
PKG_SOURCE_PROTO:=git
PKG_SOURCE_DATE:=2022-01-10
PKG_SOURCE_VERSION:=6cb4485ab49c5ab9244fb55af9fd0e1801f154f4
PKG_SOURCE_VERSION:=e749518bf14bc11ee41c445b168a26ae3860a35b
PKG_LICENSE:=BSD-3-Clause
PKG_MAINTAINER:=John Crispin <john@phrozen.org>

View File

@@ -4,11 +4,9 @@ PKG_NAME:=ucentral-schema
PKG_RELEASE:=1
PKG_SOURCE_URL=https://github.com/blogic/ucentral-schema.git
PKG_MIRROR_HASH:=562e558135f2da69ae4ae0573813b06b61d3071a026d478678822b13d5b7dee0
#PKG_MIRROR_HASH:=cffdabf01601cd9508d77301fc9f6ab65a2f99ad4a35e3eeed31e9c6b161f63c
PKG_SOURCE_PROTO:=git
PKG_SOURCE_DATE:=2022-01-17
PKG_SOURCE_VERSION:=48292adfa238fb81bb3fe0c047e28541c6fe5875
PKG_SOURCE_VERSION:=992d376434a8c47557140da574ad40349e61aa32
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
PKG_LICENSE:=BSD-3-Clause

View File

@@ -0,0 +1,91 @@
{
"uuid": 2,
"radios": [
{
"band": "5G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80,
"channel": 36
}
],
"interfaces": [
{
"name": "WAN",
"role": "upstream",
"ethernet": [
{
"select-ports": [
"WAN*"
]
}
],
"ipv4": {
"addressing": "dynamic"
},
"ssids": [
{
"name": "OpenWifi",
"wifi-bands": [
"2G", "5G"
],
"bss-mode": "ap",
"encryption": {
"proto": "wpa2",
"ieee80211w": "optional"
},
"radius": {
"authentication": {
"host": "192.168.178.192",
"port": 1812,
"secret": "secret"
},
"accounting": {
"host": "192.168.178.192",
"port": 1813,
"secret": "secret"
}
},
"services": [ "radius-gw-proxy" ]
}
]
},
{
"name": "LAN",
"role": "downstream",
"services": [ "ssh" ],
"ethernet": [
{
"select-ports": [
"LAN*"
]
}
],
"ipv4": {
"addressing": "static",
"subnet": "192.168.1.1/24",
"dhcp": {
"lease-first": 10,
"lease-count": 100,
"lease-time": "6h"
}
}
}
],
"metrics": {
"statistics": {
"interval": 120,
"types": [ "ssids", "lldp", "clients" ]
},
"health": {
"interval": 120
}
},
"services": {
"ssh": {
"port": 22
}
}
}

View File

@@ -30,6 +30,7 @@ packages:
- lldpd
- maverick
- opennds
- radius-gw-proxy
- radsecproxy
- ratelimit
- rtty-openssl