mirror of
				https://github.com/Telecominfraproject/wlan-ap.git
				synced 2025-10-31 10:28:06 +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
	 John Crispin
					John Crispin