mirror of
https://github.com/Telecominfraproject/wlan-ap.git
synced 2025-10-29 01:22:25 +00:00
spotfilter: add advance captive packet filter
Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
57
feeds/ucentral/spotfilter/Makefile
Normal file
57
feeds/ucentral/spotfilter/Makefile
Normal file
@@ -0,0 +1,57 @@
|
||||
#
|
||||
# Copyright (C) 2021 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
include $(INCLUDE_DIR)/kernel.mk
|
||||
|
||||
PKG_NAME:=spotfilter
|
||||
PKG_VERSION:=1
|
||||
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
|
||||
|
||||
PKG_BUILD_DEPENDS:=bpf-headers
|
||||
PKG_FLAGS:=nonshared
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(INCLUDE_DIR)/cmake.mk
|
||||
include $(INCLUDE_DIR)/bpf.mk
|
||||
include $(INCLUDE_DIR)/nls.mk
|
||||
|
||||
define Package/spotfilter
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
TITLE:=Network filter for hotspot services
|
||||
DEPENDS:=+libbpf +libubox +libubus +libnl-tiny +kmod-sched-cake +kmod-sched-bpf $(BPF_DEPENDS)
|
||||
endef
|
||||
|
||||
TARGET_CFLAGS += \
|
||||
-Wno-error=deprecated-declarations \
|
||||
-I$(STAGING_DIR)/usr/include/libnl-tiny \
|
||||
-I$(STAGING_DIR)/usr/include -g3
|
||||
|
||||
CMAKE_OPTIONS += \
|
||||
-DLIBNL_LIBS=-lnl-tiny
|
||||
|
||||
define Build/Compile
|
||||
$(call CompileBPF,$(PKG_BUILD_DIR)/spotfilter-bpf.c)
|
||||
$(Build/Compile/Default)
|
||||
endef
|
||||
|
||||
define Package/spotfilter/install
|
||||
$(INSTALL_DIR) \
|
||||
$(1)/etc/hotplug.d/net \
|
||||
$(1)/etc/init.d \
|
||||
$(1)/lib/bpf \
|
||||
$(1)/usr/sbin
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/spotfilter-bpf.o $(1)/lib/bpf
|
||||
$(INSTALL_BIN) ./files/spotfilter.init $(1)/etc/init.d/spotfilter
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/spotfilter $(1)/usr/sbin/
|
||||
$(INSTALL_DATA) ./files/spotfilter.hotplug $(1)/etc/hotplug.d/net/10-spotfilter
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,spotfilter))
|
||||
2
feeds/ucentral/spotfilter/files/spotfilter.hotplug
Normal file
2
feeds/ucentral/spotfilter/files/spotfilter.hotplug
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
ubus call spotfilter check_devices
|
||||
23
feeds/ucentral/spotfilter/files/spotfilter.init
Normal file
23
feeds/ucentral/spotfilter/files/spotfilter.init
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (c) 2021 OpenWrt.org
|
||||
|
||||
START=18
|
||||
|
||||
USE_PROCD=1
|
||||
PROG=/usr/sbin/spotfilter
|
||||
|
||||
reload_service() {
|
||||
ubus call spotfilter interface_add "$(cat /tmp/spotfilter.json)"
|
||||
}
|
||||
|
||||
start_service() {
|
||||
procd_open_instance
|
||||
procd_set_param command "$PROG"
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
service_started() {
|
||||
ubus -t 10 wait_for spotfilter
|
||||
[ $? = 0 ] && reload_service
|
||||
}
|
||||
24
feeds/ucentral/spotfilter/src/CMakeLists.txt
Normal file
24
feeds/ucentral/spotfilter/src/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
PROJECT(spotfilter C)
|
||||
|
||||
ADD_DEFINITIONS(-Os -Wall -Wno-unknown-warning-option -Wno-array-bounds -Wno-format-truncation -Werror --std=gnu99)
|
||||
|
||||
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
|
||||
|
||||
IF (NOT DEFINED LIBNL_LIBS)
|
||||
include(FindPkgConfig)
|
||||
pkg_search_module(LIBNL libnl-3.0 libnl-3 libnl nl-3 nl)
|
||||
IF (LIBNL_FOUND)
|
||||
include_directories(${LIBNL_INCLUDE_DIRS})
|
||||
SET(LIBNL_LIBS ${LIBNL_LIBRARIES})
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
find_library(bpf NAMES bpf)
|
||||
ADD_EXECUTABLE(spotfilter main.c bpf.c ubus.c rtnl.c interface.c snoop.c client.c dhcpv4.c icmpv6.c nl80211.c)
|
||||
TARGET_LINK_LIBRARIES(spotfilter ${bpf} ubox ubus ${LIBNL_LIBS})
|
||||
|
||||
INSTALL(TARGETS spotfilter
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
|
||||
)
|
||||
196
feeds/ucentral/spotfilter/src/bpf.c
Normal file
196
feeds/ucentral/spotfilter/src/bpf.c
Normal file
@@ -0,0 +1,196 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <sys/resource.h>
|
||||
#include <sys/stat.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <glob.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "spotfilter.h"
|
||||
|
||||
static int spotfilter_bpf_pr(enum libbpf_print_level level, const char *format,
|
||||
va_list args)
|
||||
{
|
||||
return vfprintf(stderr, format, args);
|
||||
}
|
||||
|
||||
static void
|
||||
spotfilter_fill_rodata(struct bpf_object *obj, struct spotfilter_bpf_config *val)
|
||||
{
|
||||
struct bpf_map *map = NULL;
|
||||
|
||||
while ((map = bpf_object__next_map(obj, map)) != NULL) {
|
||||
if (!strstr(bpf_map__name(map), ".rodata"))
|
||||
continue;
|
||||
|
||||
bpf_map__set_initial_value(map, val, sizeof(*val));
|
||||
}
|
||||
}
|
||||
|
||||
static void spotfilter_init_env(void)
|
||||
{
|
||||
struct rlimit limit = {
|
||||
.rlim_cur = RLIM_INFINITY,
|
||||
.rlim_max = RLIM_INFINITY,
|
||||
};
|
||||
|
||||
setrlimit(RLIMIT_MEMLOCK, &limit);
|
||||
}
|
||||
|
||||
int spotfilter_bpf_load(struct interface *iface)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts);
|
||||
struct spotfilter_bpf_config config = {
|
||||
.snoop_ifindex = spotfilter_ifb_ifindex
|
||||
};
|
||||
struct bpf_program *prog_i, *prog_e;
|
||||
struct bpf_object *obj;
|
||||
int err;
|
||||
|
||||
libbpf_set_print(spotfilter_bpf_pr);
|
||||
|
||||
spotfilter_init_env();
|
||||
|
||||
obj = bpf_object__open_file(SPOTFILTER_PROG_PATH, &opts);
|
||||
err = libbpf_get_error(obj);
|
||||
if (err) {
|
||||
perror("bpf_object__open_file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
prog_i = bpf_object__find_program_by_name(obj, "spotfilter_in");
|
||||
if (!prog_i) {
|
||||
fprintf(stderr, "Can't find ingress classifier\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
prog_e = bpf_object__find_program_by_name(obj, "spotfilter_out");
|
||||
if (!prog_e) {
|
||||
fprintf(stderr, "Can't find egress classifier\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
bpf_program__set_type(prog_i, BPF_PROG_TYPE_SCHED_CLS);
|
||||
bpf_program__set_type(prog_e, BPF_PROG_TYPE_SCHED_CLS);
|
||||
|
||||
spotfilter_fill_rodata(obj, &config);
|
||||
|
||||
err = bpf_object__load(obj);
|
||||
if (err) {
|
||||
perror("bpf_object__load");
|
||||
goto error;
|
||||
}
|
||||
|
||||
iface->bpf.prog_ingress = bpf_program__fd(prog_i);
|
||||
iface->bpf.prog_egress = bpf_program__fd(prog_e);
|
||||
if ((iface->bpf.map_class = bpf_object__find_map_fd_by_name(obj, "class")) < 0 ||
|
||||
(iface->bpf.map_client = bpf_object__find_map_fd_by_name(obj, "client")) < 0 ||
|
||||
(iface->bpf.map_whitelist_v4 = bpf_object__find_map_fd_by_name(obj, "whitelist_ipv4")) < 0 ||
|
||||
(iface->bpf.map_whitelist_v6 = bpf_object__find_map_fd_by_name(obj, "whitelist_ipv6")) < 0) {
|
||||
perror("bpf_object__find_map_fd_by_name");
|
||||
goto error;
|
||||
}
|
||||
iface->bpf.obj = obj;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
bpf_object__close(obj);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int spotfilter_bpf_get_client(struct interface *iface,
|
||||
const struct spotfilter_client_key *key,
|
||||
struct spotfilter_client_data *data)
|
||||
{
|
||||
return bpf_map_lookup_elem(iface->bpf.map_client, key, data);
|
||||
}
|
||||
|
||||
int spotfilter_bpf_set_client(struct interface *iface,
|
||||
const struct spotfilter_client_key *key,
|
||||
const struct spotfilter_client_data *data)
|
||||
{
|
||||
if (!data)
|
||||
return bpf_map_delete_elem(iface->bpf.map_client, key);
|
||||
|
||||
return bpf_map_update_elem(iface->bpf.map_client, key, data, BPF_ANY);
|
||||
}
|
||||
|
||||
static void
|
||||
__spotfilter_bpf_set_device(struct interface *iface, int ifindex, bool egress, bool enabled)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook,
|
||||
.attach_point = egress ? BPF_TC_EGRESS : BPF_TC_INGRESS,
|
||||
.ifindex = ifindex);
|
||||
DECLARE_LIBBPF_OPTS(bpf_tc_opts, attach_tc,
|
||||
.handle = 1,
|
||||
.priority = SPOTFILTER_PRIO_BASE);
|
||||
|
||||
if (!enabled) {
|
||||
bpf_tc_detach(&hook, &attach_tc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (egress)
|
||||
attach_tc.prog_fd = iface->bpf.prog_egress;
|
||||
else
|
||||
attach_tc.prog_fd = iface->bpf.prog_ingress;
|
||||
|
||||
bpf_tc_hook_create(&hook);
|
||||
bpf_tc_attach(&hook, &attach_tc);
|
||||
}
|
||||
|
||||
void spotfilter_bpf_set_device(struct interface *iface, int ifindex, bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
spotfilter_bpf_set_device(iface, ifindex, false);
|
||||
|
||||
__spotfilter_bpf_set_device(iface, ifindex, true, enabled);
|
||||
__spotfilter_bpf_set_device(iface, ifindex, false, enabled);
|
||||
}
|
||||
|
||||
void spotfilter_bpf_update_class(struct interface *iface, uint32_t index)
|
||||
{
|
||||
bpf_map_update_elem(iface->bpf.map_class, &index, &iface->cdata[index], BPF_ANY);
|
||||
}
|
||||
|
||||
bool spotfilter_bpf_whitelist_seen(struct interface *iface, const void *addr, bool ipv6)
|
||||
{
|
||||
int fd = ipv6 ? iface->bpf.map_whitelist_v6 : iface->bpf.map_whitelist_v4;
|
||||
struct spotfilter_whitelist_entry e;
|
||||
|
||||
bpf_map_lookup_elem(fd, addr, &e);
|
||||
if (!e.seen)
|
||||
return false;
|
||||
|
||||
e.seen = 0;
|
||||
bpf_map_update_elem(fd, addr, &e, BPF_ANY);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void spotfilter_bpf_set_whitelist(struct interface *iface, const void *addr,
|
||||
bool ipv6, const uint8_t *state)
|
||||
{
|
||||
int fd = ipv6 ? iface->bpf.map_whitelist_v6 : iface->bpf.map_whitelist_v4;
|
||||
struct spotfilter_whitelist_entry e = {};
|
||||
|
||||
if (!state) {
|
||||
bpf_map_delete_elem(fd, addr);
|
||||
return;
|
||||
}
|
||||
|
||||
e.val = *state;
|
||||
bpf_map_update_elem(fd, addr, &e, BPF_ANY);
|
||||
}
|
||||
|
||||
void spotfilter_bpf_free(struct interface *iface)
|
||||
{
|
||||
if (!iface->bpf.obj)
|
||||
return;
|
||||
|
||||
bpf_object__close(iface->bpf.obj);
|
||||
iface->bpf.obj = NULL;
|
||||
}
|
||||
24
feeds/ucentral/spotfilter/src/bpf.h
Normal file
24
feeds/ucentral/spotfilter/src/bpf.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#ifndef __SPOTFILTER_BPF_H
|
||||
#define __SPOTFILTER_BPF_H
|
||||
|
||||
struct interface;
|
||||
|
||||
int spotfilter_bpf_load(struct interface *iface);
|
||||
void spotfilter_bpf_free(struct interface *iface);
|
||||
void spotfilter_bpf_set_device(struct interface *iface, int ifindex, bool enabled);
|
||||
void spotfilter_bpf_update_class(struct interface *iface, uint32_t index);
|
||||
int spotfilter_bpf_get_client(struct interface *iface,
|
||||
const struct spotfilter_client_key *key,
|
||||
struct spotfilter_client_data *data);
|
||||
int spotfilter_bpf_set_client(struct interface *iface,
|
||||
const struct spotfilter_client_key *key,
|
||||
const struct spotfilter_client_data *data);
|
||||
void spotfilter_bpf_set_whitelist(struct interface *iface, const void *addr,
|
||||
bool ipv6, const uint8_t *state);
|
||||
bool spotfilter_bpf_whitelist_seen(struct interface *iface, const void *addr, bool ipv6);
|
||||
|
||||
#endif
|
||||
173
feeds/ucentral/spotfilter/src/bpf_skb_utils.h
Normal file
173
feeds/ucentral/spotfilter/src/bpf_skb_utils.h
Normal file
@@ -0,0 +1,173 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#ifndef __BPF_SKB_UTILS_H
|
||||
#define __BPF_SKB_UTILS_H
|
||||
|
||||
#include <uapi/linux/bpf.h>
|
||||
#include <uapi/linux/if_ether.h>
|
||||
#include <uapi/linux/ip.h>
|
||||
#include <uapi/linux/ipv6.h>
|
||||
#include <linux/ip.h>
|
||||
#include <net/ipv6.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
|
||||
struct skb_parser_info {
|
||||
struct __sk_buff *skb;
|
||||
__u32 offset;
|
||||
int proto;
|
||||
};
|
||||
|
||||
static __always_inline void *__skb_data(struct __sk_buff *skb)
|
||||
{
|
||||
return (void *)(long)READ_ONCE(skb->data);
|
||||
}
|
||||
|
||||
static __always_inline void *
|
||||
skb_ptr(struct __sk_buff *skb, __u32 offset, __u32 len)
|
||||
{
|
||||
void *ptr = __skb_data(skb) + offset;
|
||||
void *end = (void *)(long)(skb->data_end);
|
||||
|
||||
if (ptr + len >= end)
|
||||
return NULL;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static __always_inline void *
|
||||
skb_info_ptr(struct skb_parser_info *info, __u32 len)
|
||||
{
|
||||
__u32 offset = info->offset;
|
||||
return skb_ptr(info->skb, offset, len);
|
||||
}
|
||||
|
||||
static __always_inline void
|
||||
skb_parse_init(struct skb_parser_info *info, struct __sk_buff *skb)
|
||||
{
|
||||
*info = (struct skb_parser_info){
|
||||
.skb = skb
|
||||
};
|
||||
}
|
||||
|
||||
static __always_inline struct ethhdr *
|
||||
skb_parse_ethernet(struct skb_parser_info *info)
|
||||
{
|
||||
struct ethhdr *eth;
|
||||
int len;
|
||||
|
||||
len = sizeof(*eth) + 2 * sizeof(struct vlan_hdr) + sizeof(struct ipv6hdr);
|
||||
if (len > info->skb->len)
|
||||
len = info->skb->len;
|
||||
bpf_skb_pull_data(info->skb, len);
|
||||
|
||||
eth = skb_info_ptr(info, sizeof(*eth));
|
||||
if (!eth)
|
||||
return NULL;
|
||||
|
||||
info->proto = eth->h_proto;
|
||||
info->offset += sizeof(*eth);
|
||||
|
||||
return eth;
|
||||
}
|
||||
|
||||
static __always_inline struct vlan_hdr *
|
||||
skb_parse_vlan(struct skb_parser_info *info)
|
||||
{
|
||||
struct vlan_hdr *vlh;
|
||||
|
||||
if (info->proto != bpf_htons(ETH_P_8021Q) &&
|
||||
info->proto != bpf_htons(ETH_P_8021AD))
|
||||
return NULL;
|
||||
|
||||
vlh = skb_info_ptr(info, sizeof(*vlh));
|
||||
if (!vlh)
|
||||
return NULL;
|
||||
|
||||
info->proto = vlh->h_vlan_encapsulated_proto;
|
||||
info->offset += sizeof(*vlh);
|
||||
|
||||
return vlh;
|
||||
}
|
||||
|
||||
static __always_inline struct iphdr *
|
||||
skb_parse_ipv4(struct skb_parser_info *info, int min_l4_bytes)
|
||||
{
|
||||
struct iphdr *iph;
|
||||
int proto, hdr_len;
|
||||
__u32 pull_len;
|
||||
|
||||
if (info->proto != bpf_htons(ETH_P_IP))
|
||||
return NULL;
|
||||
|
||||
iph = skb_info_ptr(info, sizeof(*iph));
|
||||
if (!iph)
|
||||
return NULL;
|
||||
|
||||
hdr_len = iph->ihl * 4;
|
||||
if (hdr_len < sizeof(*iph))
|
||||
return NULL;
|
||||
|
||||
pull_len = info->offset + hdr_len + min_l4_bytes;
|
||||
if (pull_len > info->skb->len)
|
||||
pull_len = info->skb->len;
|
||||
|
||||
if (bpf_skb_pull_data(info->skb, pull_len))
|
||||
return NULL;
|
||||
|
||||
iph = skb_info_ptr(info, sizeof(*iph));
|
||||
if (!iph)
|
||||
return NULL;
|
||||
|
||||
info->proto = iph->protocol;
|
||||
info->offset += hdr_len;
|
||||
|
||||
return iph;
|
||||
}
|
||||
|
||||
static __always_inline struct ipv6hdr *
|
||||
skb_parse_ipv6(struct skb_parser_info *info, int max_l4_bytes)
|
||||
{
|
||||
struct ipv6hdr *ip6h;
|
||||
__u32 pull_len;
|
||||
|
||||
if (info->proto != bpf_htons(ETH_P_IPV6))
|
||||
return NULL;
|
||||
|
||||
pull_len = info->offset + sizeof(*ip6h) + max_l4_bytes;
|
||||
if (pull_len > info->skb->len)
|
||||
pull_len = info->skb->len;
|
||||
|
||||
if (bpf_skb_pull_data(info->skb, pull_len))
|
||||
return NULL;
|
||||
|
||||
ip6h = skb_info_ptr(info, sizeof(*ip6h));
|
||||
if (!ip6h)
|
||||
return NULL;
|
||||
|
||||
info->proto = READ_ONCE(ip6h->nexthdr);
|
||||
info->offset += sizeof(*ip6h);
|
||||
|
||||
return ip6h;
|
||||
}
|
||||
|
||||
static __always_inline struct tcphdr *
|
||||
skb_parse_tcp(struct skb_parser_info *info)
|
||||
{
|
||||
struct tcphdr *tcph;
|
||||
|
||||
if (info->proto != IPPROTO_TCP)
|
||||
return NULL;
|
||||
|
||||
tcph = skb_info_ptr(info, sizeof(*tcph));
|
||||
if (!tcph)
|
||||
return NULL;
|
||||
|
||||
info->offset += tcph->doff * 4;
|
||||
|
||||
return tcph;
|
||||
}
|
||||
|
||||
#endif
|
||||
203
feeds/ucentral/spotfilter/src/client.c
Normal file
203
feeds/ucentral/spotfilter/src/client.c
Normal file
@@ -0,0 +1,203 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <netinet/if_ether.h>
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/avl-cmp.h>
|
||||
#include "spotfilter.h"
|
||||
|
||||
#define CACHE_TIMEOUT 10
|
||||
|
||||
struct cache_entry {
|
||||
struct avl_node node;
|
||||
uint8_t macaddr[ETH_ALEN];
|
||||
uint32_t ip4addr;
|
||||
uint32_t ip6addr[4];
|
||||
uint32_t time;
|
||||
};
|
||||
|
||||
static int avl_mac_cmp(const void *k1, const void *k2, void *priv)
|
||||
{
|
||||
return memcmp(k1, k2, ETH_ALEN);
|
||||
}
|
||||
|
||||
static AVL_TREE(cache, avl_mac_cmp, false, NULL);
|
||||
|
||||
static uint32_t client_gettime(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
|
||||
return ts.tv_sec;
|
||||
}
|
||||
|
||||
static void client_gc(struct uloop_timeout *t)
|
||||
{
|
||||
struct cache_entry *c, *tmp;
|
||||
uint32_t now = client_gettime();
|
||||
|
||||
avl_for_each_element_safe(&cache, c, node, tmp) {
|
||||
uint32_t diff;
|
||||
|
||||
diff = now - c->time;
|
||||
if (diff < CACHE_TIMEOUT)
|
||||
continue;
|
||||
|
||||
avl_delete(&cache, &c->node);
|
||||
free(c);
|
||||
}
|
||||
|
||||
if (!avl_is_empty(&cache))
|
||||
uloop_timeout_set(t, 1000);
|
||||
}
|
||||
|
||||
void client_init_interface(struct interface *iface)
|
||||
{
|
||||
avl_init(&iface->clients, avl_mac_cmp, false, NULL);
|
||||
avl_init(&iface->client_ids, avl_strcmp, false, NULL);
|
||||
}
|
||||
|
||||
static void __client_free(struct interface *iface, struct client *cl)
|
||||
{
|
||||
if (cl->id_node.key)
|
||||
avl_delete(&iface->client_ids, &cl->id_node);
|
||||
avl_delete(&iface->clients, &cl->node);
|
||||
kvlist_free(&cl->kvdata);
|
||||
spotfilter_bpf_set_client(iface, &cl->key, NULL);
|
||||
free(cl);
|
||||
}
|
||||
|
||||
void client_free(struct interface *iface, struct client *cl)
|
||||
{
|
||||
spotfilter_ubus_notify(iface, cl, "client_delete");
|
||||
__client_free(iface, cl);
|
||||
}
|
||||
|
||||
static void client_set_id(struct interface *iface, struct client *cl, const char *id)
|
||||
{
|
||||
if (id == cl->id_node.key)
|
||||
return;
|
||||
|
||||
if (id && cl->id_node.key && !strcmp(id, cl->id_node.key))
|
||||
return;
|
||||
|
||||
if (cl->id_node.key) {
|
||||
avl_delete(&iface->client_ids, &cl->id_node);
|
||||
free((void *) cl->id_node.key);
|
||||
cl->id_node.key = NULL;
|
||||
}
|
||||
|
||||
if (!id)
|
||||
return;
|
||||
|
||||
cl->id_node.key = strdup(id);
|
||||
avl_insert(&iface->client_ids, &cl->id_node);
|
||||
}
|
||||
|
||||
int client_set(struct interface *iface, const void *addr, const char *id,
|
||||
int state, int dns_state, int accounting, struct blob_attr *data)
|
||||
{
|
||||
struct cache_entry *c;
|
||||
struct blob_attr *cur;
|
||||
struct client *cl;
|
||||
bool new_client = false;
|
||||
int rem;
|
||||
|
||||
cl = avl_find_element(&iface->clients, addr, cl, node);
|
||||
if (!cl) {
|
||||
cl = calloc(1, sizeof(*cl));
|
||||
cl->node.key = &cl->key.addr;
|
||||
memcpy(cl->key.addr, addr, ETH_ALEN);
|
||||
avl_insert(&iface->clients, &cl->node);
|
||||
cl->data.cur_class = iface->default_class;
|
||||
cl->data.dns_class = iface->default_dns_class;
|
||||
kvlist_init(&cl->kvdata, kvlist_blob_len);
|
||||
new_client = true;
|
||||
}
|
||||
|
||||
client_set_id(iface, cl, id);
|
||||
if (!new_client)
|
||||
spotfilter_bpf_get_client(iface, &cl->key, &cl->data);
|
||||
|
||||
c = avl_find_element(&cache, addr, c, node);
|
||||
if (c) {
|
||||
if (!cl->data.ip4addr)
|
||||
cl->data.ip4addr = c->ip4addr;
|
||||
if (!cl->data.ip6addr[0])
|
||||
memcpy(cl->data.ip6addr, c->ip6addr, sizeof(cl->data.ip6addr));
|
||||
}
|
||||
|
||||
if (state >= SPOTFILTER_NUM_CLASS || dns_state >= SPOTFILTER_NUM_CLASS) {
|
||||
if (new_client)
|
||||
__client_free(iface, cl);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
blobmsg_for_each_attr(cur, data, rem) {
|
||||
if (!blobmsg_check_attr(cur, true))
|
||||
continue;
|
||||
|
||||
kvlist_set(&cl->kvdata, blobmsg_name(cur), cur);
|
||||
}
|
||||
if (state >= 0)
|
||||
cl->data.cur_class = state;
|
||||
if (dns_state >= 0)
|
||||
cl->data.dns_class = dns_state;
|
||||
if (accounting >= 0)
|
||||
cl->data.flags = accounting;
|
||||
spotfilter_bpf_set_client(iface, &cl->key, &cl->data);
|
||||
|
||||
if (new_client)
|
||||
spotfilter_ubus_notify(iface, cl, "client_add");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void client_set_ipaddr(const void *mac, const void *addr, bool ipv6)
|
||||
{
|
||||
static struct uloop_timeout gc_timer = {
|
||||
.cb = client_gc
|
||||
};
|
||||
struct interface *iface;
|
||||
struct cache_entry *c;
|
||||
struct client *cl;
|
||||
|
||||
c = avl_find_element(&cache, mac, c, node);
|
||||
if (!c) {
|
||||
c = calloc(1, sizeof(*c));
|
||||
memcpy(c->macaddr, mac, ETH_ALEN);
|
||||
c->node.key = c->macaddr;
|
||||
avl_insert(&cache, &c->node);
|
||||
if (!gc_timer.pending)
|
||||
uloop_timeout_set(&gc_timer, CACHE_TIMEOUT * 1000);
|
||||
}
|
||||
|
||||
if (!ipv6 && !c->ip4addr)
|
||||
memcpy(&c->ip4addr, addr, sizeof(c->ip4addr));
|
||||
else if (ipv6 && !c->ip6addr[0])
|
||||
memcpy(&c->ip6addr, addr, sizeof(c->ip6addr));
|
||||
else
|
||||
return;
|
||||
|
||||
c->time = client_gettime();
|
||||
|
||||
avl_for_each_element(&interfaces, iface, node) {
|
||||
cl = avl_find_element(&iface->clients, mac, cl, node);
|
||||
if (!cl)
|
||||
continue;
|
||||
|
||||
spotfilter_bpf_get_client(iface, &cl->key, &cl->data);
|
||||
|
||||
if (!ipv6 && !cl->data.ip4addr)
|
||||
memcpy(&cl->data.ip4addr, addr, sizeof(cl->data.ip4addr));
|
||||
else if (ipv6 && !cl->data.ip6addr[0])
|
||||
memcpy(&cl->data.ip6addr, addr, sizeof(cl->data.ip6addr));
|
||||
else
|
||||
continue;
|
||||
|
||||
spotfilter_bpf_set_client(iface, &cl->key, &cl->data);
|
||||
}
|
||||
}
|
||||
28
feeds/ucentral/spotfilter/src/client.h
Normal file
28
feeds/ucentral/spotfilter/src/client.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#ifndef __SPOTFILTER_CLIENT_H
|
||||
#define __SPOTFILTER_CLIENT_H
|
||||
|
||||
#include <netinet/if_ether.h>
|
||||
#include <libubox/kvlist.h>
|
||||
|
||||
struct client {
|
||||
struct avl_node node;
|
||||
struct avl_node id_node;
|
||||
|
||||
struct kvlist kvdata;
|
||||
int idle;
|
||||
|
||||
struct spotfilter_client_key key;
|
||||
struct spotfilter_client_data data;
|
||||
};
|
||||
|
||||
int client_set(struct interface *iface, const void *addr, const char *id,
|
||||
int state, int dns_state, int accounting, struct blob_attr *data);
|
||||
void client_free(struct interface *iface, struct client *cl);
|
||||
void client_set_ipaddr(const void *mac, const void *addr, bool ipv6);
|
||||
void client_init_interface(struct interface *iface);
|
||||
|
||||
#endif
|
||||
111
feeds/ucentral/spotfilter/src/dhcpv4.c
Normal file
111
feeds/ucentral/spotfilter/src/dhcpv4.c
Normal file
@@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include "spotfilter.h"
|
||||
|
||||
enum dhcpv4_msg {
|
||||
DHCPV4_MSG_DISCOVER = 1,
|
||||
DHCPV4_MSG_OFFER = 2,
|
||||
DHCPV4_MSG_REQUEST = 3,
|
||||
DHCPV4_MSG_DECLINE = 4,
|
||||
DHCPV4_MSG_ACK = 5,
|
||||
DHCPV4_MSG_NAK = 6,
|
||||
DHCPV4_MSG_RELEASE = 7,
|
||||
DHCPV4_MSG_INFORM = 8,
|
||||
DHCPV4_MSG_FORCERENEW = 9,
|
||||
};
|
||||
|
||||
enum dhcpv4_opt {
|
||||
DHCPV4_OPT_PAD = 0,
|
||||
DHCPV4_OPT_NETMASK = 1,
|
||||
DHCPV4_OPT_ROUTER = 3,
|
||||
DHCPV4_OPT_DNSSERVER = 6,
|
||||
DHCPV4_OPT_DOMAIN = 15,
|
||||
DHCPV4_OPT_MTU = 26,
|
||||
DHCPV4_OPT_BROADCAST = 28,
|
||||
DHCPV4_OPT_NTPSERVER = 42,
|
||||
DHCPV4_OPT_LEASETIME = 51,
|
||||
DHCPV4_OPT_MESSAGE = 53,
|
||||
DHCPV4_OPT_SERVERID = 54,
|
||||
DHCPV4_OPT_REQOPTS = 55,
|
||||
DHCPV4_OPT_RENEW = 58,
|
||||
DHCPV4_OPT_REBIND = 59,
|
||||
DHCPV4_OPT_IPADDRESS = 50,
|
||||
DHCPV4_OPT_MSG_TYPE = 53,
|
||||
DHCPV4_OPT_HOSTNAME = 12,
|
||||
DHCPV4_OPT_REQUEST = 17,
|
||||
DHCPV4_OPT_USER_CLASS = 77,
|
||||
DHCPV4_OPT_AUTHENTICATION = 90,
|
||||
DHCPV4_OPT_SEARCH_DOMAIN = 119,
|
||||
DHCPV4_OPT_FORCERENEW_NONCE_CAPABLE = 145,
|
||||
DHCPV4_OPT_END = 255,
|
||||
};
|
||||
|
||||
struct dhcpv4_message {
|
||||
uint8_t op;
|
||||
uint8_t htype;
|
||||
uint8_t hlen;
|
||||
uint8_t hops;
|
||||
uint32_t xid;
|
||||
uint16_t secs;
|
||||
uint16_t flags;
|
||||
struct in_addr ciaddr;
|
||||
struct in_addr yiaddr;
|
||||
struct in_addr siaddr;
|
||||
struct in_addr giaddr;
|
||||
uint8_t chaddr[16];
|
||||
char sname[64];
|
||||
char file[128];
|
||||
uint32_t magic;
|
||||
uint8_t options[];
|
||||
} __attribute__((packed));
|
||||
|
||||
#define DHCPV4_MAGIC 0x63825363
|
||||
|
||||
struct dhcpv4_option {
|
||||
uint8_t type;
|
||||
uint8_t len;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
#define dhcpv4_for_each_option(opt, start, end) \
|
||||
for (opt = (const struct dhcpv4_option *)(start); \
|
||||
&opt[1] <= (const struct dhcpv4_option *)(end) && \
|
||||
&opt->data[opt->len] <= (const uint8_t *)(end); \
|
||||
opt = (const struct dhcpv4_option *)&opt->data[opt->len])
|
||||
|
||||
void spotfilter_recv_dhcpv4(const void *msgdata, int len, const void *eth_addr)
|
||||
{
|
||||
const struct dhcpv4_message *msg = msgdata;
|
||||
const struct dhcpv4_option *opt;
|
||||
uint8_t bcast_addr[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
int op = -1;
|
||||
|
||||
if (ntohl(msg->magic) != DHCPV4_MAGIC)
|
||||
return;
|
||||
|
||||
if (msg->op != 2 || msg->htype != 1 || msg->hlen != 6)
|
||||
return;
|
||||
|
||||
if (memcmp(eth_addr, bcast_addr, ETH_ALEN) != 0 &&
|
||||
memcmp(eth_addr, msg->chaddr, ETH_ALEN) != 0)
|
||||
return;
|
||||
|
||||
dhcpv4_for_each_option(opt, msg->options, msgdata + len) {
|
||||
switch (opt->type) {
|
||||
case DHCPV4_OPT_MESSAGE:
|
||||
if (opt->len != 1)
|
||||
break;
|
||||
|
||||
op = opt->data[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (op != DHCPV4_MSG_ACK)
|
||||
return;
|
||||
|
||||
client_set_ipaddr(msg->chaddr, (uint32_t *)&msg->yiaddr, false);
|
||||
}
|
||||
|
||||
36
feeds/ucentral/spotfilter/src/example.json
Normal file
36
feeds/ucentral/spotfilter/src/example.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "hotspot",
|
||||
"devices": [ "wlan1" ],
|
||||
"config": {
|
||||
"class": [
|
||||
{
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"device_macaddr": "br-test"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"macaddr": "00:11:22:33:44:55"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"fwmark": 1,
|
||||
"fwmark_mask": 127
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"redirect": "up0v2"
|
||||
}
|
||||
],
|
||||
"default_class": 1,
|
||||
"default_dns_class": 0,
|
||||
"whitelist": [
|
||||
{
|
||||
"class": 0,
|
||||
"hosts": [ "*.google.de", "*.google.com" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
46
feeds/ucentral/spotfilter/src/icmpv6.c
Normal file
46
feeds/ucentral/spotfilter/src/icmpv6.c
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <netinet/icmp6.h>
|
||||
#include "spotfilter.h"
|
||||
|
||||
struct icmpv6_opt {
|
||||
uint8_t type;
|
||||
uint8_t len;
|
||||
uint8_t data[6];
|
||||
};
|
||||
|
||||
#define icmpv6_for_each_option(opt, start, end) \
|
||||
for (opt = (const struct icmpv6_opt*)(start); \
|
||||
(const void *)(opt + 1) <= (const void *)(end) && opt->len > 0 && \
|
||||
(const void *)(opt + opt->len) <= (const void *)(end); opt += opt->len)
|
||||
|
||||
void spotfilter_recv_icmpv6(const void *data, int len, const uint8_t *src, const uint8_t *dest)
|
||||
{
|
||||
const struct nd_neighbor_advert *nd = data;
|
||||
const struct icmp6_hdr *hdr = data;
|
||||
const struct icmpv6_opt *opt;
|
||||
|
||||
if (len < sizeof(*nd) || hdr->icmp6_code)
|
||||
return;
|
||||
|
||||
if (hdr->icmp6_type != ND_NEIGHBOR_ADVERT)
|
||||
return;
|
||||
|
||||
icmpv6_for_each_option(opt, &nd[1], data + len) {
|
||||
if (opt->type != ND_OPT_TARGET_LINKADDR || opt->len != 1)
|
||||
continue;
|
||||
|
||||
if (memcmp(opt->data, src, ETH_ALEN))
|
||||
return;
|
||||
}
|
||||
|
||||
if ((nd->nd_na_target.s6_addr[0] & 0xe0) != 0x20)
|
||||
return;
|
||||
|
||||
if (opt != (const struct icmpv6_opt *)(data + len))
|
||||
return;
|
||||
|
||||
client_set_ipaddr(src, &nd->nd_na_target, true);
|
||||
}
|
||||
369
feeds/ucentral/spotfilter/src/interface.c
Normal file
369
feeds/ucentral/spotfilter/src/interface.c
Normal file
@@ -0,0 +1,369 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <netinet/ether.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include <libubox/avl-cmp.h>
|
||||
|
||||
#include "spotfilter.h"
|
||||
|
||||
AVL_TREE(interfaces, avl_strcmp, false, NULL);
|
||||
|
||||
void interface_free(struct interface *iface)
|
||||
{
|
||||
struct client *cl, *tmp;
|
||||
|
||||
spotfilter_dns_free(iface);
|
||||
|
||||
vlist_flush_all(&iface->devices);
|
||||
|
||||
avl_for_each_element_safe(&iface->clients, cl, node, tmp)
|
||||
client_free(iface, cl);
|
||||
|
||||
spotfilter_bpf_free(iface);
|
||||
|
||||
avl_delete(&interfaces, &iface->node);
|
||||
free(iface->config);
|
||||
free(iface);
|
||||
}
|
||||
|
||||
static inline const char *
|
||||
device_name(struct device *dev)
|
||||
{
|
||||
return dev->node.avl.key;
|
||||
}
|
||||
|
||||
static void
|
||||
interface_check_device(struct interface *iface, struct device *dev)
|
||||
{
|
||||
int old_ifindex = dev->ifindex;
|
||||
|
||||
dev->ifindex = if_nametoindex(device_name(dev));
|
||||
if (dev->ifindex != old_ifindex)
|
||||
spotfilter_bpf_set_device(iface, dev->ifindex, true);
|
||||
}
|
||||
|
||||
static void
|
||||
device_update_cb(struct vlist_tree *tree,
|
||||
struct vlist_node *node_new,
|
||||
struct vlist_node *node_old)
|
||||
{
|
||||
struct interface *iface = container_of(tree, struct interface, devices);
|
||||
struct device *dev_new = container_of_safe(node_new, struct device, node);
|
||||
struct device *dev_old = container_of_safe(node_old, struct device, node);
|
||||
|
||||
if (dev_new) {
|
||||
if (dev_old)
|
||||
dev_new->ifindex = dev_old->ifindex;
|
||||
interface_check_device(iface, dev_new);
|
||||
}
|
||||
|
||||
if (dev_old) {
|
||||
if (!dev_new && dev_old->ifindex)
|
||||
spotfilter_bpf_set_device(iface, dev_old->ifindex, false);
|
||||
free(dev_old);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
interface_parse_class(struct spotfilter_bpf_class *cdata, struct blob_attr *attr)
|
||||
{
|
||||
enum {
|
||||
CLASS_ATTR_INDEX,
|
||||
CLASS_ATTR_DEV_MAC,
|
||||
CLASS_ATTR_MAC,
|
||||
CLASS_ATTR_REDIRECT,
|
||||
CLASS_ATTR_FWMARK,
|
||||
CLASS_ATTR_FWMARK_MASK,
|
||||
__CLASS_ATTR_MAX,
|
||||
};
|
||||
static const struct blobmsg_policy policy[__CLASS_ATTR_MAX] = {
|
||||
[CLASS_ATTR_INDEX] = { "index", BLOBMSG_TYPE_INT32 },
|
||||
[CLASS_ATTR_DEV_MAC] = { "device_macaddr", BLOBMSG_TYPE_STRING },
|
||||
[CLASS_ATTR_MAC] = { "macaddr", BLOBMSG_TYPE_STRING },
|
||||
[CLASS_ATTR_REDIRECT] = { "redirect", BLOBMSG_TYPE_STRING },
|
||||
[CLASS_ATTR_FWMARK] = { "fwmark", BLOBMSG_TYPE_INT32 },
|
||||
[CLASS_ATTR_FWMARK_MASK] = { "fwmark_mask", BLOBMSG_TYPE_INT32 },
|
||||
};
|
||||
struct blob_attr *tb[__CLASS_ATTR_MAX];
|
||||
struct blob_attr *cur;
|
||||
unsigned int index;
|
||||
|
||||
if (blobmsg_type(attr) != BLOBMSG_TYPE_TABLE)
|
||||
return -1;
|
||||
|
||||
blobmsg_parse(policy, __CLASS_ATTR_MAX, tb,
|
||||
blobmsg_data(attr), blobmsg_len(attr));
|
||||
|
||||
if ((cur = tb[CLASS_ATTR_INDEX]) != NULL)
|
||||
index = blobmsg_get_u32(cur);
|
||||
else
|
||||
return -1;
|
||||
|
||||
if (index >= SPOTFILTER_NUM_CLASS)
|
||||
return -1;
|
||||
|
||||
if ((cur = tb[CLASS_ATTR_MAC]) != NULL) {
|
||||
void *addr;
|
||||
|
||||
addr = ether_aton(blobmsg_get_string(cur));
|
||||
if (!addr)
|
||||
goto invalid;
|
||||
|
||||
memcpy(cdata->dest_mac, addr, sizeof(cdata->dest_mac));
|
||||
cdata->actions |= SPOTFILTER_ACTION_SET_DEST_MAC;
|
||||
} else if ((cur = tb[CLASS_ATTR_DEV_MAC]) != NULL) {
|
||||
const char *name = blobmsg_get_string(cur);
|
||||
struct ifreq ifr = {};
|
||||
int sock;
|
||||
int ret;
|
||||
|
||||
if (strlen(name) > IFNAMSIZ)
|
||||
goto invalid;
|
||||
|
||||
strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
|
||||
|
||||
sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
ret = ioctl(sock, SIOCGIFHWADDR, &ifr);
|
||||
if (ret < 0)
|
||||
perror("ioctl");
|
||||
close(sock);
|
||||
|
||||
if (ret < 0)
|
||||
goto invalid;
|
||||
|
||||
if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER)
|
||||
goto invalid;
|
||||
|
||||
memcpy(cdata->dest_mac, ifr.ifr_hwaddr.sa_data, sizeof(cdata->dest_mac));
|
||||
cdata->actions |= SPOTFILTER_ACTION_SET_DEST_MAC;
|
||||
}
|
||||
|
||||
if ((cur = tb[CLASS_ATTR_REDIRECT]) != NULL) {
|
||||
unsigned int ifindex = if_nametoindex(blobmsg_get_string(cur));
|
||||
|
||||
if (!ifindex)
|
||||
goto invalid;
|
||||
|
||||
cdata->redirect_ifindex = ifindex;
|
||||
cdata->actions |= SPOTFILTER_ACTION_REDIRECT;
|
||||
}
|
||||
|
||||
if ((cur = tb[CLASS_ATTR_FWMARK_MASK]) != NULL)
|
||||
cdata->fwmark_mask = blobmsg_get_u32(cur);
|
||||
else
|
||||
cdata->fwmark_mask = ~0;
|
||||
|
||||
if ((cur = tb[CLASS_ATTR_FWMARK]) != NULL) {
|
||||
cdata->fwmark_val = blobmsg_get_u32(cur);
|
||||
cdata->actions |= SPOTFILTER_ACTION_FWMARK;
|
||||
}
|
||||
|
||||
cdata->actions |= SPOTFILTER_ACTION_VALID;
|
||||
return index;
|
||||
|
||||
invalid:
|
||||
cdata->actions = 0;
|
||||
return index;
|
||||
}
|
||||
|
||||
static bool
|
||||
__interface_check_whitelist(struct blob_attr *attr)
|
||||
{
|
||||
enum {
|
||||
WL_ATTR_CLASS,
|
||||
WL_ATTR_HOSTS,
|
||||
__WL_ATTR_MAX
|
||||
};
|
||||
static const struct blobmsg_policy policy[__WL_ATTR_MAX] = {
|
||||
[WL_ATTR_CLASS] = { "class", BLOBMSG_TYPE_INT32 },
|
||||
[WL_ATTR_HOSTS] = { "hosts", BLOBMSG_TYPE_ARRAY },
|
||||
};
|
||||
struct blob_attr *tb[__WL_ATTR_MAX];
|
||||
|
||||
blobmsg_parse(policy, __WL_ATTR_MAX, tb, blobmsg_data(attr), blobmsg_len(attr));
|
||||
|
||||
if (!tb[WL_ATTR_CLASS] || !tb[WL_ATTR_HOSTS])
|
||||
return false;
|
||||
|
||||
return blobmsg_check_array(tb[WL_ATTR_HOSTS], BLOBMSG_TYPE_STRING) >= 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
interface_check_whitelist(struct blob_attr *attr)
|
||||
{
|
||||
struct blob_attr *cur;
|
||||
int rem;
|
||||
|
||||
if (blobmsg_check_array(attr, BLOBMSG_TYPE_TABLE) <= 0)
|
||||
return false;
|
||||
|
||||
blobmsg_for_each_attr(cur, attr, rem) {
|
||||
if (!__interface_check_whitelist(cur))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
interface_set_config(struct interface *iface, bool iface_init)
|
||||
{
|
||||
enum {
|
||||
CONFIG_ATTR_CLASS,
|
||||
CONFIG_ATTR_WHITELIST,
|
||||
CONFIG_ATTR_ACTIVE_TIMEOUT,
|
||||
CONFIG_ATTR_CLIENT_AUTOCREATE,
|
||||
CONFIG_ATTR_CLIENT_AUTOREMOVE,
|
||||
CONFIG_ATTR_CLIENT_TIMEOUT,
|
||||
CONFIG_ATTR_DEFAULT_CLASS,
|
||||
CONFIG_ATTR_DEFAULT_DNS_CLASS,
|
||||
__CONFIG_ATTR_MAX,
|
||||
};
|
||||
static const struct blobmsg_policy policy[__CONFIG_ATTR_MAX] = {
|
||||
[CONFIG_ATTR_CLASS] = { "class", BLOBMSG_TYPE_ARRAY },
|
||||
[CONFIG_ATTR_WHITELIST] = { "whitelist", BLOBMSG_TYPE_ARRAY },
|
||||
[CONFIG_ATTR_ACTIVE_TIMEOUT] = { "active_timeout", BLOBMSG_TYPE_INT32 },
|
||||
[CONFIG_ATTR_CLIENT_TIMEOUT] = { "client_timeout", BLOBMSG_TYPE_INT32 },
|
||||
[CONFIG_ATTR_CLIENT_AUTOCREATE] = { "client_autocreate", BLOBMSG_TYPE_BOOL },
|
||||
[CONFIG_ATTR_CLIENT_AUTOREMOVE] = { "client_autoremove", BLOBMSG_TYPE_BOOL },
|
||||
[CONFIG_ATTR_DEFAULT_CLASS] = { "default_class", BLOBMSG_TYPE_INT32 },
|
||||
[CONFIG_ATTR_DEFAULT_DNS_CLASS] = { "default_dns_class", BLOBMSG_TYPE_INT32 },
|
||||
};
|
||||
struct blob_attr *tb[__CONFIG_ATTR_MAX];
|
||||
struct blob_attr *cur;
|
||||
uint32_t class_mask = 0;
|
||||
int i, rem;
|
||||
|
||||
blobmsg_parse(policy, __CONFIG_ATTR_MAX, tb,
|
||||
blobmsg_data(iface->config), blobmsg_len(iface->config));
|
||||
|
||||
if ((cur = tb[CONFIG_ATTR_DEFAULT_CLASS]) != NULL &&
|
||||
blobmsg_get_u32(cur) < SPOTFILTER_NUM_CLASS)
|
||||
iface->default_class = blobmsg_get_u32(cur);
|
||||
else
|
||||
iface->default_class = 0;
|
||||
|
||||
if ((cur = tb[CONFIG_ATTR_DEFAULT_DNS_CLASS]) != NULL &&
|
||||
blobmsg_get_u32(cur) < SPOTFILTER_NUM_CLASS)
|
||||
iface->default_dns_class = blobmsg_get_u32(cur);
|
||||
else
|
||||
iface->default_dns_class = 0;
|
||||
|
||||
if ((cur = tb[CONFIG_ATTR_WHITELIST]) != NULL && interface_check_whitelist(cur))
|
||||
iface->whitelist = cur;
|
||||
else
|
||||
iface->whitelist = NULL;
|
||||
|
||||
if ((cur = tb[CONFIG_ATTR_ACTIVE_TIMEOUT]) != NULL)
|
||||
iface->active_timeout = blobmsg_get_u32(cur);
|
||||
else
|
||||
iface->active_timeout = 300;
|
||||
|
||||
if ((cur = tb[CONFIG_ATTR_CLIENT_TIMEOUT]) != NULL)
|
||||
iface->client_timeout = blobmsg_get_u32(cur);
|
||||
else
|
||||
iface->client_timeout = 30;
|
||||
|
||||
if ((cur = tb[CONFIG_ATTR_CLIENT_AUTOCREATE]) != NULL)
|
||||
iface->client_autocreate = blobmsg_get_u8(cur);
|
||||
else
|
||||
iface->client_autocreate = true;
|
||||
|
||||
if ((cur = tb[CONFIG_ATTR_CLIENT_AUTOREMOVE]) != NULL)
|
||||
iface->client_autoremove = blobmsg_get_u8(cur);
|
||||
else
|
||||
iface->client_autoremove = true;
|
||||
|
||||
blobmsg_for_each_attr(cur, tb[CONFIG_ATTR_CLASS], rem) {
|
||||
struct spotfilter_bpf_class cdata = {};
|
||||
int index;
|
||||
|
||||
index = interface_parse_class(&cdata, cur);
|
||||
if (index < 0)
|
||||
continue;
|
||||
|
||||
if (iface_init ||
|
||||
memcmp(&iface->cdata[index], &cdata, sizeof(cdata)) != 0) {
|
||||
memcpy(&iface->cdata[index], &cdata, sizeof(cdata));
|
||||
spotfilter_bpf_update_class(iface, index);
|
||||
}
|
||||
|
||||
class_mask |= 1 << index;
|
||||
}
|
||||
|
||||
for (i = 0; i < SPOTFILTER_NUM_CLASS; i++) {
|
||||
if (class_mask & (1 << i))
|
||||
continue;
|
||||
|
||||
memset(&iface->cdata[i], 0, sizeof(iface->cdata[i]));
|
||||
spotfilter_bpf_update_class(iface, i);
|
||||
}
|
||||
}
|
||||
|
||||
void interface_check_devices(void)
|
||||
{
|
||||
struct interface *iface;
|
||||
struct device *dev;
|
||||
|
||||
avl_for_each_element(&interfaces, iface, node) {
|
||||
interface_set_config(iface, false);
|
||||
|
||||
vlist_for_each_element(&iface->devices, dev, node)
|
||||
interface_check_device(iface, dev);
|
||||
}
|
||||
}
|
||||
|
||||
void interface_add(const char *name, struct blob_attr *config,
|
||||
struct blob_attr *devices)
|
||||
{
|
||||
struct interface *iface;
|
||||
struct blob_attr *cur;
|
||||
char *name_buf;
|
||||
bool iface_init = false;
|
||||
int rem;
|
||||
|
||||
iface = avl_find_element(&interfaces, name, iface, node);
|
||||
if (!iface) {
|
||||
iface = calloc_a(sizeof(*iface), &name_buf, strlen(name) + 1);
|
||||
iface->node.key = strcpy(name_buf, name);
|
||||
vlist_init(&iface->devices, avl_strcmp, device_update_cb);
|
||||
client_init_interface(iface);
|
||||
spotfilter_dns_init(iface);
|
||||
|
||||
if (spotfilter_bpf_load(iface)) {
|
||||
free(iface);
|
||||
return;
|
||||
}
|
||||
|
||||
avl_insert(&interfaces, &iface->node);
|
||||
iface_init = true;
|
||||
}
|
||||
|
||||
if (config && !blob_attr_equal(iface->config, config)) {
|
||||
free(iface->config);
|
||||
iface->config = blob_memdup(config);
|
||||
interface_set_config(iface, iface_init);
|
||||
}
|
||||
|
||||
blobmsg_for_each_attr(cur, devices, rem) {
|
||||
struct device *dev;
|
||||
const char *name = blobmsg_get_string(cur);
|
||||
|
||||
dev = calloc_a(sizeof(*dev), &name_buf, strlen(name) + 1);
|
||||
vlist_add(&iface->devices, &dev->node, strcpy(name_buf, name));
|
||||
}
|
||||
}
|
||||
|
||||
void interface_done(void)
|
||||
{
|
||||
struct interface *iface, *tmp;
|
||||
|
||||
avl_for_each_element_safe(&interfaces, iface, node, tmp)
|
||||
interface_free(iface);
|
||||
}
|
||||
73
feeds/ucentral/spotfilter/src/interface.h
Normal file
73
feeds/ucentral/spotfilter/src/interface.h
Normal file
@@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#ifndef __SPOTFILTER_INTERFACE_H
|
||||
#define __SPOTFILTER_INTERFACE_H
|
||||
|
||||
#include <libubox/vlist.h>
|
||||
#include <libubox/blobmsg.h>
|
||||
#include <libubox/uloop.h>
|
||||
|
||||
struct bpf_object;
|
||||
|
||||
struct interface {
|
||||
struct avl_node node;
|
||||
|
||||
struct blob_attr *config;
|
||||
struct blob_attr *whitelist;
|
||||
|
||||
struct avl_tree cname_cache;
|
||||
struct avl_tree addr_map;
|
||||
|
||||
struct uloop_timeout addr_gc;
|
||||
uint32_t next_gc;
|
||||
|
||||
uint32_t active_timeout;
|
||||
|
||||
uint8_t default_class;
|
||||
uint8_t default_dns_class;
|
||||
|
||||
bool client_autocreate;
|
||||
bool client_autoremove;
|
||||
int client_timeout;
|
||||
|
||||
struct {
|
||||
struct bpf_object *obj;
|
||||
|
||||
int prog_ingress;
|
||||
int prog_egress;
|
||||
int map_class;
|
||||
int map_client;
|
||||
int map_whitelist_v4;
|
||||
int map_whitelist_v6;
|
||||
} bpf;
|
||||
|
||||
struct spotfilter_bpf_class cdata[SPOTFILTER_NUM_CLASS];
|
||||
|
||||
struct vlist_tree devices;
|
||||
|
||||
struct avl_tree clients;
|
||||
struct avl_tree client_ids;
|
||||
};
|
||||
|
||||
struct device {
|
||||
struct vlist_node node;
|
||||
|
||||
int ifindex;
|
||||
};
|
||||
|
||||
extern struct avl_tree interfaces;
|
||||
|
||||
static inline const char *interface_name(struct interface *iface)
|
||||
{
|
||||
return iface->node.key;
|
||||
}
|
||||
|
||||
void interface_add(const char *name, struct blob_attr *config,
|
||||
struct blob_attr *devices);
|
||||
void interface_free(struct interface *iface);
|
||||
void interface_check_devices(void);
|
||||
void interface_done(void);
|
||||
|
||||
#endif
|
||||
120
feeds/ucentral/spotfilter/src/main.c
Normal file
120
feeds/ucentral/spotfilter/src/main.c
Normal file
@@ -0,0 +1,120 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <sys/wait.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <libubox/uloop.h>
|
||||
|
||||
#include "spotfilter.h"
|
||||
|
||||
int spotfilter_run_cmd(char *cmd, bool ignore_error)
|
||||
{
|
||||
char *argv[] = { "sh", "-c", cmd, NULL };
|
||||
bool first = true;
|
||||
int status = -1;
|
||||
char buf[512];
|
||||
int fds[2];
|
||||
FILE *f;
|
||||
int pid;
|
||||
|
||||
if (pipe(fds))
|
||||
return -1;
|
||||
|
||||
pid = fork();
|
||||
if (!pid) {
|
||||
close(fds[0]);
|
||||
if (fds[1] != STDOUT_FILENO)
|
||||
dup2(fds[1], STDOUT_FILENO);
|
||||
if (fds[1] != STDERR_FILENO)
|
||||
dup2(fds[1], STDERR_FILENO);
|
||||
if (fds[1] > STDERR_FILENO)
|
||||
close(fds[1]);
|
||||
execv("/bin/sh", argv);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (pid < 0)
|
||||
return -1;
|
||||
|
||||
close(fds[1]);
|
||||
f = fdopen(fds[0], "r");
|
||||
if (!f) {
|
||||
close(fds[0]);
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (fgets(buf, sizeof(buf), f) != NULL) {
|
||||
if (!strlen(buf))
|
||||
break;
|
||||
if (ignore_error)
|
||||
continue;
|
||||
if (first) {
|
||||
ULOG_WARN("Command: %s\n", cmd);
|
||||
first = false;
|
||||
}
|
||||
ULOG_WARN("%s%s", buf, strchr(buf, '\n') ? "" : "\n");
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
out:
|
||||
while (waitpid(pid, &status, 0) < 0)
|
||||
if (errno != EINTR)
|
||||
break;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int usage(const char *progname)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s [options]\n"
|
||||
"Options:\n"
|
||||
"\n", progname);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret = 2;
|
||||
int ch;
|
||||
|
||||
while ((ch = getopt(argc, argv, "")) != -1) {
|
||||
switch (ch) {
|
||||
default:
|
||||
return usage(argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
ulog_open(ULOG_SYSLOG, LOG_DAEMON, "spotfilter");
|
||||
uloop_init();
|
||||
|
||||
if (rtnl_init())
|
||||
return 1;
|
||||
|
||||
if (spotfilter_nl80211_init())
|
||||
return 1;
|
||||
|
||||
if (spotfilter_dev_init())
|
||||
return 1;
|
||||
|
||||
if (spotfilter_ubus_init())
|
||||
goto out;
|
||||
|
||||
ret = 0;
|
||||
uloop_run();
|
||||
|
||||
spotfilter_ubus_stop();
|
||||
|
||||
out:
|
||||
interface_done();
|
||||
spotfilter_dev_done();
|
||||
spotfilter_nl80211_done();
|
||||
uloop_done();
|
||||
|
||||
return ret;
|
||||
}
|
||||
276
feeds/ucentral/spotfilter/src/nl80211.c
Normal file
276
feeds/ucentral/spotfilter/src/nl80211.c
Normal file
@@ -0,0 +1,276 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <linux/netlink.h>
|
||||
|
||||
#include <netlink/genl/genl.h>
|
||||
#include <netlink/genl/family.h>
|
||||
#include <netlink/genl/ctrl.h>
|
||||
#include <netlink/msg.h>
|
||||
#include <netlink/attr.h>
|
||||
|
||||
#include <linux/nl80211.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <libubox/uloop.h>
|
||||
|
||||
#include "spotfilter.h"
|
||||
|
||||
static struct nl_sock *genl;
|
||||
static struct nl_cb *genl_cb;
|
||||
static struct uloop_fd genl_fd;
|
||||
static struct uloop_timeout update_timer;
|
||||
static int nl80211_id;
|
||||
|
||||
static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
|
||||
void *arg)
|
||||
{
|
||||
int *ret = arg;
|
||||
*ret = err->error;
|
||||
return NL_STOP;
|
||||
}
|
||||
|
||||
static int ack_handler(struct nl_msg *msg, void *arg)
|
||||
{
|
||||
int *ret = arg;
|
||||
*ret = 0;
|
||||
return NL_STOP;
|
||||
}
|
||||
|
||||
struct handler_args {
|
||||
const char *group;
|
||||
int id;
|
||||
};
|
||||
|
||||
static int family_handler(struct nl_msg *msg, void *arg)
|
||||
{
|
||||
struct handler_args *grp = arg;
|
||||
struct nlattr *tb[CTRL_ATTR_MAX + 1];
|
||||
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
|
||||
struct nlattr *mcgrp;
|
||||
int rem_mcgrp;
|
||||
|
||||
nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
|
||||
genlmsg_attrlen(gnlh, 0), NULL);
|
||||
|
||||
if (!tb[CTRL_ATTR_MCAST_GROUPS])
|
||||
return NL_SKIP;
|
||||
|
||||
nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) {
|
||||
struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
|
||||
|
||||
nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX,
|
||||
nla_data(mcgrp), nla_len(mcgrp), NULL);
|
||||
|
||||
if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] ||
|
||||
!tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
|
||||
continue;
|
||||
if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]),
|
||||
grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])))
|
||||
continue;
|
||||
grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
|
||||
break;
|
||||
}
|
||||
|
||||
return NL_SKIP;
|
||||
}
|
||||
|
||||
static int nl_get_multicast_id(struct nl_sock *sock, const char *family, const char *group)
|
||||
{
|
||||
struct nl_msg *msg;
|
||||
struct nl_cb *cb;
|
||||
struct handler_args grp = {
|
||||
.group = group,
|
||||
.id = -ENOENT,
|
||||
};
|
||||
int ret, ctrlid;
|
||||
|
||||
msg = nlmsg_alloc();
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
cb = nl_cb_alloc(NL_CB_DEFAULT);
|
||||
if (!cb) {
|
||||
ret = -ENOMEM;
|
||||
goto out_fail_cb;
|
||||
}
|
||||
|
||||
ctrlid = genl_ctrl_resolve(sock, "nlctrl");
|
||||
|
||||
genlmsg_put(msg, 0, 0, ctrlid, 0,
|
||||
0, CTRL_CMD_GETFAMILY, 0);
|
||||
|
||||
ret = -ENOBUFS;
|
||||
NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family);
|
||||
|
||||
ret = nl_send_auto_complete(sock, msg);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = 1;
|
||||
|
||||
nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret);
|
||||
nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret);
|
||||
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, family_handler, &grp);
|
||||
|
||||
while (ret > 0)
|
||||
nl_recvmsgs(sock, cb);
|
||||
|
||||
if (ret == 0)
|
||||
ret = grp.id;
|
||||
nla_put_failure:
|
||||
out:
|
||||
nl_cb_put(cb);
|
||||
out_fail_cb:
|
||||
nlmsg_free(msg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
nl80211_sock_cb(struct uloop_fd *fd, unsigned int events)
|
||||
{
|
||||
nl_recvmsgs(genl, genl_cb);
|
||||
}
|
||||
|
||||
static void
|
||||
nl80211_device_update(struct interface *iface, struct device *dev)
|
||||
{
|
||||
struct nl_msg *msg;
|
||||
|
||||
msg = nlmsg_alloc();
|
||||
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl80211_id, 0, NLM_F_DUMP,
|
||||
NL80211_CMD_GET_STATION, 0);
|
||||
nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
|
||||
|
||||
nl_send_auto_complete(genl, msg);
|
||||
nlmsg_free(msg);
|
||||
}
|
||||
|
||||
static void
|
||||
nl80211_interface_update(struct interface *iface)
|
||||
{
|
||||
struct client *cl, *tmp;
|
||||
struct device *dev;
|
||||
|
||||
if (!iface->client_autoremove)
|
||||
return;
|
||||
|
||||
avl_for_each_element_safe(&iface->clients, cl, node, tmp) {
|
||||
if (cl->idle++ < iface->client_timeout)
|
||||
continue;
|
||||
|
||||
client_free(iface, cl);
|
||||
}
|
||||
|
||||
vlist_for_each_element(&iface->devices, dev, node)
|
||||
nl80211_device_update(iface, dev);
|
||||
}
|
||||
|
||||
static void spotfilter_nl80211_update(struct uloop_timeout *t)
|
||||
{
|
||||
struct interface *iface;
|
||||
|
||||
avl_for_each_element(&interfaces, iface, node)
|
||||
nl80211_interface_update(iface);
|
||||
|
||||
uloop_timeout_set(t, 1000);
|
||||
}
|
||||
|
||||
static int no_seq_check(struct nl_msg *msg, void *arg)
|
||||
{
|
||||
return NL_OK;
|
||||
}
|
||||
|
||||
static int valid_msg(struct nl_msg *msg, void *arg)
|
||||
{
|
||||
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
|
||||
struct nlattr *tb[NL80211_ATTR_MAX + 1];
|
||||
struct interface *iface;
|
||||
struct device *dev;
|
||||
struct client *cl;
|
||||
const void *addr;
|
||||
int ifindex;
|
||||
|
||||
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
|
||||
genlmsg_attrlen(gnlh, 0), NULL);
|
||||
|
||||
if (gnlh->cmd != NL80211_CMD_NEW_STATION)
|
||||
return NL_SKIP;
|
||||
|
||||
if (!tb[NL80211_ATTR_IFINDEX] || !tb[NL80211_ATTR_MAC])
|
||||
return NL_SKIP;
|
||||
|
||||
ifindex = nla_get_u32(tb[NL80211_ATTR_IFINDEX]);
|
||||
addr = nla_data(tb[NL80211_ATTR_MAC]);
|
||||
|
||||
avl_for_each_element(&interfaces, iface, node)
|
||||
vlist_for_each_element(&iface->devices, dev, node)
|
||||
if (dev->ifindex == ifindex)
|
||||
goto found;
|
||||
|
||||
return NL_SKIP;
|
||||
|
||||
found:
|
||||
cl = avl_find_element(&iface->clients, addr, cl, node);
|
||||
if (cl)
|
||||
cl->idle = 0;
|
||||
else if (iface->client_autocreate)
|
||||
client_set(iface, addr, NULL, -1, -1, -1, NULL);
|
||||
|
||||
return NL_SKIP;
|
||||
}
|
||||
|
||||
int spotfilter_nl80211_init(void)
|
||||
{
|
||||
int id;
|
||||
|
||||
genl = nl_socket_alloc();
|
||||
if (!genl)
|
||||
return -1;
|
||||
|
||||
nl_socket_set_buffer_size(genl, 16384, 16384);
|
||||
if (genl_connect(genl))
|
||||
goto error;
|
||||
|
||||
nl80211_id = genl_ctrl_resolve(genl, "nl80211");
|
||||
if (nl80211_id < 0)
|
||||
goto error;
|
||||
|
||||
id = nl_get_multicast_id(genl, "nl80211", "mlme");
|
||||
if (id < 0)
|
||||
goto error;
|
||||
|
||||
if (nl_socket_add_membership(genl, id) < 0)
|
||||
goto error;
|
||||
|
||||
genl_cb = nl_cb_alloc(NL_CB_DEFAULT);
|
||||
nl_cb_set(genl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL);
|
||||
nl_cb_set(genl_cb, NL_CB_VALID, NL_CB_CUSTOM, valid_msg, NULL);
|
||||
|
||||
genl_fd.fd = nl_socket_get_fd(genl);
|
||||
genl_fd.cb = nl80211_sock_cb;
|
||||
uloop_fd_add(&genl_fd, ULOOP_READ);
|
||||
|
||||
update_timer.cb = spotfilter_nl80211_update;
|
||||
uloop_timeout_set(&update_timer, 1);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
spotfilter_nl80211_done();
|
||||
return -1;
|
||||
}
|
||||
|
||||
void spotfilter_nl80211_done(void)
|
||||
{
|
||||
if (!genl)
|
||||
return;
|
||||
|
||||
uloop_timeout_cancel(&update_timer);
|
||||
uloop_fd_delete(&genl_fd);
|
||||
nl_socket_free(genl);
|
||||
genl = NULL;
|
||||
}
|
||||
99
feeds/ucentral/spotfilter/src/rtnl.c
Normal file
99
feeds/ucentral/spotfilter/src/rtnl.c
Normal file
@@ -0,0 +1,99 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netlink/msg.h>
|
||||
#include <netlink/attr.h>
|
||||
#include <netlink/socket.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include "spotfilter.h"
|
||||
|
||||
static struct nl_sock *rtnl;
|
||||
bool rtnl_ignore_errors;
|
||||
|
||||
static int
|
||||
spotfilter_nl_error_cb(struct sockaddr_nl *nla, struct nlmsgerr *err,
|
||||
void *arg)
|
||||
{
|
||||
struct nlmsghdr *nlh = (struct nlmsghdr *) err - 1;
|
||||
struct nlattr *tb[NLMSGERR_ATTR_MAX + 1];
|
||||
struct nlattr *attrs;
|
||||
int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh);
|
||||
int len = nlh->nlmsg_len;
|
||||
const char *errstr = "(unknown)";
|
||||
|
||||
if (rtnl_ignore_errors)
|
||||
return NL_STOP;
|
||||
|
||||
if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
|
||||
return NL_STOP;
|
||||
|
||||
if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
|
||||
ack_len += err->msg.nlmsg_len - sizeof(*nlh);
|
||||
|
||||
attrs = (void *) ((unsigned char *) nlh + ack_len);
|
||||
len -= ack_len;
|
||||
|
||||
nla_parse(tb, NLMSGERR_ATTR_MAX, attrs, len, NULL);
|
||||
if (tb[NLMSGERR_ATTR_MSG])
|
||||
errstr = nla_data(tb[NLMSGERR_ATTR_MSG]);
|
||||
|
||||
fprintf(stderr, "Netlink error(%d): %s\n", err->error, errstr);
|
||||
|
||||
return NL_STOP;
|
||||
}
|
||||
|
||||
int rtnl_call(struct nl_msg *msg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = nl_send_auto_complete(rtnl, msg);
|
||||
nlmsg_free(msg);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return nl_wait_for_ack(rtnl);
|
||||
}
|
||||
|
||||
int rtnl_fd(void)
|
||||
{
|
||||
return nl_socket_get_fd(rtnl);
|
||||
}
|
||||
|
||||
int rtnl_init(void)
|
||||
{
|
||||
int fd, opt;
|
||||
|
||||
if (rtnl)
|
||||
return 0;
|
||||
|
||||
rtnl = nl_socket_alloc();
|
||||
if (!rtnl)
|
||||
return -1;
|
||||
|
||||
if (nl_connect(rtnl, NETLINK_ROUTE))
|
||||
goto free;
|
||||
|
||||
nl_socket_disable_seq_check(rtnl);
|
||||
nl_socket_set_buffer_size(rtnl, 65536, 0);
|
||||
nl_cb_err(nl_socket_get_cb(rtnl), NL_CB_CUSTOM, spotfilter_nl_error_cb, NULL);
|
||||
|
||||
fd = nl_socket_get_fd(rtnl);
|
||||
|
||||
opt = 1;
|
||||
setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &opt, sizeof(opt));
|
||||
|
||||
opt = 1;
|
||||
setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &opt, sizeof(opt));
|
||||
|
||||
return 0;
|
||||
|
||||
free:
|
||||
nl_socket_free(rtnl);
|
||||
rtnl = NULL;
|
||||
return -1;
|
||||
}
|
||||
626
feeds/ucentral/spotfilter/src/snoop.c
Normal file
626
feeds/ucentral/spotfilter/src/snoop.c
Normal file
@@ -0,0 +1,626 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <netinet/if_ether.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip6.h>
|
||||
#include <netinet/udp.h>
|
||||
#include <netpacket/packet.h>
|
||||
#include <net/if.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <fnmatch.h>
|
||||
#include <resolv.h>
|
||||
|
||||
#include <libubox/uloop.h>
|
||||
|
||||
#include "spotfilter.h"
|
||||
|
||||
#define FLAG_RESPONSE 0x8000
|
||||
#define FLAG_OPCODE 0x7800
|
||||
#define FLAG_AUTHORATIVE 0x0400
|
||||
#define FLAG_RCODE 0x000f
|
||||
|
||||
#define TYPE_A 0x0001
|
||||
#define TYPE_CNAME 0x0005
|
||||
#define TYPE_PTR 0x000c
|
||||
#define TYPE_TXT 0x0010
|
||||
#define TYPE_AAAA 0x001c
|
||||
#define TYPE_SRV 0x0021
|
||||
#define TYPE_ANY 0x00ff
|
||||
|
||||
#define IS_COMPRESSED(x) ((x & 0xc0) == 0xc0)
|
||||
|
||||
#define CLASS_FLUSH 0x8000
|
||||
#define CLASS_UNICAST 0x8000
|
||||
#define CLASS_IN 0x0001
|
||||
|
||||
#define MAX_NAME_LEN 256
|
||||
#define MAX_DATA_LEN 8096
|
||||
|
||||
int spotfilter_ifb_ifindex;
|
||||
static struct uloop_fd ufd;
|
||||
static struct uloop_timeout cname_gc_timer;
|
||||
|
||||
struct vlan_hdr {
|
||||
uint16_t tci;
|
||||
uint16_t proto;
|
||||
};
|
||||
|
||||
struct packet {
|
||||
void *head;
|
||||
void *buffer;
|
||||
unsigned int len;
|
||||
};
|
||||
|
||||
struct dns_header {
|
||||
uint16_t id;
|
||||
uint16_t flags;
|
||||
uint16_t questions;
|
||||
uint16_t answers;
|
||||
uint16_t authority;
|
||||
uint16_t additional;
|
||||
} __packed;
|
||||
|
||||
struct dns_question {
|
||||
uint16_t type;
|
||||
uint16_t class;
|
||||
} __packed;
|
||||
|
||||
struct dns_answer {
|
||||
uint16_t type;
|
||||
uint16_t class;
|
||||
uint32_t ttl;
|
||||
uint16_t rdlength;
|
||||
} __packed;
|
||||
|
||||
struct addr_entry_data {
|
||||
union {
|
||||
struct {
|
||||
uint32_t _pad;
|
||||
uint32_t ip4addr;
|
||||
};
|
||||
uint32_t ip6addr[4];
|
||||
};
|
||||
uint32_t timeout;
|
||||
};
|
||||
|
||||
struct addr_entry {
|
||||
struct avl_node node;
|
||||
struct addr_entry_data data;
|
||||
};
|
||||
|
||||
struct cname_entry {
|
||||
struct avl_node node;
|
||||
uint8_t class;
|
||||
uint8_t age;
|
||||
};
|
||||
|
||||
static uint32_t spotfilter_gettime(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
|
||||
return ts.tv_sec;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
pkt_peek(struct packet *pkt, unsigned int len)
|
||||
{
|
||||
if (len > pkt->len)
|
||||
return NULL;
|
||||
|
||||
return pkt->buffer;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
pkt_pull(struct packet *pkt, unsigned int len)
|
||||
{
|
||||
void *ret = pkt_peek(pkt, len);
|
||||
|
||||
if (!ret)
|
||||
return NULL;
|
||||
|
||||
pkt->buffer += len;
|
||||
pkt->len -= len;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool
|
||||
proto_is_vlan(uint16_t proto)
|
||||
{
|
||||
return proto == ETH_P_8021Q || proto == ETH_P_8021AD;
|
||||
}
|
||||
|
||||
static int pkt_pull_name(struct packet *pkt, const void *hdr, char *dest)
|
||||
{
|
||||
int len;
|
||||
|
||||
if (dest)
|
||||
len = dn_expand(hdr, pkt->buffer + pkt->len, pkt->buffer,
|
||||
(void *)dest, MAX_NAME_LEN);
|
||||
else
|
||||
len = dn_skipname(pkt->buffer, pkt->buffer + pkt->len - 1);
|
||||
|
||||
if (len < 0 || !pkt_pull(pkt, len))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
cname_cache_set(struct interface *iface, const char *name, int class)
|
||||
{
|
||||
struct cname_entry *e;
|
||||
|
||||
if (class < 0)
|
||||
return;
|
||||
|
||||
e = avl_find_element(&iface->cname_cache, name, e, node);
|
||||
if (!e) {
|
||||
char *name_buf;
|
||||
|
||||
e = calloc_a(sizeof(*e), &name_buf, strlen(name) + 1);
|
||||
e->node.key = strcpy(name_buf, name);
|
||||
avl_insert(&iface->cname_cache, &e->node);
|
||||
}
|
||||
|
||||
e->age = 0;
|
||||
e->class = (uint8_t)class;
|
||||
}
|
||||
|
||||
static int
|
||||
cname_cache_get(struct interface *iface, const char *name, int *class)
|
||||
{
|
||||
struct cname_entry *e;
|
||||
|
||||
e = avl_find_element(&iface->cname_cache, name, e, node);
|
||||
if (!e)
|
||||
return -1;
|
||||
|
||||
if (*class < 0)
|
||||
*class = e->class;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
__spotfilter_dns_whitelist_lookup(struct blob_attr *attr, const char *name, int *class)
|
||||
{
|
||||
enum {
|
||||
WL_ATTR_CLASS,
|
||||
WL_ATTR_HOSTS,
|
||||
__WL_ATTR_MAX
|
||||
};
|
||||
static const struct blobmsg_policy policy[__WL_ATTR_MAX] = {
|
||||
[WL_ATTR_CLASS] = { "class", BLOBMSG_TYPE_INT32 },
|
||||
[WL_ATTR_HOSTS] = { "hosts", BLOBMSG_TYPE_ARRAY },
|
||||
};
|
||||
struct blob_attr *tb[__WL_ATTR_MAX];
|
||||
struct blob_attr *cur;
|
||||
int rem;
|
||||
|
||||
blobmsg_parse(policy, __WL_ATTR_MAX, tb, blobmsg_data(attr), blobmsg_len(attr));
|
||||
|
||||
if (!tb[WL_ATTR_CLASS] || !tb[WL_ATTR_HOSTS])
|
||||
return false;
|
||||
|
||||
blobmsg_for_each_attr(cur, tb[WL_ATTR_HOSTS], rem) {
|
||||
if (fnmatch(blobmsg_get_string(cur), name, 0))
|
||||
continue;
|
||||
|
||||
*class = blobmsg_get_u32(tb[WL_ATTR_CLASS]);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
spotfilter_dns_whitelist_lookup(struct interface *iface, const char *name, int *class)
|
||||
{
|
||||
struct blob_attr *cur;
|
||||
int rem;
|
||||
|
||||
if (!iface->whitelist)
|
||||
return;
|
||||
|
||||
blobmsg_for_each_attr(cur, iface->whitelist, rem) {
|
||||
if (__spotfilter_dns_whitelist_lookup(cur, name, class))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
spotfilter_dns_whitelist_map_add(struct interface *iface, const struct addr_entry_data *data,
|
||||
bool ipv6, int class)
|
||||
{
|
||||
struct addr_entry *e;
|
||||
uint8_t val = (uint8_t)class;
|
||||
int32_t delta;
|
||||
|
||||
if (class < 0)
|
||||
return;
|
||||
|
||||
e = avl_find_element(&iface->addr_map, data, e, node);
|
||||
if (!e) {
|
||||
e = calloc(1, sizeof(*e));
|
||||
memcpy(&e->data, data, sizeof(e->data));
|
||||
e->node.key = &e->data;
|
||||
avl_insert(&iface->addr_map, &e->node);
|
||||
}
|
||||
|
||||
spotfilter_bpf_set_whitelist(iface, ipv6 ? data->ip6addr : &data->ip4addr, ipv6, &val);
|
||||
e->data.timeout = spotfilter_gettime() + data->timeout;
|
||||
|
||||
delta = e->data.timeout - iface->next_gc;
|
||||
if (iface->next_gc && delta < 0)
|
||||
uloop_timeout_set(&iface->addr_gc, data->timeout);
|
||||
}
|
||||
|
||||
static int
|
||||
dns_parse_question(struct interface *iface, struct packet *pkt, const void *hdr, int *class)
|
||||
{
|
||||
char qname[MAX_NAME_LEN];
|
||||
|
||||
if (pkt_pull_name(pkt, hdr, qname) ||
|
||||
!pkt_pull(pkt, sizeof(struct dns_question)))
|
||||
return -1;
|
||||
|
||||
cname_cache_get(iface, qname, class);
|
||||
spotfilter_dns_whitelist_lookup(iface, qname, class);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dns_parse_answer(struct interface *iface, struct packet *pkt, void *hdr, int *class)
|
||||
{
|
||||
char cname[MAX_NAME_LEN];
|
||||
struct dns_answer *a;
|
||||
struct addr_entry_data data = {};
|
||||
bool ipv6 = false;
|
||||
void *rdata;
|
||||
int len;
|
||||
|
||||
if (pkt_pull_name(pkt, hdr, NULL))
|
||||
return -1;
|
||||
|
||||
a = pkt_pull(pkt, sizeof(*a));
|
||||
if (!a)
|
||||
return -1;
|
||||
|
||||
len = be16_to_cpu(a->rdlength);
|
||||
rdata = pkt_pull(pkt, len);
|
||||
if (!rdata)
|
||||
return -1;
|
||||
|
||||
switch (be16_to_cpu(a->type)) {
|
||||
case TYPE_CNAME:
|
||||
if (dn_expand(hdr, pkt->buffer + pkt->len, rdata,
|
||||
cname, sizeof(cname)) < 0)
|
||||
return -1;
|
||||
|
||||
spotfilter_dns_whitelist_lookup(iface, cname, class);
|
||||
cname_cache_set(iface, cname, *class);
|
||||
return 0;
|
||||
case TYPE_A:
|
||||
memcpy(&data.ip4addr, rdata, 4);
|
||||
if (!data.ip4addr)
|
||||
return 0;
|
||||
break;
|
||||
case TYPE_AAAA:
|
||||
ipv6 = true;
|
||||
memcpy(&data.ip6addr, rdata, 16);
|
||||
if (!data.ip6addr[0])
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (class < 0)
|
||||
return 0;
|
||||
|
||||
data.timeout = be32_to_cpu(a->ttl);
|
||||
spotfilter_dns_whitelist_map_add(iface, &data, ipv6, *class);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
spotfilter_dns_iface_recv(struct interface *iface, struct packet *pkt)
|
||||
{
|
||||
struct dns_header *h;
|
||||
int class = -1;
|
||||
int i;
|
||||
|
||||
h = pkt_pull(pkt, sizeof(*h));
|
||||
if (!h)
|
||||
return;
|
||||
|
||||
if ((h->flags & cpu_to_be16(FLAG_RESPONSE | FLAG_OPCODE | FLAG_RCODE)) !=
|
||||
cpu_to_be16(FLAG_RESPONSE))
|
||||
return;
|
||||
|
||||
if (h->questions != cpu_to_be16(1))
|
||||
return;
|
||||
|
||||
if (dns_parse_question(iface, pkt, h, &class))
|
||||
return;
|
||||
|
||||
for (i = 0; i < be16_to_cpu(h->answers); i++)
|
||||
if (dns_parse_answer(iface, pkt, h, &class))
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
spotfilter_dns_recv(struct packet *pkt)
|
||||
{
|
||||
struct interface *iface;
|
||||
|
||||
avl_for_each_element(&interfaces, iface, node) {
|
||||
struct packet tmp_pkt = *pkt;
|
||||
|
||||
spotfilter_dns_iface_recv(iface, &tmp_pkt);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
spotfilter_parse_udp_v4(struct packet *pkt, uint16_t src_port, uint16_t dst_port)
|
||||
{
|
||||
struct ethhdr *eth = pkt->head;
|
||||
|
||||
if (src_port != 67 || dst_port != 68)
|
||||
return;
|
||||
|
||||
spotfilter_recv_dhcpv4(pkt->buffer, pkt->len, eth->h_dest);
|
||||
}
|
||||
|
||||
static void
|
||||
spotfilter_packet_cb(struct packet *pkt)
|
||||
{
|
||||
uint16_t proto, src_port, dst_port;
|
||||
struct ethhdr *eth;
|
||||
struct ip6_hdr *ip6;
|
||||
struct ip *ip;
|
||||
struct udphdr *udp;
|
||||
bool ipv4;
|
||||
|
||||
eth = pkt_pull(pkt, sizeof(*eth));
|
||||
if (!eth)
|
||||
return;
|
||||
|
||||
proto = be16_to_cpu(eth->h_proto);
|
||||
if (proto_is_vlan(proto)) {
|
||||
struct vlan_hdr *vlan;
|
||||
|
||||
vlan = pkt_pull(pkt, sizeof(*vlan));
|
||||
if (!vlan)
|
||||
return;
|
||||
|
||||
proto = be16_to_cpu(vlan->proto);
|
||||
}
|
||||
|
||||
switch (proto) {
|
||||
case ETH_P_IP:
|
||||
ip = pkt_peek(pkt, sizeof(struct ip));
|
||||
if (!ip)
|
||||
return;
|
||||
|
||||
if (!pkt_pull(pkt, ip->ip_hl * 4))
|
||||
return;
|
||||
|
||||
proto = ip->ip_p;
|
||||
ipv4 = true;
|
||||
break;
|
||||
case ETH_P_IPV6:
|
||||
ip6 = pkt_pull(pkt, sizeof(*ip6));
|
||||
if (!ip6)
|
||||
return;
|
||||
|
||||
proto = ip6->ip6_nxt;
|
||||
if (proto == IPPROTO_ICMPV6) {
|
||||
if (ip6->ip6_hlim != 255)
|
||||
return;
|
||||
|
||||
spotfilter_recv_icmpv6(pkt->buffer, pkt->len, eth->h_source, eth->h_dest);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (proto != IPPROTO_UDP)
|
||||
return;
|
||||
|
||||
udp = pkt_pull(pkt, sizeof(struct udphdr));
|
||||
if (!udp)
|
||||
return;
|
||||
|
||||
src_port = ntohs(udp->uh_sport);
|
||||
dst_port = ntohs(udp->uh_dport);
|
||||
|
||||
if (ipv4)
|
||||
spotfilter_parse_udp_v4(pkt, src_port, dst_port);
|
||||
|
||||
if (src_port == 53)
|
||||
spotfilter_dns_recv(pkt);
|
||||
}
|
||||
|
||||
static void
|
||||
spotfilter_socket_cb(struct uloop_fd *fd, unsigned int events)
|
||||
{
|
||||
static uint8_t buf[8192];
|
||||
struct packet pkt = {
|
||||
.head = buf,
|
||||
.buffer = buf,
|
||||
};
|
||||
int len;
|
||||
|
||||
retry:
|
||||
len = recvfrom(fd->fd, buf, sizeof(buf), MSG_DONTWAIT, NULL, NULL);
|
||||
if (len < 0) {
|
||||
if (errno == EINTR)
|
||||
goto retry;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!len)
|
||||
return;
|
||||
|
||||
pkt.len = len;
|
||||
spotfilter_packet_cb(&pkt);
|
||||
}
|
||||
|
||||
static int
|
||||
spotfilter_open_socket(void)
|
||||
{
|
||||
struct sockaddr_ll sll = {
|
||||
.sll_family = AF_PACKET,
|
||||
.sll_protocol = htons(ETH_P_ALL),
|
||||
};
|
||||
int sock;
|
||||
|
||||
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
|
||||
if (sock == -1) {
|
||||
ULOG_ERR("failed to create raw socket: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
sll.sll_ifindex = if_nametoindex(SPOTFILTER_IFB_NAME);
|
||||
if (bind(sock, (struct sockaddr *)&sll, sizeof(sll))) {
|
||||
ULOG_ERR("failed to bind socket to "SPOTFILTER_IFB_NAME": %s\n",
|
||||
strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK);
|
||||
|
||||
ufd.fd = sock;
|
||||
ufd.cb = spotfilter_socket_cb;
|
||||
uloop_fd_add(&ufd, ULOOP_READ);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
spotfilter_addr_gc(struct uloop_timeout *t)
|
||||
{
|
||||
struct interface *iface = container_of(t, struct interface, addr_gc);
|
||||
struct addr_entry *e, *tmp;
|
||||
uint32_t now = spotfilter_gettime();
|
||||
int32_t timeout = 0;
|
||||
|
||||
iface->next_gc = 0;
|
||||
avl_for_each_element_safe(&iface->addr_map, e, node, tmp) {
|
||||
const void *addr = e->data.ip6addr[0] ? &e->data.ip6addr[0] : &e->data.ip4addr;
|
||||
bool ipv6 = !!e->data.ip6addr[0];
|
||||
int32_t cur_timeout;
|
||||
|
||||
cur_timeout = e->data.timeout - now;
|
||||
if (cur_timeout <= 0) {
|
||||
if (!spotfilter_bpf_whitelist_seen(iface, addr, ipv6)) {
|
||||
spotfilter_bpf_set_whitelist(iface, addr, ipv6, NULL);
|
||||
avl_delete(&iface->addr_map, &e->node);
|
||||
free(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
e->data.timeout = now + iface->active_timeout;
|
||||
}
|
||||
|
||||
if (!timeout || cur_timeout < timeout) {
|
||||
timeout = cur_timeout;
|
||||
iface->next_gc = e->data.timeout;
|
||||
}
|
||||
}
|
||||
|
||||
if (!timeout)
|
||||
return;
|
||||
|
||||
uloop_timeout_set(&iface->addr_gc, timeout * 1000);
|
||||
}
|
||||
|
||||
static void
|
||||
spotfilter_cname_cache_gc(struct uloop_timeout *timeout)
|
||||
{
|
||||
struct interface *iface;
|
||||
struct cname_entry *e, *tmp;
|
||||
|
||||
avl_for_each_element(&interfaces, iface, node) {
|
||||
avl_for_each_element_safe(&iface->cname_cache, e, node, tmp) {
|
||||
if (e->age++ < 5)
|
||||
continue;
|
||||
|
||||
avl_delete(&iface->cname_cache, &e->node);
|
||||
free(e);
|
||||
}
|
||||
}
|
||||
|
||||
uloop_timeout_set(timeout, 1000);
|
||||
}
|
||||
|
||||
static int avl_addr_cmp(const void *k1, const void *k2, void *ptr)
|
||||
{
|
||||
return memcmp(k1, k2, 16);
|
||||
}
|
||||
|
||||
|
||||
void spotfilter_dns_init(struct interface *iface)
|
||||
{
|
||||
avl_init(&iface->cname_cache, avl_strcmp, false, NULL);
|
||||
avl_init(&iface->addr_map, avl_addr_cmp, false, NULL);
|
||||
iface->addr_gc.cb = spotfilter_addr_gc;
|
||||
}
|
||||
|
||||
void spotfilter_dns_free(struct interface *iface)
|
||||
{
|
||||
struct cname_entry *e, *tmp;
|
||||
|
||||
avl_remove_all_elements(&iface->cname_cache, e, node, tmp)
|
||||
free(e);
|
||||
}
|
||||
|
||||
int spotfilter_dev_init(void)
|
||||
{
|
||||
cname_gc_timer.cb = spotfilter_cname_cache_gc;
|
||||
spotfilter_cname_cache_gc(&cname_gc_timer);
|
||||
|
||||
spotfilter_dev_done();
|
||||
|
||||
if (spotfilter_run_cmd("ip link add "SPOTFILTER_IFB_NAME" type ifb", false) ||
|
||||
spotfilter_run_cmd("ip link set dev "SPOTFILTER_IFB_NAME" up", false) ||
|
||||
spotfilter_open_socket())
|
||||
return -1;
|
||||
|
||||
spotfilter_ifb_ifindex = if_nametoindex(SPOTFILTER_IFB_NAME);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spotfilter_dev_done(void)
|
||||
{
|
||||
if (ufd.registered) {
|
||||
uloop_fd_delete(&ufd);
|
||||
close(ufd.fd);
|
||||
}
|
||||
|
||||
spotfilter_run_cmd("ip link del "SPOTFILTER_IFB_NAME, true);
|
||||
}
|
||||
287
feeds/ucentral/spotfilter/src/spotfilter-bpf.c
Normal file
287
feeds/ucentral/spotfilter/src/spotfilter-bpf.c
Normal file
@@ -0,0 +1,287 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#define KBUILD_MODNAME "foo"
|
||||
#include <uapi/linux/bpf.h>
|
||||
#include <uapi/linux/if_ether.h>
|
||||
#include <uapi/linux/if_packet.h>
|
||||
#include <uapi/linux/ip.h>
|
||||
#include <uapi/linux/ipv6.h>
|
||||
#include <uapi/linux/in.h>
|
||||
#include <uapi/linux/tcp.h>
|
||||
#include <uapi/linux/udp.h>
|
||||
#include <uapi/linux/icmpv6.h>
|
||||
#include <uapi/linux/filter.h>
|
||||
#include <uapi/linux/pkt_cls.h>
|
||||
#include <linux/ip.h>
|
||||
#include <net/ipv6.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include "bpf_skb_utils.h"
|
||||
#include "spotfilter-bpf.h"
|
||||
|
||||
static const volatile struct spotfilter_bpf_config config = {};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__uint(key_size, sizeof(uint32_t));
|
||||
__type(value, struct spotfilter_bpf_class);
|
||||
__uint(max_entries, SPOTFILTER_NUM_CLASS);
|
||||
} class SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(key_size, sizeof(struct spotfilter_client_key));
|
||||
__type(value, struct spotfilter_client_data);
|
||||
__uint(max_entries, 1000);
|
||||
__uint(map_flags, BPF_F_NO_PREALLOC);
|
||||
} client SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(key_size, sizeof(struct in_addr));
|
||||
__type(value, struct spotfilter_whitelist_entry);
|
||||
__uint(max_entries, 10000);
|
||||
__uint(map_flags, BPF_F_NO_PREALLOC);
|
||||
} whitelist_ipv4 SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(key_size, sizeof(struct in6_addr));
|
||||
__type(value, struct spotfilter_whitelist_entry);
|
||||
__uint(max_entries, 10000);
|
||||
__uint(map_flags, BPF_F_NO_PREALLOC);
|
||||
} whitelist_ipv6 SEC(".maps");
|
||||
|
||||
static bool
|
||||
is_dhcpv4_port(uint16_t port)
|
||||
{
|
||||
return port == bpf_htons(67) || port == bpf_htons(68);
|
||||
}
|
||||
|
||||
static __always_inline bool
|
||||
check_ipv4_control(struct skb_parser_info *info)
|
||||
{
|
||||
struct udphdr *udph;
|
||||
|
||||
if (info->proto != IPPROTO_UDP)
|
||||
return false;
|
||||
|
||||
udph = skb_info_ptr(info, sizeof(*udph));
|
||||
if (!udph)
|
||||
return false;
|
||||
|
||||
return is_dhcpv4_port(udph->source) && is_dhcpv4_port(udph->dest);
|
||||
}
|
||||
|
||||
static bool
|
||||
is_dhcpv6_port(uint16_t port)
|
||||
{
|
||||
return port == bpf_htons(546) || port == bpf_htons(547);
|
||||
}
|
||||
|
||||
static bool
|
||||
is_icmpv6_control(uint8_t type)
|
||||
{
|
||||
switch (type) {
|
||||
case ICMPV6_PKT_TOOBIG:
|
||||
case NDISC_ROUTER_SOLICITATION:
|
||||
case NDISC_ROUTER_ADVERTISEMENT:
|
||||
case NDISC_NEIGHBOUR_SOLICITATION:
|
||||
case NDISC_NEIGHBOUR_ADVERTISEMENT:
|
||||
case NDISC_REDIRECT:
|
||||
case ICMPV6_MGM_QUERY:
|
||||
case ICMPV6_MGM_REPORT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static __always_inline bool
|
||||
check_ipv6_control(struct skb_parser_info *info)
|
||||
{
|
||||
if (info->proto == IPPROTO_UDP) {
|
||||
struct udphdr *udph;
|
||||
|
||||
udph = skb_info_ptr(info, sizeof(*udph));
|
||||
if (!udph)
|
||||
return false;
|
||||
|
||||
return is_dhcpv6_port(udph->source) && is_dhcpv6_port(udph->dest);
|
||||
}
|
||||
|
||||
if (info->proto == IPPROTO_ICMPV6) {
|
||||
struct icmp6hdr *icmp6h;
|
||||
|
||||
icmp6h = skb_info_ptr(info, sizeof(*icmp6h));
|
||||
if (!icmp6h)
|
||||
return false;
|
||||
|
||||
return is_icmpv6_control(icmp6h->icmp6_type);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static __always_inline bool
|
||||
check_dns(struct skb_parser_info *info, bool ingress)
|
||||
{
|
||||
struct udphdr *udph;
|
||||
|
||||
if (info->proto != IPPROTO_UDP)
|
||||
return false;
|
||||
|
||||
udph = skb_info_ptr(info, sizeof(*udph));
|
||||
if (!udph)
|
||||
return false;
|
||||
|
||||
if (ingress)
|
||||
return udph->dest == bpf_htons(53);
|
||||
|
||||
return udph->source == bpf_htons(53);
|
||||
}
|
||||
|
||||
SEC("tc/egress")
|
||||
int spotfilter_out(struct __sk_buff *skb)
|
||||
{
|
||||
struct spotfilter_client_data *cl;
|
||||
struct skb_parser_info info;
|
||||
struct ethhdr *eth;
|
||||
bool is_control = false;
|
||||
bool is_dns = false;
|
||||
|
||||
skb_parse_init(&info, skb);
|
||||
eth = skb_parse_ethernet(&info);
|
||||
if (!eth)
|
||||
return TC_ACT_UNSPEC;
|
||||
|
||||
cl = bpf_map_lookup_elem(&client, eth->h_dest);
|
||||
if (cl) {
|
||||
if (cl->flags & SPOTFILTER_CLIENT_F_ACCT_DL)
|
||||
cl->bytes_dl += skb->len;
|
||||
}
|
||||
|
||||
skb_parse_vlan(&info);
|
||||
if (skb_parse_ipv4(&info, sizeof(struct udphdr))) {
|
||||
is_control = check_ipv4_control(&info);
|
||||
is_dns = check_dns(&info, false);
|
||||
} else if (skb_parse_ipv6(&info, sizeof(struct icmp6hdr))) {
|
||||
is_control = check_ipv6_control(&info);
|
||||
is_dns = check_dns(&info, false);
|
||||
} else {
|
||||
return TC_ACT_UNSPEC;
|
||||
}
|
||||
|
||||
if (is_control || is_dns)
|
||||
bpf_clone_redirect(skb, config.snoop_ifindex, BPF_F_INGRESS);
|
||||
|
||||
return TC_ACT_UNSPEC;
|
||||
}
|
||||
|
||||
SEC("tc/ingress")
|
||||
int spotfilter_in(struct __sk_buff *skb)
|
||||
{
|
||||
struct spotfilter_client_data *cl, cldata = {};
|
||||
struct spotfilter_bpf_class *c, cdata;
|
||||
struct skb_parser_info info;
|
||||
struct ipv6hdr *ip6h;
|
||||
struct ethhdr *eth;
|
||||
struct iphdr *iph;
|
||||
bool addr_match = false;
|
||||
bool is_control = false;
|
||||
bool has_vlan = false;
|
||||
bool is_dns = false;
|
||||
struct spotfilter_whitelist_entry *wl_val = NULL;
|
||||
uint32_t cur_class;
|
||||
|
||||
skb_parse_init(&info, skb);
|
||||
eth = skb_parse_ethernet(&info);
|
||||
if (!eth)
|
||||
return TC_ACT_UNSPEC;
|
||||
|
||||
cl = bpf_map_lookup_elem(&client, eth->h_source);
|
||||
if (cl) {
|
||||
cldata = *cl;
|
||||
if (cl->flags & SPOTFILTER_CLIENT_F_ACCT_UL)
|
||||
cl->bytes_ul += skb->len;
|
||||
}
|
||||
|
||||
has_vlan = !!skb_parse_vlan(&info);
|
||||
if ((iph = skb_parse_ipv4(&info, sizeof(struct udphdr))) != NULL) {
|
||||
addr_match = iph->saddr == cldata.ip4addr;
|
||||
is_control = check_ipv4_control(&info);
|
||||
is_dns = check_dns(&info, true);
|
||||
|
||||
if (!is_control)
|
||||
wl_val = bpf_map_lookup_elem(&whitelist_ipv4, &iph->daddr);
|
||||
} else if ((ip6h = skb_parse_ipv6(&info, sizeof(struct icmp6hdr))) != NULL) {
|
||||
addr_match = ipv6_addr_equal(&ip6h->saddr, (struct in6_addr *)&cldata.ip6addr);
|
||||
if ((ip6h->saddr.s6_addr[0] & 0xe0) != 0x20)
|
||||
addr_match = true;
|
||||
is_control = check_ipv6_control(&info);
|
||||
is_dns = check_dns(&info, true);
|
||||
|
||||
if (!is_control)
|
||||
wl_val = bpf_map_lookup_elem(&whitelist_ipv6, &ip6h->daddr);
|
||||
} else {
|
||||
return TC_ACT_UNSPEC;
|
||||
}
|
||||
|
||||
if (wl_val) {
|
||||
cldata.cur_class = wl_val->val;
|
||||
cldata.dns_class = wl_val->val;
|
||||
wl_val->seen = 1;
|
||||
}
|
||||
|
||||
if (is_control) {
|
||||
bpf_clone_redirect(skb, config.snoop_ifindex, BPF_F_INGRESS);
|
||||
return TC_ACT_UNSPEC;
|
||||
}
|
||||
|
||||
if (!addr_match) {
|
||||
if (!is_control)
|
||||
return TC_ACT_SHOT;
|
||||
|
||||
memset(&cldata, 0, sizeof(cldata));
|
||||
}
|
||||
|
||||
cur_class = is_dns ? cldata.dns_class : cldata.cur_class;
|
||||
c = bpf_map_lookup_elem(&class, &cur_class);
|
||||
if (c)
|
||||
cdata = *c;
|
||||
else
|
||||
return TC_ACT_UNSPEC;
|
||||
|
||||
if (!(cdata.actions & SPOTFILTER_ACTION_VALID))
|
||||
return TC_ACT_SHOT;
|
||||
|
||||
if (cdata.actions & SPOTFILTER_ACTION_SET_DEST_MAC) {
|
||||
eth = skb_ptr(skb, 0, sizeof(*eth));
|
||||
if (!eth)
|
||||
return TC_ACT_UNSPEC;
|
||||
|
||||
memcpy(eth->h_dest, cdata.dest_mac, ETH_ALEN);
|
||||
}
|
||||
|
||||
if (cdata.actions & SPOTFILTER_ACTION_FWMARK)
|
||||
skb->mark = (skb->mark & ~cdata.fwmark_mask) | cdata.fwmark_val;
|
||||
|
||||
if (cdata.actions & SPOTFILTER_ACTION_REDIRECT) {
|
||||
if (cdata.actions & SPOTFILTER_ACTION_REDIRECT_VLAN) {
|
||||
if (has_vlan && bpf_skb_vlan_pop(skb))
|
||||
return -1;
|
||||
|
||||
if (cdata.redirect_vlan_proto &&
|
||||
bpf_skb_vlan_push(skb, cdata.redirect_vlan_proto, cdata.redirect_vlan))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bpf_redirect(cdata.redirect_ifindex, 0);
|
||||
}
|
||||
|
||||
return TC_ACT_UNSPEC;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
57
feeds/ucentral/spotfilter/src/spotfilter-bpf.h
Normal file
57
feeds/ucentral/spotfilter/src/spotfilter-bpf.h
Normal file
@@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#ifndef __BPF_SPOTFILTER_H
|
||||
#define __BPF_SPOTFILTER_H
|
||||
|
||||
struct spotfilter_client_key {
|
||||
uint8_t addr[6];
|
||||
};
|
||||
|
||||
#define SPOTFILTER_CLIENT_F_ACCT_UL (1 << 0)
|
||||
#define SPOTFILTER_CLIENT_F_ACCT_DL (1 << 1)
|
||||
|
||||
struct spotfilter_client_data {
|
||||
uint32_t ip4addr;
|
||||
uint32_t ip6addr[4];
|
||||
uint8_t cur_class;
|
||||
uint8_t dns_class;
|
||||
uint8_t flags;
|
||||
|
||||
uint64_t bytes_ul;
|
||||
uint64_t bytes_dl;
|
||||
};
|
||||
|
||||
struct spotfilter_bpf_config {
|
||||
uint32_t snoop_ifindex;
|
||||
};
|
||||
|
||||
struct spotfilter_whitelist_entry {
|
||||
uint8_t val;
|
||||
uint8_t seen;
|
||||
};
|
||||
|
||||
#define SPOTFILTER_NUM_CLASS 16
|
||||
|
||||
#define SPOTFILTER_ACTION_FWMARK (1 << 0)
|
||||
#define SPOTFILTER_ACTION_REDIRECT (1 << 1)
|
||||
#define SPOTFILTER_ACTION_REDIRECT_VLAN (1 << 2)
|
||||
#define SPOTFILTER_ACTION_SET_DEST_MAC (1 << 3)
|
||||
|
||||
#define SPOTFILTER_ACTION_VALID (1 << 15)
|
||||
|
||||
|
||||
struct spotfilter_bpf_class {
|
||||
uint16_t actions;
|
||||
uint8_t dest_mac[6];
|
||||
|
||||
uint32_t fwmark_val;
|
||||
uint32_t fwmark_mask;
|
||||
|
||||
uint32_t redirect_ifindex;
|
||||
uint16_t redirect_vlan;
|
||||
uint16_t redirect_vlan_proto;
|
||||
};
|
||||
|
||||
#endif
|
||||
59
feeds/ucentral/spotfilter/src/spotfilter.h
Normal file
59
feeds/ucentral/spotfilter/src/spotfilter.h
Normal file
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#ifndef __SPOTFILTER_H
|
||||
#define __SPOTFILTER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <regex.h>
|
||||
|
||||
#include <bpf/bpf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
|
||||
#include <libubox/utils.h>
|
||||
#include <libubox/avl.h>
|
||||
#include <libubox/vlist.h>
|
||||
#include <libubox/blobmsg.h>
|
||||
#include <libubox/ulog.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include "spotfilter-bpf.h"
|
||||
#include "interface.h"
|
||||
#include "bpf.h"
|
||||
#include "client.h"
|
||||
|
||||
#define SPOTFILTER_IFB_NAME "spotfilter-ifb"
|
||||
|
||||
#define SPOTFILTER_PROG_PATH "/lib/bpf/spotfilter-bpf.o"
|
||||
|
||||
#define SPOTFILTER_PRIO_BASE 0x120
|
||||
|
||||
extern int spotfilter_ifb_ifindex;
|
||||
struct nl_msg;
|
||||
|
||||
int rtnl_init(void);
|
||||
int rtnl_fd(void);
|
||||
int rtnl_call(struct nl_msg *msg);
|
||||
|
||||
int spotfilter_run_cmd(char *cmd, bool ignore_error);
|
||||
|
||||
int spotfilter_ubus_init(void);
|
||||
void spotfilter_ubus_stop(void);
|
||||
void spotfilter_ubus_notify(struct interface *iface, struct client *cl, const char *type);
|
||||
|
||||
int spotfilter_dev_init(void);
|
||||
void spotfilter_dev_done(void);
|
||||
|
||||
void spotfilter_dns_init(struct interface *iface);
|
||||
void spotfilter_dns_free(struct interface *iface);
|
||||
|
||||
void spotfilter_recv_dhcpv4(const void *msg, int len, const void *eth_addr);
|
||||
void spotfilter_recv_icmpv6(const void *data, int len, const uint8_t *src, const uint8_t *dest);
|
||||
|
||||
int spotfilter_nl80211_init(void);
|
||||
void spotfilter_nl80211_done(void);
|
||||
|
||||
#endif
|
||||
470
feeds/ucentral/spotfilter/src/ubus.c
Normal file
470
feeds/ucentral/spotfilter/src/ubus.c
Normal file
@@ -0,0 +1,470 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <libubus.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/ether.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include "spotfilter.h"
|
||||
|
||||
static struct blob_buf b;
|
||||
|
||||
enum {
|
||||
IFACE_ATTR_NAME,
|
||||
IFACE_ATTR_CONFIG,
|
||||
IFACE_ATTR_DEVICES,
|
||||
__IFACE_ATTR_MAX,
|
||||
};
|
||||
|
||||
static const struct blobmsg_policy iface_policy[__IFACE_ATTR_MAX] = {
|
||||
[IFACE_ATTR_NAME] = { "name", BLOBMSG_TYPE_STRING },
|
||||
[IFACE_ATTR_CONFIG] = { "config", BLOBMSG_TYPE_TABLE },
|
||||
[IFACE_ATTR_DEVICES] = { "devices", BLOBMSG_TYPE_ARRAY },
|
||||
};
|
||||
|
||||
static int
|
||||
interface_ubus_add(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
struct blob_attr *tb[__IFACE_ATTR_MAX];
|
||||
struct blob_attr *cur;
|
||||
const char *name;
|
||||
|
||||
blobmsg_parse(iface_policy, __IFACE_ATTR_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
|
||||
|
||||
if ((cur = tb[IFACE_ATTR_NAME]) != NULL)
|
||||
name = blobmsg_get_string(tb[IFACE_ATTR_NAME]);
|
||||
else
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
if ((cur = tb[IFACE_ATTR_DEVICES]) != NULL &&
|
||||
blobmsg_check_array(cur, BLOBMSG_TYPE_STRING) < 0)
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
interface_add(name, tb[IFACE_ATTR_CONFIG], tb[IFACE_ATTR_DEVICES]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
interface_ubus_remove(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
struct interface *iface;
|
||||
struct blob_attr *tb;
|
||||
|
||||
blobmsg_parse(&iface_policy[IFACE_ATTR_NAME], 1, &tb,
|
||||
blobmsg_data(msg), blobmsg_len(msg));
|
||||
|
||||
if (tb)
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
iface = avl_find_element(&interfaces, blobmsg_get_string(tb), iface, node);
|
||||
if (!iface)
|
||||
return UBUS_STATUS_NOT_FOUND;
|
||||
|
||||
interface_free(iface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
check_devices(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
interface_check_devices();
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum {
|
||||
CLIENT_ATTR_IFACE,
|
||||
CLIENT_ATTR_ADDR,
|
||||
CLIENT_ATTR_ID,
|
||||
CLIENT_ATTR_STATE,
|
||||
CLIENT_ATTR_DNS_STATE,
|
||||
CLIENT_ATTR_ACCOUNTING,
|
||||
CLIENT_ATTR_DATA,
|
||||
__CLIENT_ATTR_MAX
|
||||
};
|
||||
|
||||
static const struct blobmsg_policy client_policy[__CLIENT_ATTR_MAX] = {
|
||||
[CLIENT_ATTR_IFACE] = { "interface", BLOBMSG_TYPE_STRING },
|
||||
[CLIENT_ATTR_ADDR] = { "address", BLOBMSG_TYPE_STRING },
|
||||
[CLIENT_ATTR_ID] = { "id", BLOBMSG_TYPE_STRING },
|
||||
[CLIENT_ATTR_STATE] = { "state", BLOBMSG_TYPE_INT32 },
|
||||
[CLIENT_ATTR_DNS_STATE] = { "dns_state", BLOBMSG_TYPE_INT32 },
|
||||
[CLIENT_ATTR_ACCOUNTING] = { "accounting", BLOBMSG_TYPE_ARRAY },
|
||||
[CLIENT_ATTR_DATA] = { "data", BLOBMSG_TYPE_TABLE },
|
||||
};
|
||||
|
||||
static int
|
||||
client_ubus_init(struct blob_attr *msg, struct blob_attr **tb,
|
||||
struct interface **iface, const void **addr,
|
||||
const char **id, struct client **cl)
|
||||
{
|
||||
struct blob_attr *cur;
|
||||
|
||||
blobmsg_parse(client_policy, __CLIENT_ATTR_MAX, tb,
|
||||
blobmsg_data(msg), blobmsg_len(msg));
|
||||
|
||||
if ((cur = tb[CLIENT_ATTR_IFACE]) != NULL)
|
||||
*iface = avl_find_element(&interfaces, blobmsg_get_string(cur),
|
||||
*iface, node);
|
||||
else
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
if (!*iface)
|
||||
return UBUS_STATUS_NOT_FOUND;
|
||||
|
||||
if ((cur = tb[CLIENT_ATTR_ADDR]) != NULL)
|
||||
*addr = ether_aton(blobmsg_get_string(cur));
|
||||
else
|
||||
*addr = NULL;
|
||||
|
||||
if ((cur = tb[CLIENT_ATTR_ID]) != NULL)
|
||||
*id = blobmsg_get_string(cur);
|
||||
else
|
||||
*id = NULL;
|
||||
|
||||
if (*addr)
|
||||
*cl = avl_find_element(&(*iface)->clients, *addr, *cl, node);
|
||||
else if (*id)
|
||||
*cl = avl_find_element(&(*iface)->client_ids, *id, *cl, id_node);
|
||||
else
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
if (*cl && !*addr)
|
||||
*addr = (*cl)->node.key;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
client_accounting_flags(struct blob_attr *attr)
|
||||
{
|
||||
struct blob_attr *cur;
|
||||
int flags = 0;
|
||||
int rem;
|
||||
|
||||
blobmsg_for_each_attr(cur, attr, rem) {
|
||||
const char *val = blobmsg_get_string(cur);
|
||||
|
||||
if (!strcmp(val, "ul"))
|
||||
flags |= SPOTFILTER_CLIENT_F_ACCT_UL;
|
||||
else if (!strcmp(val, "dl"))
|
||||
flags |= SPOTFILTER_CLIENT_F_ACCT_DL;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
client_ubus_update(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
struct blob_attr *tb[__CLIENT_ATTR_MAX];
|
||||
struct interface *iface = NULL;
|
||||
struct blob_attr *cur;
|
||||
struct client *cl = NULL;
|
||||
const void *addr = NULL;
|
||||
const char *id = NULL;
|
||||
int state = -1, dns_state = -1;
|
||||
int accounting = -1;
|
||||
int ret;
|
||||
|
||||
ret = client_ubus_init(msg, tb, &iface, &addr, &id, &cl);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if ((cur = tb[CLIENT_ATTR_STATE]) != NULL)
|
||||
dns_state = state = blobmsg_get_u32(cur);
|
||||
|
||||
if ((cur = tb[CLIENT_ATTR_DNS_STATE]) != NULL)
|
||||
dns_state = blobmsg_get_u32(cur);
|
||||
|
||||
if ((cur = tb[CLIENT_ATTR_ACCOUNTING]) != NULL &&
|
||||
blobmsg_check_array(cur, BLOBMSG_TYPE_STRING) >= 0)
|
||||
accounting = client_accounting_flags(cur);
|
||||
|
||||
if (!strcmp(method, "client_remove")) {
|
||||
if (!cl)
|
||||
return UBUS_STATUS_NOT_FOUND;
|
||||
|
||||
client_free(iface, cl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!addr)
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
client_set(iface, addr, id, state, dns_state, accounting,
|
||||
tb[CLIENT_ATTR_DATA]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
interface_dump_action(struct blob_buf *buf, struct interface *iface, uint8_t class)
|
||||
{
|
||||
struct spotfilter_bpf_class *c = &iface->cdata[class];
|
||||
char ifname[IFNAMSIZ + 1];
|
||||
|
||||
if (!(c->actions & SPOTFILTER_ACTION_VALID)) {
|
||||
blobmsg_add_u8(buf, "invalid", 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->actions & SPOTFILTER_ACTION_FWMARK) {
|
||||
blobmsg_add_u32(buf, "fwmark", c->fwmark_val);
|
||||
blobmsg_add_u32(buf, "fwmark_mask", c->fwmark_mask);
|
||||
}
|
||||
|
||||
if (c->actions & SPOTFILTER_ACTION_REDIRECT)
|
||||
blobmsg_add_string(buf, "redirect", if_indextoname(c->redirect_ifindex, ifname));
|
||||
|
||||
if (c->actions & SPOTFILTER_ACTION_SET_DEST_MAC)
|
||||
blobmsg_add_string(buf, "dest_mac", ether_ntoa((const void *)c->dest_mac));
|
||||
}
|
||||
|
||||
static void client_dump(struct interface *iface, struct client *cl)
|
||||
{
|
||||
struct blob_attr *val;
|
||||
const char *name;
|
||||
char *buf;
|
||||
void *c;
|
||||
|
||||
spotfilter_bpf_get_client(iface, &cl->key, &cl->data);
|
||||
|
||||
if (iface->client_autoremove)
|
||||
blobmsg_add_u32(&b, "idle", cl->idle);
|
||||
|
||||
blobmsg_add_u32(&b, "state", cl->data.cur_class);
|
||||
blobmsg_add_u32(&b, "dns_state", cl->data.dns_class);
|
||||
if (cl->id_node.key)
|
||||
blobmsg_add_string(&b, "id", (const char *)cl->id_node.key);
|
||||
|
||||
if (cl->data.ip4addr) {
|
||||
buf = blobmsg_alloc_string_buffer(&b, "ip4addr", INET6_ADDRSTRLEN);
|
||||
inet_ntop(AF_INET, (const void *)&cl->data.ip4addr, buf, INET6_ADDRSTRLEN);
|
||||
blobmsg_add_string_buffer(&b);
|
||||
}
|
||||
|
||||
if (cl->data.ip6addr[0]) {
|
||||
buf = blobmsg_alloc_string_buffer(&b, "ip6addr", INET6_ADDRSTRLEN);
|
||||
inet_ntop(AF_INET6, (const void *)cl->data.ip6addr, buf, INET6_ADDRSTRLEN);
|
||||
blobmsg_add_string_buffer(&b);
|
||||
}
|
||||
|
||||
c = blobmsg_open_array(&b, "accounting");
|
||||
if (cl->data.flags & SPOTFILTER_CLIENT_F_ACCT_UL)
|
||||
blobmsg_add_string(&b, NULL, "ul");
|
||||
if (cl->data.flags & SPOTFILTER_CLIENT_F_ACCT_DL)
|
||||
blobmsg_add_string(&b, NULL, "dl");
|
||||
blobmsg_close_table(&b, c);
|
||||
|
||||
c = blobmsg_open_table(&b, "data");
|
||||
kvlist_for_each(&cl->kvdata, name, val)
|
||||
blobmsg_add_blob(&b, val);
|
||||
blobmsg_close_table(&b, c);
|
||||
|
||||
c = blobmsg_open_table(&b, "action");
|
||||
interface_dump_action(&b, iface, cl->data.cur_class);
|
||||
blobmsg_close_table(&b, c);
|
||||
|
||||
c = blobmsg_open_table(&b, "dns_action");
|
||||
interface_dump_action(&b, iface, cl->data.dns_class);
|
||||
blobmsg_close_table(&b, c);
|
||||
|
||||
blobmsg_add_u64(&b, "bytes_ul", cl->data.bytes_ul);
|
||||
blobmsg_add_u64(&b, "bytes_dl", cl->data.bytes_dl);
|
||||
}
|
||||
|
||||
static int
|
||||
client_ubus_get(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
struct blob_attr *tb[__CLIENT_ATTR_MAX];
|
||||
struct interface *iface = NULL;
|
||||
const void *addr = NULL;
|
||||
const char *id = NULL;
|
||||
struct client *cl = NULL;
|
||||
int ret;
|
||||
|
||||
ret = client_ubus_init(msg, tb, &iface, &addr, &id, &cl);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!cl)
|
||||
return UBUS_STATUS_NOT_FOUND;
|
||||
|
||||
blob_buf_init(&b, 0);
|
||||
blobmsg_add_string(&b, "address", ether_ntoa(cl->node.key));
|
||||
client_dump(iface, cl);
|
||||
|
||||
ubus_send_reply(ctx, req, b.head);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
client_ubus_list(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
struct blob_attr *iface_attr;
|
||||
struct interface *iface;
|
||||
struct client *cl;
|
||||
|
||||
blobmsg_parse(&client_policy[CLIENT_ATTR_IFACE], 1, &iface_attr,
|
||||
blobmsg_data(msg), blobmsg_len(msg));
|
||||
|
||||
if (!iface_attr)
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
iface = avl_find_element(&interfaces, blobmsg_get_string(iface_attr),
|
||||
iface, node);
|
||||
if (!iface)
|
||||
return UBUS_STATUS_NOT_FOUND;
|
||||
|
||||
blob_buf_init(&b, 0);
|
||||
avl_for_each_element(&iface->clients, cl, node) {
|
||||
void *c;
|
||||
|
||||
c = blobmsg_open_table(&b, ether_ntoa(cl->node.key));
|
||||
client_dump(iface, cl);
|
||||
blobmsg_close_table(&b, c);
|
||||
}
|
||||
|
||||
ubus_send_reply(ctx, req, b.head);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum {
|
||||
WHITELIST_ATTR_IFACE,
|
||||
WHITELIST_ATTR_ADDR,
|
||||
WHITELIST_ATTR_STATE,
|
||||
__WHITELIST_ATTR_MAX
|
||||
};
|
||||
|
||||
static const struct blobmsg_policy whitelist_policy[__WHITELIST_ATTR_MAX] = {
|
||||
[WHITELIST_ATTR_IFACE] = { "interface", BLOBMSG_TYPE_STRING },
|
||||
[WHITELIST_ATTR_ADDR] = { "address", BLOBMSG_TYPE_ARRAY },
|
||||
[WHITELIST_ATTR_STATE] = { "state", BLOBMSG_TYPE_INT32 },
|
||||
};
|
||||
|
||||
static int
|
||||
whitelist_update(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
struct blob_attr *tb[__WHITELIST_ATTR_MAX];
|
||||
struct interface *iface;
|
||||
struct blob_attr *cur;
|
||||
uint8_t state = 0;
|
||||
const uint8_t *val = &state;
|
||||
int rem;
|
||||
|
||||
blobmsg_parse(whitelist_policy, __WHITELIST_ATTR_MAX, tb,
|
||||
blobmsg_data(msg), blobmsg_len(msg));
|
||||
|
||||
if ((cur = tb[WHITELIST_ATTR_IFACE]) != NULL)
|
||||
iface = avl_find_element(&interfaces, blobmsg_get_string(cur),
|
||||
iface, node);
|
||||
else
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
if ((cur = tb[WHITELIST_ATTR_STATE]) != NULL)
|
||||
state = blobmsg_get_u32(cur);
|
||||
|
||||
if ((cur = tb[WHITELIST_ATTR_ADDR]) == NULL ||
|
||||
blobmsg_check_array(cur, BLOBMSG_TYPE_STRING) < 0)
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
if (!strcmp(method, "whitelist_remove"))
|
||||
val = NULL;
|
||||
|
||||
blobmsg_for_each_attr(cur, tb[WHITELIST_ATTR_ADDR], rem) {
|
||||
const char *addrstr = blobmsg_get_string(cur);
|
||||
bool ipv6 = strchr(addrstr, ':');
|
||||
union {
|
||||
struct in_addr in;
|
||||
struct in6_addr in6;
|
||||
} addr = {};
|
||||
|
||||
if (inet_pton(ipv6 ? AF_INET6 : AF_INET, addrstr, &addr) != 1)
|
||||
continue;
|
||||
|
||||
spotfilter_bpf_set_whitelist(iface, &addr, ipv6, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ubus_method spotfilter_methods[] = {
|
||||
UBUS_METHOD_NOARG("check_devices", check_devices),
|
||||
UBUS_METHOD("client_set", client_ubus_update, client_policy),
|
||||
UBUS_METHOD_MASK("client_remove", client_ubus_update, client_policy,
|
||||
(1 << CLIENT_ATTR_IFACE) | (1 << CLIENT_ATTR_ADDR)),
|
||||
UBUS_METHOD_MASK("client_get", client_ubus_get, client_policy,
|
||||
(1 << CLIENT_ATTR_IFACE) | (1 << CLIENT_ATTR_ADDR)),
|
||||
UBUS_METHOD_MASK("client_list", client_ubus_list, client_policy,
|
||||
(1 << CLIENT_ATTR_IFACE)),
|
||||
UBUS_METHOD("interface_add", interface_ubus_add, iface_policy),
|
||||
UBUS_METHOD_MASK("interface_remove", interface_ubus_remove,
|
||||
iface_policy, 1 << IFACE_ATTR_NAME),
|
||||
UBUS_METHOD("whitelist_add", whitelist_update, whitelist_policy),
|
||||
UBUS_METHOD_MASK("whitelist_remove", whitelist_update, whitelist_policy,
|
||||
(1 << WHITELIST_ATTR_IFACE) | (1 << WHITELIST_ATTR_ADDR)),
|
||||
};
|
||||
|
||||
static struct ubus_object_type spotfilter_object_type =
|
||||
UBUS_OBJECT_TYPE("spotfilter", spotfilter_methods);
|
||||
|
||||
static struct ubus_object spotfilter_object = {
|
||||
.name = "spotfilter",
|
||||
.type = &spotfilter_object_type,
|
||||
.methods = spotfilter_methods,
|
||||
.n_methods = ARRAY_SIZE(spotfilter_methods),
|
||||
};
|
||||
|
||||
static void
|
||||
ubus_connect_handler(struct ubus_context *ctx)
|
||||
{
|
||||
ubus_add_object(ctx, &spotfilter_object);
|
||||
}
|
||||
|
||||
static struct ubus_auto_conn conn;
|
||||
|
||||
void spotfilter_ubus_notify(struct interface *iface, struct client *cl, const char *type)
|
||||
{
|
||||
blob_buf_init(&b, 0);
|
||||
blobmsg_add_string(&b, "interface", interface_name(iface));
|
||||
if (cl) {
|
||||
blobmsg_add_string(&b, "address", ether_ntoa(cl->node.key));
|
||||
if (cl->id_node.key)
|
||||
blobmsg_add_string(&b, "id", cl->id_node.key);
|
||||
}
|
||||
|
||||
ubus_notify(&conn.ctx, &spotfilter_object, type, b.head, -1);
|
||||
}
|
||||
|
||||
int spotfilter_ubus_init(void)
|
||||
{
|
||||
conn.cb = ubus_connect_handler;
|
||||
ubus_auto_connect(&conn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spotfilter_ubus_stop(void)
|
||||
{
|
||||
ubus_auto_shutdown(&conn);
|
||||
}
|
||||
Reference in New Issue
Block a user