[PATCH] server: Use SO_BINDTODEVICE in bind_to_index() if possible.

Paul Gofman pgofman at codeweavers.com
Tue Oct 12 11:55:51 CDT 2021


Signed-off-by: Paul Gofman <pgofman at codeweavers.com>
---
    Using SO_REUSEADDR is currently problematic when more than one UDP socket is bound
    to different interfaces. While that succeeds, it is unspecified which socket will
    receive a packet coming to INADDR_ANY. The filter being installed for each socket
    with SO_ATTACH_FILTER can only reject the packet coming for another interface but
    that rejected packed doesn't arrive to another socket.

    SO_BINDTODEVICE seems to do exactly what we want without installing any socket
    filters and without requiring SO_REUSEADDR or SO_REUSEPORT. It originally considered
    for implementing broadcast listen on interface bound sockets but it wasn't much
    useful by that time as the SO_BINDTODEVICE required CAP_NET_RAW privilege which
    is normally not available for a non-root user. However, that changed since Linux 5.7
    [2].

    This patch uses SO_BINDTODEVICE but falls back to the previous way if that fails.

    1. https://www.winehq.org/pipermail/wine-devel/2011-October/092681.html
    2. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/patch/?id=c427bfec18f2190b8f4718785ee8ed2db4f84ee6

 server/sock.c | 28 ++++++++++++++++++++++------
 1 file changed, 22 insertions(+), 6 deletions(-)

diff --git a/server/sock.c b/server/sock.c
index 03716cba90f..af08cd6be24 100644
--- a/server/sock.c
+++ b/server/sock.c
@@ -1756,12 +1756,14 @@ static int accept_into_socket( struct sock *sock, struct sock *acceptsock )
 
 #ifdef IP_BOUND_IF
 
-static int bind_to_index( int fd, in_addr_t bind_addr, unsigned int index )
+static int bind_to_index( int fd, in_addr_t bind_addr, const char *name, unsigned int index, BOOL *need_reuse_addr )
 {
+    *need_reuse_addr = TRUE;
+
     return setsockopt( fd, IPPROTO_IP, IP_BOUND_IF, &index, sizeof(index) );
 }
 
-#elif defined(IP_UNICAST_IF) && defined(SO_ATTACH_FILTER)
+#elif defined(IP_UNICAST_IF) && defined(SO_ATTACH_FILTER) && defined(SO_BINDTODEVICE)
 
 struct interface_filter
 {
@@ -1794,13 +1796,26 @@ static struct interface_filter generic_interface_filter =
     BPF_STMT(BPF_RET+BPF_K, 0)          /* dump packet */
 };
 
-static int bind_to_index( int fd, in_addr_t bind_addr, unsigned int index )
+static int bind_to_index( int fd, in_addr_t bind_addr, const char *name, unsigned int index, BOOL *need_reuse_addr )
 {
     in_addr_t ifindex = htonl( index );
     struct interface_filter specific_interface_filter;
     struct sock_fprog filter_prog;
     int ret;
 
+    if (!setsockopt( fd, SOL_SOCKET, SO_BINDTODEVICE, name, strlen( name ) + 1 ))
+    {
+        *need_reuse_addr = FALSE;
+        return 0;
+    }
+    /* SO_BINDTODEVICE requires NET_CAP_RAW until Linux 5.7. */
+
+    if (debug_level)
+        fprintf( stderr, "setsockopt SO_BINDTODEVICE fd %d, name %s failed: %s, falling back to SO_REUSE_ADDR\n",
+                 fd, name, strerror( errno ));
+
+    *need_reuse_addr = TRUE;
+
     if ((ret = setsockopt( fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex) )) < 0)
         return ret;
 
@@ -1814,7 +1829,7 @@ static int bind_to_index( int fd, in_addr_t bind_addr, unsigned int index )
 
 #else
 
-static int bind_to_index( int fd, in_addr_t bind_addr, unsigned int index )
+static int bind_to_index( int fd, in_addr_t bind_addr, const char *name, unsigned int index, BOOL *need_reuse_addr )
 {
     errno = EOPNOTSUPP;
     return -1;
@@ -1841,6 +1856,7 @@ static int bind_to_interface( struct sock *sock, const struct sockaddr_in *addr
     struct ifaddrs *ifaddrs, *ifaddr;
     int fd = get_unix_fd( sock->fd );
     static const int enable = 1;
+    BOOL need_reuse_addr;
     unsigned int index;
 
     if (bind_addr == htonl( INADDR_ANY ) || bind_addr == htonl( INADDR_LOOPBACK ))
@@ -1866,14 +1882,14 @@ static int bind_to_interface( struct sock *sock, const struct sockaddr_in *addr
 
             freeifaddrs( ifaddrs );
 
-            if (bind_to_index( fd, bind_addr, index ) < 0)
+            if (bind_to_index( fd, bind_addr, ifaddr->ifa_name, index, &need_reuse_addr ) < 0)
             {
                 if (debug_level)
                     fprintf( stderr, "failed to bind to interface: %s\n", strerror( errno ) );
                 return 0;
             }
 
-            if (setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable) ) < 0)
+            if (need_reuse_addr && setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable) ) < 0)
             {
                 if (debug_level)
                     fprintf( stderr, "failed to reuse address: %s\n", strerror( errno ) );
-- 
2.31.1




More information about the wine-devel mailing list