This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 9b2e7c7e9a46b6a94065516e4171e4264fbde683
Author: yezhonghui <[email protected]>
AuthorDate: Wed Nov 4 10:15:13 2026 +0800

    net/local: add connect support for DGRAM sockets
    
    POSIX allows connect() on DGRAM sockets to set the default peer address
    for subsequent send() calls.  The NuttX local socket implementation
    returned ENOSYS for DGRAM connect.  Add support by storing the peer path
    in a new lc_daddr field and implementing send() for connected DGRAM
    sockets via implicit sendto() using the stored address.  Also support
    AF_UNSPEC to dissolve the association.
    
    The default peer is recorded in a flag of its own, lc_dconnected, rather
    than in lc_state, so that bind() and connect() in either order do not
    overwrite each other, a bound socket stays under the address in-use
    check, and local_sendto() keeps rejecting the sockets that hold a real
    peer link.  A socketpair() is LOCAL_STATE_CONNECTED with its outgoing
    FIFO already open, where the DEBUGASSERT in local_sendto() would fire.
    
    local_connect() rejected anything whose sa_family was not AF_LOCAL, so
    AF_UNSPEC has to be let through the entry check; SOCK_STREAM rejects it
    explicitly because a connection-mode socket cannot dissolve its
    association.  The peer address is copied with the caller's addrlen rather
    than the full struct sockaddr_un, so that a caller supplying only
    sa_family does not have memory past its buffer read.  An address that
    carries no name is rejected with -EINVAL, as psock_local_bind() does:
    local_findconn() reads sun_path[0] and compares the name past a leading
    NUL, so an empty name would match any unbound socket, the caller's own
    included.
    
    connect() also validates the destination rather than leaving a bad name
    to be reported by the next send(): it returns -ENOENT when no socket has
    that name, or -ECONNREFUSED when the pathname exists in the file system
    but is not one of our sockets.  That lookup uses nx_stat() rather than
    stat(), which is the user-space entry point and would set the caller's
    errno, and it is skipped for an abstract name, which has no file system
    entry.  send() on a connected socket maps a late -ENOENT to
    -ECONNREFUSED, since connect() already established that the peer existed.
    
    Because connect() only records an address and does not link to a peer
    connection, lc_peer stays NULL.  local_getpeername() checked only
    lc_state before reading peer->lc_type, which was a NULL dereference for a
    connected datagram socket; it now reports lc_daddr for a datagram socket
    with no peer link, chosen by socket type because a stream socket whose
    peer has closed also has lc_peer == NULL and must keep returning
    ENOTCONN.  And per POSIX the peer address limits which sender a connected
    receiver accepts, so local_sendto() returns -EPERM when the receiver has
    connected to somebody else, as Linux does.
    
    local_sendto() built the FIFO name from unaddr->sun_path even for an
    abstract address, whose first byte is NUL, so a datagram sent to an
    abstract name went to the FIFO of the empty name and the receiver got
    nothing.  Skip the leading NUL as local_findconn() does.
    
    Finally, restore the lc_sendlock around local_send_packet() on the stream
    path: moving local_send() below local_sendto() dropped it, which would
    have left two threads sending on one stream socket unserialized.
    
    Signed-off-by: yezhonghui <[email protected]>
    Signed-off-by: zhanghongyu <[email protected]>
---
 net/local/local.h         |   8 ++
 net/local/local_bind.c    |   6 ++
 net/local/local_sendmsg.c | 221 +++++++++++++++++++++++++++-------------------
 net/local/local_sockif.c  | 119 +++++++++++++++++++++++--
 4 files changed, 257 insertions(+), 97 deletions(-)

diff --git a/net/local/local.h b/net/local/local.h
index fb273069c08..e1549c2118b 100644
--- a/net/local/local.h
+++ b/net/local/local.h
@@ -144,6 +144,14 @@ struct local_conn_s
   mutex_t lc_recvlock;           /* Make receiving multi-thread safe */
   mutex_t lc_polllock;           /* Lock for net poll */
 
+#ifdef CONFIG_NET_LOCAL_DGRAM
+  struct sockaddr_un lc_daddr;   /* DGRAM: peer address set by connect() */
+  bool lc_dconnected;            /* DGRAM: lc_daddr is valid.  Kept apart from
+                                  * lc_state so that bind() and connect()
+                                  * do not overwrite each other.
+                                  */
+#endif
+
 #ifdef CONFIG_NET_LOCAL_STREAM
   /* SOCK_STREAM fields common to both client and server */
 
diff --git a/net/local/local_bind.c b/net/local/local_bind.c
index f9bb988f36f..a66e1b56de3 100644
--- a/net/local/local_bind.c
+++ b/net/local/local_bind.c
@@ -132,7 +132,13 @@ int psock_local_bind(FAR struct socket *psock,
     }
 #endif
 
+  /* A DGRAM connect() records its default peer in lc_dconnected rather than
+   * in lc_state, so binding after connect() keeps the peer and the address
+   * stays protected by the in-use check above.
+   */
+
   conn->lc_state = LOCAL_STATE_BOUND;
+
   return OK;
 
 #ifdef CONFIG_NET_LOCAL_DGRAM
diff --git a/net/local/local_sendmsg.c b/net/local/local_sendmsg.c
index 33c488377f3..73e4b2f8556 100644
--- a/net/local/local_sendmsg.c
+++ b/net/local/local_sendmsg.c
@@ -134,91 +134,6 @@ fail:
 }
 #endif /* CONFIG_NET_LOCAL_SCM */
 
-/****************************************************************************
- * Name: local_send
- *
- * Description:
- *   Send a local packet as a stream.
- *
- * Input Parameters:
- *   psock    An instance of the internal socket structure.
- *   buf      Data to send
- *   len      Length of data to send
- *   flags    Send flags (ignored for now)
- *
- * Returned Value:
- *   On success, returns the number of characters sent.  On  error,
- *   -1 is returned, and errno is set appropriately (see send() for the
- *   list of errno numbers).
- *
- ****************************************************************************/
-
-static ssize_t local_send(FAR struct socket *psock,
-                          FAR const struct iovec *buf,
-                          size_t len, int flags)
-{
-  ssize_t ret;
-
-  switch (psock->s_type)
-    {
-#ifdef CONFIG_NET_LOCAL_STREAM
-      case SOCK_STREAM:
-#endif /* CONFIG_NET_LOCAL_STREAM */
-#ifdef CONFIG_NET_LOCAL_DGRAM
-      case SOCK_DGRAM:
-#endif /* CONFIG_NET_LOCAL_DGRAM */
-        {
-          FAR struct local_conn_s *conn = psock->s_conn;
-
-          /* Local TCP packet send */
-
-          DEBUGASSERT(buf);
-
-          /* Verify that this is a connected peer socket and that it has
-           * opened the outgoing FIFO for write-only access.
-           */
-
-          if (conn->lc_state != LOCAL_STATE_CONNECTED)
-            {
-              nerr("ERROR: not connected\n");
-              return -ENOTCONN;
-            }
-
-          /* Check shutdown state */
-
-          if (conn->lc_outfile.f_inode == NULL)
-            {
-              return -EPIPE;
-            }
-
-          /* Send the packet */
-
-          ret = nxmutex_lock(&conn->lc_sendlock);
-          if (ret < 0)
-            {
-              /* May fail because the task was canceled. */
-
-              return ret;
-            }
-
-          ret = local_send_packet(&conn->lc_outfile, buf, len);
-          nxmutex_unlock(&conn->lc_sendlock);
-        }
-        break;
-      default:
-        {
-          /* EDESTADDRREQ.  Signifies that the socket is not connection-mode
-           * and no peer address is set.
-           */
-
-          ret = -EDESTADDRREQ;
-        }
-        break;
-    }
-
-  return ret;
-}
-
 /****************************************************************************
  * Name: local_sendto
  *
@@ -254,6 +169,7 @@ static ssize_t local_sendto(FAR struct socket *psock,
   FAR struct local_conn_s *conn = psock->s_conn;
   FAR struct local_conn_s *server;
   FAR const struct sockaddr_un *unaddr = (FAR const struct sockaddr_un *)to;
+  FAR const char *path;
   ssize_t ret;
 
   /* Verify that a valid address has been provided */
@@ -286,7 +202,10 @@ static ssize_t local_sendto(FAR struct socket *psock,
 
   /* Verify that this is not a connected peer socket.  It need not be
    * bound, however.  If unbound, recvfrom will see this as a nameless
-   * connection.
+   * connection.  A DGRAM default peer set by connect() is not a state of
+   * its own, so it never reaches this check.  A socketpair() is
+   * LOCAL_STATE_CONNECTED with its outgoing FIFO already open, which the
+   * DEBUGASSERT below would catch, so refuse it here as master does.
    */
 
   if (conn->lc_state != LOCAL_STATE_UNBOUND &&
@@ -308,6 +227,23 @@ static ssize_t local_sendto(FAR struct socket *psock,
       return -ENOENT;
     }
 
+#ifdef CONFIG_NET_LOCAL_DGRAM
+  /* POSIX: for a datagram socket the peer address set by connect() "limits
+   * the remote sender for subsequent recv() functions".  Refuse to deliver
+   * to a receiver that has connected to somebody else.
+   */
+
+  if (server->lc_dconnected &&
+      strncmp(server->lc_daddr.sun_path +
+              (server->lc_daddr.sun_path[0] == '\0' ? 1 : 0),
+              conn->lc_path, UNIX_PATH_MAX - 1) != 0)
+    {
+      local_unlock();
+      nerr("ERROR: Receiver is connected to another peer\n");
+      return -EPERM;
+    }
+#endif
+
   local_unlock();
 
   /* Make sure that dgram is sent safely */
@@ -325,27 +261,32 @@ static ssize_t local_sendto(FAR struct socket *psock,
 
   DEBUGASSERT(conn->lc_outfile.f_inode == NULL);
 
+  /* An abstract address starts with a NUL, which local_findconn() skips.
+   * Skip it here too so that the FIFO name matches the one the receiver
+   * bound, instead of being the empty name.
+   */
+
+  path = &unaddr->sun_path[unaddr->sun_path[0] == '\0' ? 1 : 0];
+
   /* Make sure that half duplex FIFO has been created.
    * REVISIT:  Or should be just make sure that it already exists?
    */
 
-  ret = local_create_halfduplex(conn, unaddr->sun_path, server->lc_rcvsize);
+  ret = local_create_halfduplex(conn, path, server->lc_rcvsize);
   if (ret < 0)
     {
-      nerr("ERROR: Failed to create FIFO for %s: %zd\n",
-           unaddr->sun_path, ret);
+      nerr("ERROR: Failed to create FIFO for %s: %zd\n", path, ret);
       goto errout_with_lock;
     }
 
   /* Open the sending side of the transfer */
 
-  ret = local_open_sender(conn, unaddr->sun_path,
+  ret = local_open_sender(conn, path,
                           _SS_ISNONBLOCK(conn->lc_conn.s_flags) ||
                           (flags & MSG_DONTWAIT) != 0);
   if (ret < 0)
     {
-      nerr("ERROR: Failed to open FIFO for %s: %zd\n",
-           unaddr->sun_path, ret);
+      nerr("ERROR: Failed to open FIFO for %s: %zd\n", path, ret);
 
       goto errout_with_halfduplex;
     }
@@ -393,6 +334,102 @@ errout_with_lock:
 #endif /* CONFIG_NET_LOCAL_DGRAM */
 }
 
+/****************************************************************************
+ * Name: local_send
+ *
+ * Description:
+ *   Send a local packet as a stream.
+ *
+ * Input Parameters:
+ *   psock    An instance of the internal socket structure.
+ *   buf      Data to send
+ *   len      Length of data to send
+ *   flags    Send flags (ignored for now)
+ *
+ * Returned Value:
+ *   On success, returns the number of characters sent.  On  error,
+ *   -1 is returned, and errno is set appropriately (see send() for the
+ *   list of errno numbers).
+ *
+ ****************************************************************************/
+
+static ssize_t local_send(FAR struct socket *psock,
+                          FAR const struct iovec *buf,
+                          size_t len, int flags)
+{
+  ssize_t ret;
+
+  switch (psock->s_type)
+    {
+      case SOCK_STREAM:
+        {
+          FAR struct local_conn_s *conn = psock->s_conn;
+
+          DEBUGASSERT(buf);
+
+          if (conn->lc_state != LOCAL_STATE_CONNECTED)
+            {
+              nerr("ERROR: not connected\n");
+              return -ENOTCONN;
+            }
+
+          if (conn->lc_outfile.f_inode == NULL)
+            {
+              return -EPIPE;
+            }
+
+          /* Serialize concurrent senders on the same socket */
+
+          ret = nxmutex_lock(&conn->lc_sendlock);
+          if (ret < 0)
+            {
+              /* May fail because the task was canceled. */
+
+              return ret;
+            }
+
+          ret = local_send_packet(&conn->lc_outfile, buf, len);
+          nxmutex_unlock(&conn->lc_sendlock);
+        }
+        break;
+
+#ifdef CONFIG_NET_LOCAL_DGRAM
+      case SOCK_DGRAM:
+        {
+          FAR struct local_conn_s *conn = psock->s_conn;
+
+          if (!conn->lc_dconnected)
+            {
+              return -EDESTADDRREQ;
+            }
+
+          ret = local_sendto(psock, buf, len, flags,
+                  (FAR const struct sockaddr *)&conn->lc_daddr,
+                  sizeof(conn->lc_daddr));
+
+          /* connect() checked that the peer existed, so a peer that has
+           * gone away since is a refused connection rather than a name
+           * that was never there.
+           */
+
+          if (ret == -ENOENT)
+            {
+              ret = -ECONNREFUSED;
+            }
+        }
+        break;
+#endif /* CONFIG_NET_LOCAL_DGRAM */
+
+      default:
+        {
+          ret = -EDESTADDRREQ;
+        }
+        break;
+    }
+
+  return ret;
+}
+
 /****************************************************************************
  * Public Functions
  ****************************************************************************/
diff --git a/net/local/local_sockif.c b/net/local/local_sockif.c
index 302261d5abb..a33d8b75597 100644
--- a/net/local/local_sockif.c
+++ b/net/local/local_sockif.c
@@ -28,6 +28,7 @@
 
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <string.h>
@@ -39,6 +40,7 @@
 #include <netinet/in.h>
 #include <sys/param.h>
 
+#include <nuttx/fs/fs.h>
 #include <nuttx/fs/ioctl.h>
 #include <nuttx/net/net.h>
 #include <socket/socket.h>
@@ -449,9 +451,38 @@ static int local_getpeername(FAR struct socket *psock,
       return OK;
     }
 
+#ifdef CONFIG_NET_LOCAL_DGRAM
+  /* connect() on a datagram socket only records a default peer address; it
+   * does not link to a peer connection, so lc_peer is NULL.  Report the
+   * recorded address instead.  Decide by socket type: a stream socket whose
+   * peer has closed also has lc_peer == NULL, and must still get ENOTCONN.
+   */
+
+  if (psock->s_type == SOCK_DGRAM && peer == NULL)
+    {
+      socklen_t copylen;
+
+      if (!conn->lc_dconnected)
+        {
+          return -ENOTCONN;
+        }
+
+      copylen = sizeof(conn->lc_daddr);
+      if (copylen > *addrlen)
+        {
+          copylen = *addrlen;
+        }
+
+      memcpy(unaddr, &conn->lc_daddr, copylen);
+      unaddr->sun_family = AF_LOCAL;
+      *addrlen = copylen;
+      return OK;
+    }
+#endif
+
   /* Verify that the socket has been connected */
 
-  if (conn->lc_state != LOCAL_STATE_CONNECTED)
+  if (conn->lc_state != LOCAL_STATE_CONNECTED || peer == NULL)
     {
       return -ENOTCONN;
     }
@@ -789,9 +820,12 @@ static int local_setsockopt(FAR struct socket *psock, int 
level, int option,
 static int local_connect(FAR struct socket *psock,
                          FAR const struct sockaddr *addr, socklen_t addrlen)
 {
-  /* Verify that a valid address has been provided */
+  /* Verify that a valid address has been provided.  A connectionless
+   * socket may also pass AF_UNSPEC to dissolve an existing association.
+   */
 
-  if (addr->sa_family != AF_LOCAL || addrlen < sizeof(sa_family_t))
+  if ((addr->sa_family != AF_LOCAL && addr->sa_family != AF_UNSPEC) ||
+      addrlen < sizeof(sa_family_t))
     {
       return -EBADF;
     }
@@ -805,6 +839,13 @@ static int local_connect(FAR struct socket *psock,
         {
           FAR struct socket_conn_s *conn = psock->s_conn;
 
+          /* A connection-mode socket cannot dissolve its association. */
+
+          if (addr->sa_family == AF_UNSPEC)
+            {
+              return -EINVAL;
+            }
+
           /* Verify that the socket is not already connected */
 
           if (_SS_ISCONNECTED(conn->s_flags))
@@ -822,9 +863,77 @@ static int local_connect(FAR struct socket *psock,
 #ifdef CONFIG_NET_LOCAL_DGRAM
       case SOCK_DGRAM:
         {
-          /* Perform the datagram connection logic */
+          FAR struct local_conn_s *conn = psock->s_conn;
+          FAR const struct sockaddr_un *unaddr =
+              (FAR const struct sockaddr_un *)addr;
+
+          if (addr->sa_family == AF_UNSPEC)
+            {
+              memset(&conn->lc_daddr, 0, sizeof(conn->lc_daddr));
+              conn->lc_dconnected = false;
+              return OK;
+            }
 
-          return -ENOSYS;
+          /* local_findconn() reads sun_path[0], so the caller must have
+           * supplied at least one byte of it, and it compares the name
+           * past a leading NUL:  an empty name would match any unbound
+           * socket, this one included.  psock_local_bind() rejects the
+           * same addresses.
+           */
+
+          if (addrlen <= sizeof(sa_family_t) + 1 ||
+              unaddr->sun_path[unaddr->sun_path[0] == '\0' ? 1 : 0] == '\0')
+            {
+              return -EINVAL;
+            }
+
+          /* Validate the destination before recording it, so that
+           * connect() reports a bad name instead of letting every later
+           * send() fail.  POSIX allows a connectionless connect() to
+           * check the address.
+           */
+
+          if (conn->lc_peer == NULL)
+            {
+              FAR struct local_conn_s *server;
+
+              local_lock();
+              server = local_findconn(conn, unaddr);
+              local_unlock();
+
+              if (server == NULL)
+                {
+                  struct stat buf;
+
+                  /* No socket of that name.  A pathname that exists in the
+                   * file system but is not one of our sockets is a
+                   * connection refused, anything else does not exist.  An
+                   * abstract name has no file system entry to probe.
+                   */
+
+                  if (unaddr->sun_path[0] != '\0' &&
+                      nx_stat(unaddr->sun_path, &buf, 1) == 0)
+                    {
+                      return -ECONNREFUSED;
+                    }
+
+                  return -ENOENT;
+                }
+            }
+
+          /* Record the default peer.  Copy only what the caller supplied
+           * so that a short addrlen does not read past its buffer.
+           */
+
+          if (addrlen > sizeof(conn->lc_daddr))
+            {
+              addrlen = sizeof(conn->lc_daddr);
+            }
+
+          memset(&conn->lc_daddr, 0, sizeof(conn->lc_daddr));
+          memcpy(&conn->lc_daddr, unaddr, addrlen);
+          conn->lc_dconnected = true;
+          return OK;
         }
         break;
 #endif /* CONFIG_NET_LOCAL_DGRAM */

Reply via email to