diff options
author | Ivan Solovev <[email protected]> | 2025-09-22 11:47:14 +0200 |
---|---|---|
committer | Ivan Solovev <[email protected]> | 2025-10-09 16:43:12 +0200 |
commit | 83fc4ff9c20eb922113e08ba8566e077ce44dea3 (patch) | |
tree | 61a399b1f23e06cba5d5fcb3e8eedbd30d3f1074 | |
parent | e9778dfe6ead0c4d704570816a56aead084a0263 (diff) |
Allow binding a socket to a specific QNetworkInterface
For now, via a private API.
Provide a new socket engine option - BindInterfaceIndex, and use it if
a valid interface is passed to QAbstractSocketPrivate::bind().
Note that this feature is not fully functional on macOS:
both SO_BINDTODEVICE and IP{V6}_BOUND_IF only work for limiting
outgoing datagrams. The patch uses IP{V6}_BOUND_IF, because it allows
to avoid number -> string -> number conversion.
As of now, extra filtering of incoming data should be done on the user
side.
Task-number: QTBUG-80704
Task-number: QTBUG-139697
Pick-to: 6.10 6.8
Change-Id: Ic207908313d9d25f96c23ecc363181ff0ae8232a
Reviewed-by: Thiago Macieira <[email protected]>
Reviewed-by: MÃ¥rten Nordheim <[email protected]>
-rw-r--r-- | src/network/socket/qabstractsocket.cpp | 7 | ||||
-rw-r--r-- | src/network/socket/qabstractsocketengine_p.h | 3 | ||||
-rw-r--r-- | src/network/socket/qnativesocketengine_unix.cpp | 26 | ||||
-rw-r--r-- | src/network/socket/qnativesocketengine_win.cpp | 61 |
4 files changed, 93 insertions, 4 deletions
diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index ce3626304c8..f2474ad7e97 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -432,6 +432,7 @@ #include "qabstractsocket.h" #include "qabstractsocket_p.h" +#include "qnetworkinterface.h" #include "private/qhostinfo_p.h" @@ -1505,8 +1506,6 @@ bool QAbstractSocketPrivate::bind(const QHostAddress &address, quint16 port, QAb { Q_Q(QAbstractSocket); - Q_UNUSED(iface); // will be used in a follow-up patch - // now check if the socket engine is initialized and to the right type if (!socketEngine || !socketEngine->isValid()) { QHostAddress nullAddress; @@ -1538,6 +1537,10 @@ bool QAbstractSocketPrivate::bind(const QHostAddress &address, quint16 port, QAb socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 0); #endif } +#if QT_CONFIG(networkinterface) + if (iface && iface->isValid()) + socketEngine->setOption(QAbstractSocketEngine::BindInterfaceIndex, iface->index()); +#endif bool result = socketEngine->bind(address, port); cachedSocketDescriptor = socketEngine->socketDescriptor(); diff --git a/src/network/socket/qabstractsocketengine_p.h b/src/network/socket/qabstractsocketengine_p.h index b9cdf38b5e0..48ebfed71a2 100644 --- a/src/network/socket/qabstractsocketengine_p.h +++ b/src/network/socket/qabstractsocketengine_p.h @@ -75,7 +75,8 @@ public: ReceivePacketInformation, ReceiveHopLimit, MaxStreamsSocketOption, - PathMtuInformation + PathMtuInformation, + BindInterfaceIndex, }; enum PacketHeaderOption { diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp index a9bc2d92b44..b5fbb5c2e00 100644 --- a/src/network/socket/qnativesocketengine_unix.cpp +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -11,6 +11,7 @@ #include "qhostaddress.h" #include "qvarlengtharray.h" #include "qnetworkinterface.h" +#include "qnetworkinterface_p.h" #include "qendian.h" #ifdef Q_OS_WASM #include <private/qeventdispatcher_wasm_p.h> @@ -82,6 +83,9 @@ static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt, case QNativeSocketEngine::MaxStreamsSocketOption: Q_UNREACHABLE(); + case QNativeSocketEngine::BindInterfaceIndex: + Q_UNREACHABLE(); // handled directly in setOption() + case QNativeSocketEngine::BroadcastSocketOption: n = SO_BROADCAST; break; @@ -373,7 +377,27 @@ bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt #endif return false; } - + case QNativeSocketEngine::BindInterfaceIndex: { +#if defined(SO_BINDTOIFINDEX) // seen on Linux + return ::setsockopt(socketDescriptor, SOL_SOCKET, SO_BINDTOIFINDEX, + &v, sizeof(v)) == 0; +#elif defined(IPV6_BOUND_IF) && defined(IP_BOUND_IF) // seen on Darwin + // note: on Darwin, this only limits sending the data, not receiving it + if (socketProtocol == QAbstractSocket::IPv6Protocol + || socketProtocol == QAbstractSocket::AnyIPProtocol) { + return ::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_BOUND_IF, &v, sizeof(v)) == 0; + } else { + return ::setsockopt(socketDescriptor, IPPROTO_IP, IP_BOUND_IF, &v, sizeof(v)) == 0; + } +#elif defined(SO_BINDTODEVICE) && QT_CONFIG(networkinterface) + // need to convert to interface name + const QByteArray name = QNetworkInterfaceManager::interfaceNameFromIndex(v).toLatin1(); + return ::setsockopt(socketDescriptor, SOL_SOCKET, SO_BINDTODEVICE, + name.data(), socklen_t(name.size())) == 0; +#else + return false; +#endif + } default: break; } diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp index 9933ea06745..598467ef629 100644 --- a/src/network/socket/qnativesocketengine_win.cpp +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -153,6 +153,9 @@ static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt, case QNativeSocketEngine::MaxStreamsSocketOption: Q_UNREACHABLE(); + case QNativeSocketEngine::BindInterfaceIndex: + Q_UNREACHABLE(); // handled directly in setOption() + case QNativeSocketEngine::ReceiveBufferSocketOption: n = SO_RCVBUF; break; @@ -441,6 +444,64 @@ bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt return true; } + case QNativeSocketEngine::BindInterfaceIndex: { + int ret = 0; + if (socketProtocol == QAbstractSocket::IPv6Protocol + || socketProtocol == QAbstractSocket::AnyIPProtocol) { + // IPv6 - uses host byte order + // Bind outgoing datagrams to the interface + if (!ret) { + ret = ::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_UNICAST_IF, + reinterpret_cast<char *>(&v), sizeof(v)); + } + if (!ret) { + ret = ::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF, + reinterpret_cast<char *>(&v), sizeof(v)); + } + // Bind incoming datagrams to the interface + if (!ret) { + const int enable = 1; + ret = ::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_IFLIST, + reinterpret_cast<const char *>(&enable), sizeof(enable)); + if (!ret) { + ret = ::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_ADD_IFLIST, + reinterpret_cast<char *>(&v), sizeof(v)); + } + } + } + bool result = !ret; + if (result) { + // Try to set the IPv4 options unconditionally, but ignore + // the result if the protocol is not IPv4-only + + // IPv4 - uses network byte order + int netIdx = htonl(v); + // Bind outgoing datagrams to the interface + if (!ret) { + ret = ::setsockopt(socketDescriptor, IPPROTO_IP, IP_UNICAST_IF, + reinterpret_cast<char *>(&netIdx), sizeof(netIdx)); + } + if (!ret) { + ret = ::setsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, + reinterpret_cast<char *>(&netIdx), sizeof(netIdx)); + } + // Bind incoming datagrams to the interface + if (!ret) { + const int enable = 1; + ret = ::setsockopt(socketDescriptor, IPPROTO_IP, IP_IFLIST, + reinterpret_cast<const char *>(&enable), sizeof(enable)); + if (!ret) { + // uses host byte order here + ret = ::setsockopt(socketDescriptor, IPPROTO_IP, IP_ADD_IFLIST, + reinterpret_cast<char *>(&v), sizeof(v)); + } + } + if (socketProtocol == QAbstractSocket::IPv4Protocol) + result = !ret; + } + return result; + } + default: break; } |