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 <asiva@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
This commit is contained in:
Dan Field
2018-10-17 01:48:52 +00:00
committed by commit-bot@chromium.org
parent 9a69a5c1ee
commit 6e23c3b3cb
13 changed files with 141 additions and 29 deletions
@@ -530,7 +530,7 @@ class X509Certificate {
class RawDatagramSocket {
@patch
static Future<RawDatagramSocket> bind(host, int port,
{bool reuseAddress = true}) {
{bool reuseAddress = true, bool reusePort = false, int ttl = 1}) {
throw UnsupportedError("RawDatagramSocket.bind");
}
}
+1 -1
View File
@@ -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) \
+4 -1
View File
@@ -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);
+4 -1
View File
@@ -70,7 +70,10 @@ class Socket : public ReferenceCounted<Socket> {
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);
+15 -1
View File
@@ -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);
+4 -1
View File
@@ -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;
+32 -1
View File
@@ -10,6 +10,7 @@
#include <errno.h> // 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);
+13 -1
View File
@@ -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);
+18 -9
View File
@@ -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<int> addr, int port, int backlog, bool v6Only,
bool shared) native "ServerSocket_CreateBindListen";
nativeCreateBindDatagram(List<int> addr, int port, bool reuseAddress)
native "Socket_CreateBindDatagram";
nativeCreateBindDatagram(List<int> 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<List<int>> implements Socket {
class RawDatagramSocket {
@patch
static Future<RawDatagramSocket> 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<RawSocketEvent>
}));
}
static Future<RawDatagramSocket> bind(host, int port, bool reuseAddress) {
static Future<RawDatagramSocket> 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));
}
+21 -1
View File
@@ -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<const char*>(&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();
@@ -530,7 +530,7 @@ class X509Certificate {
class RawDatagramSocket {
@patch
static Future<RawDatagramSocket> bind(host, int port,
{bool reuseAddress: true}) {
{bool reuseAddress: true, bool reusePort: false, int ttl: 1}) {
throw new UnsupportedError("RawDatagramSocket.bind");
}
}
+1 -1
View File
@@ -757,7 +757,7 @@ abstract class RawDatagramSocket extends Stream<RawSocketEvent> {
* port.
*/
external static Future<RawDatagramSocket> bind(host, int port,
{bool reuseAddress: true});
{bool reuseAddress: true, bool reusePort: false, int ttl: 1});
/**
* Returns the port used by this socket.
@@ -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();