From 3ea06dac40ab9a96d3482746ef77a9e93fc0b35f Mon Sep 17 00:00:00 2001 From: John Crispin Date: Thu, 9 Jun 2022 07:27:39 +0200 Subject: [PATCH] dhcpsnoop: update code * Update to latest version of dhcpsnoop * always snoop all upstream interfaces * add snooped leases to state Fixes: WIFI-7838 Signed-off-by: John Crispin --- feeds/ucentral/ucentral-schema/Makefile | 4 +- feeds/ucentral/udhcpsnoop/Makefile | 17 +- .../ucentral/udhcpsnoop/files/dhcpsnoop.conf | 6 + .../udhcpsnoop/files/dhcpsnoop.hotplug | 2 + .../ucentral/udhcpsnoop/files/dhcpsnoop.init | 60 +++ .../udhcpsnoop/files/etc/config/dhcpsnoop | 4 - .../udhcpsnoop/files/etc/init.d/dhcpsnoop | 22 - feeds/ucentral/udhcpsnoop/src/CMakeLists.txt | 16 + feeds/ucentral/udhcpsnoop/src/cache.c | 77 ++++ feeds/ucentral/udhcpsnoop/src/dev.c | 425 ++++++++++++++++++ feeds/ucentral/udhcpsnoop/src/dhcp.c | 90 ++++ feeds/ucentral/udhcpsnoop/src/dhcpsnoop.h | 32 ++ feeds/ucentral/udhcpsnoop/src/main.c | 84 ++++ feeds/ucentral/udhcpsnoop/src/msg.h | 88 ++++ feeds/ucentral/udhcpsnoop/src/ubus.c | 117 +++++ ...gnore-dhcp-on-the-ifb-dhcp-interface.patch | 25 ++ 16 files changed, 1033 insertions(+), 36 deletions(-) create mode 100644 feeds/ucentral/udhcpsnoop/files/dhcpsnoop.conf create mode 100644 feeds/ucentral/udhcpsnoop/files/dhcpsnoop.hotplug create mode 100644 feeds/ucentral/udhcpsnoop/files/dhcpsnoop.init delete mode 100644 feeds/ucentral/udhcpsnoop/files/etc/config/dhcpsnoop delete mode 100755 feeds/ucentral/udhcpsnoop/files/etc/init.d/dhcpsnoop create mode 100644 feeds/ucentral/udhcpsnoop/src/CMakeLists.txt create mode 100644 feeds/ucentral/udhcpsnoop/src/cache.c create mode 100644 feeds/ucentral/udhcpsnoop/src/dev.c create mode 100644 feeds/ucentral/udhcpsnoop/src/dhcp.c create mode 100644 feeds/ucentral/udhcpsnoop/src/dhcpsnoop.h create mode 100644 feeds/ucentral/udhcpsnoop/src/main.c create mode 100644 feeds/ucentral/udhcpsnoop/src/msg.h create mode 100644 feeds/ucentral/udhcpsnoop/src/ubus.c create mode 100644 patches/base/0023-dnsmasq-ignore-dhcp-on-the-ifb-dhcp-interface.patch diff --git a/feeds/ucentral/ucentral-schema/Makefile b/feeds/ucentral/ucentral-schema/Makefile index 030d78d25..e76beec2c 100644 --- a/feeds/ucentral/ucentral-schema/Makefile +++ b/feeds/ucentral/ucentral-schema/Makefile @@ -4,10 +4,10 @@ PKG_NAME:=ucentral-schema PKG_RELEASE:=1 PKG_SOURCE_URL=https://github.com/Telecominfraproject/wlan-ucentral-schema.git -PKG_MIRROR_HASH:=3ba2d66f8e52c784f136bf340ab2fb81568a1d8df8dbebfa487fc57652bea04f +PKG_MIRROR_HASH:=5e67f448e77e918a0225d5174c7eafc2ded09c5e0803f5bcf21f91749649f501 PKG_SOURCE_PROTO:=git PKG_SOURCE_DATE:=2022-05-29 -PKG_SOURCE_VERSION:=96324e2f7443cb3ae70f8fca10f37548f673e3f8 +PKG_SOURCE_VERSION:=a292ac3e41ec97fd2e22e6f614c0edeeb7eb7966 PKG_MAINTAINER:=John Crispin PKG_LICENSE:=BSD-3-Clause diff --git a/feeds/ucentral/udhcpsnoop/Makefile b/feeds/ucentral/udhcpsnoop/Makefile index a813b7fe2..e5f1105c1 100644 --- a/feeds/ucentral/udhcpsnoop/Makefile +++ b/feeds/ucentral/udhcpsnoop/Makefile @@ -6,12 +6,6 @@ PKG_RELEASE:=1 PKG_LICENSE:=GPL-2.0 PKG_MAINTAINER:=John Crispin -PKG_SOURCE_URL=https://github.com/blogic/udhcpsnoop.git -PKG_MIRROR_HASH:=721f005e51c46b9381f3e5a6576b8a31afd3903ddb0e7b569d7337a57ca33dd2 -PKG_SOURCE_PROTO:=git -PKG_SOURCE_DATE:=2021-04-12 -PKG_SOURCE_VERSION:=b86639904147a40be32ac43cd89c21109ffc3543 - include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/cmake.mk @@ -19,13 +13,20 @@ define Package/udhcpsnoop SECTION:=net CATEGORY:=Network TITLE:=DHCP Snooping Daemon - DEPENDS:=+libubox +libubus +libuci + 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 $(INSTALL_DIR) $(1)/usr/sbin $(INSTALL_BIN) $(PKG_BUILD_DIR)/udhcpsnoop $(1)/usr/sbin/ - $(CP) ./files/* $(1) + $(INSTALL_BIN) ./files/dhcpsnoop.init $(1)/etc/init.d/dhcpsnoop + $(INSTALL_DATA) ./files/dhcpsnoop.conf $(1)/etc/config/dhcpsnoop + $(INSTALL_DATA) ./files/dhcpsnoop.hotplug $(1)/etc/hotplug.d/net/10-dhcpsnoop endef $(eval $(call BuildPackage,udhcpsnoop)) diff --git a/feeds/ucentral/udhcpsnoop/files/dhcpsnoop.conf b/feeds/ucentral/udhcpsnoop/files/dhcpsnoop.conf new file mode 100644 index 000000000..0b480eda4 --- /dev/null +++ b/feeds/ucentral/udhcpsnoop/files/dhcpsnoop.conf @@ -0,0 +1,6 @@ +#config device +# option disabled 1 +# option name eth0 +# option ingress 1 +# option egress 1 + diff --git a/feeds/ucentral/udhcpsnoop/files/dhcpsnoop.hotplug b/feeds/ucentral/udhcpsnoop/files/dhcpsnoop.hotplug new file mode 100644 index 000000000..f45e0d14e --- /dev/null +++ b/feeds/ucentral/udhcpsnoop/files/dhcpsnoop.hotplug @@ -0,0 +1,2 @@ +#!/bin/sh +ubus call dhcpsnoop check_devices diff --git a/feeds/ucentral/udhcpsnoop/files/dhcpsnoop.init b/feeds/ucentral/udhcpsnoop/files/dhcpsnoop.init new file mode 100644 index 000000000..27ec852c7 --- /dev/null +++ b/feeds/ucentral/udhcpsnoop/files/dhcpsnoop.init @@ -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 +} diff --git a/feeds/ucentral/udhcpsnoop/files/etc/config/dhcpsnoop b/feeds/ucentral/udhcpsnoop/files/etc/config/dhcpsnoop deleted file mode 100644 index 21b1c41be..000000000 --- a/feeds/ucentral/udhcpsnoop/files/etc/config/dhcpsnoop +++ /dev/null @@ -1,4 +0,0 @@ -config snooping - option enable 0 - #list network lan - #list network wan diff --git a/feeds/ucentral/udhcpsnoop/files/etc/init.d/dhcpsnoop b/feeds/ucentral/udhcpsnoop/files/etc/init.d/dhcpsnoop deleted file mode 100755 index 30fe27247..000000000 --- a/feeds/ucentral/udhcpsnoop/files/etc/init.d/dhcpsnoop +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh /etc/rc.common - -START=80 - -USE_PROCD=1 -PROG=/usr/sbin/udhcpsnoop - -service_triggers() { - procd_add_reload_trigger dhcpsnoop -} - -start_service() { - [ "$(uci get dhcpsnoop.@snooping[-1].enable)" -eq 1 ] || return - procd_open_instance - procd_set_param command "$PROG" - procd_set_param respawn - procd_close_instance -} - -reload_service() { - restart -} diff --git a/feeds/ucentral/udhcpsnoop/src/CMakeLists.txt b/feeds/ucentral/udhcpsnoop/src/CMakeLists.txt new file mode 100644 index 000000000..1b677dd15 --- /dev/null +++ b/feeds/ucentral/udhcpsnoop/src/CMakeLists.txt @@ -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} +) diff --git a/feeds/ucentral/udhcpsnoop/src/cache.c b/feeds/ucentral/udhcpsnoop/src/cache.c new file mode 100644 index 000000000..df606a1d5 --- /dev/null +++ b/feeds/ucentral/udhcpsnoop/src/cache.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ + +#include + +#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); + } +} diff --git a/feeds/ucentral/udhcpsnoop/src/dev.c b/feeds/ucentral/udhcpsnoop/src/dev.c new file mode 100644 index 000000000..865fb1a76 --- /dev/null +++ b/feeds/ucentral/udhcpsnoop/src/dev.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 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; + + 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_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); +} + +static void +dhcpsnoop_dev_attach_filters(struct device *dev, bool egress) +{ + int prio = DHCPSNOOP_PRIO_BASE; + char buf[256]; + 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); + 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); + 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); + 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); + dhcpsnoop_run_cmd(buf, false); + + 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); + 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); + 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 + 6; 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) +{ + struct blob_attr *cur; + int rem; + + vlist_update(&devices); + blobmsg_for_each_attr(cur, data, rem) + dhcpsnoop_dev_config_add(cur); + 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); +} diff --git a/feeds/ucentral/udhcpsnoop/src/dhcp.c b/feeds/ucentral/udhcpsnoop/src/dhcp.c new file mode 100644 index 000000000..27ba4d62c --- /dev/null +++ b/feeds/ucentral/udhcpsnoop/src/dhcp.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ + +#include "dhcpsnoop.h" +#include "msg.h" + +const char *dhcpsnoop_parse_ipv4(const void *buf, size_t len, uint16_t port, uint32_t *rebind) +{ + const struct dhcpv4_message *msg = buf; + const uint8_t *pos, *end; + 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_REBIND: + if (!rebind || opt[1] != 4) + continue; + *rebind = *((uint32_t *) &opt[2]); + break; + } + } + + 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; + } +} + + diff --git a/feeds/ucentral/udhcpsnoop/src/dhcpsnoop.h b/feeds/ucentral/udhcpsnoop/src/dhcpsnoop.h new file mode 100644 index 000000000..2ae799c06 --- /dev/null +++ b/feeds/ucentral/udhcpsnoop/src/dhcpsnoop.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#ifndef __DHCPSNOOP_H +#define __DHCPSNOOP_H + +#include +#include +#include + +#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); +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 diff --git a/feeds/ucentral/udhcpsnoop/src/main.c b/feeds/ucentral/udhcpsnoop/src/main.c new file mode 100644 index 000000000..8a0e588bd --- /dev/null +++ b/feeds/ucentral/udhcpsnoop/src/main.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include +#include +#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(); + + uloop_run(); + + dhcpsnoop_ubus_done(); + dhcpsnoop_dev_done(); + uloop_done(); + + return 0; +} + diff --git a/feeds/ucentral/udhcpsnoop/src/msg.h b/feeds/ucentral/udhcpsnoop/src/msg.h new file mode 100644 index 000000000..7fc3483fc --- /dev/null +++ b/feeds/ucentral/udhcpsnoop/src/msg.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef __DHCPSNOOP_MSG_H +#define __DHCPSNOOP_MSG_H + +#include +#include + +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 diff --git a/feeds/ucentral/udhcpsnoop/src/ubus.c b/feeds/ucentral/udhcpsnoop/src/ubus.c new file mode 100644 index 000000000..e95849009 --- /dev/null +++ b/feeds/ucentral/udhcpsnoop/src/ubus.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include + +#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]); + + 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_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); +} diff --git a/patches/base/0023-dnsmasq-ignore-dhcp-on-the-ifb-dhcp-interface.patch b/patches/base/0023-dnsmasq-ignore-dhcp-on-the-ifb-dhcp-interface.patch new file mode 100644 index 000000000..0d1803f4f --- /dev/null +++ b/patches/base/0023-dnsmasq-ignore-dhcp-on-the-ifb-dhcp-interface.patch @@ -0,0 +1,25 @@ +From c5b68d334fa19e5fa0632d9d361cb613b1384b75 Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Mon, 13 Jun 2022 13:33:31 +0200 +Subject: [PATCH] dnsmasq: ignore dhcp on the ifb-dhcp interface + +Signed-off-by: John Crispin +--- + package/network/services/dnsmasq/files/dnsmasq.init | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/package/network/services/dnsmasq/files/dnsmasq.init b/package/network/services/dnsmasq/files/dnsmasq.init +index dacd476cd4..d00485da90 100644 +--- a/package/network/services/dnsmasq/files/dnsmasq.init ++++ b/package/network/services/dnsmasq/files/dnsmasq.init +@@ -1108,6 +1108,7 @@ dnsmasq_start() + [ -n "$BOOT" ] || config_foreach filter_dnsmasq dhcp dhcp_add "$cfg" + fi + ++ xappend "except-interface=ifb-dhcp" + + echo >> $CONFIGFILE_TMP + config_foreach filter_dnsmasq cname dhcp_cname_add "$cfg" +-- +2.25.1 +