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