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();