mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-23 15:32:33 +00:00
udhcpsnoop: add an eBPF based DHCP snooper
Add a DHCP snooping daemon. It offers two features * snoop dhcp leases on interfaces where the device not serve DHCP - ubus call dhcpsnoop dump * forward snooped frames via ubus pub/sub - ubus subscribe dhcpsnoop This package requires a BPF toolchain to be enabled in menuconfig. Signed-off-by: Felix Fietkau <nbd@nbd.name> Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
parent
123cb3793f
commit
a695f59344
34
package/network/utils/udhcpsnoop/Makefile
Normal file
34
package/network/utils/udhcpsnoop/Makefile
Normal file
@ -0,0 +1,34 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=udhcpsnoop
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(INCLUDE_DIR)/cmake.mk
|
||||
|
||||
define Package/udhcpsnoop
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=DHCP Snooping Daemon
|
||||
DEPENDS:=+libubox +libubus +kmod-ifb +tc
|
||||
endef
|
||||
|
||||
define Package/udhcpsnoop/install
|
||||
$(INSTALL_DIR) \
|
||||
$(1)/usr/sbin \
|
||||
$(1)/etc/init.d \
|
||||
$(1)/etc/config \
|
||||
$(1)/etc/hotplug.d/net \
|
||||
$(1)/etc/uci-defaults
|
||||
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/udhcpsnoop $(1)/usr/sbin/
|
||||
$(INSTALL_BIN) ./files/dhcpsnoop.init $(1)/etc/init.d/dhcpsnoop
|
||||
$(INSTALL_DATA) ./files/dhcpsnoop.conf $(1)/etc/config/dhcpsnoop
|
||||
$(INSTALL_DATA) ./files/dhcpsnoop.defaults $(1)/etc/uci-defaults/50-dhcpsnoop
|
||||
$(INSTALL_DATA) ./files/dhcpsnoop.hotplug $(1)/etc/hotplug.d/net/10-dhcpsnoop
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,udhcpsnoop))
|
6
package/network/utils/udhcpsnoop/files/dhcpsnoop.conf
Normal file
6
package/network/utils/udhcpsnoop/files/dhcpsnoop.conf
Normal file
@ -0,0 +1,6 @@
|
||||
#config device
|
||||
# option disabled 1
|
||||
# option name eth0
|
||||
# option ingress 1
|
||||
# option egress 1
|
||||
|
14
package/network/utils/udhcpsnoop/files/dhcpsnoop.defaults
Normal file
14
package/network/utils/udhcpsnoop/files/dhcpsnoop.defaults
Normal file
@ -0,0 +1,14 @@
|
||||
add_device() {
|
||||
uci add dhcpsnoop device
|
||||
uci set dhcpsnoop.@device[-1].name="$1"
|
||||
uci set dhcpsnoop.@device[-1].disabled="$2"
|
||||
uci set dhcpsnoop.@device[-1].ingress=1
|
||||
uci set dhcpsnoop.@device[-1].egress=1
|
||||
}
|
||||
|
||||
wan=$(cat /etc/board.json | jsonfilter -e '@.network.wan.device')
|
||||
[ -z "$wan" ] || add_device $wan 1
|
||||
|
||||
lan=$(cat /etc/board.json | jsonfilter -e '@.network.lan.device')
|
||||
[ -z "$lan" ] && $(cat /etc/board.json | jsonfilter -e '@.network.lan.ports.*')
|
||||
[ -z "$wan" ] || add_device br-lan 0
|
2
package/network/utils/udhcpsnoop/files/dhcpsnoop.hotplug
Normal file
2
package/network/utils/udhcpsnoop/files/dhcpsnoop.hotplug
Normal file
@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
ubus call dhcpsnoop check_devices
|
60
package/network/utils/udhcpsnoop/files/dhcpsnoop.init
Normal file
60
package/network/utils/udhcpsnoop/files/dhcpsnoop.init
Normal file
@ -0,0 +1,60 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (c) 2021 OpenWrt.org
|
||||
|
||||
START=40
|
||||
|
||||
USE_PROCD=1
|
||||
PROG=/usr/sbin/udhcpsnoop
|
||||
|
||||
add_option() {
|
||||
local type="$1"
|
||||
local name="$2"
|
||||
local default="$3"
|
||||
|
||||
config_get val "$cfg" "$name"
|
||||
|
||||
[ -n "$val" ] && json_add_$type "$name" "${val:-$default}"
|
||||
}
|
||||
|
||||
add_device() {
|
||||
local cfg="$1"
|
||||
|
||||
config_get_bool disabled "$cfg" disabled 0
|
||||
[ "$disabled" -gt 0 ] && return
|
||||
|
||||
config_get name "$cfg" name
|
||||
json_add_object "$name"
|
||||
|
||||
add_option boolean ingress 1
|
||||
add_option boolean egress 1
|
||||
|
||||
json_close_object
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
json_init
|
||||
|
||||
config_load dhcpsnoop
|
||||
|
||||
json_add_object devices
|
||||
config_foreach add_device device
|
||||
json_close_object
|
||||
|
||||
ubus call dhcpsnoop config "$(json_dump)"
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger dhcpsnoop
|
||||
}
|
||||
|
||||
start_service() {
|
||||
procd_open_instance
|
||||
procd_set_param command "$PROG"
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
service_started() {
|
||||
ubus -t 10 wait_for dhcpsnoop
|
||||
[ $? = 0 ] && reload_service
|
||||
}
|
16
package/network/utils/udhcpsnoop/src/CMakeLists.txt
Normal file
16
package/network/utils/udhcpsnoop/src/CMakeLists.txt
Normal file
@ -0,0 +1,16 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
PROJECT(udhcpsnoop C)
|
||||
INCLUDE(GNUInstallDirs)
|
||||
ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations)
|
||||
|
||||
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
|
||||
|
||||
SET(SOURCES main.c ubus.c dev.c dhcp.c cache.c)
|
||||
SET(LIBS ubox ubus)
|
||||
|
||||
ADD_EXECUTABLE(udhcpsnoop ${SOURCES})
|
||||
TARGET_LINK_LIBRARIES(udhcpsnoop ${LIBS})
|
||||
INSTALL(TARGETS udhcpsnoop
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
|
||||
)
|
77
package/network/utils/udhcpsnoop/src/cache.c
Normal file
77
package/network/utils/udhcpsnoop/src/cache.c
Normal file
@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
|
||||
#include <libubox/avl.h>
|
||||
|
||||
#include "dhcpsnoop.h"
|
||||
#include "msg.h"
|
||||
|
||||
#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
|
||||
#define MAC_VAR(x) x[0], x[1], x[2], x[3], x[4], x[5]
|
||||
|
||||
#define IP_FMT "%d.%d.%d.%d"
|
||||
#define IP_VAR(x) x[0], x[1], x[2], x[3]
|
||||
|
||||
struct mac {
|
||||
struct avl_node avl;
|
||||
uint8_t mac[6];
|
||||
uint8_t ip[4];
|
||||
struct uloop_timeout rebind;
|
||||
};
|
||||
|
||||
static int
|
||||
avl_mac_cmp(const void *k1, const void *k2, void *ptr)
|
||||
{
|
||||
return memcmp(k1, k2, 6);
|
||||
}
|
||||
|
||||
static struct avl_tree mac_tree = AVL_TREE_INIT(mac_tree, avl_mac_cmp, false, NULL);
|
||||
|
||||
static void
|
||||
cache_expire(struct uloop_timeout *t)
|
||||
{
|
||||
struct mac *mac = container_of(t, struct mac, rebind);
|
||||
|
||||
avl_delete(&mac_tree, &mac->avl);
|
||||
free(mac);
|
||||
}
|
||||
|
||||
void
|
||||
cache_entry(void *_msg, uint32_t rebind)
|
||||
{
|
||||
struct dhcpv4_message *msg = (struct dhcpv4_message *) _msg;
|
||||
struct mac *mac;
|
||||
|
||||
mac = avl_find_element(&mac_tree, msg->chaddr, mac, avl);
|
||||
|
||||
if (!mac) {
|
||||
mac = malloc(sizeof(*mac));
|
||||
if (!mac)
|
||||
return;
|
||||
memset(mac, 0, sizeof(*mac));
|
||||
memcpy(mac->mac, msg->chaddr, 6);
|
||||
mac->avl.key = mac->mac;
|
||||
mac->rebind.cb = cache_expire;
|
||||
avl_insert(&mac_tree, &mac->avl);
|
||||
}
|
||||
memcpy(mac->ip, &msg->yiaddr.s_addr, 4);
|
||||
uloop_timeout_set(&mac->rebind, rebind * 1000);
|
||||
}
|
||||
|
||||
void
|
||||
cache_dump(struct blob_buf *b)
|
||||
{
|
||||
struct mac *mac;
|
||||
|
||||
avl_for_each_element(&mac_tree, mac, avl) {
|
||||
char addr[18];
|
||||
char ip[16];
|
||||
|
||||
snprintf(addr, sizeof(addr), MAC_FMT, MAC_VAR(mac->mac));
|
||||
snprintf(ip, sizeof(ip), IP_FMT, IP_VAR(mac->ip));
|
||||
|
||||
blobmsg_add_string(b, addr, ip);
|
||||
}
|
||||
}
|
496
package/network/utils/udhcpsnoop/src/dev.c
Normal file
496
package/network/utils/udhcpsnoop/src/dev.c
Normal file
@ -0,0 +1,496 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* 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 <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <libubox/vlist.h>
|
||||
#include <libubox/avl-cmp.h>
|
||||
|
||||
#include "dhcpsnoop.h"
|
||||
|
||||
#define APPEND(_buf, _ofs, _format, ...) _ofs += snprintf(_buf + _ofs, sizeof(_buf) - _ofs, _format, ##__VA_ARGS__)
|
||||
|
||||
struct vlan_hdr {
|
||||
uint16_t tci;
|
||||
uint16_t proto;
|
||||
};
|
||||
|
||||
struct gre_hdr {
|
||||
uint16_t flags;
|
||||
uint16_t proto;
|
||||
};
|
||||
|
||||
struct packet {
|
||||
void *buffer;
|
||||
unsigned int len;
|
||||
};
|
||||
|
||||
|
||||
struct device {
|
||||
struct vlist_node node;
|
||||
char ifname[IFNAMSIZ + 1];
|
||||
|
||||
int ifindex;
|
||||
bool ingress;
|
||||
bool egress;
|
||||
|
||||
bool changed;
|
||||
bool active;
|
||||
};
|
||||
|
||||
static void dev_update_cb(struct vlist_tree *tree, struct vlist_node *node_new,
|
||||
struct vlist_node *node_old);
|
||||
|
||||
static struct uloop_fd ufd;
|
||||
static VLIST_TREE(devices, avl_strcmp, dev_update_cb, true, false);
|
||||
|
||||
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 void
|
||||
dhcpsnoop_packet_cb(struct packet *pkt)
|
||||
{
|
||||
struct ethhdr *eth;
|
||||
struct ip6_hdr *ip6;
|
||||
struct ip *ip;
|
||||
struct udphdr *udp;
|
||||
uint16_t proto, port;
|
||||
const char *type;
|
||||
bool ipv6 = false;
|
||||
uint32_t rebind = 0;
|
||||
|
||||
inside_tunnel:
|
||||
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;
|
||||
break;
|
||||
case ETH_P_IPV6:
|
||||
ip6 = pkt_pull(pkt, sizeof(*ip6));
|
||||
if (!ip6)
|
||||
return;
|
||||
|
||||
proto = ip6->ip6_nxt;
|
||||
ipv6 = true;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (proto == IPPROTO_GRE) {
|
||||
struct gre_hdr *gre;
|
||||
gre = pkt_pull(pkt, sizeof(*gre));
|
||||
if (!gre) return;
|
||||
proto = be16_to_cpu(gre->proto);
|
||||
if (proto != 0x6558) return;
|
||||
goto inside_tunnel;
|
||||
}
|
||||
|
||||
if (proto != IPPROTO_UDP)
|
||||
return;
|
||||
|
||||
udp = pkt_pull(pkt, sizeof(struct udphdr));
|
||||
if (!udp)
|
||||
return;
|
||||
|
||||
port = ntohs(udp->uh_sport);
|
||||
|
||||
if (!ipv6)
|
||||
type = dhcpsnoop_parse_ipv4(pkt->buffer, pkt->len, port, &rebind);
|
||||
else
|
||||
type = dhcpsnoop_parse_ipv6(pkt->buffer, pkt->len, port);
|
||||
|
||||
if (!type)
|
||||
return;
|
||||
|
||||
dhcpsnoop_ubus_notify(type, pkt->buffer, pkt->len);
|
||||
if (!ipv6 && !strcmp(type, "ack") && rebind)
|
||||
cache_entry(pkt->buffer, rebind);
|
||||
}
|
||||
|
||||
static void
|
||||
dhcpsnoop_socket_cb(struct uloop_fd *fd, unsigned int events)
|
||||
{
|
||||
static uint8_t buf[8192];
|
||||
struct packet pkt = {
|
||||
.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;
|
||||
dhcpsnoop_packet_cb(&pkt);
|
||||
}
|
||||
|
||||
static int
|
||||
dhcpsnoop_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(DHCPSNOOP_IFB_NAME);
|
||||
if (bind(sock, (struct sockaddr *)&sll, sizeof(sll))) {
|
||||
ULOG_ERR("failed to bind socket to "DHCPSNOOP_IFB_NAME": %s\n",
|
||||
strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK);
|
||||
|
||||
ufd.fd = sock;
|
||||
ufd.cb = dhcpsnoop_socket_cb;
|
||||
uloop_fd_add(&ufd, ULOOP_READ);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
prepare_filter_cmd(char *buf, int len, const char *dev, int prio, bool add, bool egress)
|
||||
{
|
||||
return snprintf(buf, len, "tc filter %s dev '%s' %sgress prio %d",
|
||||
add ? "add" : "del", dev, egress ? "e" : "in", prio);
|
||||
}
|
||||
|
||||
#define MATCH_GRE_ETH_IP_UDP_DHCP_67 \
|
||||
" match u16 0x6558 0xffff at 22 " \
|
||||
" match u16 0x0800 0xffff at 36 " \
|
||||
" match u8 17 0xff at 47 " \
|
||||
" match u16 67 0xffff at 58 "
|
||||
|
||||
#define MATCH_GRE_ETH_VLAN_IP_UDP_DHCP_67 \
|
||||
" match u16 0x6558 0xffff at 22 " \
|
||||
" match u16 0x8100 0xffff at 36 " \
|
||||
" match u16 0x0800 0xffff at 40 " \
|
||||
" match u8 17 0xff at 51 " \
|
||||
" match u16 67 0xffff at 62 "
|
||||
|
||||
#define MATCH_GRE_ETH_IP_UDP_DHCP_68 \
|
||||
" match u16 0x6558 0xffff at 22 " \
|
||||
" match u16 0x0800 0xffff at 36 " \
|
||||
" match u8 17 0xff at 47 " \
|
||||
" match u16 68 0xffff at 58 "
|
||||
|
||||
#define MATCH_GRE_ETH_VLAN_IP_UDP_DHCP_68 \
|
||||
" match u16 0x6558 0xffff at 22 " \
|
||||
" match u16 0x8100 0xffff at 36 " \
|
||||
" match u16 0x0800 0xffff at 40 " \
|
||||
" match u8 17 0xff at 51 " \
|
||||
" match u16 68 0xffff at 62 "
|
||||
|
||||
static void
|
||||
dhcpsnoop_dev_attach_filters(struct device *dev, bool egress)
|
||||
{
|
||||
int prio = DHCPSNOOP_PRIO_BASE;
|
||||
char buf[350];
|
||||
int ofs;
|
||||
|
||||
ofs = prepare_filter_cmd(buf, sizeof(buf), dev->ifname, prio++, true, egress);
|
||||
APPEND(buf, ofs, " protocol ip u32 match ip sport 67 0xffff"
|
||||
" flowid 1:1 action mirred ingress mirror dev " DHCPSNOOP_IFB_NAME " continue");
|
||||
dhcpsnoop_run_cmd(buf, false);
|
||||
|
||||
ofs = prepare_filter_cmd(buf, sizeof(buf), dev->ifname, prio++, true, egress);
|
||||
APPEND(buf, ofs, " protocol 802.1Q u32 offset plus 4 match ip sport 67 0xffff"
|
||||
" flowid 1:1 action mirred ingress mirror dev " DHCPSNOOP_IFB_NAME " continue");
|
||||
dhcpsnoop_run_cmd(buf, false);
|
||||
|
||||
ofs = prepare_filter_cmd(buf, sizeof(buf), dev->ifname, prio++, true, egress);
|
||||
APPEND(buf, ofs, " protocol ip u32 match ip sport 68 0xffff"
|
||||
" flowid 1:1 action mirred ingress mirror dev " DHCPSNOOP_IFB_NAME " continue");
|
||||
dhcpsnoop_run_cmd(buf, false);
|
||||
|
||||
ofs = prepare_filter_cmd(buf, sizeof(buf), dev->ifname, prio++, true, egress);
|
||||
APPEND(buf, ofs, " protocol 802.1Q u32 offset plus 4 match ip sport 68 0xffff"
|
||||
" flowid 1:1 action mirred ingress mirror dev " DHCPSNOOP_IFB_NAME " continue");
|
||||
dhcpsnoop_run_cmd(buf, false);
|
||||
|
||||
/* GRE */
|
||||
ofs = prepare_filter_cmd(buf, sizeof(buf), dev->ifname, prio++, true, egress);
|
||||
APPEND(buf, ofs, " protocol ip u32 match ip protocol 47 0xff"
|
||||
MATCH_GRE_ETH_IP_UDP_DHCP_67
|
||||
" flowid 1:1 action mirred ingress mirror dev " DHCPSNOOP_IFB_NAME " continue");
|
||||
dhcpsnoop_run_cmd(buf, false);
|
||||
|
||||
ofs = prepare_filter_cmd(buf, sizeof(buf), dev->ifname, prio++, true, egress);
|
||||
APPEND(buf, ofs, " protocol ip u32 match ip protocol 47 0xff"
|
||||
MATCH_GRE_ETH_IP_UDP_DHCP_68
|
||||
" flowid 1:1 action mirred ingress mirror dev " DHCPSNOOP_IFB_NAME " continue");
|
||||
dhcpsnoop_run_cmd(buf, false);
|
||||
|
||||
ofs = prepare_filter_cmd(buf, sizeof(buf), dev->ifname, prio++, true, egress);
|
||||
APPEND(buf, ofs, " protocol ip u32 match ip protocol 47 0xff "
|
||||
MATCH_GRE_ETH_VLAN_IP_UDP_DHCP_67
|
||||
" flowid 1:1 action mirred ingress mirror dev " DHCPSNOOP_IFB_NAME " continue");
|
||||
dhcpsnoop_run_cmd(buf, false);
|
||||
|
||||
ofs = prepare_filter_cmd(buf, sizeof(buf), dev->ifname, prio++, true, egress);
|
||||
APPEND(buf, ofs, " protocol ip u32 match ip protocol 47 0xff"
|
||||
MATCH_GRE_ETH_VLAN_IP_UDP_DHCP_68
|
||||
" flowid 1:1 action mirred ingress mirror dev " DHCPSNOOP_IFB_NAME " continue");
|
||||
dhcpsnoop_run_cmd(buf, false);
|
||||
|
||||
/* IPv6 */
|
||||
ofs = prepare_filter_cmd(buf, sizeof(buf), dev->ifname, prio++, true, egress);
|
||||
APPEND(buf, ofs, " protocol ipv6 u32 match ip6 sport 546 0xfffe"
|
||||
" flowid 1:1 action mirred ingress mirror dev " DHCPSNOOP_IFB_NAME " continue");
|
||||
dhcpsnoop_run_cmd(buf, false);
|
||||
|
||||
ofs = prepare_filter_cmd(buf, sizeof(buf), dev->ifname, prio++, true, egress);
|
||||
APPEND(buf, ofs, " protocol 802.1Q u32 offset plus 4 match ip6 sport 546 0xfffe"
|
||||
" flowid 1:1 action mirred ingress mirror dev " DHCPSNOOP_IFB_NAME " continue");
|
||||
dhcpsnoop_run_cmd(buf, false);
|
||||
}
|
||||
|
||||
static void
|
||||
dhcpsnoop_dev_cleanup_filters(struct device *dev, bool egress)
|
||||
{
|
||||
char buf[128];
|
||||
int i;
|
||||
|
||||
for (i = DHCPSNOOP_PRIO_BASE; i < DHCPSNOOP_PRIO_BASE + 10; i++) {
|
||||
prepare_filter_cmd(buf, sizeof(buf), dev->ifname, i, false, egress);
|
||||
dhcpsnoop_run_cmd(buf, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dhcpsnoop_dev_attach(struct device *dev)
|
||||
{
|
||||
char buf[64];
|
||||
|
||||
dev->active = true;
|
||||
snprintf(buf, sizeof(buf), "tc qdisc add dev '%s' clsact", dev->ifname);
|
||||
dhcpsnoop_run_cmd(buf, true);
|
||||
|
||||
if (dev->ingress)
|
||||
dhcpsnoop_dev_attach_filters(dev, false);
|
||||
if (dev->egress)
|
||||
dhcpsnoop_dev_attach_filters(dev, true);
|
||||
}
|
||||
|
||||
static void
|
||||
dhcpsnoop_dev_cleanup(struct device *dev)
|
||||
{
|
||||
dev->active = false;
|
||||
dhcpsnoop_dev_cleanup_filters(dev, true);
|
||||
dhcpsnoop_dev_cleanup_filters(dev, false);
|
||||
}
|
||||
|
||||
static void
|
||||
__dhcpsnoop_dev_check(struct device *dev)
|
||||
{
|
||||
int ifindex;
|
||||
|
||||
ifindex = if_nametoindex(dev->ifname);
|
||||
if (ifindex != dev->ifindex) {
|
||||
dev->ifindex = ifindex;
|
||||
dev->changed = true;
|
||||
}
|
||||
|
||||
if (!dev->changed)
|
||||
return;
|
||||
|
||||
dev->changed = false;
|
||||
dhcpsnoop_dev_cleanup(dev);
|
||||
if (ifindex)
|
||||
dhcpsnoop_dev_attach(dev);
|
||||
}
|
||||
|
||||
static void dev_update_cb(struct vlist_tree *tree, struct vlist_node *node_new,
|
||||
struct vlist_node *node_old)
|
||||
{
|
||||
struct device *dev = NULL, *dev_free = NULL;
|
||||
|
||||
if (node_old && node_new) {
|
||||
dev = container_of(node_old, struct device, node);
|
||||
dev_free = container_of(node_new, struct device, node);
|
||||
|
||||
if (dev->ingress != dev_free->ingress ||
|
||||
dev->egress != dev_free->egress)
|
||||
dev->changed = true;
|
||||
|
||||
dev->ingress = dev_free->ingress;
|
||||
dev->egress = dev_free->egress;
|
||||
} else if (node_old) {
|
||||
dev_free = container_of(node_old, struct device, node);
|
||||
if (dev_free->active)
|
||||
dhcpsnoop_dev_cleanup(dev_free);
|
||||
} else if (node_new) {
|
||||
dev = container_of(node_new, struct device, node);
|
||||
}
|
||||
|
||||
if (dev)
|
||||
__dhcpsnoop_dev_check(dev);
|
||||
if (dev_free)
|
||||
free(dev_free);
|
||||
}
|
||||
|
||||
static void
|
||||
dhcpsnoop_dev_config_add(struct blob_attr *data)
|
||||
{
|
||||
enum {
|
||||
DEV_ATTR_INGRESS,
|
||||
DEV_ATTR_EGRESS,
|
||||
__DEV_ATTR_MAX
|
||||
};
|
||||
static const struct blobmsg_policy policy[__DEV_ATTR_MAX] = {
|
||||
[DEV_ATTR_INGRESS] = { "ingress", BLOBMSG_TYPE_BOOL },
|
||||
[DEV_ATTR_EGRESS] = { "egress", BLOBMSG_TYPE_BOOL },
|
||||
};
|
||||
struct blob_attr *tb[__DEV_ATTR_MAX];
|
||||
struct blob_attr *cur;
|
||||
struct device *dev;
|
||||
int len;
|
||||
|
||||
if (blobmsg_type(data) != BLOBMSG_TYPE_TABLE)
|
||||
return;
|
||||
|
||||
dev = calloc(1, sizeof(*dev));
|
||||
len = snprintf(dev->ifname, sizeof(dev->ifname), "%s", blobmsg_name(data));
|
||||
if (!len || len > IFNAMSIZ)
|
||||
goto free;
|
||||
|
||||
blobmsg_parse(policy, ARRAY_SIZE(tb), tb, blobmsg_data(data), blobmsg_len(data));
|
||||
|
||||
if ((cur = tb[DEV_ATTR_INGRESS]) != NULL)
|
||||
dev->ingress = blobmsg_get_bool(cur);
|
||||
if ((cur = tb[DEV_ATTR_EGRESS]) != NULL)
|
||||
dev->egress = blobmsg_get_bool(cur);
|
||||
|
||||
if (!dev->ingress && !dev->egress)
|
||||
goto free;
|
||||
|
||||
vlist_add(&devices, &dev->node, dev->ifname);
|
||||
return;
|
||||
|
||||
free:
|
||||
free(dev);
|
||||
return;
|
||||
}
|
||||
|
||||
void dhcpsnoop_dev_config_update(struct blob_attr *data, bool add_only)
|
||||
{
|
||||
struct blob_attr *cur;
|
||||
int rem;
|
||||
|
||||
if (!add_only)
|
||||
vlist_update(&devices);
|
||||
|
||||
blobmsg_for_each_attr(cur, data, rem)
|
||||
dhcpsnoop_dev_config_add(cur);
|
||||
|
||||
if (!add_only)
|
||||
vlist_flush(&devices);
|
||||
}
|
||||
|
||||
void dhcpsnoop_dev_check(void)
|
||||
{
|
||||
struct device *dev;
|
||||
|
||||
vlist_for_each_element(&devices, dev, node)
|
||||
__dhcpsnoop_dev_check(dev);
|
||||
}
|
||||
|
||||
int dhcpsnoop_dev_init(void)
|
||||
{
|
||||
dhcpsnoop_dev_done();
|
||||
|
||||
if (dhcpsnoop_run_cmd("ip link add "DHCPSNOOP_IFB_NAME" type ifb", false) ||
|
||||
dhcpsnoop_run_cmd("ip link set dev "DHCPSNOOP_IFB_NAME" up", false) ||
|
||||
dhcpsnoop_open_socket())
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dhcpsnoop_dev_done(void)
|
||||
{
|
||||
if (ufd.registered) {
|
||||
uloop_fd_delete(&ufd);
|
||||
close(ufd.fd);
|
||||
}
|
||||
|
||||
dhcpsnoop_run_cmd("ip link del "DHCPSNOOP_IFB_NAME, true);
|
||||
vlist_flush_all(&devices);
|
||||
}
|
111
package/network/utils/udhcpsnoop/src/dhcp.c
Normal file
111
package/network/utils/udhcpsnoop/src/dhcp.c
Normal file
@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
|
||||
#include "dhcpsnoop.h"
|
||||
#include "msg.h"
|
||||
|
||||
const char *dhcpsnoop_parse_ipv4(const void *buf, size_t len, uint16_t port, uint32_t *expire)
|
||||
{
|
||||
const struct dhcpv4_message *msg = buf;
|
||||
const uint8_t *pos, *end;
|
||||
uint32_t leasetime = 0, rebind = 0, renew = 0;
|
||||
char type = 0;
|
||||
|
||||
if (port != 67 && port != 68)
|
||||
return NULL;
|
||||
|
||||
if (len < sizeof(*msg))
|
||||
return NULL;
|
||||
|
||||
if (ntohl(msg->magic) != DHCPV4_MAGIC)
|
||||
return NULL;
|
||||
|
||||
pos = msg->options;
|
||||
end = buf + len;
|
||||
|
||||
while (pos < end) {
|
||||
const uint8_t *opt = pos++;
|
||||
|
||||
if (*opt == DHCPV4_OPT_END)
|
||||
break;
|
||||
|
||||
if (*opt == DHCPV4_OPT_PAD)
|
||||
continue;
|
||||
|
||||
if (pos >= end || 1 + *pos > end - pos)
|
||||
break;
|
||||
|
||||
pos += *pos + 1;
|
||||
if (pos >= end)
|
||||
break;
|
||||
|
||||
switch (*opt) {
|
||||
case DHCPV4_OPT_MSG_TYPE:
|
||||
if (!opt[1])
|
||||
continue;
|
||||
type = opt[2];
|
||||
break;
|
||||
case DHCPV4_OPT_LEASETIME:
|
||||
if (opt[1] != 4)
|
||||
continue;
|
||||
leasetime = *((uint32_t *) &opt[2]);
|
||||
break;
|
||||
case DHCPV4_OPT_REBIND:
|
||||
if (opt[1] != 4)
|
||||
continue;
|
||||
rebind = *((uint32_t *) &opt[2]);
|
||||
break;
|
||||
case DHCPV4_OPT_RENEW:
|
||||
if (opt[1] != 4)
|
||||
continue;
|
||||
renew = *((uint32_t *) &opt[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (renew)
|
||||
*expire = renew;
|
||||
else if (rebind)
|
||||
*expire = rebind;
|
||||
else if (leasetime)
|
||||
*expire = leasetime;
|
||||
else
|
||||
*expire = 24 * 60 * 60;
|
||||
*expire = ntohl(*expire);
|
||||
|
||||
switch(type) {
|
||||
case DHCPV4_MSG_ACK:
|
||||
return "ack";
|
||||
case DHCPV4_MSG_DISCOVER:
|
||||
return "discover";
|
||||
case DHCPV4_MSG_OFFER:
|
||||
return "offer";
|
||||
case DHCPV4_MSG_REQUEST:
|
||||
return "request";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *dhcpsnoop_parse_ipv6(const void *buf, size_t len, uint16_t port)
|
||||
{
|
||||
const struct dhcpv6_message *msg = buf;
|
||||
|
||||
if (port != 546 && port != 547)
|
||||
return NULL;
|
||||
|
||||
switch(msg->msg_type) {
|
||||
case DHCPV6_MSG_SOLICIT:
|
||||
return "solicit";
|
||||
case DHCPV6_MSG_REPLY:
|
||||
return "reply";
|
||||
case DHCPV6_MSG_RENEW:
|
||||
return "renew";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
32
package/network/utils/udhcpsnoop/src/dhcpsnoop.h
Normal file
32
package/network/utils/udhcpsnoop/src/dhcpsnoop.h
Normal file
@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#ifndef __DHCPSNOOP_H
|
||||
#define __DHCPSNOOP_H
|
||||
|
||||
#include <libubox/blobmsg.h>
|
||||
#include <libubox/ulog.h>
|
||||
#include <libubox/uloop.h>
|
||||
|
||||
#define DHCPSNOOP_IFB_NAME "ifb-dhcp"
|
||||
#define DHCPSNOOP_PRIO_BASE 0x100
|
||||
|
||||
int dhcpsnoop_run_cmd(char *cmd, bool ignore_error);
|
||||
|
||||
int dhcpsnoop_dev_init(void);
|
||||
void dhcpsnoop_dev_done(void);
|
||||
void dhcpsnoop_dev_config_update(struct blob_attr *data, bool add_only);
|
||||
void dhcpsnoop_dev_check(void);
|
||||
|
||||
void dhcpsnoop_ubus_init(void);
|
||||
void dhcpsnoop_ubus_done(void);
|
||||
void dhcpsnoop_ubus_notify(const char *type, const uint8_t *msg, size_t len);
|
||||
|
||||
const char *dhcpsnoop_parse_ipv4(const void *buf, size_t len, uint16_t port, uint32_t *rebind);
|
||||
const char *dhcpsnoop_parse_ipv6(const void *buf, size_t len, uint16_t port);
|
||||
|
||||
void cache_entry(void *msg, uint32_t rebind);
|
||||
void cache_dump(struct blob_buf *b);
|
||||
|
||||
#endif
|
84
package/network/utils/udhcpsnoop/src/main.c
Normal file
84
package/network/utils/udhcpsnoop/src/main.c
Normal file
@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include "dhcpsnoop.h"
|
||||
|
||||
int dhcpsnoop_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;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
ulog_open(ULOG_STDIO | ULOG_SYSLOG, LOG_DAEMON, "udhcpsnoop");
|
||||
uloop_init();
|
||||
dhcpsnoop_ubus_init();
|
||||
dhcpsnoop_dev_init();
|
||||
|
||||
ulog_threshold(LOG_INFO);
|
||||
uloop_run();
|
||||
|
||||
dhcpsnoop_ubus_done();
|
||||
dhcpsnoop_dev_done();
|
||||
uloop_done();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
88
package/network/utils/udhcpsnoop/src/msg.h
Normal file
88
package/network/utils/udhcpsnoop/src/msg.h
Normal file
@ -0,0 +1,88 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
#ifndef __DHCPSNOOP_MSG_H
|
||||
#define __DHCPSNOOP_MSG_H
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.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
|
||||
|
||||
enum dhcpv6_opt {
|
||||
DHCPV6_MSG_SOLICIT = 1,
|
||||
DHCPV6_MSG_ADVERTISE = 2,
|
||||
DHCPV6_MSG_REQUEST = 3,
|
||||
DHCPV6_MSG_CONFIRM = 4,
|
||||
DHCPV6_MSG_RENEW = 5,
|
||||
DHCPV6_MSG_REBIND = 6,
|
||||
DHCPV6_MSG_REPLY = 7,
|
||||
DHCPV6_MSG_RELEASE = 8,
|
||||
DHCPV6_MSG_DECLINE = 9,
|
||||
DHCPV6_MSG_RECONFIGURE = 10,
|
||||
DHCPV6_MSG_INFORMATION_REQUEST = 11,
|
||||
DHCPV6_MSG_RELAY_FORW = 12,
|
||||
DHCPV6_MSG_RELAY_REPL = 13,
|
||||
};
|
||||
struct dhcpv6_message {
|
||||
uint8_t msg_type;
|
||||
uint8_t transaction_id[3];
|
||||
uint8_t options[];
|
||||
} __attribute__((packed));
|
||||
|
||||
#endif
|
135
package/network/utils/udhcpsnoop/src/ubus.c
Normal file
135
package/network/utils/udhcpsnoop/src/ubus.c
Normal file
@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <libubus.h>
|
||||
|
||||
#include "dhcpsnoop.h"
|
||||
|
||||
enum {
|
||||
DS_CONFIG_DEVICES,
|
||||
__DS_CONFIG_MAX
|
||||
};
|
||||
|
||||
static const struct blobmsg_policy dhcpsnoop_config_policy[__DS_CONFIG_MAX] = {
|
||||
[DS_CONFIG_DEVICES] = { "devices", BLOBMSG_TYPE_TABLE },
|
||||
};
|
||||
|
||||
static struct blob_buf b;
|
||||
|
||||
static int
|
||||
dhcpsnoop_ubus_config(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
struct blob_attr *tb[__DS_CONFIG_MAX];
|
||||
|
||||
blobmsg_parse(dhcpsnoop_config_policy, __DS_CONFIG_MAX, tb,
|
||||
blobmsg_data(msg), blobmsg_len(msg));
|
||||
|
||||
dhcpsnoop_dev_config_update(tb[DS_CONFIG_DEVICES], false);
|
||||
|
||||
dhcpsnoop_dev_check();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dhcpsnoop_ubus_add_devices(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
struct blob_attr *tb[__DS_CONFIG_MAX];
|
||||
|
||||
blobmsg_parse(dhcpsnoop_config_policy, __DS_CONFIG_MAX, tb,
|
||||
blobmsg_data(msg), blobmsg_len(msg));
|
||||
|
||||
dhcpsnoop_dev_config_update(tb[DS_CONFIG_DEVICES], true);
|
||||
|
||||
dhcpsnoop_dev_check();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dhcpsnoop_ubus_check_devices(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
dhcpsnoop_dev_check();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dhcpsnoop_ubus_dump(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
blob_buf_init(&b, 0);
|
||||
|
||||
cache_dump(&b);
|
||||
|
||||
ubus_send_reply(ctx, req, b.head);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ubus_method dhcpsnoop_methods[] = {
|
||||
UBUS_METHOD("config", dhcpsnoop_ubus_config, dhcpsnoop_config_policy),
|
||||
UBUS_METHOD("add_devices", dhcpsnoop_ubus_add_devices, dhcpsnoop_config_policy),
|
||||
UBUS_METHOD_NOARG("check_devices", dhcpsnoop_ubus_check_devices),
|
||||
UBUS_METHOD_NOARG("dump", dhcpsnoop_ubus_dump),
|
||||
};
|
||||
|
||||
static struct ubus_object_type dhcpsnoop_object_type =
|
||||
UBUS_OBJECT_TYPE("dhcpsnoop", dhcpsnoop_methods);
|
||||
|
||||
static struct ubus_object dhcpsnoop_object = {
|
||||
.name = "dhcpsnoop",
|
||||
.type = &dhcpsnoop_object_type,
|
||||
.methods = dhcpsnoop_methods,
|
||||
.n_methods = ARRAY_SIZE(dhcpsnoop_methods),
|
||||
};
|
||||
|
||||
static void
|
||||
ubus_connect_handler(struct ubus_context *ctx)
|
||||
{
|
||||
ubus_add_object(ctx, &dhcpsnoop_object);
|
||||
}
|
||||
|
||||
static struct ubus_auto_conn conn;
|
||||
|
||||
void dhcpsnoop_ubus_init(void)
|
||||
{
|
||||
conn.cb = ubus_connect_handler;
|
||||
ubus_auto_connect(&conn);
|
||||
}
|
||||
|
||||
void dhcpsnoop_ubus_done(void)
|
||||
{
|
||||
ubus_auto_shutdown(&conn);
|
||||
blob_buf_free(&b);
|
||||
}
|
||||
|
||||
void dhcpsnoop_ubus_notify(const char *type, const uint8_t *msg, size_t len)
|
||||
{
|
||||
char *buf;
|
||||
|
||||
fprintf(stderr, "dhcp message type=%s\n", type);
|
||||
|
||||
if (!dhcpsnoop_object.has_subscribers)
|
||||
return;
|
||||
|
||||
blob_buf_init(&b, 0);
|
||||
buf = blobmsg_alloc_string_buffer(&b, "packet", 2 * len + 1);
|
||||
while (len > 0) {
|
||||
buf += sprintf(buf, "%02x", *msg);
|
||||
msg++;
|
||||
len--;
|
||||
}
|
||||
blobmsg_add_string_buffer(&b);
|
||||
|
||||
ubus_notify(&conn.ctx, &dhcpsnoop_object, type, b.head, -1);
|
||||
}
|
Loading…
Reference in New Issue
Block a user