From 8cad2e708a9b4492fb731a1e10f52e7e8e831a8b Mon Sep 17 00:00:00 2001 From: Daryl Borth Date: Sun, 18 Oct 2026 18:20:05 +0000 Subject: [PATCH] network_wii: split large send/recv to fit the IOS net heap IOS copies the whole payload of net_send()/net_recv() through the 64 KB net heap, so a single call with a large buffer fails with IPC_ENOMEM and callers such as libsmb2 (directory listings, large SMB2 replies) lose the connection. - net_sendto(): when there is no explicit destination, send in blocks of at most NET_MAX_TRANSFER (half the heap, so a concurrent user can still allocate). A short send, or an error after some blocks were already accepted, returns the byte count so far instead of the error, so the caller never resends data that IOS already has. Datagrams with an explicit destination are never split. - net_recvfrom(): with no source address, limit a single call to NET_MAX_TRANSFER and return the short read. Do not loop for more: that would block a blocking socket waiting for data that may never come, and on a non-blocking socket a later EAGAIN would discard data already read. - Parenthesize NET_HEAP_SIZE. Based on devkitPro/libogc#178 by DacoTaco, reworked for the mspace-based net heap and to fix the partial-transfer cases above. Co-authored-by: DacoTaco --- libogc/network_wii.c | 71 +++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/libogc/network_wii.c b/libogc/network_wii.c index 4dfa8140..49569fd6 100644 --- a/libogc/network_wii.c +++ b/libogc/network_wii.c @@ -59,7 +59,12 @@ distribution. #include "network.h" #include "ogcsys.h" -#define NET_HEAP_SIZE 64*1024 +#define NET_HEAP_SIZE (64*1024) + +/* IOS copies the whole payload of a send/recv through the net heap, so a + * single call can only move a fraction of it (half, so a second thread can + * still allocate). Stream transfers larger than this are done in blocks. */ +#define NET_MAX_TRANSFER (NET_HEAP_SIZE / 2) #define IOS_O_NONBLOCK 0x04 //(O_NONBLOCK >> 16) - it's in octal representation, so this shift leads to 0 and hence nonblocking sockets didn't work. changed it to the right value. @@ -882,41 +887,65 @@ s32 net_send(s32 s, const void *data, size_t size, u32 flags) s32 net_sendto(s32 s, const void *data, size_t len, u32 flags, struct sockaddr *to, socklen_t tolen) { s32 ret; + size_t done = 0; + size_t blocksize; u8 *message_buf = NULL; STACK_ALIGN(struct sendto_params,params,1,32); if (net_ip_top_fd < 0) return -ENXIO; if (tolen > 28) return -EOVERFLOW; - message_buf = net_malloc(len); + // A datagram with an explicit destination must go out in one piece. + // Everything else is stream-like and may be sent in blocks. + blocksize = (to || len <= NET_MAX_TRANSFER) ? len : NET_MAX_TRANSFER; + + message_buf = net_malloc(blocksize); if (message_buf == NULL) { - debug_printf("net_send: failed to alloc %d bytes\n", len); + debug_printf("net_send: failed to alloc %d bytes\n", blocksize); return IPC_ENOMEM; } - debug_printf("net_sendto(%d, %p, %d, %d, %p, %d)\n", s, data, len, flags, to, tolen); - if (to && to->sa_len != tolen) { debug_printf("warning: to->sa_len was %d, setting to %d\n", to->sa_len, tolen); to->sa_len = tolen; } - memset(params, 0, sizeof(struct sendto_params)); - memcpy(message_buf, data, len); // ensure message buf is aligned + do { + size_t chunk = len - done; + if (chunk > blocksize) chunk = blocksize; - params->socket = s; - params->flags = flags; - if (to) { - params->has_destaddr = 1; - memcpy(params->destaddr, to, to->sa_len); - } else { - params->has_destaddr = 0; - } + debug_printf("net_sendto(%d, %p, %d, %d, %p, %d)\n", s, (const u8 *)data + done, chunk, flags, to, tolen); - ret = _net_convert_error(IOS_IoctlvFormat(__net_hid, net_ip_top_fd, IOCTLV_SO_SENDTO, "dd:", message_buf, len, params, sizeof(struct sendto_params))); - debug_printf("net_send retuned %d\n", ret); + memset(params, 0, sizeof(struct sendto_params)); + memcpy(message_buf, (const u8 *)data + done, chunk); // ensure message buf is aligned - if(message_buf!=NULL) net_free(message_buf); + params->socket = s; + params->flags = flags; + if (to) { + params->has_destaddr = 1; + memcpy(params->destaddr, to, to->sa_len); + } else { + params->has_destaddr = 0; + } + + ret = _net_convert_error(IOS_IoctlvFormat(__net_hid, net_ip_top_fd, IOCTLV_SO_SENDTO, "dd:", message_buf, chunk, params, sizeof(struct sendto_params))); + debug_printf("net_send retuned %d\n", ret); + + // error, or a short send: report what we have and let the caller retry + if (ret <= 0 || (size_t)ret < chunk) { + if (ret > 0) done += ret; + break; + } + + done += ret; + } while (done < len); + + // Bytes that were already handed to IOS must be reported, even if the + // following block failed (e.g. EAGAIN on a non-blocking socket); + // otherwise the caller would resend them and corrupt the stream. + if (done > 0) ret = done; + + net_free(message_buf); return ret; } @@ -939,6 +968,12 @@ s32 net_recvfrom(s32 s, void *mem, size_t len, u32 flags, struct sockaddr *from, from->sa_len = *fromlen; } + // A short read is always valid for a stream, so limit a single call to + // what the net heap can carry. Do not loop for more: on a blocking socket + // that would wait for data that may never come, and on a non-blocking one + // a later EAGAIN would discard the data already read. + if (!from && len > NET_MAX_TRANSFER) len = NET_MAX_TRANSFER; + message_buf = net_malloc(len); if (message_buf == NULL) { debug_printf("net_recv: failed to alloc %d bytes\n", len);