casaroli opened a new pull request, #21412: URL: https://github.com/apache/nuttx/pull/21412
## Summary POSIX `send()`: a send on a connection-mode socket that is no longer connected fails with `EPIPE`, and the caller gets `SIGPIPE` unless `MSG_NOSIGNAL` is given. After the peer closed a TCP connection, the first send gets a reset, and every send after it failed with `ENOTCONN` in NuttX. So `psock_sigpipe()` raised no `SIGPIPE`, and a program that writes to a closed connection did not end. The TCP send paths (buffered send, unbuffered send and `tcp_sendfile()`, and the lost-connection branch of the event handlers of the last two) now return `-EPIPE` when the connection was closed by the peer (`_SF_CLOSED` set), and `-ENOTCONN` as before for a socket that never connected. A connection lost by a timeout or a network down event clears `_SF_CLOSED`, so it still gives `ENOTCONN`. `recv()` and `poll()` do not change: after the reset `recv()` returns 0 and `poll()` reports `POLLHUP`. The PR has five commits: - `net/tcp/tcp_sendfile.c`: a whitespace nxstyle fix, so that CI can check the whole file. - `fs/nfs`: `nfs_request()` connects to the server again when a request fails with `-ENOTCONN` (for example after the server closed an idle TCP connection). It now does this for `-EPIPE` too, so the reconnect continues to work with the next commit. - `net/tcp`: the `EPIPE` change. - `fs/vfs`: `sendfile()` to a socket through `psock_sendfile()` now raises `SIGPIPE` on `EPIPE`, as its slow path through `write()` already does. - `Documentation`: the `EPIPE` entry of `sendto()` in `11_network.rst` names the closed connection too. ## Impact All build modes with `CONFIG_NET_TCP` and the NuttX TCP stack. usrsock sockets do not change (they return what the usrsock daemon gives). This changes the behaviour of the flat build too: a send, `write()` or `sendfile()` on a TCP socket whose peer closed the connection now fails with `EPIPE` instead of `ENOTCONN`. A program that checks for `ENOTCONN` after a send must also check for `EPIPE`. In the tree, only `fs/nfs` did this, and this PR changes it. With `EPIPE`, `send()`, `sendto()`, `sendmsg()`, `write()` and now `sendfile()` raise `SIGPIPE` (unless `MSG_NOSIGNAL`). A program with a handler: the handler now runs. A program that ignores `SIGPIPE`: no change except the errno. A program without a handler: with `CONFIG_SIG_SIGPIPE_ACTION=y` it now ends, as the POSIX default action says; without that option (the default, no in-tree defconfig sets it) the signal is discarded and only the errno changes. The `psock_send*()` interfaces that the OS uses (telnet driver, NFS, 9P, VNC, OP-TEE, usrsock server) raise no signal; they only see the new errno. In-tree programs that write to a TCP connection without `MSG_NOSIGNAL` and without ignoring `SIGPIPE` end with `CONFIG_SIG_SIGPIPE_ACTION=y` when they write after the peer closed: for example `netutils/webclient` (wget), `netutils/ftpc`, `netutils/netcat`, `netutils/iperf` (TCP client), `netutils/smtp`, `system/telnet`, `system/gdbstub`. The servers in nuttx-apps (ftpd, webserver, thttpd, telnet chatd, fastboot, nxmodbus, dropbear, rexecd and the examples) already use `MSG_NOSIGNAL` or ignore `SIGPIPE`. ## Testing Base: master ed6f81a1be7 (all five configurations ran on ae2f4f9d87f, the first again on ed6f81a1be7; no change in `net/` or `fs/` between them). Test program `tcpepipe.c` (one TCP connection over loopback per case; a handler counts the `SIGPIPE` signals), QEMU with HVF, with `CONFIG_NET`, `CONFIG_NET_TCP`, `CONFIG_NET_IPv4`, `CONFIG_NET_LOOPBACK`, `CONFIG_NETDEV_LATEINIT`, `CONFIG_NET_SOCKOPTS`, `CONFIG_SIG_DEFAULT`, `CONFIG_SIG_SIGPIPE_ACTION`, `CONFIG_SCHED_WAITPID`, `CONFIG_SCHED_CHILD_STATUS`, `CONFIG_FS_TMPFS`, `CONFIG_IOB_NBUFFERS=96`, `CONFIG_NET_RECV_BUFSIZE=4096`, in five configurations: - qemu-armv8a:knsh (kernel build): unbuffered TCP with `CONFIG_NET_SENDFILE`; `CONFIG_NET_TCP_WRITE_BUFFERS` with `CONFIG_NET_SENDFILE`; `CONFIG_NET_TCP_WRITE_BUFFERS` without `CONFIG_NET_SENDFILE` (the `sendfile()` slow path). - qemu-armv8a:nsh (flat build, the test as a builtin app): unbuffered TCP and `CONFIG_NET_TCP_WRITE_BUFFERS`, both with `CONFIG_NET_SENDFILE`. The results are the same in all five configurations: | Case | master | this PR | |---|---|---| | `send()` on a socket that never connected | `ENOTCONN`, no signal: PASS | `ENOTCONN`, no signal: PASS | | `send()` after own `shutdown(SHUT_WR)` | `EPIPE`, 1 signal: PASS | `EPIPE`, 1 signal: PASS | | `send()` after the peer closed | `ENOTCONN`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | one more `send()` | `ENOTCONN`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | `send(MSG_NOSIGNAL)` | `ENOTCONN`: FAIL | `EPIPE`, no signal: PASS | | `write()` | `ENOTCONN`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | `sendmsg()` | `ENOTCONN`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | `sendfile()` from a tmpfs file | `ENOTCONN`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | `recv()` after the reset | 0: PASS | 0: PASS | | `poll()` after the reset | `POLLHUP` set (revents 0x19): PASS | the same: PASS | | a thread blocked in a 32 KiB `send()` when the peer closes | `ENOTCONN`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | a child without a handler (default action) sends after the peer closed | exits with status 0: FAIL | ends (wait status 0x100): PASS | ostest exits with status 0 in all five configurations and on qemu-armv8a:nsh with the same options, on master and with this PR. The NFS commit is checked by reading the code only: no configuration here has an NFS server. Build with `EXTRAFLAGS="-Wno-cpp -Werror"`, each OK: qemu-armv8a:netnsh (flat, arm64, with `CONFIG_NFS`, `CONFIG_NET_SENDFILE`, `CONFIG_SIG_SIGPIPE_ACTION`); rv-virt:netnsh64 (flat, rv64, `CONFIG_NFS`, `CONFIG_NET_SENDFILE`, `CONFIG_NET_TCP_WRITE_BUFFERS`); rv-virt:knetnsh64 (kernel, rv64, `CONFIG_NFS`, `CONFIG_NET_SENDFILE`); rv-virt:pnsh64 (protected, rv64) with the network options and `CONFIG_NET_SENDFILE`; zcu111:netnsh (arm64); spresense:rndis (arm, `CONFIG_NFS`); sama5d2-xult:netnsh (arm, `CONFIG_NET_SENDFILE`; the link is OK, only the final `mkimage` step is missing on the build host). qemu-armv8a:nsh with the network options, `CONFIG_NET_SENDFILE` and `CONFIG_DISABLE_ALL_SIGNALS`: the changed files compile without warnings; the link fails because `init_main` and `romfs_img` are not built in this configuration, not in the changed code. `tools/checkpatch.sh -c -u -m -g` passes. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
