Exposes a thin layer over getsockopt/setsockopt for supported platforms.
This allows developers to have more fine grained control over socket
options supported by their platforms, particularly when there is not a
nice way to encapsulate differences between IPv4 and IPv6 options (as
with IP_MULTICAST_IF and IPV6_MULTICAST_IF). It also begins the work
of exposing socket level and option values, although keeping it for now
only to a minimum necessary to assist with setting the multicast
interface for datagram sockets.
This CL also marks `multicastInterface` as deprecated.
Bug: https://github.com/dart-lang/sdk/issues/17057
Change-Id: I39b3bf3d32d39de1c777acea4425d6eb2226355d
Reviewed-on: https://dart-review.googlesource.com/c/89164
Commit-Queue: Zach Anderson <zra@google.com>
Reviewed-by: Zach Anderson <zra@google.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8dce680..72262c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,10 @@
[35484]: https://github.com/dart-lang/sdk/issues/35484
[35510]: https://github.com/dart-lang/sdk/issues/35510
+#### `dart:io`
+
+* Added ability to get and set low level socket options.
+
### Dart VM
### Tool Changes
@@ -2800,7 +2804,7 @@
people in practice.
* **Breaking:** Support for `barback` versions prior to 0.15.0 (released July
- 2014) has been dropped. Pub will no longer install these older barback
+ 1) has been dropped. Pub will no longer install these older barback
versions.
* `pub serve` now GZIPs the assets it serves to make load times more similar
diff --git a/DEPS b/DEPS
index 2df2e7c..1b8fc0c 100644
--- a/DEPS
+++ b/DEPS
@@ -87,7 +87,7 @@
"func_rev": "25eec48146a58967d75330075ab376b3838b18a8",
"glob_tag": "1.1.7",
"html_tag" : "0.13.3+2",
- "http_io_rev": "265e90afbffacb7b2988385d4a6aa2f14e970d44",
+ "http_io_rev": "57da05a66f5bf7df3dd7aebe7b7efe0dfc477baa",
"http_multi_server_tag" : "2.0.5",
"http_parser_tag" : "3.1.1",
"http_retry_tag": "0.1.1",
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 1d3c216..13c6f64 100644
--- a/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart
+++ b/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart
@@ -501,6 +501,14 @@
}
@patch
+class RawSocketOption {
+ @patch
+ static int _getOptionValue(int key) {
+ throw UnsupportedError("RawSocketOption._getOptionValue");
+ }
+}
+
+@patch
class SecurityContext {
@patch
factory SecurityContext({bool withTrustedRoots = false}) {
diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc
index e5b885e..94a0c35258 100644
--- a/runtime/bin/io_natives.cc
+++ b/runtime/bin/io_natives.cc
@@ -9,6 +9,7 @@
#include "bin/builtin.h"
#include "bin/dartutils.h"
+#include "bin/socket_base.h"
#include "include/dart_api.h"
#include "platform/assert.h"
@@ -109,6 +110,7 @@
V(Process_ClearSignalHandler, 1) \
V(ProcessInfo_CurrentRSS, 0) \
V(ProcessInfo_MaxRSS, 0) \
+ V(RawSocketOption_GetOptionValue, 1) \
V(SecureSocket_Connect, 7) \
V(SecureSocket_Destroy, 1) \
V(SecureSocket_FilterPointer, 1) \
@@ -137,6 +139,7 @@
V(Socket_GetRemotePeer, 1) \
V(Socket_GetError, 1) \
V(Socket_GetOption, 3) \
+ V(Socket_GetRawOption, 4) \
V(Socket_GetSocketId, 1) \
V(Socket_GetStdioHandle, 2) \
V(Socket_GetType, 1) \
@@ -146,6 +149,7 @@
V(Socket_RecvFrom, 1) \
V(Socket_SendTo, 6) \
V(Socket_SetOption, 4) \
+ V(Socket_SetRawOption, 4) \
V(Socket_SetSocketId, 3) \
V(Socket_WriteList, 4) \
V(Stdin_ReadByte, 1) \
diff --git a/runtime/bin/socket.cc b/runtime/bin/socket.cc
index 85cb8e0..113ea22 100644
--- a/runtime/bin/socket.cc
+++ b/runtime/bin/socket.cc
@@ -315,11 +315,11 @@
Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 0));
intptr_t available = SocketBase::Available(socket->fd());
if (available >= 0) {
- Dart_SetReturnValue(args, Dart_NewInteger(available));
+ Dart_SetIntegerReturnValue(args, available);
} else {
// Available failed. Mark socket as having data, to trigger a future read
// event where the actual error can be reported.
- Dart_SetReturnValue(args, Dart_NewInteger(1));
+ Dart_SetIntegerReturnValue(args, 1);
}
}
@@ -482,9 +482,9 @@
if (short_write) {
// If the write was forced 'short', indicate by returning the negative
// number of bytes. A forced short write may not trigger a write event.
- Dart_SetReturnValue(args, Dart_NewInteger(-bytes_written));
+ Dart_SetIntegerReturnValue(args, -bytes_written);
} else {
- Dart_SetReturnValue(args, Dart_NewInteger(bytes_written));
+ Dart_SetIntegerReturnValue(args, bytes_written);
}
} else {
// Extract OSError before we release data, as it may override the error.
@@ -521,7 +521,7 @@
addr, SocketBase::kAsync);
if (bytes_written >= 0) {
Dart_TypedDataReleaseData(buffer_obj);
- Dart_SetReturnValue(args, Dart_NewInteger(bytes_written));
+ Dart_SetIntegerReturnValue(args, bytes_written);
} else {
// Extract OSError before we release data, as it may override the error.
OSError os_error;
@@ -536,7 +536,7 @@
OSError os_error;
intptr_t port = SocketBase::GetPort(socket->fd());
if (port > 0) {
- Dart_SetReturnValue(args, Dart_NewInteger(port));
+ Dart_SetIntegerReturnValue(args, port);
} else {
Dart_SetReturnValue(args, DartUtils::NewDartOSError());
}
@@ -581,7 +581,7 @@
OSError os_error;
intptr_t type = SocketBase::GetType(socket->fd());
if (type >= 0) {
- Dart_SetReturnValue(args, Dart_NewInteger(type));
+ Dart_SetIntegerReturnValue(args, type);
} else {
Dart_SetReturnValue(args, DartUtils::NewDartOSError());
}
@@ -600,7 +600,7 @@
Socket* socket =
Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 0));
intptr_t id = reinterpret_cast<intptr_t>(socket);
- Dart_SetReturnValue(args, Dart_NewInteger(id));
+ Dart_SetIntegerReturnValue(args, id);
}
void FUNCTION_NAME(Socket_SetSocketId)(Dart_NativeArguments args) {
@@ -783,7 +783,7 @@
bool enabled;
ok = SocketBase::GetNoDelay(socket->fd(), &enabled);
if (ok) {
- Dart_SetReturnValue(args, enabled ? Dart_True() : Dart_False());
+ Dart_SetBooleanReturnValue(args, enabled);
}
break;
}
@@ -791,7 +791,7 @@
bool enabled;
ok = SocketBase::GetMulticastLoop(socket->fd(), protocol, &enabled);
if (ok) {
- Dart_SetReturnValue(args, enabled ? Dart_True() : Dart_False());
+ Dart_SetBooleanReturnValue(args, enabled);
}
break;
}
@@ -799,7 +799,7 @@
int value;
ok = SocketBase::GetMulticastHops(socket->fd(), protocol, &value);
if (ok) {
- Dart_SetReturnValue(args, Dart_NewInteger(value));
+ Dart_SetIntegerReturnValue(args, value);
}
break;
}
@@ -811,7 +811,7 @@
bool enabled;
ok = SocketBase::GetBroadcast(socket->fd(), &enabled);
if (ok) {
- Dart_SetReturnValue(args, enabled ? Dart_True() : Dart_False());
+ Dart_SetBooleanReturnValue(args, enabled);
}
break;
}
@@ -869,6 +869,106 @@
}
}
+void FUNCTION_NAME(Socket_SetRawOption)(Dart_NativeArguments args) {
+ Socket* socket =
+ Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 0));
+ int64_t level = DartUtils::GetIntegerValue(Dart_GetNativeArgument(args, 1));
+ int64_t option = DartUtils::GetIntegerValue(Dart_GetNativeArgument(args, 2));
+ Dart_Handle data_obj = Dart_GetNativeArgument(args, 3);
+ ASSERT(Dart_IsList(data_obj));
+ char* data = NULL;
+ intptr_t length;
+ Dart_TypedData_Type type;
+ Dart_Handle data_result = Dart_TypedDataAcquireData(
+ data_obj, &type, reinterpret_cast<void**>(&data), &length);
+ if (Dart_IsError(data_result)) {
+ Dart_PropagateError(data_result);
+ }
+
+ bool result = SocketBase::SetOption(socket->fd(), static_cast<int>(level),
+ static_cast<int>(option), data,
+ static_cast<int>(length));
+
+ Dart_TypedDataReleaseData(data_obj);
+
+ if (result) {
+ Dart_SetReturnValue(args, Dart_Null());
+ } else {
+ Dart_SetReturnValue(args, DartUtils::NewDartOSError());
+ }
+}
+
+void FUNCTION_NAME(Socket_GetRawOption)(Dart_NativeArguments args) {
+ Socket* socket =
+ Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 0));
+ int64_t level = DartUtils::GetIntegerValue(Dart_GetNativeArgument(args, 1));
+ int64_t option = DartUtils::GetIntegerValue(Dart_GetNativeArgument(args, 2));
+ Dart_Handle data_obj = Dart_GetNativeArgument(args, 3);
+ ASSERT(Dart_IsList(data_obj));
+ char* data = NULL;
+ intptr_t length;
+ Dart_TypedData_Type type;
+ Dart_Handle data_result = Dart_TypedDataAcquireData(
+ data_obj, &type, reinterpret_cast<void**>(&data), &length);
+ if (Dart_IsError(data_result)) {
+ Dart_PropagateError(data_result);
+ }
+ unsigned int int_length = static_cast<unsigned int>(length);
+ bool result =
+ SocketBase::GetOption(socket->fd(), static_cast<int>(level),
+ static_cast<int>(option), data, &int_length);
+
+ Dart_TypedDataReleaseData(data_obj);
+
+ if (result) {
+ Dart_SetReturnValue(args, Dart_Null());
+ } else {
+ Dart_SetReturnValue(args, DartUtils::NewDartOSError());
+ }
+}
+
+// Keep in sync with _RawSocketOptions in socket.dart
+enum _RawSocketOptions : int64_t {
+ DART_SOL_SOCKET = 0,
+ DART_IPPROTO_IP = 1,
+ DART_IP_MULTICAST_IF = 2,
+ DART_IPPROTO_IPV6 = 3,
+ DART_IPV6_MULTICAST_IF = 4,
+ DART_IPPROTO_TCP = 5,
+ DART_IPPROTO_UDP = 6
+};
+
+void FUNCTION_NAME(RawSocketOption_GetOptionValue)(Dart_NativeArguments args) {
+ _RawSocketOptions key = static_cast<_RawSocketOptions>(
+ DartUtils::GetIntegerValue(Dart_GetNativeArgument(args, 0)));
+ switch (key) {
+ case DART_SOL_SOCKET:
+ Dart_SetIntegerReturnValue(args, SOL_SOCKET);
+ break;
+ case DART_IPPROTO_IP:
+ Dart_SetIntegerReturnValue(args, IPPROTO_IP);
+ break;
+ case DART_IP_MULTICAST_IF:
+ Dart_SetIntegerReturnValue(args, IP_MULTICAST_IF);
+ break;
+ case DART_IPPROTO_IPV6:
+ Dart_SetIntegerReturnValue(args, DART_IPPROTO_IPV6);
+ break;
+ case DART_IPV6_MULTICAST_IF:
+ Dart_SetIntegerReturnValue(args, IPV6_MULTICAST_IF);
+ break;
+ case DART_IPPROTO_TCP:
+ Dart_SetIntegerReturnValue(args, IPPROTO_TCP);
+ break;
+ case DART_IPPROTO_UDP:
+ Dart_SetIntegerReturnValue(args, IPPROTO_UDP);
+ break;
+ default:
+ Dart_PropagateError(Dart_NewApiError("Value outside of expected range"));
+ break;
+ }
+}
+
void FUNCTION_NAME(Socket_JoinMulticast)(Dart_NativeArguments args) {
Socket* socket =
Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 0));
diff --git a/runtime/bin/socket_base.h b/runtime/bin/socket_base.h
index f8c2f01..8cab1aa 100644
--- a/runtime/bin/socket_base.h
+++ b/runtime/bin/socket_base.h
@@ -182,6 +182,16 @@
static bool SetMulticastHops(intptr_t fd, intptr_t protocol, int value);
static bool GetBroadcast(intptr_t fd, bool* value);
static bool SetBroadcast(intptr_t fd, bool value);
+ static bool GetOption(intptr_t fd,
+ int level,
+ int option,
+ char* data,
+ unsigned int* length);
+ static bool SetOption(intptr_t fd,
+ int level,
+ int option,
+ const char* data,
+ int length);
static bool JoinMulticast(intptr_t fd,
const RawAddr& addr,
const RawAddr& interface,
diff --git a/runtime/bin/socket_base_android.cc b/runtime/bin/socket_base_android.cc
index d54a24c..c1ed4f4 100644
--- a/runtime/bin/socket_base_android.cc
+++ b/runtime/bin/socket_base_android.cc
@@ -350,6 +350,22 @@
sizeof(on))) == 0;
}
+bool SocketBase::SetOption(intptr_t fd,
+ int level,
+ int option,
+ const char* data,
+ int length) {
+ return NO_RETRY_EXPECTED(setsockopt(fd, level, option, data, length)) == 0;
+}
+
+bool SocketBase::GetOption(intptr_t fd,
+ int level,
+ int option,
+ char* data,
+ unsigned int* length) {
+ return NO_RETRY_EXPECTED(getsockopt(fd, level, option, data, length)) == 0;
+}
+
bool SocketBase::JoinMulticast(intptr_t fd,
const RawAddr& addr,
const RawAddr&,
diff --git a/runtime/bin/socket_base_fuchsia.cc b/runtime/bin/socket_base_fuchsia.cc
index d6c9495a..d90a48c 100644
--- a/runtime/bin/socket_base_fuchsia.cc
+++ b/runtime/bin/socket_base_fuchsia.cc
@@ -375,6 +375,26 @@
return false;
}
+bool SocketBase::SetOption(intptr_t fd,
+ int level,
+ int option,
+ const char* data,
+ int length) {
+ IOHandle* handle = reinterpret_cast<IOHandle*>(fd);
+ return NO_RETRY_EXPECTED(
+ setsockopt(handle->fd(), level, option, data, length)) == 0;
+}
+
+bool SocketBase::GetOption(intptr_t fd,
+ int level,
+ int option,
+ char* data,
+ unsigned int* length) {
+ IOHandle* handle = reinterpret_cast<IOHandle*>(fd);
+ return NO_RETRY_EXPECTED(
+ getsockopt(handle->fd(), level, option, data, length)) == 0;
+}
+
bool SocketBase::JoinMulticast(intptr_t fd,
const RawAddr& addr,
const RawAddr&,
diff --git a/runtime/bin/socket_base_linux.cc b/runtime/bin/socket_base_linux.cc
index 9fcc2ab..37c7530 100644
--- a/runtime/bin/socket_base_linux.cc
+++ b/runtime/bin/socket_base_linux.cc
@@ -391,6 +391,22 @@
sizeof(on))) == 0;
}
+bool SocketBase::SetOption(intptr_t fd,
+ int level,
+ int option,
+ const char* data,
+ int length) {
+ return NO_RETRY_EXPECTED(setsockopt(fd, level, option, data, length)) == 0;
+}
+
+bool SocketBase::GetOption(intptr_t fd,
+ int level,
+ int option,
+ char* data,
+ unsigned int* length) {
+ return NO_RETRY_EXPECTED(getsockopt(fd, level, option, data, length)) == 0;
+}
+
bool SocketBase::JoinMulticast(intptr_t fd,
const RawAddr& addr,
const RawAddr&,
diff --git a/runtime/bin/socket_base_macos.cc b/runtime/bin/socket_base_macos.cc
index ee8bd8d..4a53bac 100644
--- a/runtime/bin/socket_base_macos.cc
+++ b/runtime/bin/socket_base_macos.cc
@@ -381,6 +381,22 @@
sizeof(on))) == 0;
}
+bool SocketBase::SetOption(intptr_t fd,
+ int level,
+ int option,
+ const char* data,
+ int length) {
+ return NO_RETRY_EXPECTED(setsockopt(fd, level, option, data, length)) == 0;
+}
+
+bool SocketBase::GetOption(intptr_t fd,
+ int level,
+ int option,
+ char* data,
+ unsigned int* length) {
+ return NO_RETRY_EXPECTED(getsockopt(fd, level, option, data, length)) == 0;
+}
+
static bool JoinOrLeaveMulticast(intptr_t fd,
const RawAddr& addr,
const RawAddr& interface,
diff --git a/runtime/bin/socket_base_win.cc b/runtime/bin/socket_base_win.cc
index 0cd36f9..69d566b 100644
--- a/runtime/bin/socket_base_win.cc
+++ b/runtime/bin/socket_base_win.cc
@@ -396,6 +396,27 @@
reinterpret_cast<char*>(&on), sizeof(on)) == 0;
}
+bool SocketBase::SetOption(intptr_t fd,
+ int level,
+ int option,
+ const char* data,
+ int length) {
+ SocketHandle* handle = reinterpret_cast<SocketHandle*>(fd);
+ return setsockopt(handle->socket(), level, option, data, length) == 0;
+}
+
+bool SocketBase::GetOption(intptr_t fd,
+ int level,
+ int option,
+ char* data,
+ unsigned int* length) {
+ SocketHandle* handle = reinterpret_cast<SocketHandle*>(fd);
+ int optlen = static_cast<int>(*length);
+ auto result = getsockopt(handle->socket(), level, option, data, &optlen);
+ *length = static_cast<unsigned int>(optlen);
+ return result == 0;
+}
+
bool SocketBase::JoinMulticast(intptr_t fd,
const RawAddr& addr,
const RawAddr&,
diff --git a/runtime/bin/socket_patch.dart b/runtime/bin/socket_patch.dart
index 6b3e495..fb7b69d 100644
--- a/runtime/bin/socket_patch.dart
+++ b/runtime/bin/socket_patch.dart
@@ -29,6 +29,24 @@
}
@patch
+class RawSocketOption {
+ static final List<int> _optionsCache =
+ List<int>(_RawSocketOptions.values.length);
+
+ @patch
+ static int _getOptionValue(int key) {
+ if (key > _RawSocketOptions.values.length) {
+ throw ArgumentError.value(key, 'key');
+ }
+ _optionsCache[key] ??= _getNativeOptionValue(key);
+ return _optionsCache[key];
+ }
+
+ static int _getNativeOptionValue(int key)
+ native "RawSocketOption_GetOptionValue";
+}
+
+@patch
class InternetAddress {
@patch
static InternetAddress get LOOPBACK_IP_V4 {
@@ -1084,6 +1102,23 @@
return true;
}
+ Uint8List getRawOption(RawSocketOption option) {
+ if (option == null) throw new ArgumentError.notNull("option");
+ if (option.value == null) throw new ArgumentError.notNull("option.value");
+
+ var result = nativeGetRawOption(option.level, option.option, option.value);
+ if (result != null) throw result;
+ return option.value;
+ }
+
+ void setRawOption(RawSocketOption option) {
+ if (option == null) throw new ArgumentError.notNull("option");
+ if (option.value == null) throw new ArgumentError.notNull("option.value");
+
+ var result = nativeSetRawOption(option.level, option.option, option.value);
+ if (result != null) throw result;
+ }
+
InternetAddress multicastAddress(
InternetAddress addr, NetworkInterface interface) {
// On Mac OS using the interface index for joining IPv4 multicast groups
@@ -1146,8 +1181,12 @@
int nativeGetSocketId() native "Socket_GetSocketId";
OSError nativeGetError() native "Socket_GetError";
nativeGetOption(int option, int protocol) native "Socket_GetOption";
+ OSError nativeGetRawOption(int level, int option, Uint8List data)
+ native "Socket_GetRawOption";
OSError nativeSetOption(int option, int protocol, value)
native "Socket_SetOption";
+ OSError nativeSetRawOption(int level, int option, Uint8List data)
+ native "Socket_SetRawOption";
OSError nativeJoinMulticast(List<int> addr, List<int> interfaceAddr,
int interfaceIndex) native "Socket_JoinMulticast";
bool nativeLeaveMulticast(List<int> addr, List<int> interfaceAddr,
@@ -1377,6 +1416,10 @@
bool setOption(SocketOption option, bool enabled) =>
_socket.setOption(option, enabled);
+ Uint8List getRawOption(RawSocketOption option) =>
+ _socket.getRawOption(option);
+ void setRawOption(RawSocketOption option) => _socket.setRawOption(option);
+
_pause() {
_socket.setListening(read: false, write: false);
}
@@ -1642,6 +1685,15 @@
return _raw.setOption(option, enabled);
}
+ Uint8List getRawOption(RawSocketOption option) {
+ if (_raw == null) return null;
+ return _raw.getRawOption(option);
+ }
+
+ void setRawOption(RawSocketOption option) {
+ _raw?.setRawOption(option);
+ }
+
int get port {
if (_raw == null) throw const SocketException.closed();
;
@@ -1913,6 +1965,10 @@
_socket.close();
}
}
+
+ Uint8List getRawOption(RawSocketOption option) =>
+ _socket.getRawOption(option);
+ void setRawOption(RawSocketOption option) => _socket.setRawOption(option);
}
@pragma("vm:entry-point")
diff --git a/sdk/lib/_http/http_impl.dart b/sdk/lib/_http/http_impl.dart
index 80a5589..ecb18e0 100644
--- a/sdk/lib/_http/http_impl.dart
+++ b/sdk/lib/_http/http_impl.dart
@@ -2926,6 +2926,14 @@
return _socket.setOption(option, enabled);
}
+ Uint8List getRawOption(RawSocketOption option) {
+ return _socket.getRawOption(option);
+ }
+
+ void setRawOption(RawSocketOption option) {
+ _socket.setRawOption(option);
+ }
+
Map _toJSON(bool ref) {
return (_socket as dynamic)._toJSON(ref);
}
diff --git a/sdk/lib/_internal/js_runtime/lib/io_patch.dart b/sdk/lib/_internal/js_runtime/lib/io_patch.dart
index 40e7c73..b8619d1 100644
--- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart
+++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart
@@ -501,6 +501,14 @@
}
@patch
+class RawSocketOption {
+ @patch
+ static int _getOptionValue(int key) {
+ throw UnsupportedError("RawSocketOption._getOptionValue");
+ }
+}
+
+@patch
class SecurityContext {
@patch
factory SecurityContext({bool withTrustedRoots: false}) {
diff --git a/sdk/lib/io/secure_socket.dart b/sdk/lib/io/secure_socket.dart
index b4c0132..463a445 100644
--- a/sdk/lib/io/secure_socket.dart
+++ b/sdk/lib/io/secure_socket.dart
@@ -736,6 +736,14 @@
return _socket.setOption(option, enabled);
}
+ Uint8List getRawOption(RawSocketOption option) {
+ return _socket?.getRawOption(option);
+ }
+
+ void setRawOption(RawSocketOption option) {
+ _socket?.setRawOption(option);
+ }
+
void _eventDispatcher(RawSocketEvent event) {
try {
if (event == RawSocketEvent.read) {
diff --git a/sdk/lib/io/socket.dart b/sdk/lib/io/socket.dart
index fb46acf..02fb46a 100644
--- a/sdk/lib/io/socket.dart
+++ b/sdk/lib/io/socket.dart
@@ -395,6 +395,109 @@
const SocketOption._(this._value);
}
+// Must be kept in sync with enum in socket.cc
+enum _RawSocketOptions {
+ SOL_SOCKET, // 0
+ IPPROTO_IP, // 1
+ IP_MULTICAST_IF, // 2
+ IPPROTO_IPV6, // 3
+ IPV6_MULTICAST_IF, // 4
+ IPPROTO_TCP, // 5
+ IPPROTO_UDP, // 6
+}
+
+/// The [RawSocketOption] is used as a parameter to [Socket.setRawOption] and
+/// [RawSocket.setRawOption] to set customize the behaviour of the underlying
+/// socket.
+///
+/// It allows for fine grained control of the socket options, and its values will
+/// be passed to the underlying platform's implementation of setsockopt and
+/// getsockopt.
+class RawSocketOption {
+ /// Creates a RawSocketOption for getRawOption andSetRawOption.
+ ///
+ /// All arguments are required and must not be null.
+ ///
+ /// The level and option arguments correspond to level and optname arguments
+ /// on the get/setsockopt native calls.
+ ///
+ /// The value argument and its length correspond to the optval and length
+ /// arguments on the native call.
+ ///
+ /// For a [getRawOption] call, the value parameter will be updated after a
+ /// successful call (although its length will not be changed).
+ ///
+ /// For a [setRawOption] call, the value parameter will be used set the
+ /// option.
+ const RawSocketOption(this.level, this.option, this.value);
+
+ /// Convenience constructor for creating an int based RawSocketOption.
+ factory RawSocketOption.fromInt(int level, int option, int value) {
+ if (value == null) {
+ value = 0;
+ }
+ final Uint8List list = Uint8List(4);
+ final buffer = ByteData.view(list.buffer);
+ buffer.setInt32(0, value);
+ return RawSocketOption(level, option, list);
+ }
+
+ /// Convenience constructor for creating a bool based RawSocketOption.
+ factory RawSocketOption.fromBool(int level, int option, bool value) =>
+ RawSocketOption.fromInt(level, option, value == true ? 1 : 0);
+
+ /// The level for the option to set or get.
+ ///
+ /// See also:
+ /// * [RawSocketOption.levelSocket]
+ /// * [RawSocketOption.levelIPv4]
+ /// * [RawSocketOption.levelIPv6]
+ /// * [RawSocketOption.levelTcp]
+ /// * [RawSocketOption.levelUdp]
+ final int level;
+
+ /// The option to set or get.
+ final int option;
+
+ /// The raw data to set, or the array to write the current option value into.
+ ///
+ /// This list must be the correct length for the expected option. For most
+ /// options that take int or bool values, the length should be 4. For options
+ /// that expect a struct (such as an in_addr_t), the length should be the
+ /// correct length for that struct.
+ final Uint8List value;
+
+ /// Socket level option for SOL_SOCKET.
+ static int get levelSocket =>
+ _getOptionValue(_RawSocketOptions.SOL_SOCKET.index);
+
+ /// Socket level option for IPPROTO_IP.
+ static int get levelIPv4 =>
+ _getOptionValue(_RawSocketOptions.IPPROTO_IP.index);
+
+ /// Socket option for IP_MULTICAST_IF.
+ static int get IPv4MulticastInterface =>
+ _getOptionValue(_RawSocketOptions.IP_MULTICAST_IF.index);
+
+ /// Socket level option for IPPROTO_IPV6.
+ static int get levelIPv6 =>
+ _getOptionValue(_RawSocketOptions.IPPROTO_IPV6.index);
+
+ /// Socket option for IPV6_MULTICAST_IF.
+ static int get IPv6MulticastInterface =>
+ _getOptionValue(_RawSocketOptions.IPV6_MULTICAST_IF.index);
+
+ /// Socket level option for IPPROTO_TCP.
+ static int get levelTcp =>
+ _getOptionValue(_RawSocketOptions.IPPROTO_TCP.index);
+
+ /// Socket level option for IPPROTO_UDP.
+ static int get levelUdp =>
+ _getOptionValue(_RawSocketOptions.IPPROTO_UDP.index);
+
+ external static int _getOptionValue(int key);
+}
+
/**
* Events for the [RawSocket].
*/
@@ -573,6 +676,24 @@
* Returns [:true:] if the option was set successfully, false otherwise.
*/
bool setOption(SocketOption option, bool enabled);
+
+ /**
+ * Use [getRawOption] to get low level information about the [RawSocket]. See
+ * [RawSocketOption] for available options.
+ *
+ * Returns the [RawSocketOption.value] on success.
+ *
+ * Throws an [OSError] on failure.
+ */
+ Uint8List getRawOption(RawSocketOption option);
+
+ /**
+ * Use [setRawOption] to customize the [RawSocket]. See [RawSocketOption] for
+ * available options.
+ *
+ * Throws an [OSError] on failure.
+ */
+ void setRawOption(RawSocketOption option);
}
/**
@@ -653,6 +774,24 @@
bool setOption(SocketOption option, bool enabled);
/**
+ * Use [getRawOption] to get low level information about the [RawSocket]. See
+ * [RawSocketOption] for available options.
+ *
+ * Returns the [RawSocketOption.value] on success.
+ *
+ * Throws an [OSError] on failure.
+ */
+ Uint8List getRawOption(RawSocketOption option);
+
+ /**
+ * Use [setRawOption] to customize the [RawSocket]. See [RawSocketOption] for
+ * available options.
+ *
+ * Throws an [OSError] on failure.
+ */
+ void setRawOption(RawSocketOption option);
+
+ /**
* Returns the port used by this socket.
*/
int get port;
@@ -739,6 +878,8 @@
*
* By default this value is `null`
*/
+ @Deprecated("This property is not implemented. Use getRawOption and "
+ "setRawOption instead.")
NetworkInterface multicastInterface;
/**
@@ -805,6 +946,24 @@
* exception is thrown.
*/
void leaveMulticast(InternetAddress group, [NetworkInterface interface]);
+
+ /**
+ * Use [getRawOption] to get low level information about the [RawSocket]. See
+ * [RawSocketOption] for available options.
+ *
+ * Returns [RawSocketOption.value] on success.
+ *
+ * Throws an [OSError] on failure.
+ */
+ Uint8List getRawOption(RawSocketOption option);
+
+ /**
+ * Use [setRawOption] to customize the [RawSocket]. See [RawSocketOption] for
+ * available options.
+ *
+ * Throws an [OSError] on failure.
+ */
+ void setRawOption(RawSocketOption option);
}
class SocketException implements IOException {
diff --git a/tests/standalone_2/io/raw_datagram_socket_test.dart b/tests/standalone_2/io/raw_datagram_socket_test.dart
index a933305..5e64fdb 100644
--- a/tests/standalone_2/io/raw_datagram_socket_test.dart
+++ b/tests/standalone_2/io/raw_datagram_socket_test.dart
@@ -116,6 +116,35 @@
test(InternetAddress.loopbackIPv6, null, false);
}
+testDatagramSocketMulticastIf() {
+ test(address) async {
+ asyncStart();
+ final socket = await RawDatagramSocket.bind(address, 0);
+ RawSocketOption option;
+ if (address.type == InternetAddressType.IPv4) {
+ option = RawSocketOption(RawSocketOption.levelIPv4,
+ RawSocketOption.IPv4MulticastInterface, address.rawAddress);
+ } else {
+ // We'll need a Uint8List(4) for this option, since it will be an 4 byte
+ // word value sent into get/setsockopt.
+ option = RawSocketOption(RawSocketOption.levelIPv6,
+ RawSocketOption.IPv6MulticastInterface, Uint8List(4));
+ }
+
+ socket.setRawOption(option);
+ final getResult = socket.getRawOption(option);
+
+ if (address.type == InternetAddressType.IPv4) {
+ Expect.listEquals(getResult, address.rawAddress);
+ } else {
+ Expect.listEquals(getResult, [0, 0, 0, 0]);
+ }
+
+ asyncSuccess(socket);
+ asyncEnd();
+ }
+}
+
testBroadcast() {
test(bindAddress, broadcastAddress, enabled) {
asyncStart();
@@ -363,6 +392,7 @@
testDatagramMulticastOptions();
testDatagramSocketReuseAddress();
testDatagramSocketTtl();
+ testDatagramSocketMulticastIf();
testBroadcast();
testLoopbackMulticast();
testLoopbackMulticastError();