[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