diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dce6808dcf..72262c98099 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 @@ during isolate initialization. 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 2df2e7cec4b..1b8fc0c066d 100644 --- a/DEPS +++ b/DEPS @@ -87,7 +87,7 @@ vars = { "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 1d3c21698dd..13c6f646846 100644 --- a/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart +++ b/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart @@ -500,6 +500,14 @@ class RawSynchronousSocket { } } +@patch +class RawSocketOption { + @patch + static int _getOptionValue(int key) { + throw UnsupportedError("RawSocketOption._getOptionValue"); + } +} + @patch class SecurityContext { @patch diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc index e5b885eccbc..94a0c352589 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 @@ namespace bin { 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 @@ namespace bin { 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 @@ namespace bin { 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 85cb8e01d2e..113ea22a113 100644 --- a/runtime/bin/socket.cc +++ b/runtime/bin/socket.cc @@ -315,11 +315,11 @@ void FUNCTION_NAME(Socket_Available)(Dart_NativeArguments args) { 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 @@ void FUNCTION_NAME(Socket_WriteList)(Dart_NativeArguments args) { 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 @@ void FUNCTION_NAME(Socket_SendTo)(Dart_NativeArguments args) { 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 @@ void FUNCTION_NAME(Socket_GetPort)(Dart_NativeArguments args) { 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 @@ void FUNCTION_NAME(Socket_GetType)(Dart_NativeArguments args) { 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 @@ void FUNCTION_NAME(Socket_GetSocketId)(Dart_NativeArguments args) { Socket* socket = Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 0)); intptr_t id = reinterpret_cast(socket); - Dart_SetReturnValue(args, Dart_NewInteger(id)); + Dart_SetIntegerReturnValue(args, id); } void FUNCTION_NAME(Socket_SetSocketId)(Dart_NativeArguments args) { @@ -783,7 +783,7 @@ void FUNCTION_NAME(Socket_GetOption)(Dart_NativeArguments args) { 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 @@ void FUNCTION_NAME(Socket_GetOption)(Dart_NativeArguments args) { 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 @@ void FUNCTION_NAME(Socket_GetOption)(Dart_NativeArguments args) { 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 @@ void FUNCTION_NAME(Socket_GetOption)(Dart_NativeArguments args) { 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_SetOption)(Dart_NativeArguments args) { } } +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(&data), &length); + if (Dart_IsError(data_result)) { + Dart_PropagateError(data_result); + } + + bool result = SocketBase::SetOption(socket->fd(), static_cast(level), + static_cast(option), data, + static_cast(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(&data), &length); + if (Dart_IsError(data_result)) { + Dart_PropagateError(data_result); + } + unsigned int int_length = static_cast(length); + bool result = + SocketBase::GetOption(socket->fd(), static_cast(level), + static_cast(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 f8c2f01aac0..8cab1aad769 100644 --- a/runtime/bin/socket_base.h +++ b/runtime/bin/socket_base.h @@ -182,6 +182,16 @@ class SocketBase : public AllStatic { 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 d54a24c1635..c1ed4f4394f 100644 --- a/runtime/bin/socket_base_android.cc +++ b/runtime/bin/socket_base_android.cc @@ -350,6 +350,22 @@ bool SocketBase::SetBroadcast(intptr_t fd, bool enabled) { 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 d6c9495a757..d90a48c34c9 100644 --- a/runtime/bin/socket_base_fuchsia.cc +++ b/runtime/bin/socket_base_fuchsia.cc @@ -375,6 +375,26 @@ bool SocketBase::SetBroadcast(intptr_t fd, bool enabled) { return false; } +bool SocketBase::SetOption(intptr_t fd, + int level, + int option, + const char* data, + int length) { + IOHandle* handle = reinterpret_cast(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(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 9fcc2ab9afc..37c7530e65e 100644 --- a/runtime/bin/socket_base_linux.cc +++ b/runtime/bin/socket_base_linux.cc @@ -391,6 +391,22 @@ bool SocketBase::SetBroadcast(intptr_t fd, bool enabled) { 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 ee8bd8d136b..4a53bac0303 100644 --- a/runtime/bin/socket_base_macos.cc +++ b/runtime/bin/socket_base_macos.cc @@ -381,6 +381,22 @@ bool SocketBase::SetBroadcast(intptr_t fd, bool enabled) { 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 0cd36f9fe5b..69d566bb67c 100644 --- a/runtime/bin/socket_base_win.cc +++ b/runtime/bin/socket_base_win.cc @@ -396,6 +396,27 @@ bool SocketBase::SetBroadcast(intptr_t fd, bool enabled) { reinterpret_cast(&on), sizeof(on)) == 0; } +bool SocketBase::SetOption(intptr_t fd, + int level, + int option, + const char* data, + int length) { + SocketHandle* handle = reinterpret_cast(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(fd); + int optlen = static_cast(*length); + auto result = getsockopt(handle->socket(), level, option, data, &optlen); + *length = static_cast(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 6b3e4951f3a..fb7b69d7085 100644 --- a/runtime/bin/socket_patch.dart +++ b/runtime/bin/socket_patch.dart @@ -28,6 +28,24 @@ class RawSocket { } } +@patch +class RawSocketOption { + static final List _optionsCache = + List(_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 @@ -1084,6 +1102,23 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { 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 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { 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 addr, List interfaceAddr, int interfaceIndex) native "Socket_JoinMulticast"; bool nativeLeaveMulticast(List addr, List interfaceAddr, @@ -1377,6 +1416,10 @@ class _RawSocket extends Stream implements RawSocket { 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 @@ class _Socket extends Stream> implements Socket { 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 @@ class _RawDatagramSocket extends Stream _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 80a558983d5..ecb18e0da4a 100644 --- a/sdk/lib/_http/http_impl.dart +++ b/sdk/lib/_http/http_impl.dart @@ -2926,6 +2926,14 @@ class _DetachedSocket extends Stream> implements Socket { 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 40e7c73f4a5..b8619d1d037 100644 --- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart @@ -500,6 +500,14 @@ class RawSynchronousSocket { } } +@patch +class RawSocketOption { + @patch + static int _getOptionValue(int key) { + throw UnsupportedError("RawSocketOption._getOptionValue"); + } +} + @patch class SecurityContext { @patch diff --git a/sdk/lib/io/secure_socket.dart b/sdk/lib/io/secure_socket.dart index b4c01329542..463a44568f2 100644 --- a/sdk/lib/io/secure_socket.dart +++ b/sdk/lib/io/secure_socket.dart @@ -736,6 +736,14 @@ class _RawSecureSocket extends Stream 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 fb46acfbc5c..02fb46a3f7c 100644 --- a/sdk/lib/io/socket.dart +++ b/sdk/lib/io/socket.dart @@ -395,6 +395,109 @@ class SocketOption { 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 @@ abstract class RawSocket implements Stream { * 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); } /** @@ -652,6 +773,24 @@ abstract class Socket implements Stream>, IOSink { */ 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. */ @@ -739,6 +878,8 @@ abstract class RawDatagramSocket extends Stream { * * By default this value is `null` */ + @Deprecated("This property is not implemented. Use getRawOption and " + "setRawOption instead.") NetworkInterface multicastInterface; /** @@ -805,6 +946,24 @@ abstract class RawDatagramSocket extends Stream { * 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 a9333052781..5e64fdb459f 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 @@ testDatagramSocketTtl() { 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 @@ main() { testDatagramMulticastOptions(); testDatagramSocketReuseAddress(); testDatagramSocketTtl(); + testDatagramSocketMulticastIf(); testBroadcast(); testLoopbackMulticast(); testLoopbackMulticastError();