diff mbox series

[v2,09/10] test: dm: cover the shared lwIP runtime

Message ID 20260901-submit-lwip-runtime-netconsole-v1-v2-9-3e3d95627cf9@gmail.com
State New
Delegated to: Jerome Forissier
Headers show
Series net: share the lwIP runtime and support netconsole | expand

Commit Message

James Hilliard Sept. 1, 2026, 5:09 p.m. UTC
Exercise two clients and UDP PCBs on one netif, address ownership and
refresh, reentrant polling, per-client detach and final Ethernet
shutdown. Also cover DHCP shutdown, DNS callback cancellation and
immediate abort of an established HTTP connection when the corresponding
protocols are enabled.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 test/dm/Makefile |   3 +
 test/dm/lwip.c   | 375 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 378 insertions(+)
diff mbox series

Patch

diff --git a/test/dm/Makefile b/test/dm/Makefile
index fb3e6a7008f..a3e8bb4bc7e 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -49,6 +49,9 @@  obj-$(CONFIG_ECDSA_VERIFY) += ecdsa.o
 obj-$(CONFIG_DM_HASH) += hash.o
 obj-$(CONFIG_EFI_MEDIA_SANDBOX) += efi_media.o
 obj-$(CONFIG_DM_ETH) += eth.o
+ifeq ($(CONFIG_NET_LWIP)$(CONFIG_PROT_UDP_LWIP),yy)
+obj-y += lwip.o
+endif
 obj-$(CONFIG_EXTCON) += extcon.o
 ifneq ($(CONFIG_EFI_PARTITION),)
 obj-$(CONFIG_FASTBOOT_FLASH_MMC) += fastboot.o
diff --git a/test/dm/lwip.c b/test/dm/lwip.c
new file mode 100644
index 00000000000..5df8ad138df
--- /dev/null
+++ b/test/dm/lwip.c
@@ -0,0 +1,375 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright 2026 James Hilliard <james.hilliard1@gmail.com> */
+
+#include <dm.h>
+#include <env.h>
+#include <lwip/pbuf.h>
+#include <lwip/udp.h>
+#include <net.h>
+#include <asm/eth.h>
+#include <dm/test.h>
+#include <linux/kconfig.h>
+#include <test/ut.h>
+
+#if CONFIG_IS_ENABLED(PROT_DNS_LWIP)
+#include <lwip/dns.h>
+#endif
+#if CONFIG_IS_ENABLED(PROT_DHCP_LWIP)
+#include <lwip/dhcp.h>
+#include <lwip/prot/dhcp.h>
+#endif
+#if CONFIG_IS_ENABLED(WGET)
+#include <lwip/apps/http_client.h>
+#include <lwip/priv/tcp_priv.h>
+#endif
+
+struct lwip_test_eth_hdr {
+	u8 dst[ARP_HLEN];
+	u8 src[ARP_HLEN];
+	__be16 type;
+} __packed;
+
+struct lwip_test_recv_ctx {
+	unsigned int packets;
+	int nested_poll_ret;
+};
+
+#if CONFIG_IS_ENABLED(PROT_DNS_LWIP)
+struct lwip_test_dns_ctx {
+	unsigned int callbacks;
+	bool address_missing;
+};
+
+static void lwip_test_dns_found(const char *name, const ip_addr_t *ipaddr,
+				void *arg)
+{
+	struct lwip_test_dns_ctx *ctx = arg;
+
+	ctx->callbacks++;
+	ctx->address_missing = !ipaddr;
+}
+#endif
+
+#if CONFIG_IS_ENABLED(WGET)
+struct lwip_test_http_ctx {
+	unsigned int callbacks;
+	httpc_result_t result;
+};
+
+static void lwip_test_http_result(void *arg, httpc_result_t result,
+				  u32_t rx_content_len, u32_t server_response,
+				  err_t err)
+{
+	struct lwip_test_http_ctx *ctx = arg;
+
+	ctx->callbacks++;
+	ctx->result = result;
+}
+
+static err_t lwip_test_http_recv(void *arg, struct altcp_pcb *conn,
+				 struct pbuf *p, err_t err)
+{
+	if (p)
+		pbuf_free(p);
+
+	return ERR_OK;
+}
+#endif
+
+static void lwip_test_udp_recv(void *arg, struct udp_pcb *pcb,
+			       struct pbuf *p, const ip_addr_t *addr,
+			       u16_t port)
+{
+	struct lwip_test_recv_ctx *recv_ctx = arg;
+
+	recv_ctx->packets++;
+	recv_ctx->nested_poll_ret = net_lwip_poll();
+	pbuf_free(p);
+}
+
+#if CONFIG_IS_ENABLED(PROT_DHCP_LWIP)
+static int lwip_test_dhcp_stop(struct unit_test_state *uts,
+			       struct netif *netif)
+{
+	struct dhcp *dhcp;
+	ip4_addr_t address;
+	int i;
+
+	ip4addr_aton("1.1.2.5", &address);
+	for (i = 0; i < 2; i++) {
+		ut_asserteq(ERR_OK, dhcp_start(netif));
+		dhcp = netif_dhcp_data(netif);
+		ut_assertnonnull(dhcp);
+		dhcp->state = DHCP_STATE_BOUND;
+		netif_set_ipaddr(netif, &address);
+		ut_assert(dhcp_supplied_address(netif));
+		dhcp_stop_without_release(netif);
+		ut_assert(ip4_addr_cmp(netif_ip4_addr(netif), &address));
+		ut_asserteq(0, dhcp->pcb_allocated);
+		dhcp_cleanup(netif);
+		ut_assertnull(netif_dhcp_data(netif));
+		ut_assertok(net_lwip_poll());
+	}
+
+	return 0;
+}
+#endif
+
+static int lwip_test_inject_udp(struct udevice *dev, const char *payload,
+				struct in_addr src_ip, u16 src_port,
+				u16 dest_port)
+{
+	struct eth_sandbox_priv *priv = dev_get_priv(dev);
+	struct eth_pdata *pdata = dev_get_plat(dev);
+	struct lwip_test_eth_hdr *eth;
+	unsigned int payload_len = strlen(payload);
+	unsigned int packet_len;
+	struct ip_udp_hdr *ip;
+
+	packet_len = sizeof(*eth) + IP_UDP_HDR_SIZE + payload_len;
+	if (priv->recv_packets >= PKTBUFSRX || packet_len > PKTSIZE_ALIGN)
+		return -ENOSPC;
+
+	eth = (void *)priv->recv_packet_buffer[priv->recv_packets];
+	memcpy(eth->dst, pdata->enetaddr, ARP_HLEN);
+	memcpy(eth->src, priv->fake_host_hwaddr, ARP_HLEN);
+	eth->type = htons(PROT_IP);
+
+	ip = (void *)(eth + 1);
+	memset(ip, 0, IP_UDP_HDR_SIZE);
+	ip->ip_hl_v = 0x45;
+	ip->ip_len = htons(IP_UDP_HDR_SIZE + payload_len);
+	ip->ip_off = htons(IP_FLAGS_DFRAG);
+	ip->ip_ttl = 64;
+	ip->ip_p = IPPROTO_UDP;
+	ip->ip_src = src_ip;
+	ip->ip_dst = string_to_ip(env_get("ipaddr"));
+	ip->udp_src = htons(src_port);
+	ip->udp_dst = htons(dest_port);
+	ip->udp_len = htons(UDP_HDR_SIZE + payload_len);
+	ip->ip_sum = compute_ip_checksum(ip, IP_HDR_SIZE);
+	memcpy(ip + 1, payload, payload_len);
+
+	priv->recv_packet_length[priv->recv_packets] = packet_len;
+	priv->recv_packets++;
+
+	return 0;
+}
+
+#if CONFIG_IS_ENABLED(PROT_DNS_LWIP)
+static int lwip_test_dns_cancel(struct unit_test_state *uts)
+{
+	struct lwip_test_dns_ctx cancelled = {};
+	struct lwip_test_dns_ctx active = {};
+	ip_addr_t dns_server;
+	ip_addr_t result;
+	int ret;
+	int i;
+
+	ipaddr_aton("1.1.2.2", &dns_server);
+	dns_setserver(0, &dns_server);
+
+	ret = dns_gethostbyname("cancel.test", &result,
+				lwip_test_dns_found, &cancelled);
+	ut_asserteq(ERR_INPROGRESS, ret);
+
+	/* Start the query so a second request can share it. */
+	dns_tmr();
+	ut_assertok(net_lwip_poll());
+	dns_cancel(lwip_test_dns_found, &cancelled);
+
+	ret = dns_gethostbyname("cancel.test", &result,
+				lwip_test_dns_found, &active);
+	ut_asserteq(ERR_INPROGRESS, ret);
+
+	for (i = 0; i < 16; i++)
+		dns_tmr();
+
+	ut_asserteq(0, cancelled.callbacks);
+	ut_asserteq(1, active.callbacks);
+	ut_assert(active.address_missing);
+
+	return 0;
+}
+#endif
+
+#if CONFIG_IS_ENABLED(WGET)
+static int lwip_test_http_abort(struct unit_test_state *uts)
+{
+	struct lwip_test_http_ctx ctx = {};
+	httpc_connection_t settings = {
+		.result_fn = lwip_test_http_result,
+	};
+	httpc_state_t *state = NULL;
+	ip_addr_t server;
+	err_t err;
+#if CONFIG_IS_ENABLED(PROT_DNS_LWIP)
+	int i;
+
+	err = httpc_get_file_dns("abort-http.test", HTTP_DEFAULT_PORT, "/",
+				 &settings, lwip_test_http_recv, &ctx, &state);
+	ut_asserteq(ERR_OK, err);
+	ut_assertnonnull(state);
+	ut_asserteq(ERR_OK, httpc_abort(state));
+	ut_asserteq(1, ctx.callbacks);
+	ut_asserteq(HTTPC_RESULT_LOCAL_ABORT, ctx.result);
+
+	/* A later DNS timeout must not call into the freed HTTP state. */
+	for (i = 0; i < 16; i++)
+		dns_tmr();
+	ut_asserteq(1, ctx.callbacks);
+
+	ctx = (struct lwip_test_http_ctx){};
+	state = NULL;
+#endif
+	ipaddr_aton("1.1.2.2", &server);
+	err = httpc_get_file(&server, HTTP_DEFAULT_PORT, "/", &settings,
+			     lwip_test_http_recv, &ctx, &state);
+	ut_asserteq(ERR_OK, err);
+	ut_assertnonnull(state);
+	ut_assertnonnull(tcp_active_pcbs);
+	ut_asserteq(SYN_SENT, tcp_active_pcbs->state);
+	ut_assertnull(tcp_active_pcbs->next);
+
+	/* An established connection must be removed, not left closing. */
+	tcp_active_pcbs->state = ESTABLISHED;
+	ut_asserteq(ERR_OK, httpc_abort(state));
+	ut_asserteq(1, ctx.callbacks);
+	ut_asserteq(HTTPC_RESULT_LOCAL_ABORT, ctx.result);
+	ut_assertnull(tcp_active_pcbs);
+
+	return 0;
+}
+#endif
+
+static int _dm_test_lwip_runtime(struct unit_test_state *uts,
+				 struct net_lwip_ctx *client_a,
+				 struct net_lwip_ctx *client_b,
+				 struct net_lwip_ctx *no_addr_client,
+				 struct udp_pcb **pcb_a,
+				 struct udp_pcb **pcb_b)
+{
+	struct lwip_test_recv_ctx recv_a = {};
+	struct lwip_test_recv_ctx recv_b = {};
+	struct in_addr host = string_to_ip("1.1.2.2");
+	ip4_addr_t expected;
+
+	ut_assertok(env_set("ethact", "eth@10002000"));
+	ut_assertok(env_set("ipaddr", "1.1.2.1"));
+	ut_assertok(env_set("netmask", "255.255.255.0"));
+	ut_assertok(env_set("gatewayip", "1.1.2.254"));
+
+	ut_assertok(net_lwip_start(client_a, NET_LWIP_ADDR_ENV_STRICT));
+	ut_assertnonnull(client_a->dev);
+	ut_assertnonnull(client_a->netif);
+	ut_assert(eth_is_active(client_a->dev));
+	ip4addr_aton("1.1.2.1", &expected);
+	ut_assert(ip4_addr_cmp(netif_ip4_addr(client_a->netif), &expected));
+
+	ut_assertok(net_lwip_start(client_b, NET_LWIP_ADDR_ENV_FLEXIBLE));
+	ut_asserteq_ptr(client_a->dev, client_b->dev);
+	ut_asserteq_ptr(client_a->netif, client_b->netif);
+
+	/* Strict clients prevent every change to the shared IPv4 address. */
+	ut_assertok(env_set("ipaddr", "1.1.2.3"));
+	ut_asserteq(-EBUSY, net_lwip_refresh(client_b));
+	ut_asserteq(-EBUSY,
+		    net_lwip_start(no_addr_client, NET_LWIP_ADDR_NONE));
+	ut_assertnull(no_addr_client->dev);
+	ut_assertnull(no_addr_client->netif);
+	ip4addr_aton("1.1.2.1", &expected);
+	ut_assert(ip4_addr_cmp(netif_ip4_addr(client_a->netif), &expected));
+
+	net_lwip_stop(client_a);
+	ut_assertok(net_lwip_refresh(client_b));
+	ip4addr_aton("1.1.2.3", &expected);
+	ut_assert(ip4_addr_cmp(netif_ip4_addr(client_b->netif), &expected));
+
+	ut_assertok(net_lwip_start(no_addr_client, NET_LWIP_ADDR_NONE));
+	ut_assert(ip4_addr_isany_val(*netif_ip4_addr(client_b->netif)));
+	ut_asserteq(-EBUSY,
+		    net_lwip_start(client_a, NET_LWIP_ADDR_NONE));
+	ut_assertnull(client_a->dev);
+	ut_assertnull(client_a->netif);
+	ut_asserteq(-EBUSY,
+		    net_lwip_start(client_a, NET_LWIP_ADDR_ENV_STRICT));
+	ut_assertnull(client_a->dev);
+	ut_assertnull(client_a->netif);
+	ut_assertok(env_set("ipaddr", "1.1.2.4"));
+	ut_assertok(net_lwip_refresh(client_b));
+	ut_assert(ip4_addr_isany_val(*netif_ip4_addr(client_b->netif)));
+#if CONFIG_IS_ENABLED(PROT_DHCP_LWIP)
+	ut_assertok(lwip_test_dhcp_stop(uts, no_addr_client->netif));
+#endif
+	net_lwip_stop(no_addr_client);
+	ip4addr_aton("1.1.2.4", &expected);
+	ut_assert(ip4_addr_cmp(netif_ip4_addr(client_b->netif), &expected));
+
+	ut_assertok(net_lwip_start(client_a, NET_LWIP_ADDR_ENV_STRICT));
+	ut_asserteq_ptr(client_a->dev, client_b->dev);
+	ut_asserteq_ptr(client_a->netif, client_b->netif);
+#if CONFIG_IS_ENABLED(PROT_DNS_LWIP)
+	ut_assertok(lwip_test_dns_cancel(uts));
+#endif
+#if CONFIG_IS_ENABLED(WGET)
+	ut_assertok(lwip_test_http_abort(uts));
+#endif
+
+	*pcb_a = udp_new();
+	ut_assertnonnull(*pcb_a);
+	ut_asserteq(ERR_OK, udp_bind(*pcb_a, IP_ADDR_ANY, 10000));
+	udp_recv(*pcb_a, lwip_test_udp_recv, &recv_a);
+	*pcb_b = udp_new();
+	ut_assertnonnull(*pcb_b);
+	ut_asserteq(ERR_OK, udp_bind(*pcb_b, IP_ADDR_ANY, 10001));
+	udp_recv(*pcb_b, lwip_test_udp_recv, &recv_b);
+
+	ut_assertok(lwip_test_inject_udp(client_a->dev, "one", host,
+					 20000, 10000));
+	ut_assertok(net_lwip_poll());
+	ut_assertok(lwip_test_inject_udp(client_a->dev, "two", host,
+					 20001, 10001));
+	ut_assertok(net_lwip_poll());
+	ut_asserteq(1, recv_a.packets);
+	ut_asserteq(1, recv_b.packets);
+	ut_asserteq(-EBUSY, recv_a.nested_poll_ret);
+	ut_asserteq(-EBUSY, recv_b.nested_poll_ret);
+
+	net_lwip_stop(client_a);
+	ut_assertnonnull(client_b->netif);
+	ut_assert(eth_is_active(client_b->dev));
+
+	return 0;
+}
+
+static int dm_test_lwip_runtime(struct unit_test_state *uts)
+{
+	struct net_lwip_ctx no_addr_client = {};
+	struct net_lwip_ctx client_a = {};
+	struct net_lwip_ctx client_b = {};
+	struct udp_pcb *pcb_a = NULL;
+	struct udp_pcb *pcb_b = NULL;
+	struct udevice *dev;
+	int ret;
+
+	ret = _dm_test_lwip_runtime(uts, &client_a, &client_b,
+				    &no_addr_client, &pcb_a, &pcb_b);
+
+	if (pcb_b)
+		udp_remove(pcb_b);
+	if (pcb_a)
+		udp_remove(pcb_a);
+	net_lwip_stop(&no_addr_client);
+	net_lwip_stop(&client_a);
+	dev = client_b.dev;
+	net_lwip_stop(&client_b);
+
+	if (!ret) {
+		ut_assert(!eth_is_active(dev));
+		ut_asserteq(-ENODEV, net_lwip_poll());
+	}
+
+	return ret;
+}
+
+DM_TEST(dm_test_lwip_runtime, UTF_SCAN_FDT);