mirror of
				https://github.com/Telecominfraproject/wlan-ap.git
				synced 2025-10-31 02:17:58 +00:00 
			
		
		
		
	udhcprelay: add new package
Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
		
							
								
								
									
										36
									
								
								feeds/ucentral/udhcprelay/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								feeds/ucentral/udhcprelay/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| include $(TOPDIR)/rules.mk | ||||
|  | ||||
| PKG_NAME:=udhcprelay | ||||
| 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/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)) | ||||
							
								
								
									
										11
									
								
								feeds/ucentral/udhcprelay/files/dhcprelay.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								feeds/ucentral/udhcprelay/files/dhcprelay.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
|  | ||||
							
								
								
									
										2
									
								
								feeds/ucentral/udhcprelay/files/dhcprelay.hotplug
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								feeds/ucentral/udhcprelay/files/dhcprelay.hotplug
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| #!/bin/sh | ||||
| ubus call dhcprelay check_devices | ||||
							
								
								
									
										81
									
								
								feeds/ucentral/udhcprelay/files/dhcprelay.init
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								feeds/ucentral/udhcprelay/files/dhcprelay.init
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										16
									
								
								feeds/ucentral/udhcprelay/src/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								feeds/ucentral/udhcprelay/src/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -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} | ||||
| ) | ||||
							
								
								
									
										392
									
								
								feeds/ucentral/udhcprelay/src/dev.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								feeds/ucentral/udhcprelay/src/dev.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,392 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0+ | ||||
| /* | ||||
|  * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name> | ||||
|  */ | ||||
| #include <netpacket/packet.h> | ||||
| #include <netinet/if_ether.h> | ||||
| #include <arpa/inet.h> | ||||
| #include <sys/socket.h> | ||||
| #include <sys/types.h> | ||||
| #include <sys/ioctl.h> | ||||
| #include <stdio.h> | ||||
| #include <unistd.h> | ||||
| #include <fcntl.h> | ||||
|  | ||||
| #include <libubox/vlist.h> | ||||
| #include <libubox/avl-cmp.h> | ||||
|  | ||||
| #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); | ||||
| } | ||||
							
								
								
									
										239
									
								
								feeds/ucentral/udhcprelay/src/dhcp.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								feeds/ucentral/udhcprelay/src/dhcp.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,239 @@ | ||||
| // 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 <arpa/inet.h> | ||||
| #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); | ||||
| } | ||||
							
								
								
									
										107
									
								
								feeds/ucentral/udhcprelay/src/dhcprelay.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								feeds/ucentral/udhcprelay/src/dhcprelay.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0+ | ||||
| /* | ||||
|  * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name> | ||||
|  */ | ||||
| #ifndef __DHCPRELAY_H | ||||
| #define __DHCPRELAY_H | ||||
|  | ||||
| #include <libubox/blobmsg.h> | ||||
| #include <libubox/ulog.h> | ||||
| #include <libubox/uloop.h> | ||||
| #include <libubox/vlist.h> | ||||
| #include <libubox/utils.h> | ||||
| #include <net/if.h> | ||||
| #include <stdint.h> | ||||
|  | ||||
| #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 | ||||
							
								
								
									
										83
									
								
								feeds/ucentral/udhcprelay/src/main.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								feeds/ucentral/udhcprelay/src/main.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| // 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 "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; | ||||
| } | ||||
							
								
								
									
										93
									
								
								feeds/ucentral/udhcprelay/src/msg.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								feeds/ucentral/udhcprelay/src/msg.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| /* SPDX-License-Identifier: BSD-3-Clause */ | ||||
| #ifndef __DHCPRELAY_MSG_H | ||||
| #define __DHCPRELAY_MSG_H | ||||
|  | ||||
| #include <netinet/in.h> | ||||
| #include <stdint.h> | ||||
|  | ||||
| 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 | ||||
							
								
								
									
										472
									
								
								feeds/ucentral/udhcprelay/src/relay.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										472
									
								
								feeds/ucentral/udhcprelay/src/relay.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,472 @@ | ||||
| #include <netinet/if_ether.h> | ||||
| #include <netinet/in.h> | ||||
| #include <netinet/ip.h> | ||||
| #include <netinet/udp.h> | ||||
| #include <endian.h> | ||||
| #include <libubox/uloop.h> | ||||
| #include <libubox/usock.h> | ||||
| #include <libubox/avl.h> | ||||
| #include <libubox/avl-cmp.h> | ||||
| #include <libubox/utils.h> | ||||
| #include <libubus.h> | ||||
| #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; | ||||
| } | ||||
							
								
								
									
										288
									
								
								feeds/ucentral/udhcprelay/src/ubus.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								feeds/ucentral/udhcprelay/src/ubus.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,288 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0+ | ||||
| /* | ||||
|  * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name> | ||||
|  */ | ||||
| #include <sys/types.h> | ||||
| #include <sys/socket.h> | ||||
| #include <net/if.h> | ||||
| #include <libubus.h> | ||||
|  | ||||
| #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); | ||||
| } | ||||
							
								
								
									
										147
									
								
								feeds/ucentral/udhcprelay/src/utils.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								feeds/ucentral/udhcprelay/src/utils.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | ||||
| #include <sys/types.h> | ||||
| #include <sys/socket.h> | ||||
| #include <sys/stat.h> | ||||
| #include <arpa/inet.h> | ||||
| #include <netdb.h> | ||||
| #include <stdio.h> | ||||
| #include <netinet/in.h> | ||||
| #include <netinet/ip.h> | ||||
| #include <netinet/ip6.h> | ||||
| #include <netinet/udp.h> | ||||
|  | ||||
| #include <libubox/utils.h> | ||||
| #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); | ||||
| } | ||||
| @@ -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" | ||||
|   | ||||
| @@ -44,6 +44,7 @@ packages: | ||||
|   - ucentral-event | ||||
|   - ucentral-schema | ||||
|   - ucentral-tools | ||||
|   - udhcprelay | ||||
|   - ugps | ||||
|   - usteer2 | ||||
|   - ucrun | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 John Crispin
					John Crispin