[PATCH usteer 1/2] remote: bound updates to MTU-safe UDP datagrams

Jan Leon jan at leoncode.de
Tue Sep 8 08:10:54 PDT 2026


Split oversized updates into existing-format node/station batches without
changing the receive protocol. Validate indivisible records before sending
and cover the splitter with parser and UDP regression tests.

Signed-off-by: Jan Leon <jan at leoncode.de>
---
 CMakeLists.txt         |  12 +-
 remote-message.c       | 142 ++++++++++++++++++++++
 remote-message.h       |  18 +++
 remote.c               |  40 +++++--
 tests/README.md        |  52 ++++++++
 tests/remote-message.c | 264 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 520 insertions(+), 8 deletions(-)
 create mode 100644 remote-message.c
 create mode 100644 remote-message.h
 create mode 100644 tests/README.md
 create mode 100644 tests/remote-message.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f889659..2d9bae5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,7 +24,7 @@ IF(NOT HAVE_PCAP_H)
 	MESSAGE(FATAL_ERROR "pcap/pcap.h is not found")
 ENDIF()
 
-SET(SOURCES main.c local_node.c node.c sta.c policy.c ubus.c remote.c parse.c netifd.c timeout.c event.c measurement.c band_steering.c)
+SET(SOURCES main.c local_node.c node.c sta.c policy.c ubus.c remote.c remote-message.c parse.c netifd.c timeout.c event.c measurement.c band_steering.c)
 
 IF(NL_CFLAGS)
 	ADD_DEFINITIONS(${NL_CFLAGS})
@@ -44,6 +44,16 @@ TARGET_LINK_LIBRARIES(fakeap ubox ubus)
 ADD_EXECUTABLE(ap-monitor monitor.c parse.c)
 TARGET_LINK_LIBRARIES(ap-monitor ubox pcap blobmsg_json)
 
+OPTION(BUILD_TESTING "Build remote message regression tests" OFF)
+IF(BUILD_TESTING)
+  ENABLE_TESTING()
+  ADD_EXECUTABLE(test-remote-message tests/remote-message.c remote-message.c parse.c)
+  TARGET_INCLUDE_DIRECTORIES(test-remote-message PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+  TARGET_COMPILE_OPTIONS(test-remote-message PRIVATE -UNDEBUG)
+  TARGET_LINK_LIBRARIES(test-remote-message ubox)
+  ADD_TEST(NAME remote-message COMMAND test-remote-message)
+ENDIF()
+
 SET(CMAKE_INSTALL_PREFIX /usr)
 
 INSTALL(TARGETS usteerd
diff --git a/remote-message.c b/remote-message.c
new file mode 100644
index 0000000..6dd259b
--- /dev/null
+++ b/remote-message.c
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "remote-message.h"
+#include "remote.h"
+
+size_t usteer_message_payload_limit(unsigned int mtu, bool ipv6)
+{
+	unsigned int overhead = ipv6 ? 48 : 28;
+
+	if (mtu <= overhead)
+		return 0;
+
+	return mtu - overhead < USTEER_REMOTE_MAX_PAYLOAD ?
+		mtu - overhead : USTEER_REMOTE_MAX_PAYLOAD;
+}
+
+/* The input is a locally constructed message, not an untrusted receive buffer. */
+static struct blob_attr *message_field(struct blob_attr *data, unsigned int id)
+{
+	struct blob_attr *cur;
+	int rem;
+
+	blob_for_each_attr(cur, data, rem)
+		if (blob_id(cur) == id)
+			return cur;
+
+	return NULL;
+}
+
+static size_t metadata_size(struct blob_attr *data, unsigned int skip)
+{
+	struct blob_attr *cur;
+	size_t len = sizeof(*data);
+	int rem;
+
+	blob_for_each_attr(cur, data, rem)
+		if (blob_id(cur) != skip)
+			len += blob_pad_len(cur);
+
+	return len;
+}
+
+static size_t copy_metadata(char *out, struct blob_attr *data, unsigned int skip)
+{
+	struct blob_attr *cur;
+	size_t len = sizeof(*data);
+	int rem;
+
+	memcpy(out, data, sizeof(*data));
+	blob_for_each_attr(cur, data, rem) {
+		if (blob_id(cur) == skip)
+			continue;
+		memcpy(out + len, cur, blob_pad_len(cur));
+		len += blob_pad_len(cur);
+	}
+
+	return len;
+}
+
+static int emit_chunk(char *out, size_t len, size_t nodes, size_t node,
+		      size_t stations, usteer_message_emit emit, void *priv)
+{
+	blob_set_raw_len((struct blob_attr *)out, len);
+	blob_set_raw_len((struct blob_attr *)(out + nodes), len - nodes);
+	blob_set_raw_len((struct blob_attr *)(out + node), len - node);
+	blob_set_raw_len((struct blob_attr *)(out + stations), len - stations);
+	return emit((struct blob_attr *)out, priv);
+}
+
+int usteer_message_send(struct blob_attr *data, size_t limit,
+			usteer_message_emit emit, void *priv)
+{
+	struct blob_attr *nodes, *node, *stations, *sta;
+	size_t top_len, node_len, overhead, nodes_off, node_off, sta_off, len;
+	int rem, sta_rem, ret = 0;
+	char *out;
+
+	if (blob_pad_len(data) <= limit)
+		return emit(data, priv);
+
+	nodes = message_field(data, APMSG_NODES);
+	if (!nodes)
+		return -EINVAL;
+
+	top_len = metadata_size(data, APMSG_NODES) + sizeof(*nodes);
+	if (top_len > limit)
+		return -EMSGSIZE;
+
+	/* Validate the entire update before emitting any part of it. Metadata and
+	 * individual station records are indivisible in the existing protocol.
+	 * Never silently truncate them or fall back to IP fragmentation.
+	 */
+	blob_for_each_attr(node, nodes, rem) {
+		stations = message_field(node, APMSG_NODE_STATIONS);
+		if (!stations)
+			return -EINVAL;
+
+		node_len = metadata_size(node, APMSG_NODE_STATIONS) + sizeof(*stations);
+		if (node_len > limit - top_len)
+			return -EMSGSIZE;
+
+		overhead = top_len + node_len;
+		blob_for_each_attr(sta, stations, sta_rem)
+			if (blob_pad_len(sta) > limit - overhead)
+				return -EMSGSIZE;
+	}
+
+	out = malloc(limit);
+	if (!out)
+		return -ENOMEM;
+
+	nodes_off = copy_metadata(out, data, APMSG_NODES);
+	memcpy(out + nodes_off, nodes, sizeof(*nodes));
+	node_off = nodes_off + sizeof(*nodes);
+	blob_for_each_attr(node, nodes, rem) {
+		stations = message_field(node, APMSG_NODE_STATIONS);
+		sta_off = node_off + copy_metadata(out + node_off, node, APMSG_NODE_STATIONS);
+		memcpy(out + sta_off, stations, sizeof(*stations));
+		overhead = sta_off + sizeof(*stations);
+		len = overhead;
+
+		blob_for_each_attr(sta, stations, sta_rem) {
+			if (blob_pad_len(sta) > limit - len) {
+				ret = emit_chunk(out, len, nodes_off, node_off, sta_off, emit, priv);
+				if (ret)
+					goto out;
+				len = overhead;
+			}
+			memcpy(out + len, sta, blob_pad_len(sta));
+			len += blob_pad_len(sta);
+		}
+		ret = emit_chunk(out, len, nodes_off, node_off, sta_off, emit, priv);
+		if (ret)
+			goto out;
+	}
+out:
+	free(out);
+	return ret;
+}
diff --git a/remote-message.h b/remote-message.h
new file mode 100644
index 0000000..f3c4e4e
--- /dev/null
+++ b/remote-message.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __USTEER_REMOTE_MESSAGE_H
+#define __USTEER_REMOTE_MESSAGE_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <libubox/blob.h>
+
+/* Leave room for IPv6 and UDP even on a 1280-byte link. */
+#define USTEER_REMOTE_MAX_PAYLOAD 1200
+
+typedef int (*usteer_message_emit)(struct blob_attr *data, void *priv);
+
+size_t usteer_message_payload_limit(unsigned int mtu, bool ipv6);
+int usteer_message_send(struct blob_attr *data, size_t limit,
+			usteer_message_emit emit, void *priv);
+
+#endif
diff --git a/remote.c b/remote.c
index bf58ea3..5424bdd 100644
--- a/remote.c
+++ b/remote.c
@@ -21,6 +21,7 @@
 
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/ioctl.h>
 #include <netinet/in.h>
 #include <net/if.h>
 #include <arpa/inet.h>
@@ -32,6 +33,7 @@
 #include <libubox/usock.h>
 #include "usteer.h"
 #include "remote.h"
+#include "remote-message.h"
 #include "node.h"
 
 static uint32_t local_id;
@@ -485,7 +487,7 @@ static void interface_recv_v6(struct uloop_fd *u, unsigned int events){
 	} while (1);
 }
 
-static void interface_send_msg_v4(struct interface *iface, struct blob_attr *data)
+static int interface_send_msg_v4(struct interface *iface, struct blob_attr *data)
 {
 	static size_t cmsg_data[( CMSG_SPACE(sizeof(struct in_pktinfo)) / sizeof(size_t)) + 1];
 	static struct sockaddr_in a;
@@ -518,11 +520,12 @@ static void interface_send_msg_v4(struct interface *iface, struct blob_attr *dat
 	iov.iov_len = blob_pad_len(data);
 
 	if (sendmsg(remote_fd.fd, &m, 0) < 0)
-		perror("sendmsg");
+		return -errno;
+	return 0;
 }
 
 
-static void interface_send_msg_v6(struct interface *iface, struct blob_attr *data) {
+static int interface_send_msg_v6(struct interface *iface, struct blob_attr *data) {
 	static struct sockaddr_in6 groupSock = {};
 
 	groupSock.sin6_family = AF_INET6;
@@ -532,15 +535,38 @@ static void interface_send_msg_v6(struct interface *iface, struct blob_attr *dat
 	setsockopt(remote_fd.fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &iface->ifindex, sizeof(iface->ifindex));
 
 	if (sendto(remote_fd.fd, data, blob_pad_len(data), 0, (const struct sockaddr *)&groupSock, sizeof(groupSock)) < 0)
-		perror("sendmsg");
+		return -errno;
+	return 0;
 }
 
-static void interface_send_msg(struct interface *iface, struct blob_attr *data){
+static int interface_emit_msg(struct blob_attr *data, void *priv)
+{
+	struct interface *iface = priv;
+
 	if (config.ipv6) {
-		interface_send_msg_v6(iface, data);
+		return interface_send_msg_v6(iface, data);
 	} else {
-		interface_send_msg_v4(iface, data);
+		return interface_send_msg_v4(iface, data);
+	}
+}
+
+static void interface_send_msg(struct interface *iface, struct blob_attr *data)
+{
+	struct ifreq ifr = {};
+	size_t limit;
+	int ret;
+
+	snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface_name(iface));
+	if (ioctl(remote_fd.fd, SIOCGIFMTU, &ifr) < 0) {
+		MSG(FATAL, "Cannot read MTU for %s: %s\n", interface_name(iface), strerror(errno));
+		return;
 	}
+	limit = usteer_message_payload_limit(ifr.ifr_mtu > 0 ? ifr.ifr_mtu : 0,
+					    config.ipv6);
+	ret = usteer_message_send(data, limit, interface_emit_msg, iface);
+	if (ret)
+		MSG(FATAL, "Cannot send remote update on %s (payload limit %zu): %s\n",
+		    interface_name(iface), limit, strerror(-ret));
 }
 
 static void usteer_send_sta_info(struct sta_info *sta)
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..df71be5
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,52 @@
+# Begrenzte Remote-Updates testen
+
+Mit den normalen Usteer-Buildabhängigkeiten:
+
+```sh
+cmake -S . -B build-test -DBUILD_TESTING=ON
+cmake --build build-test
+ctest --test-dir build-test --output-on-failure
+```
+
+Für ASan/UBSan zusätzlich beim Konfigurieren:
+
+```sh
+-DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \
+-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined"
+```
+
+Der Test nutzt den unveränderten Parser aus `parse.c`. Er prüft die
+byteidentische Weitergabe kleiner Nachrichten, eine Grenze exakt auf und
+knapp unter der Nachrichtengröße, 1.001 Paketgrenzen von 200 bis 1.200 Byte
+mit jeweils 300 Stationen auf zwei APs sowie einen dritten leeren AP.
+Stationsdaten dürfen weder fehlen noch doppelt vorkommen. Metadaten,
+unbekannte AP-Felder und der Eingabepuffer müssen unverändert bleiben.
+Weitere Fälle: unteilbare übergroße Metadaten im letzten AP, übergroße
+Stationsdatensätze, zu kleine MTUs, reine Host-Updates und Sendefehler.
+Zwei Tests senden und empfangen die Nachrichten über lokale IPv4-/IPv6-
+UDP-Sockets; IPv6-Loopback muss in der Testumgebung verfügbar sein.
+
+## Protokoll und Grenzen
+
+Die Aufteilung verwendet ausschließlich bestehende Nachrichtenfelder.
+Jeder Teil enthält dieselben Host-Metadaten und dieselbe Sequenznummer des
+logischen Updates. Der bestehende Empfänger verwendet die Sequenznummer
+nicht zur Duplikatunterdrückung und aktualisiert Stationsdatensätze einzeln;
+fehlende Stationen werden nicht wegen eines Teilupdates gelöscht.
+Der Patch benötigt keine neue Empfangslogik und keinen gleichzeitigen
+Versionswechsel aller Gegenstellen. Dies ist durch Parser- und Datentests
+gestützt, ersetzt aber keinen Mehr-AP-Betriebstest.
+
+Das UDP-Nutzlastlimit ist höchstens 1.200 Byte und bei kleineren lokalen
+Interface-MTUs zusätzlich um IPv4-/IPv6- und UDP-Header reduziert. Das
+ist keine allgemeine Path-MTU-Ermittlung für Tunnel oder fremde Routen.
+Die gesamte Nachricht wird vor dem ersten Teil auf Teilbarkeit geprüft.
+Ein einzelner zu großer Metadatenblock oder Stationsdatensatz führt zu
+`EMSGSIZE` und einem protokollierten Sendefehler, nicht zu Abschneiden oder
+einem Rückfall auf übergroße Datagramme. Ein späterer Socket-Sendefehler
+kann wie gewöhnlicher UDP-Verlust ein unvollständig empfangenes Update
+hinterlassen; die nächste periodische Aktualisierung erfolgt weiterhin.
+
+Der Patch soll IP-Fragmentierung der Usteer-Updates auf den vorgesehenen
+LAN-Interfaces vermeiden. Er ist kein Nachweis einer Behebung sonstiger
+WLAN-, Shelly- oder HomePod-Probleme.
diff --git a/tests/remote-message.c b/tests/remote-message.c
new file mode 100644
index 0000000..70e4787
--- /dev/null
+++ b/tests/remote-message.c
@@ -0,0 +1,264 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include "remote-message.h"
+#include "remote.h"
+
+static struct blob_buf input;
+static unsigned int seen[3][200];
+static unsigned int nodes_seen[3], emitted, fail_at;
+static size_t cap;
+static bool expect_identical;
+static unsigned int station_extra;
+static int udp_tx, udp_rx;
+
+static struct blob_attr *field(struct blob_attr *data, unsigned int id)
+{
+	struct blob_attr *cur;
+	int rem;
+	blob_for_each_attr(cur, data, rem)
+		if (blob_id(cur) == id)
+			return cur;
+	return NULL;
+}
+
+static void make_message(unsigned int count, unsigned int metadata)
+{
+	unsigned int n, i;
+	void *nodes, *node, *stations, *station;
+	char extra[2048] = {}, name[16];
+	uint8_t addr[6] = {};
+
+	assert(metadata <= sizeof(extra));
+	blob_buf_init(&input, 0);
+	blob_put_int32(&input, APMSG_ID, 123);
+	blob_put_int32(&input, APMSG_SEQ, 456);
+	blob_put(&input, APMSG_HOST_INFO, NULL, 0);
+	nodes = blob_nest_start(&input, APMSG_NODES);
+	for (n = 0; n < 3; n++) {
+		node = blob_nest_start(&input, 0);
+		snprintf(name, sizeof(name), "radio%u", n);
+		blob_put_string(&input, APMSG_NODE_NAME, name);
+		blob_put_string(&input, APMSG_NODE_SSID, "HomeNET");
+		addr[0] = n;
+		blob_put(&input, APMSG_NODE_BSSID, addr, 6);
+		blob_put_int32(&input, APMSG_NODE_FREQ, 2412);
+		blob_put_int32(&input, APMSG_NODE_N_ASSOC, n == 2 ? 0 : count);
+		blob_put(&input, APMSG_NODE_NODE_INFO, extra, n == 2 ? metadata : 0);
+		blob_put(&input, 31, "abc", 3);
+		stations = blob_nest_start(&input, APMSG_NODE_STATIONS);
+		for (i = 0; n != 2 && i < count; i++) {
+			station = blob_nest_start(&input, 0);
+			addr[1] = i;
+			blob_put(&input, APMSG_STA_ADDR, addr, 6);
+			blob_put_int32(&input, APMSG_STA_SIGNAL, -40);
+			blob_put_int32(&input, APMSG_STA_TIMEOUT, 1000);
+			blob_put_int32(&input, APMSG_STA_SEEN, 10);
+			blob_put_int32(&input, APMSG_STA_LAST_CONNECTED, 20);
+			blob_put_int8(&input, APMSG_STA_CONNECTED, 1);
+			if (station_extra)
+				blob_put(&input, 31, extra, station_extra);
+			blob_nest_end(&input, station);
+		}
+		blob_nest_end(&input, stations);
+		blob_nest_end(&input, node);
+	}
+	blob_nest_end(&input, nodes);
+}
+
+static int receive_message(struct blob_attr *data, void *priv)
+{
+	struct apmsg msg;
+	struct apmsg_node node_msg = {};
+	struct apmsg_sta sta_msg;
+	struct blob_attr *node, *sta, *cur, *original, *original_nodes;
+	int rem, sta_rem, attr_rem, orig_rem;
+	unsigned int n;
+
+	assert(priv == &input);
+	assert(blob_pad_len(data) <= cap);
+	emitted++;
+	if (fail_at && emitted == fail_at)
+		return -EIO;
+	if (expect_identical)
+		assert(!memcmp(data, input.head, blob_pad_len(input.head)));
+	/* Use the unchanged upstream parser, including mandatory-field checks. */
+	assert(parse_apmsg(&msg, data));
+	assert(msg.id == 123 && msg.seq == 456);
+	assert(msg.host_info && !blob_len(msg.host_info));
+	original_nodes = field(input.head, APMSG_NODES);
+	blob_for_each_attr(node, msg.nodes, rem) {
+		assert(parse_apmsg_node(&node_msg, node));
+		n = (unsigned char)node_msg.bssid[0];
+		assert(n < 3);
+		nodes_seen[n]++;
+		original = NULL;
+		blob_for_each_attr(cur, original_nodes, orig_rem)
+			if (((unsigned char *)blob_data(field(cur, APMSG_NODE_BSSID)))[0] == n)
+				original = cur;
+		assert(original);
+		blob_for_each_attr(cur, original, attr_rem) {
+			struct blob_attr *copy;
+			if (blob_id(cur) == APMSG_NODE_STATIONS)
+				continue;
+			copy = field(node, blob_id(cur));
+			assert(copy && blob_pad_len(copy) == blob_pad_len(cur));
+			assert(!memcmp(copy, cur, blob_pad_len(cur)));
+		}
+		blob_for_each_attr(sta, node_msg.stations, sta_rem) {
+			assert(parse_apmsg_sta(&sta_msg, sta));
+			assert(sta_msg.addr[0] == n && sta_msg.addr[1] < 200);
+			assert(sta_msg.signal == -40 && sta_msg.timeout == 1000);
+			assert(sta_msg.seen == 10 && sta_msg.last_connected == 20);
+			assert(sta_msg.connected);
+			assert(++seen[n][sta_msg.addr[1]] == 1);
+		}
+	}
+	return 0;
+}
+
+static void reset(size_t limit)
+{
+	memset(seen, 0, sizeof(seen));
+	memset(nodes_seen, 0, sizeof(nodes_seen));
+	emitted = fail_at = 0;
+	expect_identical = false;
+	cap = limit;
+}
+
+static int udp_emit(struct blob_attr *data, void *priv)
+{
+	unsigned int received[512];
+	ssize_t len = blob_pad_len(data);
+
+	assert(send(udp_tx, data, len, 0) == len);
+	assert(recv(udp_rx, received, sizeof(received), 0) == len);
+	assert(!memcmp(received, data, len));
+	return receive_message((struct blob_attr *)received, priv);
+}
+
+static void test_udp(int family)
+{
+	struct sockaddr_storage addr = {};
+	struct sockaddr_in *v4 = (void *)&addr;
+	struct sockaddr_in6 *v6 = (void *)&addr;
+	struct timeval timeout = { .tv_sec = 2 };
+	socklen_t len = family == AF_INET ? sizeof(*v4) : sizeof(*v6);
+	unsigned int n, i;
+
+	addr.ss_family = family;
+	if (family == AF_INET)
+		v4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+	else
+		v6->sin6_addr = in6addr_loopback;
+	udp_rx = socket(family, SOCK_DGRAM, 0);
+	udp_tx = socket(family, SOCK_DGRAM, 0);
+	assert(udp_rx >= 0 && udp_tx >= 0);
+	assert(!bind(udp_rx, (void *)&addr, len));
+	assert(!getsockname(udp_rx, (void *)&addr, &len));
+	assert(!connect(udp_tx, (void *)&addr, len));
+	assert(!setsockopt(udp_rx, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)));
+	make_message(150, 16);
+	reset(1200);
+	assert(!usteer_message_send(input.head, cap, udp_emit, &input));
+	for (n = 0; n < 2; n++)
+		for (i = 0; i < 150; i++)
+			assert(seen[n][i] == 1);
+	assert(nodes_seen[2]);
+	close(udp_rx);
+	close(udp_tx);
+}
+
+int main(void)
+{
+	unsigned int n, i;
+	size_t limit;
+	int ret;
+	void *original;
+
+	assert(usteer_message_payload_limit(1500, false) == 1200);
+	assert(usteer_message_payload_limit(1280, true) == 1200);
+	assert(usteer_message_payload_limit(576, false) == 548);
+	assert(usteer_message_payload_limit(1000, true) == 952);
+	assert(!usteer_message_payload_limit(28, false));
+	assert(!usteer_message_payload_limit(48, true));
+	assert(!usteer_message_payload_limit(0, true));
+
+	make_message(1, 0);
+	reset(blob_pad_len(input.head));
+	expect_identical = true;
+	assert(!usteer_message_send(input.head, cap, receive_message, &input));
+	assert(emitted == 1);
+	reset(blob_pad_len(input.head) - 1);
+	assert(!usteer_message_send(input.head, cap, receive_message, &input));
+	assert(emitted > 1);
+
+	make_message(150, 16);
+	original = malloc(blob_pad_len(input.head));
+	assert(original);
+	memcpy(original, input.head, blob_pad_len(input.head));
+	for (limit = 200; limit <= 1200; limit++) {
+		reset(limit);
+		assert(!usteer_message_send(input.head, cap, receive_message, &input));
+		assert(!memcmp(original, input.head, blob_pad_len(input.head)));
+		for (n = 0; n < 3; n++) {
+			assert(nodes_seen[n]);
+			for (i = 0; i < 150; i++)
+				assert(seen[n][i] == (n != 2));
+		}
+	}
+	free(original);
+	reset(1200);
+	fail_at = 2;
+	assert(usteer_message_send(input.head, cap, receive_message, &input) == -EIO);
+	assert(emitted == 2);
+
+	/* Metadata and individual records cannot be truncated to fit. */
+	for (limit = 0; limit < 100; limit++) {
+		reset(limit);
+		ret = usteer_message_send(input.head, cap, receive_message, &input);
+		assert(ret == -EMSGSIZE && !emitted);
+	}
+	make_message(1, 1400);
+	reset(1200);
+	assert(usteer_message_send(input.head, cap, receive_message, &input) == -EMSGSIZE);
+	assert(!emitted);
+	station_extra = 1400;
+	make_message(1, 0);
+	reset(1200);
+	assert(usteer_message_send(input.head, cap, receive_message, &input) == -EMSGSIZE);
+	assert(!emitted);
+	station_extra = 0;
+
+	make_message(0, 0);
+	reset(200);
+	assert(!usteer_message_send(input.head, cap, receive_message, &input));
+	assert(nodes_seen[0] && nodes_seen[1] && nodes_seen[2]);
+
+	/* A host-only update remains a complete, valid old-format message. */
+	blob_buf_init(&input, 0);
+	blob_put_int32(&input, APMSG_ID, 123);
+	blob_put_int32(&input, APMSG_SEQ, 456);
+	blob_put(&input, APMSG_HOST_INFO, NULL, 0);
+	blob_put(&input, APMSG_NODES, NULL, 0);
+	reset(1200);
+	expect_identical = true;
+	assert(!usteer_message_send(input.head, cap, receive_message, &input));
+	assert(emitted == 1);
+	reset(20);
+	assert(usteer_message_send(input.head, cap, receive_message, &input) == -EMSGSIZE);
+	assert(!emitted);
+
+	test_udp(AF_INET);
+	test_udp(AF_INET6);
+	blob_buf_free(&input);
+	puts("remote-message: all tests passed");
+	return 0;
+}





More information about the openwrt-devel mailing list