summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--arch/arm/mach-aspeed/ast2600/u-boot-spl.lds2
-rw-r--r--cmd/lwip/ping.c19
-rw-r--r--cmd/lwip/sntp.c10
-rw-r--r--doc/develop/release_cycle.rst2
-rw-r--r--drivers/net/airoha_eth.c78
-rw-r--r--include/net-lwip.h1
-rw-r--r--net/cdp.c8
-rw-r--r--net/lwip/dhcp.c15
-rw-r--r--net/lwip/dns.c7
-rw-r--r--net/lwip/net-lwip.c16
-rw-r--r--net/lwip/nfs.c4
-rw-r--r--net/lwip/tftp.c4
-rw-r--r--net/lwip/wget.c107
-rw-r--r--net/net.c9
-rw-r--r--test/dm/Makefile1
-rw-r--r--test/dm/net_defrag.c82
17 files changed, 272 insertions, 95 deletions
diff --git a/Makefile b/Makefile
index 46ecced8daa..503ac59e654 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@
VERSION = 2026
PATCHLEVEL = 07
SUBLEVEL =
-EXTRAVERSION = -rc4
+EXTRAVERSION = -rc5
NAME =
# *DOCUMENTATION*
diff --git a/arch/arm/mach-aspeed/ast2600/u-boot-spl.lds b/arch/arm/mach-aspeed/ast2600/u-boot-spl.lds
index 303ace2f61c..894eda1db77 100644
--- a/arch/arm/mach-aspeed/ast2600/u-boot-spl.lds
+++ b/arch/arm/mach-aspeed/ast2600/u-boot-spl.lds
@@ -42,6 +42,7 @@ SECTIONS
. = ALIGN(4);
__u_boot_list : {
KEEP(*(SORT(__u_boot_list*)));
+ . = ALIGN(8);
} > .nor
. = ALIGN(4);
@@ -49,7 +50,6 @@ SECTIONS
__binman_sym_start = .;
KEEP(*(SORT(.binman_sym*)));
__binman_sym_end = .;
- . = ALIGN(8);
} > .nor
/*
diff --git a/cmd/lwip/ping.c b/cmd/lwip/ping.c
index fc4cf7bde5f..98fa8e22bce 100644
--- a/cmd/lwip/ping.c
+++ b/cmd/lwip/ping.c
@@ -163,6 +163,7 @@ static int ping_loop(struct udevice *udev, const ip_addr_t *addr)
int do_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
ip_addr_t addr;
+ int ret;
if (argc < 2)
return CMD_RET_USAGE;
@@ -171,13 +172,15 @@ int do_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
return CMD_RET_USAGE;
net_try_count = 1;
-restart:
- if (net_lwip_eth_start() < 0 || ping_loop(eth_get_dev(), &addr) < 0) {
- if (net_start_again() == 0)
- goto restart;
- else
- return CMD_RET_FAILURE;
- }
- return CMD_RET_SUCCESS;
+ do {
+ if (net_lwip_eth_start() == 0) {
+ ret = ping_loop(eth_get_dev(), &addr);
+ net_lwip_eth_stop();
+ if (ret == 0)
+ return CMD_RET_SUCCESS;
+ }
+ } while (net_start_again() == 0);
+
+ return CMD_RET_FAILURE;
}
diff --git a/cmd/lwip/sntp.c b/cmd/lwip/sntp.c
index 608345c873b..5fa400b104a 100644
--- a/cmd/lwip/sntp.c
+++ b/cmd/lwip/sntp.c
@@ -101,6 +101,7 @@ int do_sntp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
ip_addr_t *srvip;
char *server;
ip_addr_t ipaddr;
+ int ret = CMD_RET_FAILURE;
switch (argc) {
case 1:
@@ -127,7 +128,12 @@ int do_sntp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
return CMD_RET_FAILURE;
if (sntp_loop(eth_get_dev(), srvip) < 0)
- return CMD_RET_FAILURE;
+ goto out;
+
+ ret = CMD_RET_SUCCESS;
+
+out:
+ net_lwip_eth_stop();
- return CMD_RET_SUCCESS;
+ return ret;
}
diff --git a/doc/develop/release_cycle.rst b/doc/develop/release_cycle.rst
index 1e3e179d77f..2f024905157 100644
--- a/doc/develop/release_cycle.rst
+++ b/doc/develop/release_cycle.rst
@@ -79,7 +79,7 @@ For the next scheduled release, release candidates were made on:
* U-Boot |next_ver|-rc4 was released on Mon 08 June 2026.
-.. * U-Boot |next_ver|-rc5 was released on Mon 22 June 2026.
+* U-Boot |next_ver|-rc5 was released on Mon 22 June 2026.
Please note that the following dates are planned only and may be deviated from
as needed.
diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c
index 84ee9b2ad76..e5d39b95cc5 100644
--- a/drivers/net/airoha_eth.c
+++ b/drivers/net/airoha_eth.c
@@ -845,11 +845,45 @@ static int airoha_alloc_gdm_port(struct udevice *dev, ofnode node)
(ulong)eth, node, &gdm_dev);
}
+static struct udevice *airoha_switch_mdio_init(struct udevice *dev)
+{
+ struct airoha_eth_soc_data *data = (void *)dev_get_driver_data(dev);
+ ofnode switch_node, mdio_node;
+ struct udevice *mdio_dev;
+ int ret;
+
+ if (!CONFIG_IS_ENABLED(MDIO_MT7531_MMIO))
+ return NULL;
+
+ switch_node = ofnode_by_compatible(ofnode_null(),
+ data->switch_compatible);
+ if (!ofnode_valid(switch_node)) {
+ debug("Warning: missing airoha switch node\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ mdio_node = ofnode_find_subnode(switch_node, "mdio");
+ if (!ofnode_valid(mdio_node)) {
+ debug("Warning: missing airoha switch mdio subnode\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ ret = device_bind_driver_to_node(dev, "mt7531-mdio-mmio", "mt7531-mdio",
+ mdio_node, &mdio_dev);
+ if (ret) {
+ debug("Warning: failed to bind airoha switch mdio\n");
+ return ERR_PTR(ret);
+ }
+
+ return mdio_dev;
+}
+
static int airoha_eth_probe(struct udevice *dev)
{
struct airoha_eth_soc_data *data = (void *)dev_get_driver_data(dev);
struct airoha_eth *eth = dev_get_priv(dev);
struct regmap *scu_regmap;
+ struct udevice *mdio_dev;
ofnode node;
int i, ret;
@@ -908,10 +942,10 @@ static int airoha_eth_probe(struct udevice *dev)
if (ret)
return ret;
- if (eth->switch_mdio_dev) {
- if (!device_probe(eth->switch_mdio_dev))
- debug("Warning: failed to probe airoha switch mdio\n");
- }
+ /* Airoha switch mdio PHYs maybe used by several GDM devices */
+ mdio_dev = airoha_switch_mdio_init(dev);
+ if (!IS_ERR_OR_NULL(mdio_dev))
+ eth->switch_mdio_dev = mdio_dev;
ofnode_for_each_subnode(node, dev_ofnode(dev)) {
if (!ofnode_device_is_compatible(node, "airoha,eth-mac"))
@@ -957,6 +991,16 @@ static int airoha_eth_port_probe(struct udevice *dev)
#else
return -EINVAL;
#endif
+ } else {
+ /*
+ * GDM1 device connected to airoha switch. Probe airoha switch
+ * mdio to be able set/query states of corresponding LAN ports.
+ */
+ ret = device_probe(eth->switch_mdio_dev);
+ if (ret) {
+ debug("Warning: failed to probe airoha switch mdio\n");
+ eth->switch_mdio_dev = NULL;
+ }
}
return 0;
@@ -1202,38 +1246,12 @@ static int arht_eth_write_hwaddr(struct udevice *dev)
static int airoha_eth_bind(struct udevice *dev)
{
- struct airoha_eth_soc_data *data = (void *)dev_get_driver_data(dev);
- struct airoha_eth *eth = dev_get_priv(dev);
- ofnode switch_node, mdio_node;
- int ret;
-
/*
* Force Probe as we set the Main ETH driver as misc
* to register multiple eth port for each GDM
*/
dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
- if (!CONFIG_IS_ENABLED(MDIO_MT7531_MMIO))
- return 0;
-
- switch_node = ofnode_by_compatible(ofnode_null(),
- data->switch_compatible);
- if (!ofnode_valid(switch_node)) {
- debug("Warning: missing switch node\n");
- return 0;
- }
-
- mdio_node = ofnode_find_subnode(switch_node, "mdio");
- if (!ofnode_valid(mdio_node)) {
- debug("Warning: missing mdio node\n");
- return 0;
- }
-
- ret = device_bind_driver_to_node(dev, "mt7531-mdio-mmio", "mt7531-mdio",
- mdio_node, &eth->switch_mdio_dev);
- if (ret)
- debug("Warning: failed to bind mdio controller\n");
-
return 0;
}
diff --git a/include/net-lwip.h b/include/net-lwip.h
index 20cb0992cce..5d0627eb271 100644
--- a/include/net-lwip.h
+++ b/include/net-lwip.h
@@ -35,6 +35,7 @@ int eth_init_state_only(void); /* Set active state */
int net_lwip_dns_init(void);
int net_lwip_eth_start(void);
+void net_lwip_eth_stop(void);
struct netif *net_lwip_new_netif(struct udevice *udev);
struct netif *net_lwip_new_netif_noip(struct udevice *udev);
void net_lwip_remove_netif(struct netif *netif);
diff --git a/net/cdp.c b/net/cdp.c
index 6e404981d4a..300b3d5c409 100644
--- a/net/cdp.c
+++ b/net/cdp.c
@@ -276,7 +276,13 @@ void cdp_receive(const uchar *pkt, unsigned len)
ss = (const ushort *)pkt;
type = ntohs(ss[0]);
tlen = ntohs(ss[1]);
- if (tlen > len)
+ /*
+ * tlen includes the 4-byte TLV header, so it must be at
+ * least 4. Without this check a crafted tlen < 4 makes the
+ * "tlen -= 4" below underflow (tlen is a ushort), and a tlen
+ * of 0 also fails to advance pkt/len, hanging the loop.
+ */
+ if (tlen < 4 || tlen > len)
goto pkt_short;
pkt += tlen;
diff --git a/net/lwip/dhcp.c b/net/lwip/dhcp.c
index acdf601d7eb..18dc36ae7ca 100644
--- a/net/lwip/dhcp.c
+++ b/net/lwip/dhcp.c
@@ -138,18 +138,25 @@ int do_dhcp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
dev = eth_get_dev();
if (!dev) {
log_err("No network device\n");
- return CMD_RET_FAILURE;
+ ret = CMD_RET_FAILURE;
+ goto out;
}
ret = dhcp_loop(dev);
if (ret)
- return ret;
+ goto out;
if (argc > 1) {
struct cmd_tbl cmdtp = {};
- return do_tftpb(&cmdtp, 0, argc, argv);
+ ret = do_tftpb(&cmdtp, 0, argc, argv);
+ goto out;
}
- return CMD_RET_SUCCESS;
+ ret = CMD_RET_SUCCESS;
+
+out:
+ net_lwip_eth_stop();
+
+ return ret;
}
diff --git a/net/lwip/dns.c b/net/lwip/dns.c
index 8b7b3b7f970..b620b0611d6 100644
--- a/net/lwip/dns.c
+++ b/net/lwip/dns.c
@@ -91,6 +91,7 @@ int do_dns(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
char *name;
char *var = NULL;
+ int ret;
if (argc == 1 || argc > 3)
return CMD_RET_USAGE;
@@ -103,5 +104,9 @@ int do_dns(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
if (net_lwip_eth_start() < 0)
return CMD_RET_FAILURE;
- return dns_loop(eth_get_dev(), name, var);
+ ret = dns_loop(eth_get_dev(), name, var);
+
+ net_lwip_eth_stop();
+
+ return ret;
}
diff --git a/net/lwip/net-lwip.c b/net/lwip/net-lwip.c
index 0c83c004cab..cfe5a6a640d 100644
--- a/net/lwip/net-lwip.c
+++ b/net/lwip/net-lwip.c
@@ -31,6 +31,7 @@ void (*push_packet)(void *, int len) = 0;
int net_try_count;
static int net_restarted;
int net_restart_wrap;
+static int net_lwip_eth_started;
static uchar net_pkt_buf[(PKTBUFSRX) * PKTSIZE_ALIGN + PKTALIGN]
__aligned(PKTALIGN);
const u8 net_bcast_ethaddr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
@@ -180,11 +181,15 @@ int net_lwip_eth_start(void)
{
int ret;
+ if (net_lwip_eth_started++ > 0)
+ return 0;
+
net_init();
eth_halt();
eth_set_current();
ret = eth_init();
if (ret < 0) {
+ net_lwip_eth_started--;
eth_halt();
return ret;
}
@@ -192,6 +197,17 @@ int net_lwip_eth_start(void)
return 0;
}
+void net_lwip_eth_stop(void)
+{
+ if (!net_lwip_eth_started)
+ return;
+
+ if (--net_lwip_eth_started)
+ return;
+
+ eth_halt();
+}
+
static struct netif *new_netif(struct udevice *udev, bool with_ip)
{
unsigned char enetaddr[ARP_HLEN];
diff --git a/net/lwip/nfs.c b/net/lwip/nfs.c
index 9e6b801e465..4cc36373fdd 100644
--- a/net/lwip/nfs.c
+++ b/net/lwip/nfs.c
@@ -187,6 +187,7 @@ static int nfs_loop(struct udevice *udev, ulong addr, char *fname,
int do_nfs(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
int ret = CMD_RET_SUCCESS;
+ bool started = false;
char *arg = NULL;
char *words[2] = { };
char *fname = NULL;
@@ -281,10 +282,13 @@ int do_nfs(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
ret = CMD_RET_FAILURE;
goto out;
}
+ started = true;
if (nfs_loop(eth_get_dev(), laddr, fname, srvip) < 0)
ret = CMD_RET_FAILURE;
out:
+ if (started)
+ net_lwip_eth_stop();
if (arg != net_boot_file_name)
free(arg);
return ret;
diff --git a/net/lwip/tftp.c b/net/lwip/tftp.c
index 7f3b28b8507..571c38172f9 100644
--- a/net/lwip/tftp.c
+++ b/net/lwip/tftp.c
@@ -261,6 +261,7 @@ static int tftp_loop(struct udevice *udev, ulong addr, char *fname,
int do_tftpb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
int ret = CMD_RET_SUCCESS;
+ bool started = false;
char *arg = NULL;
char *words[3] = { };
char *fname = NULL;
@@ -365,12 +366,15 @@ int do_tftpb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
ret = CMD_RET_FAILURE;
goto out;
}
+ started = true;
if (tftp_loop(eth_get_dev(), laddr, fname, srvip, port) < 0)
ret = CMD_RET_FAILURE;
else
image_load_addr = laddr;
out:
+ if (started)
+ net_lwip_eth_stop();
if (arg != net_boot_file_name)
free(arg);
return ret;
diff --git a/net/lwip/wget.c b/net/lwip/wget.c
index 502c0faebb2..247ece18e2b 100644
--- a/net/lwip/wget.c
+++ b/net/lwip/wget.c
@@ -292,46 +292,22 @@ static err_t httpc_headers_done_cb(httpc_state_t *connection, void *arg, struct
#if CONFIG_IS_ENABLED(WGET_CACERT)
#endif
-int wget_do_request(ulong dst_addr, char *uri)
+static int wget_handle_request(struct wget_ctx *ctx, bool is_https,
+ struct udevice *udev, struct netif *netif)
{
#if CONFIG_IS_ENABLED(WGET_HTTPS)
altcp_allocator_t tls_allocator;
#endif
httpc_connection_t conn;
httpc_state_t *state;
- struct udevice *udev;
- struct netif *netif;
- struct wget_ctx ctx;
- char *path;
- bool is_https;
-
- ctx.daddr = dst_addr;
- ctx.saved_daddr = dst_addr;
- ctx.done = NOT_DONE;
- ctx.size = 0;
- ctx.prevsize = 0;
- ctx.start_time = 0;
- ctx.content_len = 0;
- ctx.hash_count = 0;
-
- if (parse_url(uri, ctx.server_name, &ctx.port, &path, &is_https))
- return CMD_RET_USAGE;
-
- if (net_lwip_eth_start() < 0)
- return CMD_RET_FAILURE;
-
- if (!wget_info)
- wget_info = &default_wget_info;
-
- udev = eth_get_dev();
-
- netif = net_lwip_new_netif(udev);
- if (!netif)
- return -1;
+ int ret;
/* if URL with hostname init dns */
- if (!ipaddr_aton(ctx.server_name, NULL) && net_lwip_dns_init())
- return CMD_RET_FAILURE;
+ if (!ipaddr_aton(ctx->server_name, NULL)) {
+ ret = net_lwip_dns_init();
+ if (ret)
+ return ret;
+ }
memset(&conn, 0, sizeof(conn));
#if CONFIG_IS_ENABLED(WGET_HTTPS)
@@ -353,7 +329,7 @@ int wget_do_request(ulong dst_addr, char *uri)
printf("Error: cacert authentication "
"mode is 'required' but no CA "
"certificates given\n");
- return CMD_RET_FAILURE;
+ return -EINVAL;
}
} else if (cacert_auth_mode == AUTH_NONE) {
ca = NULL;
@@ -374,12 +350,11 @@ int wget_do_request(ulong dst_addr, char *uri)
tls_allocator.alloc = &altcp_tls_alloc;
tls_allocator.arg =
altcp_tls_create_config_client(ca, ca_sz,
- ctx.server_name);
+ ctx->server_name);
if (!tls_allocator.arg) {
log_err("error: Cannot create a TLS connection\n");
- net_lwip_remove_netif(netif);
- return -1;
+ return -ENODEV;
}
conn.altcp_allocator = &tls_allocator;
@@ -388,30 +363,70 @@ int wget_do_request(ulong dst_addr, char *uri)
conn.result_fn = httpc_result_cb;
conn.headers_done_fn = httpc_headers_done_cb;
- ctx.path = path;
- if (httpc_get_file_dns(ctx.server_name, ctx.port, path, &conn, httpc_recv_cb,
- &ctx, &state)) {
- net_lwip_remove_netif(netif);
- return CMD_RET_FAILURE;
+ if (httpc_get_file_dns(ctx->server_name, ctx->port, ctx->path, &conn,
+ httpc_recv_cb, ctx, &state)) {
+ return -ENODEV;
}
errno = 0;
- while (!ctx.done) {
+ while (!ctx->done) {
net_lwip_rx(udev, netif);
if (ctrlc())
break;
}
- net_lwip_remove_netif(netif);
-
- if (ctx.done == SUCCESS)
+ if (ctx->done == SUCCESS)
return 0;
if (errno == EPERM && !wget_info->silent)
printf("Certificate verification failed\n");
- return -1;
+ return -errno ?: -EIO;
+}
+
+int wget_do_request(ulong dst_addr, char *uri)
+{
+ struct udevice *udev;
+ struct wget_ctx ctx;
+ struct netif *netif;
+ bool is_https;
+ int ret;
+
+ ctx.daddr = dst_addr;
+ ctx.saved_daddr = dst_addr;
+ ctx.done = NOT_DONE;
+ ctx.size = 0;
+ ctx.prevsize = 0;
+ ctx.start_time = 0;
+ ctx.content_len = 0;
+ ctx.hash_count = 0;
+
+ ret = parse_url(uri, ctx.server_name, &ctx.port, &ctx.path, &is_https);
+ if (ret)
+ return ret;
+
+ ret = net_lwip_eth_start();
+ if (ret)
+ return ret;
+
+ if (!wget_info)
+ wget_info = &default_wget_info;
+
+ udev = eth_get_dev();
+
+ netif = net_lwip_new_netif(udev);
+ if (!netif) {
+ net_lwip_eth_stop();
+ return -ENODEV;
+ }
+
+ ret = wget_handle_request(&ctx, is_https, udev, netif);
+
+ net_lwip_remove_netif(netif);
+ net_lwip_eth_stop();
+
+ return ret;
}
/**
diff --git a/net/net.c b/net/net.c
index ae3b977781f..61c5a6ef6c4 100644
--- a/net/net.c
+++ b/net/net.c
@@ -1103,6 +1103,15 @@ static struct ip_udp_hdr *__net_defragment(struct ip_udp_hdr *ip, int *lenp)
*lenp = total_len + IP_HDR_SIZE;
localip->ip_len = htons(*lenp);
+
+ /*
+ * Mark the reassembly state empty so that any further
+ * fragment goes through the normal re-init path and
+ * rebuilds a clean hole list
+ */
+ total_len = 0;
+ first_hole = 0;
+
return localip;
}
diff --git a/test/dm/Makefile b/test/dm/Makefile
index d69b0e08d66..0e3c63568dd 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_MULTIPLEXER) += mux-emul.o
obj-$(CONFIG_MUX_MMIO) += mux-mmio.o
obj-y += fdtdec.o
obj-$(CONFIG_MTD_RAW_NAND) += nand.o
+obj-$(CONFIG_IP_DEFRAG) += net_defrag.o
obj-$(CONFIG_UT_DM) += nop.o
obj-y += ofnode.o
obj-y += ofread.o
diff --git a/test/dm/net_defrag.c b/test/dm/net_defrag.c
new file mode 100644
index 00000000000..3fd40de90cd
--- /dev/null
+++ b/test/dm/net_defrag.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Regression test for IP fragment reassembly.
+ *
+ * The test drives the real RX path via net_process_received_packet(). Final IP
+ * fragment (MF=0) is duplicated, crafted payload triggers redelivery of the datagram,
+ * which fails the test for the unfixed code.
+ */
+
+#include <net.h>
+#include <string.h>
+#include <test/ut.h>
+#include <dm/test.h>
+
+#define FRAG_LEN (8)
+#define PAYLOAD_OFFSET (ETHER_HDR_SIZE + IP_HDR_SIZE)
+#define FRAME_LEN (PAYLOAD_OFFSET + FRAG_LEN)
+
+static int udp_rx_count;
+
+static void defrag_udp_handler(uchar *pkt, unsigned int dport,
+ struct in_addr sip, unsigned int sport,
+ unsigned int len)
+{
+ udp_rx_count++;
+}
+
+static int build_frag(uchar *buf, u16 off_flags, const u16 *payload)
+{
+ struct ethernet_hdr *et = (struct ethernet_hdr *)buf;
+ struct ip_udp_hdr *ip = (struct ip_udp_hdr *)(buf + ETHER_HDR_SIZE);
+
+ memset(buf, 0, FRAME_LEN);
+ et->et_protlen = htons(PROT_IP);
+
+ ip->ip_hl_v = 0x45;
+ ip->ip_len = htons(IP_HDR_SIZE + FRAG_LEN);
+ ip->ip_id = htons(0x4321);
+ ip->ip_off = htons(off_flags);
+ ip->ip_ttl = 64;
+ ip->ip_p = IPPROTO_UDP;
+ /* Broadcast destination is accepted regardless of net_ip. */
+ ip->ip_dst.s_addr = 0xffffffff;
+ ip->ip_sum = compute_ip_checksum(ip, IP_HDR_SIZE);
+
+ memcpy(buf + PAYLOAD_OFFSET, payload, FRAG_LEN);
+
+ return FRAME_LEN;
+}
+
+static int dm_test_net_ip_defrag_dup_last(struct unit_test_state *uts)
+{
+ rxhand_f *saved_handler = net_get_udp_handler();
+ uchar frame[FRAME_LEN];
+ /* UDP header, carried by first fragment. */
+ u16 udp_hdr[4] = { htons(5000), htons(5001),
+ htons(UDP_HDR_SIZE + FRAG_LEN), 0 };
+ /*
+ * Second fragment's payload doubles as a fake hole
+ * {last_byte >= FRAG_LEN, next_hole = 0, prev_hole = 0}, so that the
+ * buggy code re-reading it on a duplicate re-delivers the datagram.
+ */
+ u16 frag_b[4] = { 2 * FRAG_LEN, 0, 0, 0 };
+
+ udp_rx_count = 0;
+ net_set_udp_handler(defrag_udp_handler);
+
+ /* UDP header, offset 0, MF=1; then data, offset 1, MF=0 */
+ net_process_received_packet(frame, build_frag(frame, IP_FLAGS_MFRAG, udp_hdr));
+ net_process_received_packet(frame, build_frag(frame, 1, frag_b));
+ ut_asserteq(1, udp_rx_count);
+
+ /* Duplicate the final fragment: UDP datagram must not be delivered again. */
+ net_process_received_packet(frame, build_frag(frame, 1, frag_b));
+ ut_asserteq(1, udp_rx_count);
+
+ net_set_udp_handler(saved_handler);
+
+ return 0;
+}
+
+DM_TEST(dm_test_net_ip_defrag_dup_last, 0);