From 6e23c3b3cb39b7bd08095a238b0a33dd74f02ca4 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Wed, 17 Oct 2018 01:48:52 +0000 Subject: [PATCH] Support SO_REUSEPORT and IP_MULTICAST_TTL for datagram sockets. mDNS queries (and many other local queries) should be allowed to set their TTL to 255. This patch enables that on all platforms. On macOS in particular, a system service reserves the mDNS port. The socket option SO_REUSEPORT is required in addition to SO_REUSEADDR to successfully bind there; the flag is also supported on Linux, so this patch allows it to be used there as well (but prints an error message if it's used on any other platform). I could use some guidance as to tests for this - is it support availble for running tests only on specific platforms? Bug: https://github.com/dart-lang/sdk/issues/34799 and Change-Id: I29b620d8ec04343f356a8171bae3d385ddfa9564 https://github.com/dart-lang/sdk/issues/34782 Reviewed-on: https://dart-review.googlesource.com/c/80082 Commit-Queue: Siva Annamalai Reviewed-by: Siva Annamalai --- .../tool/input_sdk/patch/io_patch.dart | 2 +- runtime/bin/io_natives.cc | 2 +- runtime/bin/socket.cc | 5 ++- runtime/bin/socket.h | 5 ++- runtime/bin/socket_android.cc | 16 ++++++++- runtime/bin/socket_fuchsia.cc | 5 ++- runtime/bin/socket_linux.cc | 33 ++++++++++++++++- runtime/bin/socket_macos.cc | 14 +++++++- runtime/bin/socket_patch.dart | 27 +++++++++----- runtime/bin/socket_win.cc | 22 +++++++++++- .../_internal/js_runtime/lib/io_patch.dart | 2 +- sdk/lib/io/socket.dart | 2 +- .../io/raw_datagram_socket_test.dart | 35 ++++++++++++++----- 13 files changed, 141 insertions(+), 29 deletions(-) diff --git a/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart b/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart index b05a0241415..1d3c21698dd 100644 --- a/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart +++ b/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart @@ -530,7 +530,7 @@ class X509Certificate { class RawDatagramSocket { @patch static Future bind(host, int port, - {bool reuseAddress = true}) { + {bool reuseAddress = true, bool reusePort = false, int ttl = 1}) { throw UnsupportedError("RawDatagramSocket.bind"); } } diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc index 324eb1a0022..e5b885eccbc 100644 --- a/runtime/bin/io_natives.cc +++ b/runtime/bin/io_natives.cc @@ -131,7 +131,7 @@ namespace bin { V(SocketBase_IsBindError, 2) \ V(Socket_Available, 1) \ V(Socket_CreateBindConnect, 4) \ - V(Socket_CreateBindDatagram, 4) \ + V(Socket_CreateBindDatagram, 6) \ V(Socket_CreateConnect, 3) \ V(Socket_GetPort, 1) \ V(Socket_GetRemotePeer, 1) \ diff --git a/runtime/bin/socket.cc b/runtime/bin/socket.cc index d8ee1bc0809..85cb8e01d2e 100644 --- a/runtime/bin/socket.cc +++ b/runtime/bin/socket.cc @@ -296,7 +296,10 @@ void FUNCTION_NAME(Socket_CreateBindDatagram)(Dart_NativeArguments args) { int64_t port = DartUtils::GetInt64ValueCheckRange(port_arg, 0, 65535); SocketAddress::SetAddrPort(&addr, port); bool reuse_addr = DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 3)); - intptr_t socket = Socket::CreateBindDatagram(addr, reuse_addr); + bool reuse_port = DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 4)); + int ttl = DartUtils::GetIntegerValue(Dart_GetNativeArgument(args, 5)); + intptr_t socket = + Socket::CreateBindDatagram(addr, reuse_addr, reuse_port, ttl); if (socket >= 0) { Socket::SetSocketIdNativeField(Dart_GetNativeArgument(args, 0), socket, Socket::kFinalizerNormal); diff --git a/runtime/bin/socket.h b/runtime/bin/socket.h index 05a25121bd1..24d95b96c3a 100644 --- a/runtime/bin/socket.h +++ b/runtime/bin/socket.h @@ -70,7 +70,10 @@ class Socket : public ReferenceCounted { const RawAddr& source_addr); // Creates a datagram socket which is bound. The port to bind // to is specified as the port component of the RawAddr structure. - static intptr_t CreateBindDatagram(const RawAddr& addr, bool reuseAddress); + static intptr_t CreateBindDatagram(const RawAddr& addr, + bool reuseAddress, + bool reusePort, + int ttl = 1); static CObject* LookupRequest(const CObjectArray& request); static CObject* ListInterfacesRequest(const CObjectArray& request); diff --git a/runtime/bin/socket_android.cc b/runtime/bin/socket_android.cc index c3409cad0ef..5055098c02e 100644 --- a/runtime/bin/socket_android.cc +++ b/runtime/bin/socket_android.cc @@ -79,7 +79,10 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, return Connect(fd, addr); } -intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress) { +intptr_t Socket::CreateBindDatagram(const RawAddr& addr, + bool reuseAddress, + bool reusePort, + int ttl) { intptr_t fd; fd = NO_RETRY_EXPECTED(socket(addr.addr.sa_family, SOCK_DGRAM, IPPROTO_UDP)); @@ -98,6 +101,17 @@ intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress) { setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))); } + if (reusePort) { + // ignore reusePort - not supported on this platform. + Log::PrintErr( + "Dart Socket ERROR: %s:%d: `reusePort` not supported for " + "Android." __FILE__, + __LINE__); + } + + VOID_NO_RETRY_EXPECTED( + setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl))); + if (NO_RETRY_EXPECTED( bind(fd, &addr.addr, SocketAddress::GetAddrLength(addr))) < 0) { FDUtils::SaveErrorAndClose(fd); diff --git a/runtime/bin/socket_fuchsia.cc b/runtime/bin/socket_fuchsia.cc index 8fb2d433fb3..9655e23d08f 100644 --- a/runtime/bin/socket_fuchsia.cc +++ b/runtime/bin/socket_fuchsia.cc @@ -109,7 +109,10 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, return -1; } -intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress) { +intptr_t Socket::CreateBindDatagram(const RawAddr& addr, + bool reuseAddress, + bool reusePort, + int ttl) { LOG_ERR("SocketBase::CreateBindDatagram is unimplemented\n"); UNIMPLEMENTED(); return -1; diff --git a/runtime/bin/socket_linux.cc b/runtime/bin/socket_linux.cc index 2d7f546cb19..2e35640f810 100644 --- a/runtime/bin/socket_linux.cc +++ b/runtime/bin/socket_linux.cc @@ -10,6 +10,7 @@ #include // NOLINT #include "bin/fdutils.h" +#include "bin/log.h" #include "platform/signal_blocker.h" namespace dart { @@ -71,7 +72,10 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, return Connect(fd, addr); } -intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress) { +intptr_t Socket::CreateBindDatagram(const RawAddr& addr, + bool reuseAddress, + bool reusePort, + int ttl) { intptr_t fd; fd = NO_RETRY_EXPECTED(socket(addr.addr.sa_family, @@ -87,6 +91,33 @@ intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress) { setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))); } + if (reusePort) { +#ifdef SO_REUSEPORT // Not all Linux versions support this. + int optval = 1; + int reuse_port_success = + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); + // Even if it's defined, we might be running on a kernel + // that doesn't support it at runtime. + if (reuse_port_success != 0) { + if (errno == EINTR) { + FATAL("Unexpected EINTR errno"); + } + const int kBufferSize = 1024; + char error_buf[kBufferSize]; + Log::PrintErr("Dart Socket ERROR: %s:%d: %s.", __FILE__, __LINE__, + Utils::StrError(errno, error_buf, kBufferSize)); + } +#else // !defined SO_REUSEPORT + Log::PrintErr( + "Dart Socket ERROR: %s:%d: `reusePort` not available on this Linux " + "version.", + __FILE__, __LINE__); +#endif // SO_REUSEPORT + } + + VOID_NO_RETRY_EXPECTED( + setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl))); + if (NO_RETRY_EXPECTED( bind(fd, &addr.addr, SocketAddress::GetAddrLength(addr))) < 0) { FDUtils::SaveErrorAndClose(fd); diff --git a/runtime/bin/socket_macos.cc b/runtime/bin/socket_macos.cc index e45c1a4345f..7e2f8f0eb09 100644 --- a/runtime/bin/socket_macos.cc +++ b/runtime/bin/socket_macos.cc @@ -79,7 +79,10 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, return Connect(fd, addr); } -intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress) { +intptr_t Socket::CreateBindDatagram(const RawAddr& addr, + bool reuseAddress, + bool reusePort, + int ttl) { intptr_t fd; fd = NO_RETRY_EXPECTED(socket(addr.addr.sa_family, SOCK_DGRAM, IPPROTO_UDP)); @@ -98,6 +101,15 @@ intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress) { setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))); } + if (reusePort) { + int optval = 1; + VOID_NO_RETRY_EXPECTED( + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval))); + } + + VOID_NO_RETRY_EXPECTED( + setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl))); + if (NO_RETRY_EXPECTED( bind(fd, &addr.addr, SocketAddress::GetAddrLength(addr))) < 0) { FDUtils::SaveErrorAndClose(fd); diff --git a/runtime/bin/socket_patch.dart b/runtime/bin/socket_patch.dart index b3593cb977b..9d11929be0a 100644 --- a/runtime/bin/socket_patch.dart +++ b/runtime/bin/socket_patch.dart @@ -95,6 +95,12 @@ void _throwOnBadPort(int port) { } } +void _throwOnBadTtl(int ttl) { + if (ttl == null || ttl < 1 || ttl > 255) { + throw new ArgumentError('Invalid ttl $ttl'); + } +} + class _InternetAddress implements InternetAddress { static const int _addressLoopbackIPv4 = 0; static const int _addressLoopbackIPv6 = 1; @@ -578,14 +584,15 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { } static Future<_NativeSocket> bindDatagram( - host, int port, bool reuseAddress) async { + host, int port, bool reuseAddress, bool reusePort, int ttl) async { _throwOnBadPort(port); + _throwOnBadTtl(ttl); final address = await _resolveHost(host); var socket = new _NativeSocket.datagram(address); - var result = - socket.nativeCreateBindDatagram(address._in_addr, port, reuseAddress); + var result = socket.nativeCreateBindDatagram( + address._in_addr, port, reuseAddress, reusePort, ttl); if (result is OSError) { throw new SocketException("Failed to create datagram socket", osError: result, address: address, port: port); @@ -1130,8 +1137,8 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { bool isBindError(int errorNumber) native "SocketBase_IsBindError"; nativeCreateBindListen(List addr, int port, int backlog, bool v6Only, bool shared) native "ServerSocket_CreateBindListen"; - nativeCreateBindDatagram(List addr, int port, bool reuseAddress) - native "Socket_CreateBindDatagram"; + nativeCreateBindDatagram(List addr, int port, bool reuseAddress, + bool reusePort, int ttl) native "Socket_CreateBindDatagram"; nativeAccept(_NativeSocket socket) native "ServerSocket_Accept"; int nativeGetPort() native "Socket_GetPort"; List nativeGetRemotePeer() native "Socket_GetRemotePeer"; @@ -1775,8 +1782,8 @@ class _Socket extends Stream> implements Socket { class RawDatagramSocket { @patch static Future bind(host, int port, - {bool reuseAddress: true}) { - return _RawDatagramSocket.bind(host, port, reuseAddress); + {bool reuseAddress: true, bool reusePort: false, int ttl: 1}) { + return _RawDatagramSocket.bind(host, port, reuseAddress, reusePort, ttl); } } @@ -1814,9 +1821,11 @@ class _RawDatagramSocket extends Stream })); } - static Future bind(host, int port, bool reuseAddress) { + static Future bind( + host, int port, bool reuseAddress, bool reusePort, int ttl) { _throwOnBadPort(port); - return _NativeSocket.bindDatagram(host, port, reuseAddress) + _throwOnBadTtl(ttl); + return _NativeSocket.bindDatagram(host, port, reuseAddress, reusePort, ttl) .then((socket) => new _RawDatagramSocket(socket)); } diff --git a/runtime/bin/socket_win.cc b/runtime/bin/socket_win.cc index 1595aba485a..c02c1bbae07 100644 --- a/runtime/bin/socket_win.cc +++ b/runtime/bin/socket_win.cc @@ -147,7 +147,10 @@ intptr_t ServerSocket::Accept(intptr_t fd) { } } -intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress) { +intptr_t Socket::CreateBindDatagram(const RawAddr& addr, + bool reuseAddress, + bool reusePort, + int ttl) { SOCKET s = socket(addr.ss.ss_family, SOCK_DGRAM, IPPROTO_UDP); if (s == INVALID_SOCKET) { return -1; @@ -166,6 +169,23 @@ intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress) { } } + if (reusePort) { + // ignore reusePort - not supported on this platform. + Log::PrintErr( + "Dart Socket ERROR: %s:%d: `reusePort` not supported for " + "Windows." __FILE__, + __LINE__); + } + + status = setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, + reinterpret_cast(&ttl), sizeof(ttl)); + if (status == SOCKET_ERROR) { + DWORD rc = WSAGetLastError(); + closesocket(s); + SetLastError(rc); + return -1; + } + status = bind(s, &addr.addr, SocketAddress::GetAddrLength(addr)); if (status == SOCKET_ERROR) { DWORD rc = WSAGetLastError(); diff --git a/sdk/lib/_internal/js_runtime/lib/io_patch.dart b/sdk/lib/_internal/js_runtime/lib/io_patch.dart index 8a4c7bb74b7..40e7c73f4a5 100644 --- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart @@ -530,7 +530,7 @@ class X509Certificate { class RawDatagramSocket { @patch static Future bind(host, int port, - {bool reuseAddress: true}) { + {bool reuseAddress: true, bool reusePort: false, int ttl: 1}) { throw new UnsupportedError("RawDatagramSocket.bind"); } } diff --git a/sdk/lib/io/socket.dart b/sdk/lib/io/socket.dart index 1d8a26346cf..b66e42bc5b1 100644 --- a/sdk/lib/io/socket.dart +++ b/sdk/lib/io/socket.dart @@ -757,7 +757,7 @@ abstract class RawDatagramSocket extends Stream { * port. */ external static Future bind(host, int port, - {bool reuseAddress: true}); + {bool reuseAddress: true, bool reusePort: false, int ttl: 1}); /** * Returns the port used by this socket. diff --git a/tests/standalone/io/raw_datagram_socket_test.dart b/tests/standalone/io/raw_datagram_socket_test.dart index 5fe68e4b30a..3976c54cdf3 100644 --- a/tests/standalone/io/raw_datagram_socket_test.dart +++ b/tests/standalone/io/raw_datagram_socket_test.dart @@ -70,17 +70,17 @@ testDatagramMulticastOptions() { testDatagramSocketReuseAddress() { test(address, reuseAddress) { asyncStart(); - RawDatagramSocket - .bind(address, 0, reuseAddress: reuseAddress) + RawDatagramSocket.bind(address, 0, + reuseAddress: reuseAddress, + reusePort: Platform.isMacOS && reuseAddress) .then((socket) { if (reuseAddress) { - RawDatagramSocket - .bind(address, socket.port) + RawDatagramSocket.bind(address, socket.port, + reusePort: Platform.isMacOS) .then((s) => Expect.isTrue(s is RawDatagramSocket)) .then(asyncSuccess); } else { - FutureExpect - .throws(RawDatagramSocket.bind(address, socket.port)) + FutureExpect.throws(RawDatagramSocket.bind(address, socket.port)) .then(asyncSuccess); } }); @@ -92,6 +92,24 @@ testDatagramSocketReuseAddress() { test(InternetAddress.loopbackIPv6, false); } +testDatagramSocketTtl() { + test(address, ttl, shouldSucceed) { + asyncStart(); + if (shouldSucceed) { + RawDatagramSocket.bind(address, 0, ttl: ttl).then(asyncSuccess); + } else { + Expect.throws(() => RawDatagramSocket.bind(address, 0, ttl: ttl)); + asyncEnd(); + } + } + + test(InternetAddress.loopbackIPv4, 1, true); + test(InternetAddress.loopbackIPv4, 255, true); + test(InternetAddress.loopbackIPv4, 256, false); + test(InternetAddress.loopbackIPv4, 0, false); + test(InternetAddress.loopbackIPv4, null, false); +} + testBroadcast() { test(bindAddress, broadcastAddress, enabled) { asyncStart(); @@ -337,9 +355,8 @@ testSendReceive(InternetAddress bindAddress, int dataSize) { main() { testDatagramBroadcastOptions(); testDatagramMulticastOptions(); - if (!Platform.isMacOS) { - testDatagramSocketReuseAddress(); - } + testDatagramSocketReuseAddress(); + testDatagramSocketTtl(); testBroadcast(); testLoopbackMulticast(); testLoopbackMulticastError();