[PATCH 09/10] test: dm: cover the shared lwIP runtime
James Hilliard <[email protected]>
| Newsgroups | org.u-boot-project.lists.u-boot |
|---|---|
| Message-ID | <20260825-submit-lwip-runtime-netconsole-v1-v1-9-0892966aa758@gmail.com> |
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 <[email protected]> --- test/dm/Makefile | 3 + test/dm/lwip.c | 353 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 356 insertions(+) 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..0d7ff22e644 --- /dev/null +++ b/test/dm/lwip.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright 2026 James Hilliard <[email protected]> */ + +#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)); + 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)); + ut_asserteq_ptr(client_a->dev, client_b->dev); + ut_asserteq_ptr(client_a->netif, client_b->netif); + + ut_assertok(env_set("ipaddr", "1.1.2.3")); + ut_assertok(net_lwip_refresh(client_a)); + ip4addr_aton("1.1.2.3", &expected); + ut_assert(ip4_addr_cmp(netif_ip4_addr(client_a->netif), &expected)); + + ut_assertok(net_lwip_start(no_addr_client, NET_LWIP_ADDR_NONE)); + ut_assert(ip4_addr_isany_val(*netif_ip4_addr(client_a->netif))); + ut_assertok(env_set("ipaddr", "1.1.2.4")); + ut_assertok(net_lwip_refresh(client_a)); + ut_assert(ip4_addr_isany_val(*netif_ip4_addr(client_a->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_a->netif), &expected)); +#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); -- 2.53.0