From d4a14106b7568f736e6abd0a61c9939e6ae188f5 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Tue, 24 May 2022 09:17:47 +0200 Subject: [PATCH] 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 --- feeds/ucentral/radius-gw-proxy/Makefile | 25 ++ .../files/etc/init.d/radius-gw-proxy | 11 + .../radius-gw-proxy/src/CMakeLists.txt | 30 ++ feeds/ucentral/radius-gw-proxy/src/main.c | 356 ++++++++++++++++++ feeds/ucentral/ucentral-client/Makefile | 4 +- feeds/ucentral/ucentral-schema/Makefile | 4 +- .../ucentral/examples/radius-gw-proxy.json | 91 +++++ profiles/ucentral-ap.yml | 1 + 8 files changed, 517 insertions(+), 5 deletions(-) create mode 100644 feeds/ucentral/radius-gw-proxy/Makefile create mode 100755 feeds/ucentral/radius-gw-proxy/files/etc/init.d/radius-gw-proxy create mode 100644 feeds/ucentral/radius-gw-proxy/src/CMakeLists.txt create mode 100644 feeds/ucentral/radius-gw-proxy/src/main.c create mode 100644 feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/radius-gw-proxy.json diff --git a/feeds/ucentral/radius-gw-proxy/Makefile b/feeds/ucentral/radius-gw-proxy/Makefile new file mode 100644 index 000000000..fae918ccb --- /dev/null +++ b/feeds/ucentral/radius-gw-proxy/Makefile @@ -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 + +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)) diff --git a/feeds/ucentral/radius-gw-proxy/files/etc/init.d/radius-gw-proxy b/feeds/ucentral/radius-gw-proxy/files/etc/init.d/radius-gw-proxy new file mode 100755 index 000000000..5c56b5a04 --- /dev/null +++ b/feeds/ucentral/radius-gw-proxy/files/etc/init.d/radius-gw-proxy @@ -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 +} diff --git a/feeds/ucentral/radius-gw-proxy/src/CMakeLists.txt b/feeds/ucentral/radius-gw-proxy/src/CMakeLists.txt new file mode 100644 index 000000000..f6f5a346e --- /dev/null +++ b/feeds/ucentral/radius-gw-proxy/src/CMakeLists.txt @@ -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 +) diff --git a/feeds/ucentral/radius-gw-proxy/src/main.c b/feeds/ucentral/radius-gw-proxy/src/main.c new file mode 100644 index 000000000..b3015155b --- /dev/null +++ b/feeds/ucentral/radius-gw-proxy/src/main.c @@ -0,0 +1,356 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#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; +} diff --git a/feeds/ucentral/ucentral-client/Makefile b/feeds/ucentral/ucentral-client/Makefile index cfb8bfd8d..acc4f5837 100644 --- a/feeds/ucentral/ucentral-client/Makefile +++ b/feeds/ucentral/ucentral-client/Makefile @@ -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 diff --git a/feeds/ucentral/ucentral-schema/Makefile b/feeds/ucentral/ucentral-schema/Makefile index b2c274523..4098bdc9a 100644 --- a/feeds/ucentral/ucentral-schema/Makefile +++ b/feeds/ucentral/ucentral-schema/Makefile @@ -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 PKG_LICENSE:=BSD-3-Clause diff --git a/feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/radius-gw-proxy.json b/feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/radius-gw-proxy.json new file mode 100644 index 000000000..5eb224a34 --- /dev/null +++ b/feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/radius-gw-proxy.json @@ -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 + } + } +} diff --git a/profiles/ucentral-ap.yml b/profiles/ucentral-ap.yml index 59f50f740..9c5a91ab8 100644 --- a/profiles/ucentral-ap.yml +++ b/profiles/ucentral-ap.yml @@ -30,6 +30,7 @@ packages: - lldpd - maverick - opennds + - radius-gw-proxy - radsecproxy - ratelimit - rtty-openssl