diff --git a/feeds/ucentral/udhcprelay/Makefile b/feeds/ucentral/udhcprelay/Makefile new file mode 100644 index 000000000..b1cd875b0 --- /dev/null +++ b/feeds/ucentral/udhcprelay/Makefile @@ -0,0 +1,36 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=udhcprelay +PKG_RELEASE:=1 + +PKG_LICENSE:=GPL-2.0 +PKG_MAINTAINER:=John Crispin + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +define Package/udhcprelay + SECTION:=net + CATEGORY:=Network + TITLE:=DHCP Relay Daemon + DEPENDS:=+libubox +libubus +kmod-ifb +tc +kmod-sched +endef + +define Package/udhcprelay/conffiles +/etc/config/dhcprelay +endef + +define Package/udhcprelay/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)/udhcprelay $(1)/usr/sbin/ + $(INSTALL_BIN) ./files/dhcprelay.init $(1)/etc/init.d/dhcprelay + $(INSTALL_DATA) ./files/dhcprelay.conf $(1)/etc/config/dhcprelay + $(INSTALL_DATA) ./files/dhcprelay.hotplug $(1)/etc/hotplug.d/net/10-dhcprelay +endef + +$(eval $(call BuildPackage,udhcprelay)) diff --git a/feeds/ucentral/udhcprelay/files/dhcprelay.conf b/feeds/ucentral/udhcprelay/files/dhcprelay.conf new file mode 100644 index 000000000..cdce05ecf --- /dev/null +++ b/feeds/ucentral/udhcprelay/files/dhcprelay.conf @@ -0,0 +1,11 @@ +#config bridge +# option name up +# list vlans 2 +# list upstream wan +# +#config device +# option disabled 1 +# option name eth0 +# option ingress 1 +# option egress 1 + diff --git a/feeds/ucentral/udhcprelay/files/dhcprelay.hotplug b/feeds/ucentral/udhcprelay/files/dhcprelay.hotplug new file mode 100644 index 000000000..5e0f9b9cf --- /dev/null +++ b/feeds/ucentral/udhcprelay/files/dhcprelay.hotplug @@ -0,0 +1,2 @@ +#!/bin/sh +ubus call dhcprelay check_devices diff --git a/feeds/ucentral/udhcprelay/files/dhcprelay.init b/feeds/ucentral/udhcprelay/files/dhcprelay.init new file mode 100644 index 000000000..3a1198c91 --- /dev/null +++ b/feeds/ucentral/udhcprelay/files/dhcprelay.init @@ -0,0 +1,81 @@ +#!/bin/sh /etc/rc.common +# Copyright (c) 2021 OpenWrt.org + +START=40 + +USE_PROCD=1 +PROG=/usr/sbin/udhcprelay + +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" + + json_close_object +} + +add_string() { + json_add_string "" "$@" +} + +add_array() { + local cfg="$1" + local name="$2" + + json_add_array "$name" + config_list_foreach "$cfg" "$name" add_string + json_close_array +} + +add_bridge() { + local cfg="$1" + + config_get_bool disabled "$cfg" disabled 0 + [ "$disabled" -gt 0 ] && return + + config_get name "$cfg" name + json_add_object "$name" + + add_array "$cfg" vlans + add_array "$cfg" ignore + add_array "$cfg" upstream + + json_close_object +} + +reload_service() { + json_init + + config_load dhcprelay + + json_add_object devices + config_foreach add_device device + json_close_object + + json_add_object bridges + config_foreach add_bridge bridge + json_close_object + + ubus call dhcprelay config "$(json_dump)" +} + +service_triggers() { + procd_add_reload_trigger dhcprelay +} + +start_service() { + procd_open_instance + procd_set_param command "$PROG" + procd_set_param respawn + procd_set_param limits core="unlimited" + procd_close_instance +} + +service_started() { + ubus -t 10 wait_for dhcprelay + [ $? = 0 ] && reload_service +} diff --git a/feeds/ucentral/udhcprelay/src/CMakeLists.txt b/feeds/ucentral/udhcprelay/src/CMakeLists.txt new file mode 100644 index 000000000..dc059a774 --- /dev/null +++ b/feeds/ucentral/udhcprelay/src/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.10) + +PROJECT(udhcprelay C) +INCLUDE(GNUInstallDirs) +ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations -Wno-address-of-packed-member -fwrapv -fno-strict-aliasing) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +SET(SOURCES main.c ubus.c dev.c dhcp.c relay.c) +SET(LIBS ubox ubus) + +ADD_EXECUTABLE(udhcprelay ${SOURCES}) +TARGET_LINK_LIBRARIES(udhcprelay ${LIBS}) +INSTALL(TARGETS udhcprelay + RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} +) diff --git a/feeds/ucentral/udhcprelay/src/dev.c b/feeds/ucentral/udhcprelay/src/dev.c new file mode 100644 index 000000000..03ced1716 --- /dev/null +++ b/feeds/ucentral/udhcprelay/src/dev.c @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dhcprelay.h" + +#define APPEND(_buf, _ofs, _format, ...) _ofs += snprintf(_buf + _ofs, sizeof(_buf) - _ofs, _format, ##__VA_ARGS__) + +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 AVL_TREE(bridges, avl_strcmp, false, NULL); +static struct blob_attr *devlist; + +static void +dhcprelay_socket_cb(struct uloop_fd *fd, unsigned int events) +{ + static uint8_t buf[8192]; + struct packet pkt = { + .head = buf, + .data = buf + 32, + .end = &buf[sizeof(buf)], + }; + struct sockaddr_ll sll; + socklen_t socklen = sizeof(sll); + int len; + +retry: + len = recvfrom(fd->fd, pkt.data, pkt.end - pkt.data, MSG_DONTWAIT, (struct sockaddr *)&sll, &socklen); + if (len < 0) { + if (errno == EINTR) + goto retry; + return; + } + + if (!len) + return; + + pkt.l2.ifindex = ntohl(*(uint32_t *)(pkt.data + 2)); + pkt.len = len; + dhcprelay_packet_cb(&pkt); +} + +static int +dhcprelay_open_socket(void) +{ + struct sockaddr_ll sll = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_ALL), + }; + int sock, yes = 1; + + 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; + } + + setsockopt(sock, SOL_PACKET, PACKET_ORIGDEV, &yes, sizeof(yes)); + + sll.sll_ifindex = if_nametoindex(DHCPRELAY_IFB_NAME); + if (bind(sock, (struct sockaddr *)&sll, sizeof(sll))) { + ULOG_ERR("failed to bind socket to "DHCPRELAY_IFB_NAME": %s\n", + strerror(errno)); + goto error; + } + + fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK); + + ufd.fd = sock; + ufd.cb = dhcprelay_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) +{ + return snprintf(buf, len, "tc filter %s dev '%s' ingress prio %d", + add ? "add" : "del", dev, prio); +} + +static void +dhcprelay_add_filter(struct device *dev, int prio, const char *match_string) +{ + const char *ifname = dev->ifname; + int ifindex = dev->ifindex; + char buf[350]; + int ofs; + + ofs = prepare_filter_cmd(buf, sizeof(buf), ifname, prio++, true); + APPEND(buf, ofs, + " %s flowid 1:1", match_string); + if (!dev->upstream) + APPEND(buf, ofs, + " action pedit ex munge eth dst set ff:ff:%02x:%02x:%02x:%02x pipe", + (uint8_t)(ifindex >> 24), + (uint8_t)(ifindex >> 16), + (uint8_t)(ifindex >> 8), + (uint8_t)(ifindex)); + APPEND(buf, ofs, + " action mirred ingress %s dev " DHCPRELAY_IFB_NAME "%s", + dev->upstream ? "mirror" : "redirect", + dev->upstream ? " continue" : ""); + dhcprelay_run_cmd(buf, false); +} + +static void +dhcprelay_dev_attach_filters(struct device *dev) +{ + int prio = DHCPRELAY_PRIO_BASE; + + dhcprelay_add_filter(dev, prio++, + "protocol ip u32 match ip dport 67 0xffff"); + dhcprelay_add_filter(dev, prio++, + "protocol 802.1Q u32 offset plus 4 match ip dport 67 0xffff"); +} + +static void +dhcprelay_dev_cleanup_filters(struct device *dev) +{ + char buf[128]; + int i; + + for (i = DHCPRELAY_PRIO_BASE; i < DHCPRELAY_PRIO_BASE + 2; i++) { + prepare_filter_cmd(buf, sizeof(buf), dev->ifname, i, false); + dhcprelay_run_cmd(buf, true); + } + dev->active = false; +} + +static void +dhcprelay_dev_attach(struct device *dev) +{ + char buf[64]; + + dev->active = true; + snprintf(buf, sizeof(buf), "tc qdisc add dev '%s' clsact", dev->ifname); + dhcprelay_run_cmd(buf, true); + + dhcprelay_dev_attach_filters(dev); +} + +static void +__dhcprelay_dev_check(struct device *dev) +{ + int ifindex = if_nametoindex(dev->ifname); + + if (ifindex == dev->ifindex) + return; + + dev->ifindex = ifindex; + dhcprelay_dev_cleanup_filters(dev); + if (ifindex) + dhcprelay_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->upstream != dev_free->upstream) { + dev->upstream = dev_free->upstream; + dev->ifindex = 0; + dhcprelay_dev_cleanup_filters(dev_free); + } + } else if (node_old) { + dev_free = container_of(node_old, struct device, node); + if (dev_free->active) + dhcprelay_dev_cleanup_filters(dev_free); + } else if (node_new) { + dev = container_of(node_new, struct device, node); + } + + if (dev) + __dhcprelay_dev_check(dev); + if (dev_free) + free(dev_free); +} + +void dhcprelay_dev_add(const char *name, bool upstream) +{ + struct device *dev; + int len; + + dev = calloc(1, sizeof(*dev)); + len = snprintf(dev->ifname, sizeof(dev->ifname), "%s", name); + if (!len || len > IFNAMSIZ) { + free(dev); + return; + } + dev->upstream = upstream; + + vlist_add(&devices, &dev->node, dev->ifname); +} + +static struct device * +dhcprelay_dev_get_by_index(int ifindex) +{ + struct device *dev; + + vlist_for_each_element(&devices, dev, node) { + if (dev->ifindex == ifindex) + return dev; + } + + return NULL; +} + +void dhcprelay_dev_send(struct packet *pkt, int ifindex, const uint8_t *addr, uint16_t proto) +{ + struct sockaddr_ll sll = { + .sll_family = AF_PACKET, + .sll_protocol = cpu_to_be16(ETH_P_ALL), + .sll_ifindex = ifindex, + }; + struct ifreq ifr = {}; + struct ethhdr *eth; + struct device *dev; + int fd = ufd.fd; + + if (!ifindex) + return; + + dev = dhcprelay_dev_get_by_index(ifindex); + if (!dev) + return; + + strncpy(ifr.ifr_name, dev->node.avl.key, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0 || + ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) + return; + + eth = pkt_push(pkt, sizeof(*eth)); + if (!eth) + return; + + memcpy(eth->h_source, ifr.ifr_hwaddr.sa_data, sizeof(eth->h_source)); + memcpy(eth->h_dest, addr, sizeof(eth->h_dest)); + eth->h_proto = cpu_to_be16(proto); + sendto(fd, pkt->data, pkt->len, 0, (struct sockaddr *)&sll, sizeof(sll)); +} + +void dhcprelay_update_devices(void) +{ + struct bridge_entry *br; + struct blob_attr *cur; + int rem; + + vlist_update(&devices); + + avl_for_each_element(&bridges, br, node) { + blobmsg_for_each_attr(cur, br->upstream, rem) { + if (!blobmsg_check_attr(cur, false) || + blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + continue; + + dhcprelay_dev_add(blobmsg_get_string(cur), true); + } + + dhcprelay_ubus_query_bridge(br); + } + + blobmsg_for_each_attr(cur, devlist, rem) { + if (!blobmsg_check_attr(cur, false) || + blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + continue; + + dhcprelay_dev_add(blobmsg_get_string(cur), false); + } + + vlist_flush(&devices); +} + +static void dhcprelay_bridge_add(struct blob_attr *data) +{ + enum { + BRCONF_ATTR_VLANS, + BRCONF_ATTR_IGNORE, + BRCONF_ATTR_UPSTREAM, + __BRCONF_ATTR_MAX, + }; + static const struct blobmsg_policy policy[__BRCONF_ATTR_MAX] = { + [BRCONF_ATTR_VLANS] = { "vlans", BLOBMSG_TYPE_ARRAY }, + [BRCONF_ATTR_IGNORE] = { "ignore", BLOBMSG_TYPE_ARRAY }, + [BRCONF_ATTR_UPSTREAM] = { "upstream", BLOBMSG_TYPE_ARRAY }, + }; + struct blob_attr *tb[__BRCONF_ATTR_MAX]; + struct blob_atttr *vlan_buf, *ignore_buf, *upstream_buf; + size_t vlan_size = 0, ignore_size = 0, upstream_size = 0; + struct bridge_entry *br; + char *name_buf; + + blobmsg_parse(policy, __BRCONF_ATTR_MAX, tb, blobmsg_data(data), blobmsg_len(data)); + + if (tb[BRCONF_ATTR_VLANS] && + blobmsg_check_array(tb[BRCONF_ATTR_VLANS], BLOBMSG_TYPE_UNSPEC) > 0) + vlan_size = blob_pad_len(tb[BRCONF_ATTR_VLANS]); + + if (tb[BRCONF_ATTR_IGNORE] && + blobmsg_check_array(tb[BRCONF_ATTR_IGNORE], BLOBMSG_TYPE_STRING) > 0) + ignore_size = blob_pad_len(tb[BRCONF_ATTR_IGNORE]); + + if (tb[BRCONF_ATTR_UPSTREAM] && + blobmsg_check_array(tb[BRCONF_ATTR_UPSTREAM], BLOBMSG_TYPE_STRING) > 0) + upstream_size = blob_pad_len(tb[BRCONF_ATTR_UPSTREAM]); + + br = calloc_a(sizeof(*br), + &vlan_buf, vlan_size, + &ignore_buf, ignore_size, + &upstream_buf, upstream_size, + &name_buf, strlen(blobmsg_name(data)) + 1); + if (vlan_size) + br->vlans = memcpy(vlan_buf, tb[BRCONF_ATTR_VLANS], vlan_size); + if (ignore_size) + br->ignore = memcpy(ignore_buf, tb[BRCONF_ATTR_IGNORE], ignore_size); + if (upstream_size) + br->upstream = memcpy(upstream_buf, tb[BRCONF_ATTR_UPSTREAM], upstream_size); + + br->node.key = strcpy(name_buf, blobmsg_name(data)); + avl_insert(&bridges, &br->node); +} + +void dhcprelay_dev_config_update(struct blob_attr *br_attr, struct blob_attr *dev) +{ + struct bridge_entry *br, *tmp; + struct blob_attr *cur; + int rem; + + avl_remove_all_elements(&bridges, br, node, tmp) + free(br); + + blobmsg_for_each_attr(cur, br_attr, rem) { + if (!blobmsg_check_attr(cur, true) || + blobmsg_type(cur) != BLOBMSG_TYPE_TABLE) + continue; + + dhcprelay_bridge_add(cur); + } + + free(devlist); + devlist = dev ? blob_memdup(dev) : NULL; + + dhcprelay_update_devices(); +} + +int dhcprelay_dev_init(void) +{ + dhcprelay_dev_done(); + + if (dhcprelay_run_cmd("ip link add "DHCPRELAY_IFB_NAME" type ifb", false) || + dhcprelay_run_cmd("ip link set dev "DHCPRELAY_IFB_NAME" up", false) || + dhcprelay_open_socket()) + return -1; + + return 0; +} + +void dhcprelay_dev_done(void) +{ + if (ufd.registered) { + uloop_fd_delete(&ufd); + close(ufd.fd); + } + + dhcprelay_run_cmd("ip link del "DHCPRELAY_IFB_NAME, true); + vlist_flush_all(&devices); +} diff --git a/feeds/ucentral/udhcprelay/src/dhcp.c b/feeds/ucentral/udhcprelay/src/dhcp.c new file mode 100644 index 000000000..00531325f --- /dev/null +++ b/feeds/ucentral/udhcprelay/src/dhcp.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ + +#include +#include +#include +#include +#include +#include +#include +#include "dhcprelay.h" +#include "msg.h" + +static const char * +dhcprelay_parse_ipv4(const void *buf, size_t len, uint16_t port, uint32_t *expire, + size_t *tail, bool *response) +{ + 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; + *tail = (const void *)pos - buf; + + while (pos < end) { + const uint8_t *opt; + + opt = pos++; + if (*opt == DHCPV4_OPT_PAD) + continue; + + if (*opt == DHCPV4_OPT_END) + break; + + if (pos >= end || 1 + *pos > end - pos) + break; + + pos += *pos + 1; + if (pos >= end) + break; + + *tail = (const void *)pos - buf; + 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_DISCOVER: + return "discover"; + case DHCPV4_MSG_REQUEST: + return "request"; + case DHCPV4_MSG_DECLINE: + return "decline"; + case DHCPV4_MSG_RELEASE: + return "release"; + case DHCPV4_MSG_INFORM: + return "inform"; + + case DHCPV4_MSG_OFFER: + case DHCPV4_MSG_ACK: + case DHCPV4_MSG_NAK: + *response = true; + return NULL; + } + + return NULL; +} + +static bool +proto_is_vlan(uint16_t proto) +{ + return proto == ETH_P_8021Q || proto == ETH_P_8021AD; +} + +static int +dhcprelay_add_option(struct packet *pkt, uint32_t opt, const void *data, size_t len) +{ + uint8_t *tail = pkt->data + pkt->dhcp_tail; + + if (opt > 255 || len > 255) + return -1; + + if ((void *)tail + len + 1 > pkt->end) + return -1; + + *(tail++) = opt; + *(tail++) = len; + if (data && len) + memcpy(tail, data, len); + pkt->dhcp_tail += len + 2; + + return 0; +} + +int dhcprelay_add_options(struct packet *pkt, struct blob_attr *data) +{ + static const struct blobmsg_policy policy[2] = { + { .type = BLOBMSG_TYPE_INT32 }, + { .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb[2], *cur; + int rem; + + if (!data) + goto out; + + if (blobmsg_check_array(data, BLOBMSG_TYPE_ARRAY) < 0) + return -1; + + blobmsg_for_each_attr(cur, data, rem) { + blobmsg_parse_array(policy, 2, tb, blobmsg_data(cur), blobmsg_len(cur)); + + if (!tb[0] || !tb[1]) + return -1; + + if (!blobmsg_len(tb[1])) + return -1; + + if (dhcprelay_add_option(pkt, blobmsg_get_u32(tb[0]), + blobmsg_get_string(tb[1]), blobmsg_len(tb[1]) - 1)) + return -1; + } + +out: + dhcprelay_add_option(pkt, DHCPV4_OPT_END, NULL, 0); + + pkt->len = pkt->dhcp_tail; + if (pkt->len < 300) { + pkt->len = 300; + memset(pkt->data + pkt->dhcp_tail, 0, pkt->len - pkt->dhcp_tail); + } + + return 0; +} + +void dhcprelay_packet_cb(struct packet *pkt) +{ + struct ethhdr *eth; + struct ip *ip; + struct udphdr *udp; + uint16_t proto, port; + const char *type; + uint32_t rebind = 0; + size_t tail = 0; + bool response = false; + + eth = pkt_pull(pkt, sizeof(*eth)); + if (!eth) + return; + + memcpy(pkt->l2.addr, eth->h_source, ETH_ALEN); + + 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; + + pkt->l2.vlan_proto = proto; + pkt->l2.vlan_tci = ntohs(vlan->tci); + proto = be16_to_cpu(vlan->proto); + } + + if (proto != ETH_P_IP) + return; + + ip = pkt_peek(pkt, sizeof(struct ip)); + if (!ip) + return; + + if (!pkt_pull(pkt, ip->ip_hl * 4)) + return; + + if (ip->ip_p != IPPROTO_UDP) + return; + + udp = pkt_pull(pkt, sizeof(struct udphdr)); + if (!udp) + return; + + port = ntohs(udp->uh_sport); + type = dhcprelay_parse_ipv4(pkt->data, pkt->len, port, &rebind, &tail, &response); + + if (response) { + dhcprelay_handle_response(pkt); + return; + } + + if (!type) + return; + + pkt->dhcp_tail = tail; + dhcprelay_ubus_notify(type, pkt); +} diff --git a/feeds/ucentral/udhcprelay/src/dhcprelay.h b/feeds/ucentral/udhcprelay/src/dhcprelay.h new file mode 100644 index 000000000..afbbf7c02 --- /dev/null +++ b/feeds/ucentral/udhcprelay/src/dhcprelay.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#ifndef __DHCPRELAY_H +#define __DHCPRELAY_H + +#include +#include +#include +#include +#include +#include +#include + +#define DHCPRELAY_IFB_NAME "ifb-dhcprelay" +#define DHCPRELAY_PRIO_BASE 0x130 + +struct packet_l2 { + int ifindex; + uint16_t vlan_tci; + uint16_t vlan_proto; + uint8_t addr[6]; +}; + +struct packet { + struct packet_l2 l2; + + uint16_t len; + uint16_t dhcp_tail; + + void *head; + void *data; + void *end; +}; + +struct device { + struct vlist_node node; + char ifname[IFNAMSIZ + 1]; + + int ifindex; + bool upstream; + bool active; +}; + +struct bridge_entry { + struct avl_node node; + struct blob_attr *vlans, *ignore, *upstream; +}; + +static inline void *pkt_push(struct packet *pkt, unsigned int len) +{ + if (pkt->data - pkt->head < len) + return NULL; + + pkt->data -= len; + pkt->len += len; + + return pkt->data; +} + +static inline void *pkt_peek(struct packet *pkt, unsigned int len) +{ + if (len > pkt->len) + return NULL; + + return pkt->data; +} + +static inline void *pkt_pull(struct packet *pkt, unsigned int len) +{ + void *ret = pkt_peek(pkt, len); + + if (!ret) + return NULL; + + pkt->data += len; + pkt->len -= len; + + return ret; +} + +static inline const char *bridge_entry_name(struct bridge_entry *br) +{ + return br->node.key; +} + +int dhcprelay_run_cmd(char *cmd, bool ignore_error); + +int dhcprelay_dev_init(void); +void dhcprelay_dev_done(void); +void dhcprelay_dev_add(const char *name, bool upstream); +void dhcprelay_dev_config_update(struct blob_attr *br, struct blob_attr *dev); +void dhcprelay_dev_send(struct packet *pkt, int ifindex, const uint8_t *addr, uint16_t proto); +void dhcprelay_update_devices(void); + +void dhcprelay_ubus_init(void); +void dhcprelay_ubus_done(void); +void dhcprelay_ubus_notify(const char *type, struct packet *pkt); +void dhcprelay_ubus_query_bridge(struct bridge_entry *br); + +void dhcprelay_packet_cb(struct packet *pkt); +void dhcprelay_handle_response(struct packet *pkt); +int dhcprelay_forward_request(struct packet *pkt, struct blob_attr *data); +int dhcprelay_add_options(struct packet *pkt, struct blob_attr *data); + +#endif diff --git a/feeds/ucentral/udhcprelay/src/main.c b/feeds/ucentral/udhcprelay/src/main.c new file mode 100644 index 000000000..e165a8a1c --- /dev/null +++ b/feeds/ucentral/udhcprelay/src/main.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include +#include +#include "dhcprelay.h" + +int dhcprelay_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, "udhcprelay"); + uloop_init(); + dhcprelay_ubus_init(); + dhcprelay_dev_init(); + + ulog_threshold(LOG_INFO); + uloop_run(); + + dhcprelay_ubus_done(); + dhcprelay_dev_done(); + uloop_done(); + + return 0; +} diff --git a/feeds/ucentral/udhcprelay/src/msg.h b/feeds/ucentral/udhcprelay/src/msg.h new file mode 100644 index 000000000..5db8c163b --- /dev/null +++ b/feeds/ucentral/udhcprelay/src/msg.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef __DHCPRELAY_MSG_H +#define __DHCPRELAY_MSG_H + +#include +#include + +struct vlan_hdr { + uint16_t tci; + uint16_t proto; +}; + +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/udhcprelay/src/relay.c b/feeds/ucentral/udhcprelay/src/relay.c new file mode 100644 index 000000000..e2bd742f4 --- /dev/null +++ b/feeds/ucentral/udhcprelay/src/relay.c @@ -0,0 +1,472 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dhcprelay.h" +#include "msg.h" + +struct dhcprelay_req_key { + uint32_t xid; + uint8_t addr[6]; +}; + +struct dhcprelay_req { + struct avl_node node; + struct uloop_timeout timeout; + struct dhcprelay_req_key key; + + struct packet_l2 l2; +}; + +struct dhcprelay_local { + struct avl_node node; + struct in_addr addr; + struct uloop_fd fd; + + struct list_head conn_list; +}; + +struct dhcprelay_conn { + struct avl_node node; + + struct dhcprelay_local *local; + struct list_head local_list; + struct sockaddr_in local_addr; + + struct uloop_timeout timeout; + struct uloop_fd fd; +}; + +static int relay_req_cmp(const void *k1, const void *k2, void *ptr) +{ + return memcmp(k1, k2, sizeof(struct dhcprelay_req_key)); +} + +static int in_addr_cmp(const void *k1, const void *k2, void *ptr) +{ + return memcmp(k1, k2, sizeof(struct in_addr)); +} + +static AVL_TREE(requests, relay_req_cmp, false, NULL); +static AVL_TREE(connections, avl_strcmp, false, NULL); +static AVL_TREE(local_addr, in_addr_cmp, false, NULL); + +static void +dhcprelay_req_timeout_cb(struct uloop_timeout *t) +{ + struct dhcprelay_req *req = container_of(t, struct dhcprelay_req, timeout); + + avl_delete(&requests, &req->node); + free(req); +} + +static void +__dhcprelay_conn_free(struct dhcprelay_conn *conn) +{ + uloop_timeout_cancel(&conn->timeout); + avl_delete(&connections, &conn->node); + uloop_fd_delete(&conn->fd); + close(conn->fd.fd); + free(conn); +} + +static void +dhcprelay_local_free(struct dhcprelay_local *local) +{ + struct dhcprelay_conn *conn; + + while (!list_empty(&local->conn_list)) { + conn = list_first_entry(&local->conn_list, struct dhcprelay_conn, local_list); + list_del_init(&conn->local_list); + __dhcprelay_conn_free(conn); + } + + avl_delete(&local_addr, &local->node); + uloop_fd_delete(&local->fd); + close(local->fd.fd); + free(local); +} + +static void +dhcprelay_conn_free(struct dhcprelay_conn *conn) +{ + struct dhcprelay_local *local = conn->local; + + if (local) { + list_del_init(&conn->local_list); + if (list_empty(&local->conn_list)) + dhcprelay_local_free(local); + } + + __dhcprelay_conn_free(conn); +} + +static void +dhcprelay_conn_timeout_cb(struct uloop_timeout *t) +{ + struct dhcprelay_conn *conn = container_of(t, struct dhcprelay_conn, timeout); + + dhcprelay_conn_free(conn); +} + +static void +dhcprelay_req_key_init(struct dhcprelay_req_key *key, struct dhcpv4_message *msg) +{ + key->xid = msg->xid; + memcpy(key->addr, msg->chaddr, sizeof(key->addr)); +} + +static struct dhcprelay_req * +dhcprelay_req_from_pkt(struct packet *pkt) +{ + struct dhcpv4_message *msg = pkt->data; + struct dhcprelay_req_key key = {}; + struct dhcprelay_req *req; + + dhcprelay_req_key_init(&key, msg); + req = avl_find_element(&requests, &key, req, node); + if (req) + goto out; + + req = calloc(1, sizeof(*req)); + memcpy(&req->key, &key, sizeof(key)); + req->node.key = &req->key; + req->timeout.cb = dhcprelay_req_timeout_cb; + avl_insert(&requests, &req->node); + +out: + req->l2 = pkt->l2; + uloop_timeout_set(&req->timeout, 10 * 1000); + + return req; +} + +static void +dhcprelay_forward_response(struct packet *pkt, struct dhcprelay_req *req, + struct dhcpv4_message *msg) +{ + uint16_t proto = ETH_P_IP; + + if (req->l2.vlan_proto) { + struct vlan_hdr *vlan; + + vlan = pkt_push(pkt, sizeof(*vlan)); + if (!vlan) + return; + + vlan->tci = cpu_to_be16(req->l2.vlan_tci); + vlan->proto = cpu_to_be16(proto); + proto = req->l2.vlan_proto; + } + + dhcprelay_dev_send(pkt, req->l2.ifindex, msg->chaddr, proto); +} + +static inline uint32_t +csum_tcpudp_nofold(uint32_t saddr, uint32_t daddr, uint8_t proto, uint32_t len) +{ + uint64_t sum = 0; + + sum += saddr; + sum += daddr; +#if __BYTE_ORDER == __LITTLE_ENDIAN + sum += (proto + len) << 8; +#else + sum += proto + len; +#endif + + sum = (sum & 0xffffffff) + (sum >> 32); + sum = (sum & 0xffffffff) + (sum >> 32); + + return (uint32_t)sum; +} + +static inline uint32_t csum_add(uint32_t sum, uint32_t addend) +{ + sum += addend; + return sum + (sum < addend); +} + +static inline uint16_t csum_fold(uint32_t sum) +{ + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + + return (uint16_t)~sum; +} + +static uint32_t csum_partial(const void *buf, int len) +{ + const uint16_t *data = buf; + uint32_t sum = 0; + + while (len > 1) { + sum += *data++; + len -= 2; + } + + if (len == 1) +#if __BYTE_ORDER == __LITTLE_ENDIAN + sum += *(uint8_t *)data; +#else + sum += *(uint8_t *)data << 8; +#endif + + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + + return sum; +} + +static int +dhcprelay_add_ip_udp(struct packet *pkt) +{ + struct dhcpv4_message *msg = pkt->data; + struct udphdr *udp; + struct ip *ip; + uint16_t msg_len = pkt->len; + uint16_t udp_len = sizeof(*udp) + msg_len; + uint16_t ip_len = sizeof(*ip) + udp_len; + uint32_t sum; + + udp = pkt_push(pkt, sizeof(*udp)); + ip = pkt_push(pkt, sizeof(*ip)); + if (!udp || !ip) + return -1; + + ip->ip_v = 4; + ip->ip_hl = sizeof(*ip) / 4; + ip->ip_len = cpu_to_be16(ip_len); + ip->ip_p = IPPROTO_UDP; + ip->ip_src = msg->siaddr; + if (msg->flags & cpu_to_be16((1 << 15))) + ip->ip_dst = (struct in_addr){ ~0 }; + else + ip->ip_dst = msg->yiaddr; + + udp->uh_sport = cpu_to_be16(67); + udp->uh_dport = cpu_to_be16(68); + udp->uh_ulen = cpu_to_be16(udp_len); + + udp->uh_sum = 0; + udp->uh_ulen = cpu_to_be16(udp_len); + sum = csum_tcpudp_nofold(*(uint32_t *)&ip->ip_src, *(uint32_t *)&ip->ip_dst, + ip->ip_p, udp_len); + sum = csum_add(sum, csum_partial(udp, sizeof(*udp))); + sum = csum_add(sum, csum_partial(msg, msg_len)); + udp->uh_sum = csum_fold(sum); + + ip->ip_sum = 0; + ip->ip_sum = csum_fold(csum_partial(ip, sizeof(*ip))); + + return 0; +} + +void dhcprelay_handle_response(struct packet *pkt) +{ + struct dhcpv4_message *msg = pkt->data; + struct dhcprelay_req_key key = {}; + struct dhcprelay_req *req; + + if (pkt->len < sizeof(*msg)) + return; + + dhcprelay_req_key_init(&key, msg); + req = avl_find_element(&requests, &key, req, node); + if (!req) + return; + + if (dhcprelay_add_ip_udp(pkt)) + return; + + dhcprelay_forward_response(pkt, req, msg); +} + +static int +dhcprelay_read_relay_fd(int fd) +{ + static struct { + uint8_t l2[sizeof(struct ethhdr) + 4]; + struct ip ip; + struct udphdr udp; + uint8_t data[1500]; + } __packed buf = {}; + struct packet pkt = { + .head = &buf, + .data = buf.data, + .end = &buf.data[sizeof(buf.data)], + }; + ssize_t len; + +retry: + len = recv(fd, pkt.data, sizeof(pkt.data), 0); + if (len < 0) { + if (errno == EINTR) + goto retry; + + if (errno == EAGAIN) + return 0; + + return -1; + } + + pkt.len = len; + dhcprelay_handle_response(&pkt); + + return 0; +} + +static void +dhcprelay_local_cb(struct uloop_fd *fd, unsigned int events) +{ + struct dhcprelay_local *local = container_of(fd, struct dhcprelay_local, fd); + + if (dhcprelay_read_relay_fd(fd->fd) < 0) + dhcprelay_local_free(local); +} + +static void +dhcprelay_conn_cb(struct uloop_fd *fd, unsigned int events) +{ + struct dhcprelay_conn *conn = container_of(fd, struct dhcprelay_conn, fd); + + uloop_timeout_set(&conn->timeout, 30 * 1000); + + if (dhcprelay_read_relay_fd(fd->fd) < 0) + dhcprelay_conn_free(conn); +} + +static struct dhcprelay_local * +dhcprelay_local_get(struct in_addr *addr) +{ + struct dhcprelay_local *local; + struct sockaddr_in sin = { + .sin_addr = *addr, + .sin_port = cpu_to_be16(67), + }; + const int yes = 1; + int fd; + + local = avl_find_element(&local_addr, addr, local, node); + if (local) + return local; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return NULL; + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + close(fd); + return NULL; + } + + local = calloc(1, sizeof(*local)); + local->fd.fd = fd; + local->fd.cb = dhcprelay_local_cb; + uloop_fd_add(&local->fd, ULOOP_READ); + INIT_LIST_HEAD(&local->conn_list); + local->node.key = &local->addr; + memcpy(&local->addr, addr, sizeof(local->addr)); + avl_insert(&local_addr, &local->node); + + return local; +} + +static struct dhcprelay_conn * +dhcprelay_conn_get(const char *addr) +{ + struct dhcprelay_conn *conn; + const char *host = addr; + const char *port = "67"; + const char *sep; + char *name_buf; + socklen_t sl = sizeof(local_addr); + int fd; + + conn = avl_find_element(&connections, addr, conn, node); + if (conn) + goto out; + + sep = strchr(addr, ':'); + if (sep) { + char *buf = alloca(strlen(addr) + 1); + int ofs = sep - addr; + + buf[ofs++] = 0; + host = buf; + port = buf + ofs; + } + + fd = usock(USOCK_UDP | USOCK_IPV4ONLY | USOCK_NONBLOCK, host, port); + if (fd < 0) + return NULL; + + conn = calloc_a(sizeof(*conn), &name_buf, strlen(addr) + 1); + conn->fd.fd = fd; + conn->fd.cb = dhcprelay_conn_cb; + conn->timeout.cb = dhcprelay_conn_timeout_cb; + uloop_fd_add(&conn->fd, ULOOP_READ); + INIT_LIST_HEAD(&conn->local_list); + conn->node.key = strcpy(name_buf, addr); + getsockname(fd, (struct sockaddr *)&conn->local_addr, &sl); + conn->local = dhcprelay_local_get(&conn->local_addr.sin_addr); + if (conn->local) + list_add_tail(&conn->local_list, &conn->local->conn_list); + + avl_insert(&connections, &conn->node); + +out: + uloop_timeout_set(&conn->timeout, 30 * 1000); + + return conn; +} + +int dhcprelay_forward_request(struct packet *pkt, struct blob_attr *data) +{ + enum { + FWD_ATTR_ADDRESS, + FWD_ATTR_OPTIONS, + __FWD_ATTR_MAX, + }; + static const struct blobmsg_policy policy[] = { + [FWD_ATTR_ADDRESS] = { "address", BLOBMSG_TYPE_STRING }, + [FWD_ATTR_OPTIONS] = { "options", BLOBMSG_TYPE_ARRAY }, + }; + struct dhcpv4_message *msg = pkt->data; + struct blob_attr *tb[__FWD_ATTR_MAX]; + struct dhcprelay_conn *conn; + struct packet cur_pkt = *pkt; + int ret; + + blobmsg_parse(policy, __FWD_ATTR_MAX, tb, blobmsg_data(data), blobmsg_len(data)); + + if (!tb[FWD_ATTR_ADDRESS]) + return UBUS_STATUS_INVALID_ARGUMENT; + + pkt = &cur_pkt; + if (dhcprelay_add_options(pkt, tb[FWD_ATTR_OPTIONS])) + return UBUS_STATUS_INVALID_ARGUMENT; + + conn = dhcprelay_conn_get(blobmsg_get_string(tb[FWD_ATTR_ADDRESS])); + if (!conn) + return UBUS_STATUS_CONNECTION_FAILED; + + msg->giaddr = conn->local_addr.sin_addr; + if (msg->hops++ >= 20) + return 0; + + dhcprelay_req_from_pkt(pkt); + do { + ret = send(conn->fd.fd, pkt->data, pkt->len, 0); + } while (ret < 0 && errno == EINTR); + + return 0; +} diff --git a/feeds/ucentral/udhcprelay/src/ubus.c b/feeds/ucentral/udhcprelay/src/ubus.c new file mode 100644 index 000000000..eb71cdf32 --- /dev/null +++ b/feeds/ucentral/udhcprelay/src/ubus.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include +#include +#include + +#include "dhcprelay.h" + +enum { + DS_CONFIG_BRIDGES, + DS_CONFIG_DEVICES, + __DS_CONFIG_MAX +}; + +static const struct blobmsg_policy dhcprelay_config_policy[__DS_CONFIG_MAX] = { + [DS_CONFIG_BRIDGES] = { "bridges", BLOBMSG_TYPE_TABLE }, + [DS_CONFIG_DEVICES] = { "devices", BLOBMSG_TYPE_ARRAY }, +}; + +static struct blob_buf b; + +static int +dhcprelay_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(dhcprelay_config_policy, __DS_CONFIG_MAX, tb, + blobmsg_data(msg), blobmsg_len(msg)); + + dhcprelay_dev_config_update(tb[DS_CONFIG_BRIDGES], tb[DS_CONFIG_DEVICES]); + + return 0; +} + + +static int +dhcprelay_ubus_check_devices(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + dhcprelay_update_devices(); + + return 0; +} + +static const struct ubus_method dhcprelay_methods[] = { + UBUS_METHOD("config", dhcprelay_ubus_config, dhcprelay_config_policy), + UBUS_METHOD_NOARG("check_devices", dhcprelay_ubus_check_devices), +}; + +static struct ubus_object_type dhcprelay_object_type = + UBUS_OBJECT_TYPE("dhcprelay", dhcprelay_methods); + +static struct ubus_object dhcprelay_object = { + .name = "dhcprelay", + .type = &dhcprelay_object_type, + .methods = dhcprelay_methods, + .n_methods = ARRAY_SIZE(dhcprelay_methods), +}; + +static void +ubus_connect_handler(struct ubus_context *ctx) +{ + ubus_add_object(ctx, &dhcprelay_object); +} + +static struct ubus_auto_conn conn; + +void dhcprelay_ubus_init(void) +{ + conn.cb = ubus_connect_handler; + ubus_auto_connect(&conn); +} + +void dhcprelay_ubus_done(void) +{ + ubus_auto_shutdown(&conn); + blob_buf_free(&b); +} + +static bool +dhcprelay_vlan_match(struct blob_attr *vlans, int id) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, vlans, rem) { + unsigned int vlan; + + switch (blobmsg_type(cur)) { + case BLOBMSG_TYPE_STRING: + vlan = atoi(blobmsg_get_string(cur)); + break; + case BLOBMSG_TYPE_INT32: + vlan = blobmsg_get_u32(cur); + break; + default: + continue; + } + + + if (vlan == id) + return true; + } + + return false; +} + +static bool +dhcprelay_ignore_match(struct blob_attr *ignore, const char *name) +{ + struct blob_attr *cur; + int rem; + + if (!ignore) + return false; + + blobmsg_for_each_attr(cur, ignore, rem) { + if (!strcmp(blobmsg_get_string(cur), name)) + return true; + } + + return false; +} + +static void +dhcprelay_parse_port_list(struct bridge_entry *br, struct blob_attr *attr) +{ + struct blob_attr *cur; + int rem; + + if (blobmsg_check_array(attr, BLOBMSG_TYPE_STRING) < 0) + return; + + blobmsg_for_each_attr(cur, attr, rem) { + char *sep, *name = blobmsg_get_string(cur); + bool tagged = false; + + sep = strchr(name, '\n'); + if (sep) + *sep = 0; + + sep = strchr(name, ':'); + if (sep) { + *sep = 0; + tagged = strchr(sep + 1, 't'); + } + + if (dhcprelay_ignore_match(br->ignore, name)) + continue; + + if (tagged) + continue; + + dhcprelay_dev_add(blobmsg_get_string(cur), false); + } +} + +static void +dhcprelay_parse_vlan(struct bridge_entry *br, struct blob_attr *attr) +{ + enum { + VL_ATTR_ID, + VL_ATTR_PORTS, + __VL_ATTR_MAX, + }; + struct blobmsg_policy policy[__VL_ATTR_MAX] = { + [VL_ATTR_ID] = { "id", BLOBMSG_TYPE_INT32 }, + [VL_ATTR_PORTS] = { "ports", BLOBMSG_TYPE_ARRAY }, + }; + struct blob_attr *tb[__VL_ATTR_MAX]; + int id; + + blobmsg_parse(policy, __VL_ATTR_MAX, tb, blobmsg_data(attr), blobmsg_len(attr)); + + if (!tb[VL_ATTR_ID] || !tb[VL_ATTR_PORTS]) + return; + + id = blobmsg_get_u32(tb[VL_ATTR_ID]); + if (!dhcprelay_vlan_match(br->vlans, id)) + return; + + dhcprelay_parse_port_list(br, tb[VL_ATTR_PORTS]); +} + +static void +dhcprelay_parse_bridge_ports(struct bridge_entry *br, struct blob_attr *msg) +{ + struct blobmsg_policy policy = { "bridge-members", BLOBMSG_TYPE_ARRAY }; + struct blob_attr *attr; + + blobmsg_parse(&policy, 1, &attr, blobmsg_data(msg), blobmsg_len(msg)); + if (!attr) + return; + + dhcprelay_parse_port_list(br, attr); +} + +static void +dhcprelay_query_bridge_cb(struct ubus_request *req, int type, + struct blob_attr *msg) +{ + struct blobmsg_policy policy = { "bridge-vlans", BLOBMSG_TYPE_ARRAY }; + struct bridge_entry *br = req->priv; + struct blob_attr *attr, *cur; + int rem; + + if (!br->vlans) { + dhcprelay_parse_bridge_ports(br, msg); + return; + } + + blobmsg_parse(&policy, 1, &attr, blobmsg_data(msg), blobmsg_len(msg)); + if (!attr) + return; + + blobmsg_for_each_attr(cur, attr, rem) + dhcprelay_parse_vlan(br, cur); +} + +void dhcprelay_ubus_query_bridge(struct bridge_entry *br) +{ + struct ubus_request req; + uint32_t id; + + if (ubus_lookup_id(&conn.ctx, "network.device", &id)) + return; + + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "name", bridge_entry_name(br)); + if (ubus_invoke_async(&conn.ctx, id, "status", b.head, &req)) + return; + + req.priv = br; + req.data_cb = dhcprelay_query_bridge_cb; + ubus_complete_request(&conn.ctx, &req, 1000); +} + +static void dhcprelay_notify_cb(struct ubus_notify_request *req, + int type, struct blob_attr *msg) +{ + struct packet *pkt = req->req.priv; + + dhcprelay_forward_request(pkt, msg); +} + +void dhcprelay_ubus_notify(const char *type, struct packet *pkt) +{ + const uint8_t *msg = pkt->data; + struct ubus_notify_request req; + size_t len = pkt->len; + char *buf; + void *c; + + if (!dhcprelay_object.has_subscribers) + return; + + blob_buf_init(&b, 0); + + c = blobmsg_open_table(&b, "info"); + buf = blobmsg_alloc_string_buffer(&b, "device", IFNAMSIZ + 1); + if (!if_indextoname(pkt->l2.ifindex, buf)) + return; + blobmsg_add_string_buffer(&b); + if (pkt->l2.vlan_proto) { + blobmsg_add_u32(&b, "vlan_proto", pkt->l2.vlan_proto); + blobmsg_add_u32(&b, "vlan_tci", pkt->l2.vlan_tci); + } + blobmsg_close_table(&b, c); + + 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_async(&conn.ctx, &dhcprelay_object, type, b.head, &req); + req.data_cb = dhcprelay_notify_cb; + req.req.priv = pkt; + ubus_complete_request(&conn.ctx, &req.req, 5000); +} diff --git a/feeds/ucentral/udhcprelay/src/utils.c b/feeds/ucentral/udhcprelay/src/utils.c new file mode 100644 index 000000000..2d78b4d53 --- /dev/null +++ b/feeds/ucentral/udhcprelay/src/utils.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "dhcprelay.h" + +static inline uint32_t +csum_tcpudp_nofold(uint32_t saddr, uint32_t daddr, uint32_t len, uint8_t proto) +{ + uint64_t sum = 0; + + sum += saddr; + sum += daddr; +#if __BYTE_ORDER == __LITTLE_ENDIAN + sum += (proto + len) << 8; +#else + sum += proto + len; +#endif + + sum = (sum & 0xffffffff) + (sum >> 32); + sum = (sum & 0xffffffff) + (sum >> 32); + + return (uint32_t)sum; +} + +static inline uint32_t csum_add(uint32_t sum, uint32_t addend) +{ + sum += addend; + return sum + (sum < addend); +} + +static inline uint16_t csum_fold(uint32_t sum) +{ + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + + return (uint16_t)~sum; +} + +static uint32_t csum_partial(const void *buf, int len) +{ + const uint16_t *data = buf; + uint32_t sum = 0; + + while (len > 1) { + sum += *data++; + len -= 2; + } + + if (len == 1) +#if __BYTE_ORDER == __LITTLE_ENDIAN + sum += *(uint8_t *)data; +#else + sum += *(uint8_t *)data << 8; +#endif + + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + + return sum; +} + +static void fixup_udpv4(void *hdr, size_t hdrlen, const void *data, size_t len) +{ + struct ip *ip = hdr; + struct udphdr *udp = hdr + ip->ip_hl * 4; + uint16_t udp_len = sizeof(*udp) + len; + uint32_t sum; + + if ((void *)&udp[1] > hdr + hdrlen) + return; + + udp->uh_sum = 0; + udp->uh_ulen = htons(udp_len); + sum = csum_tcpudp_nofold(*(uint32_t *)&ip->ip_src, *(uint32_t *)&ip->ip_dst, + ip->ip_p, udp_len); + sum = csum_add(sum, csum_partial(udp, sizeof(*udp))); + sum = csum_add(sum, csum_partial(data, len)); + udp->uh_sum = csum_fold(sum); + + ip->ip_len = htons(hdrlen + len); + ip->ip_sum = 0; + ip->ip_sum = csum_fold(csum_partial(ip, sizeof(*ip))); + +#ifdef __APPLE__ + ip->ip_len = hdrlen + len; +#endif +} + +static void fixup_udpv6(void *hdr, size_t hdrlen, const void *data, size_t len) +{ + struct ip6_hdr *ip = hdr; + struct udphdr *udp = hdr + sizeof(*ip); + uint16_t udp_len = htons(sizeof(*udp) + len); + + if ((void *)&udp[1] > hdr + hdrlen) + return; + + ip->ip6_plen = htons(sizeof(*udp) + len); + udp->uh_sum = 0; + udp->uh_ulen = udp_len; + udp->uh_sum = csum_fold(csum_partial(hdr, sizeof(*ip) + sizeof(*udp))); + +#ifdef __APPLE__ + ip->ip6_plen = sizeof(*udp) + len; +#endif +} + +static void fixup_ip_udp_header(void *hdr, size_t hdrlen, const void *data, size_t len) +{ + if (hdrlen >= sizeof(struct ip6_hdr) + sizeof(struct udphdr)) + fixup_udpv6(hdr, hdrlen, data, len); + else if (hdrlen >= sizeof(struct ip) + sizeof(struct udphdr)) + fixup_udpv4(hdr, hdrlen, data, len); +} + +int sendto_rawudp(int fd, const void *addr, void *ip_hdr, size_t ip_hdrlen, + const void *data, size_t len) +{ + const struct sockaddr *sa = addr; + struct iovec iov[2] = { + { .iov_base = ip_hdr, .iov_len = ip_hdrlen }, + { .iov_base = (void *)data, .iov_len = len } + }; + struct msghdr msg = { + .msg_name = (void *)addr, + .msg_iov = iov, + .msg_iovlen = ARRAY_SIZE(iov), + }; + + if (sa->sa_family == AF_INET6) + msg.msg_namelen = sizeof(struct sockaddr_in6); + else + msg.msg_namelen = sizeof(struct sockaddr_in); + + fixup_ip_udp_header(ip_hdr, ip_hdrlen, data, len); + + return sendmsg(fd, &msg, 0); +} 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 index b1028b49e..d56dd043e 100644 --- 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 @@ -12,12 +12,13 @@ diff --git a/package/network/services/dnsmasq/files/dnsmasq.init b/package/netwo index 205bfb4cf6..415a6b013d 100644 --- a/package/network/services/dnsmasq/files/dnsmasq.init +++ b/package/network/services/dnsmasq/files/dnsmasq.init -@@ -1106,6 +1106,8 @@ dnsmasq_start() +@@ -1106,6 +1106,9 @@ dnsmasq_start() [ -n "$BOOT" ] || config_foreach filter_dnsmasq dhcp dhcp_add "$cfg" fi + xappend "except-interface=ifb-dhcp" + xappend "except-interface=spotfilter-ifb" ++ xappend "except-interface=ifb-dhcprelay" echo >> $CONFIGFILE_TMP config_foreach filter_dnsmasq cname dhcp_cname_add "$cfg" diff --git a/profiles/ucentral-ap.yml b/profiles/ucentral-ap.yml index ef20361d2..322743726 100644 --- a/profiles/ucentral-ap.yml +++ b/profiles/ucentral-ap.yml @@ -44,6 +44,7 @@ packages: - ucentral-event - ucentral-schema - ucentral-tools + - udhcprelay - ugps - usteer2 - ucrun