diff --git a/CHANGELOG.md b/CHANGELOG.md index e8b8af55bcd..f28251a9830 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,23 @@ used (see Issue [39627][]). now gives `v;a=A;b="(B)";c="";d="ΓΈ";e="\\\""`. +* [Unix domain sockets](https://en.wikipedia.org/wiki/Unix_domain_socket) are + now supported on Linux, Android and MacOS, which can be used by passing a + `InternetAddress` of `InternetAddressType.Unix` into `connect`, `startConnect` + and `bind` methods. `port` argument in those methods will be ignored. Getter + of `port` will always return 0 for Unix domain sockets. + +* Class `InternetAddressType` gains one more option `Unix`, which represents a + Unix domain address. + +* Class `InternetAddress`: + * `InternetAddress` constructor gains an optional `type` parameter. To create + a Unix domain address, `type` is set to `InternetAddressType.Unix` and + `address` is a file path. + * `InternetAddress` gains a new constructor `fromRawAddress` that takes an + address in byte format for Internet addresses or raw file path for Unix + domain addresses. + #### `dart:mirrors` * Added `MirrorSystem.neverType`. diff --git a/runtime/bin/file.cc b/runtime/bin/file.cc index 0ac80653a32..886970e2f5b 100644 --- a/runtime/bin/file.cc +++ b/runtime/bin/file.cc @@ -763,7 +763,7 @@ void FUNCTION_NAME(File_AreIdentical)(Dart_NativeArguments args) { Namespace* namespc = Namespace::GetNamespace(args, 0); const char* path_1 = DartUtils::GetNativeStringArgument(args, 1); const char* path_2 = DartUtils::GetNativeStringArgument(args, 2); - File::Identical result = File::AreIdentical(namespc, path_1, path_2); + File::Identical result = File::AreIdentical(namespc, path_1, namespc, path_2); if (result == File::kError) { Dart_SetReturnValue(args, DartUtils::NewDartOSError()); } else { @@ -1455,7 +1455,7 @@ CObject* File::IdenticalRequest(const CObjectArray& request) { CObjectString path1(request[1]); CObjectString path2(request[2]); File::Identical result = - File::AreIdentical(namespc, path1.CString(), path2.CString()); + File::AreIdentical(namespc, path1.CString(), namespc, path2.CString()); if (result == File::kError) { return CObject::NewOSError(); } diff --git a/runtime/bin/file.h b/runtime/bin/file.h index ac8968889d4..642ba9e0bc6 100644 --- a/runtime/bin/file.h +++ b/runtime/bin/file.h @@ -250,8 +250,9 @@ class File : public ReferenceCounted { static const char* PathSeparator(); static const char* StringEscapedPathSeparator(); static Type GetType(Namespace* namespc, const char* path, bool follow_links); - static Identical AreIdentical(Namespace* namespc, + static Identical AreIdentical(Namespace* namespc_1, const char* file_1, + Namespace* namespc_2, const char* file_2); static StdioHandleType GetStdioHandleType(int fd); diff --git a/runtime/bin/file_android.cc b/runtime/bin/file_android.cc index 151c9694ad2..51a7599ecbf 100644 --- a/runtime/bin/file_android.cc +++ b/runtime/bin/file_android.cc @@ -702,22 +702,28 @@ File::StdioHandleType File::GetStdioHandleType(int fd) { return kOther; } -File::Identical File::AreIdentical(Namespace* namespc, +File::Identical File::AreIdentical(Namespace* namespc_1, const char* file_1, + Namespace* namespc_2, const char* file_2) { - NamespaceScope ns1(namespc, file_1); - NamespaceScope ns2(namespc, file_2); struct stat file_1_info; struct stat file_2_info; - int status = TEMP_FAILURE_RETRY( - fstatat(ns1.fd(), ns1.path(), &file_1_info, AT_SYMLINK_NOFOLLOW)); - if (status == -1) { - return File::kError; + int status; + { + NamespaceScope ns1(namespc_1, file_1); + status = TEMP_FAILURE_RETRY( + fstatat(ns1.fd(), ns1.path(), &file_1_info, AT_SYMLINK_NOFOLLOW)); + if (status == -1) { + return File::kError; + } } - status = TEMP_FAILURE_RETRY( - fstatat(ns2.fd(), ns2.path(), &file_2_info, AT_SYMLINK_NOFOLLOW)); - if (status == -1) { - return File::kError; + { + NamespaceScope ns2(namespc_2, file_2); + status = TEMP_FAILURE_RETRY( + fstatat(ns2.fd(), ns2.path(), &file_2_info, AT_SYMLINK_NOFOLLOW)); + if (status == -1) { + return File::kError; + } } return ((file_1_info.st_ino == file_2_info.st_ino) && (file_1_info.st_dev == file_2_info.st_dev)) diff --git a/runtime/bin/file_fuchsia.cc b/runtime/bin/file_fuchsia.cc index 0fde6aed5d4..354dd65eb92 100644 --- a/runtime/bin/file_fuchsia.cc +++ b/runtime/bin/file_fuchsia.cc @@ -655,22 +655,28 @@ File::StdioHandleType File::GetStdioHandleType(int fd) { return kOther; } -File::Identical File::AreIdentical(Namespace* namespc, +File::Identical File::AreIdentical(Namespace* namespc_1, const char* file_1, + Namespace* namespc_2, const char* file_2) { - NamespaceScope ns1(namespc, file_1); - NamespaceScope ns2(namespc, file_2); struct stat file_1_info; struct stat file_2_info; - int status = TEMP_FAILURE_RETRY( - fstatat(ns1.fd(), ns1.path(), &file_1_info, AT_SYMLINK_NOFOLLOW)); - if (status == -1) { - return File::kError; + int status; + { + NamespaceScope ns1(namespc_1, file_1); + status = TEMP_FAILURE_RETRY( + fstatat(ns1.fd(), ns1.path(), &file_1_info, AT_SYMLINK_NOFOLLOW)); + if (status == -1) { + return File::kError; + } } - status = TEMP_FAILURE_RETRY( - fstatat(ns2.fd(), ns2.path(), &file_2_info, AT_SYMLINK_NOFOLLOW)); - if (status == -1) { - return File::kError; + { + NamespaceScope ns2(namespc_2, file_2); + status = TEMP_FAILURE_RETRY( + fstatat(ns2.fd(), ns2.path(), &file_2_info, AT_SYMLINK_NOFOLLOW)); + if (status == -1) { + return File::kError; + } } return ((file_1_info.st_ino == file_2_info.st_ino) && (file_1_info.st_dev == file_2_info.st_dev)) diff --git a/runtime/bin/file_linux.cc b/runtime/bin/file_linux.cc index e8efaa1ddab..16c17705dd3 100644 --- a/runtime/bin/file_linux.cc +++ b/runtime/bin/file_linux.cc @@ -699,22 +699,28 @@ File::StdioHandleType File::GetStdioHandleType(int fd) { return kOther; } -File::Identical File::AreIdentical(Namespace* namespc, +File::Identical File::AreIdentical(Namespace* namespc_1, const char* file_1, + Namespace* namespc_2, const char* file_2) { - NamespaceScope ns1(namespc, file_1); - NamespaceScope ns2(namespc, file_2); struct stat64 file_1_info; struct stat64 file_2_info; - int status = TEMP_FAILURE_RETRY( - fstatat64(ns1.fd(), ns1.path(), &file_1_info, AT_SYMLINK_NOFOLLOW)); - if (status == -1) { - return File::kError; + int status; + { + NamespaceScope ns1(namespc_1, file_1); + status = TEMP_FAILURE_RETRY( + fstatat64(ns1.fd(), ns1.path(), &file_1_info, AT_SYMLINK_NOFOLLOW)); + if (status == -1) { + return File::kError; + } } - status = TEMP_FAILURE_RETRY( - fstatat64(ns2.fd(), ns2.path(), &file_2_info, AT_SYMLINK_NOFOLLOW)); - if (status == -1) { - return File::kError; + { + NamespaceScope ns2(namespc_2, file_2); + status = TEMP_FAILURE_RETRY( + fstatat64(ns2.fd(), ns2.path(), &file_2_info, AT_SYMLINK_NOFOLLOW)); + if (status == -1) { + return File::kError; + } } return ((file_1_info.st_ino == file_2_info.st_ino) && (file_1_info.st_dev == file_2_info.st_dev)) diff --git a/runtime/bin/file_macos.cc b/runtime/bin/file_macos.cc index d763adddea1..bc6ef9305b9 100644 --- a/runtime/bin/file_macos.cc +++ b/runtime/bin/file_macos.cc @@ -619,9 +619,12 @@ File::StdioHandleType File::GetStdioHandleType(int fd) { return kOther; } -File::Identical File::AreIdentical(Namespace* namespc, +File::Identical File::AreIdentical(Namespace* namespc_1, const char* file_1, + Namespace* namespc_2, const char* file_2) { + USE(namespc_1); + USE(namespc_2); struct stat file_1_info; struct stat file_2_info; if ((NO_RETRY_EXPECTED(lstat(file_1, &file_1_info)) == -1) || diff --git a/runtime/bin/file_win.cc b/runtime/bin/file_win.cc index bc4c6b1c43d..2dc6b95bc77 100644 --- a/runtime/bin/file_win.cc +++ b/runtime/bin/file_win.cc @@ -760,9 +760,12 @@ File::Type File::GetType(Namespace* namespc, return result; } -File::Identical File::AreIdentical(Namespace* namespc, +File::Identical File::AreIdentical(Namespace* namespc_1, const char* file_1, + Namespace* namespc_2, const char* file_2) { + USE(namespc_1); + USE(namespc_2); BY_HANDLE_FILE_INFORMATION file_info[2]; const char* file_names[2] = {file_1, file_2}; for (int i = 0; i < 2; ++i) { diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc index f34671c80a8..155d415f240 100644 --- a/runtime/bin/io_natives.cc +++ b/runtime/bin/io_natives.cc @@ -82,6 +82,7 @@ namespace bin { V(Filter_Process, 4) \ V(Filter_Processed, 3) \ V(InternetAddress_Parse, 1) \ + V(InternetAddress_RawAddrToString, 1) \ V(IOService_NewServicePort, 0) \ V(Namespace_Create, 2) \ V(Namespace_GetDefault, 0) \ @@ -130,11 +131,14 @@ namespace bin { V(SecurityContext_UseCertificateChainBytes, 3) \ V(ServerSocket_Accept, 2) \ V(ServerSocket_CreateBindListen, 7) \ + V(ServerSocket_CreateUnixDomainBindListen, 5) \ V(SocketBase_IsBindError, 2) \ V(Socket_Available, 1) \ V(Socket_CreateBindConnect, 5) \ + V(Socket_CreateUnixDomainBindConnect, 4) \ V(Socket_CreateBindDatagram, 6) \ V(Socket_CreateConnect, 4) \ + V(Socket_CreateUnixDomainConnect, 3) \ V(Socket_GetPort, 1) \ V(Socket_GetRemotePeer, 1) \ V(Socket_GetError, 1) \ diff --git a/runtime/bin/socket.cc b/runtime/bin/socket.cc index c96530ba7ae..1b79c666813 100644 --- a/runtime/bin/socket.cc +++ b/runtime/bin/socket.cc @@ -6,6 +6,7 @@ #include "bin/dartutils.h" #include "bin/eventhandler.h" +#include "bin/file.h" #include "bin/io_buffer.h" #include "bin/isolate_data.h" #include "bin/lockers.h" @@ -178,7 +179,7 @@ Dart_Handle ListeningSocketRegistry::CreateBindListen(Dart_Handle socket_object, Socket* socketfd = new Socket(fd); OSSocket* os_socket = - new OSSocket(addr, allocated_port, v6_only, shared, socketfd); + new OSSocket(addr, allocated_port, v6_only, shared, socketfd, NULL); os_socket->ref_count = 1; os_socket->next = first_os_socket; @@ -192,6 +193,73 @@ Dart_Handle ListeningSocketRegistry::CreateBindListen(Dart_Handle socket_object, return Dart_True(); } +Dart_Handle ListeningSocketRegistry::CreateUnixDomainBindListen( + Dart_Handle socket_object, + Namespace* namespc, + const char* path, + intptr_t backlog, + bool shared) { + MutexLocker ml(&mutex_); + + if (unix_domain_sockets_ != NULL && File::Exists(namespc, path)) { + // If there is a socket listening on this file. Ensure + // that it was created with `shared` mode and current `shared` + // is also true. + OSSocket* os_socket = unix_domain_sockets_; + OSSocket* os_socket_same_addr = + FindOSSocketWithPath(os_socket, namespc, path); + if (os_socket_same_addr != NULL) { + if (!os_socket_same_addr->shared || !shared) { + OSError os_error(-1, + "The shared flag to bind() needs to be `true` if " + "binding multiple times on the same path.", + OSError::kUnknown); + return DartUtils::NewDartOSError(&os_error); + } + + // This socket creation is the exact same as the one which originally + // created the socket. Feed the same fd and store it into the native field + // of dart socket_object. Sockets here will share same fd but contain a + // different port() through EventHandler_SendData. + Socket* socketfd = new Socket(os_socket->fd); + os_socket->ref_count++; + // We set as a side-effect the file descriptor on the dart + // socket_object. + Socket::ReuseSocketIdNativeField(socket_object, socketfd, + Socket::kFinalizerListening); + InsertByFd(socketfd, os_socket); + return Dart_True(); + } + } + + RawAddr addr; + Dart_Handle result = + SocketAddress::GetUnixDomainSockAddr(path, namespc, &addr); + if (!Dart_IsNull(result)) { + return result; + } + + // There is no socket listening on that path, so we create new one. + intptr_t fd = ServerSocket::CreateUnixDomainBindListen(addr, backlog); + + if (fd < 0) { + return DartUtils::NewDartOSError(); + } + + Socket* socketfd = new Socket(fd); + OSSocket* os_socket = + new OSSocket(addr, -1, false, shared, socketfd, namespc); + os_socket->ref_count = 1; + os_socket->next = unix_domain_sockets_; + unix_domain_sockets_ = os_socket; + InsertByFd(socketfd, os_socket); + + Socket::ReuseSocketIdNativeField(socket_object, socketfd, + Socket::kFinalizerListening); + + return Dart_True(); +} + bool ListeningSocketRegistry::CloseOneSafe(OSSocket* os_socket, Socket* socket) { ASSERT(!mutex_.TryLock()); @@ -202,7 +270,12 @@ bool ListeningSocketRegistry::CloseOneSafe(OSSocket* os_socket, if (os_socket->ref_count > 0) { return false; } - + // Unlink the socket file, if os_socket contains unix domain sockets. + if (os_socket->address.addr.sa_family == AF_UNIX) { + unlink(os_socket->address.un.sun_path); + delete os_socket; + return true; + } OSSocket* prev = NULL; OSSocket* current = LookupByPort(os_socket->port); while (current != os_socket) { @@ -298,6 +371,72 @@ void FUNCTION_NAME(Socket_CreateBindConnect)(Dart_NativeArguments args) { } } +void FUNCTION_NAME(Socket_CreateUnixDomainBindConnect)( + Dart_NativeArguments args) { +#if defined(HOST_OS_WINDOWS) || defined(HOST_OS_FUCHSIA) + OSError os_error( + -1, "Unix domain sockets are not available on this operating system.", + OSError::kUnknown); + Dart_SetReturnValue(args, DartUtils::NewDartOSError(&os_error)); +#else + RawAddr addr; + Dart_Handle address = Dart_GetNativeArgument(args, 1); + ASSERT(Dart_IsString(address)); + Dart_Handle result = SocketAddress::GetUnixDomainSockAddr( + DartUtils::GetStringValue(address), Namespace::GetNamespace(args, 3), + &addr); + if (!Dart_IsNull(result)) { + return Dart_SetReturnValue(args, result); + } + + RawAddr sourceAddr; + address = Dart_GetNativeArgument(args, 2); + ASSERT(Dart_IsString(address)); + result = SocketAddress::GetUnixDomainSockAddr( + DartUtils::GetStringValue(address), Namespace::GetNamespace(args, 3), + &sourceAddr); + if (!Dart_IsNull(result)) { + return Dart_SetReturnValue(args, result); + } + + intptr_t socket = Socket::CreateUnixDomainBindConnect(addr, sourceAddr); + if (socket >= 0) { + Socket::SetSocketIdNativeField(Dart_GetNativeArgument(args, 0), socket, + Socket::kFinalizerNormal); + Dart_SetReturnValue(args, Dart_True()); + } else { + Dart_SetReturnValue(args, DartUtils::NewDartOSError()); + } +#endif // defined(HOST_OS_WINDOWS) || defined(HOST_OS_FUCHSIA) +} + +void FUNCTION_NAME(Socket_CreateUnixDomainConnect)(Dart_NativeArguments args) { +#if defined(HOST_OS_WINDOWS) || defined(HOST_OS_FUCHSIA) + OSError os_error( + -1, "Unix domain sockets are only available on linux, android and macos.", + OSError::kUnknown); + Dart_SetReturnValue(args, DartUtils::NewDartOSError(&os_error)); +#else + RawAddr addr; + Dart_Handle address = Dart_GetNativeArgument(args, 1); + ASSERT(Dart_IsString(address)); + Dart_Handle result = SocketAddress::GetUnixDomainSockAddr( + DartUtils::GetStringValue(address), Namespace::GetNamespace(args, 2), + &addr); + if (!Dart_IsNull(result)) { + return Dart_SetReturnValue(args, result); + } + intptr_t socket = Socket::CreateUnixDomainConnect(addr); + if (socket >= 0) { + Socket::SetSocketIdNativeField(Dart_GetNativeArgument(args, 0), socket, + Socket::kFinalizerNormal); + Dart_SetReturnValue(args, Dart_True()); + } else { + Dart_SetReturnValue(args, DartUtils::NewDartOSError()); + } +#endif // defined(HOST_OS_WINDOWS) || defined(HOST_OS_FUCHSIA) +} + void FUNCTION_NAME(Socket_CreateBindDatagram)(Dart_NativeArguments args) { RawAddr addr; SocketAddress::GetSockAddr(Dart_GetNativeArgument(args, 1), &addr); @@ -423,18 +562,24 @@ void FUNCTION_NAME(Socket_RecvFrom)(Dart_NativeArguments args) { // Get the port and clear it in the sockaddr structure. int port = SocketAddress::GetAddrPort(addr); + // TODO(21403): Add checks for AF_UNIX, if unix domain sockets + // are used in SOCK_DGRAM. + enum internet_type { IPv4, IPv6 }; + internet_type type; if (addr.addr.sa_family == AF_INET) { addr.in.sin_port = 0; + type = IPv4; } else { ASSERT(addr.addr.sa_family == AF_INET6); addr.in6.sin6_port = 0; + type = IPv6; } // Format the address to a string using the numeric format. char numeric_address[INET6_ADDRSTRLEN]; SocketBase::FormatNumericAddress(addr, numeric_address, INET6_ADDRSTRLEN); // Create a Datagram object with the data and sender address and port. - const int kNumArgs = 4; + const int kNumArgs = 5; Dart_Handle dart_args[kNumArgs]; dart_args[0] = data; dart_args[1] = Dart_NewStringFromCString(numeric_address); @@ -443,6 +588,7 @@ void FUNCTION_NAME(Socket_RecvFrom)(Dart_NativeArguments args) { } dart_args[2] = SocketAddress::ToTypedData(addr); dart_args[3] = Dart_NewInteger(port); + dart_args[4] = Dart_NewInteger(type); if (Dart_IsError(dart_args[3])) { Dart_PropagateError(dart_args[3]); } @@ -550,7 +696,7 @@ void FUNCTION_NAME(Socket_GetPort)(Dart_NativeArguments args) { if (port > 0) { Dart_SetIntegerReturnValue(args, port); } else { - Dart_ThrowException(DartUtils::NewDartOSError()); + Dart_SetReturnValue(args, DartUtils::NewDartOSError()); } } @@ -561,14 +707,18 @@ void FUNCTION_NAME(Socket_GetRemotePeer)(Dart_NativeArguments args) { SocketAddress* addr = SocketBase::GetRemotePeer(socket->fd(), &port); if (addr != NULL) { Dart_Handle list = Dart_NewList(2); - - Dart_Handle entry = Dart_NewList(3); - Dart_ListSetAt(entry, 0, Dart_NewInteger(addr->GetType())); + int type = addr->GetType(); + Dart_Handle entry; + if (type == SocketAddress::TYPE_UNIX) { + entry = Dart_NewList(2); + } else { + entry = Dart_NewList(3); + RawAddr raw = addr->addr(); + Dart_ListSetAt(entry, 2, SocketAddress::ToTypedData(raw)); + } + Dart_ListSetAt(entry, 0, Dart_NewInteger(type)); Dart_ListSetAt(entry, 1, Dart_NewStringFromCString(addr->as_string())); - RawAddr raw = addr->addr(); - Dart_ListSetAt(entry, 2, SocketAddress::ToTypedData(raw)); - Dart_ListSetAt(list, 0, entry); Dart_ListSetAt(list, 1, Dart_NewInteger(port)); Dart_SetReturnValue(args, list); @@ -651,6 +801,29 @@ void FUNCTION_NAME(ServerSocket_CreateBindListen)(Dart_NativeArguments args) { Dart_SetReturnValue(args, result); } +void FUNCTION_NAME(ServerSocket_CreateUnixDomainBindListen)( + Dart_NativeArguments args) { +#if defined(HOST_OS_WINDOWS) + OSError os_error( + -1, "Unix domain sockets are only available on linux, android and macos.", + OSError::kUnknown); + Dart_SetReturnValue(args, DartUtils::NewDartOSError(&os_error)); +#else + Dart_Handle address = Dart_GetNativeArgument(args, 1); + ASSERT(Dart_IsString(address)); + const char* path = DartUtils::GetStringValue(address); + int64_t backlog = DartUtils::GetInt64ValueCheckRange( + Dart_GetNativeArgument(args, 2), 0, 65535); + bool shared = DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 3)); + Namespace* namespc = Namespace::GetNamespace(args, 4); + Dart_Handle socket_object = Dart_GetNativeArgument(args, 0); + Dart_Handle result = + ListeningSocketRegistry::Instance()->CreateUnixDomainBindListen( + socket_object, namespc, path, backlog, shared); + Dart_SetReturnValue(args, result); +#endif // defined(HOST_OS_WINDOWS) +} + void FUNCTION_NAME(ServerSocket_Accept)(Dart_NativeArguments args) { Socket* socket = Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 0)); diff --git a/runtime/bin/socket.h b/runtime/bin/socket.h index c3ea9fe81b9..caed3bbf7c1 100644 --- a/runtime/bin/socket.h +++ b/runtime/bin/socket.h @@ -7,6 +7,7 @@ #include "bin/builtin.h" #include "bin/dartutils.h" +#include "bin/file.h" #include "bin/reference_counting.h" #include "bin/socket_base.h" #include "bin/thread.h" @@ -70,10 +71,13 @@ class Socket : public ReferenceCounted { // Creates a socket which is bound and connected. The port to connect to is // specified as the port component of the passed RawAddr structure. static intptr_t CreateConnect(const RawAddr& addr); + static intptr_t CreateUnixDomainConnect(const RawAddr& addr); // Creates a socket which is bound and connected. The port to connect to is // specified as the port component of the passed RawAddr structure. static intptr_t CreateBindConnect(const RawAddr& addr, const RawAddr& source_addr); + static intptr_t CreateUnixDomainBindConnect(const RawAddr& addr, + 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, @@ -146,6 +150,8 @@ class ServerSocket { static intptr_t CreateBindListen(const RawAddr& addr, intptr_t backlog, bool v6_only = false); + static intptr_t CreateUnixDomainBindListen(const RawAddr& addr, + intptr_t backlog); // Start accepting on a newly created listening socket. If it was unable to // start accepting incoming sockets, the fd is invalidated. @@ -173,6 +179,8 @@ class ListeningSocketRegistry { static void Cleanup(); + // Bind `socket_object` to `addr`. + // Return Dart_True() if succeed. // This function should be called from a dart runtime call in order to create // a new (potentially shared) socket. Dart_Handle CreateBindListen(Dart_Handle socket_object, @@ -180,6 +188,15 @@ class ListeningSocketRegistry { intptr_t backlog, bool v6_only, bool shared); + // Bind unix domain socket`socket_object` to `path`. + // Return Dart_True() if succeed. + // This function should be called from a dart runtime call in order to create + // a new socket. + Dart_Handle CreateUnixDomainBindListen(Dart_Handle socket_object, + Namespace* namespc, + const char* path, + intptr_t backlog, + bool shared); // This should be called from the event handler for every kCloseEvent it gets // on listening sockets. @@ -202,6 +219,10 @@ class ListeningSocketRegistry { int ref_count; intptr_t fd; + // Only applicable to Unix domain socket, where address.addr.sa_family + // == AF_UNIX. + Namespace* namespc; + // Singly linked lists of OSSocket instances which listen on the same port // but on different addresses. OSSocket* next; @@ -210,12 +231,14 @@ class ListeningSocketRegistry { int port, bool v6_only, bool shared, - Socket* socketfd) + Socket* socketfd, + Namespace* namespc) : address(address), port(port), v6_only(v6_only), shared(shared), ref_count(0), + namespc(namespc), next(NULL) { fd = socketfd->fd(); } @@ -233,6 +256,20 @@ class ListeningSocketRegistry { return NULL; } + OSSocket* FindOSSocketWithPath(OSSocket* current, + Namespace* namespc, + const char* path) { + while (current != NULL) { + ASSERT(current->address.addr.sa_family == AF_UNIX); + if (File::AreIdentical(current->namespc, current->address.un.sun_path, + namespc, path) == File::kIdentical) { + return current; + } + current = current->next; + } + return NULL; + } + static bool SameIntptrValue(void* key1, void* key2) { return reinterpret_cast(key1) == reinterpret_cast(key2); } @@ -259,6 +296,8 @@ class ListeningSocketRegistry { SimpleHashMap sockets_by_port_; SimpleHashMap sockets_by_fd_; + OSSocket* unix_domain_sockets_; + Mutex mutex_; DISALLOW_COPY_AND_ASSIGN(ListeningSocketRegistry); diff --git a/runtime/bin/socket_android.cc b/runtime/bin/socket_android.cc index 78e1f8e98f1..196cb360612 100644 --- a/runtime/bin/socket_android.cc +++ b/runtime/bin/socket_android.cc @@ -37,7 +37,7 @@ static intptr_t Create(const RawAddr& addr) { if (fd < 0) { return -1; } - if (!FDUtils::SetCloseOnExec(fd)) { + if (!FDUtils::SetCloseOnExec(fd) || !FDUtils::SetNonBlocking(fd)) { FDUtils::SaveErrorAndClose(fd); return -1; } @@ -60,13 +60,23 @@ intptr_t Socket::CreateConnect(const RawAddr& addr) { return fd; } - if (!FDUtils::SetNonBlocking(fd)) { - FDUtils::SaveErrorAndClose(fd); - return -1; - } return Connect(fd, addr); } +intptr_t Socket::CreateUnixDomainConnect(const RawAddr& addr) { + intptr_t fd = Create(addr); + if (fd < 0) { + return fd; + } + intptr_t result = TEMP_FAILURE_RETRY(connect( + fd, (struct sockaddr*)&addr.un, SocketAddress::GetAddrLength(addr))); + if (result == 0 || errno == EAGAIN) { + return fd; + } + FDUtils::SaveErrorAndClose(fd); + return -1; +} + intptr_t Socket::CreateBindConnect(const RawAddr& addr, const RawAddr& source_addr) { intptr_t fd = Create(addr); @@ -76,7 +86,7 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, intptr_t result = TEMP_FAILURE_RETRY( bind(fd, &source_addr.addr, SocketAddress::GetAddrLength(source_addr))); - if ((result != 0) && (errno != EINPROGRESS)) { + if (result != 0) { FDUtils::SaveErrorAndClose(fd); return -1; } @@ -84,6 +94,29 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, return Connect(fd, addr); } +intptr_t Socket::CreateUnixDomainBindConnect(const RawAddr& addr, + const RawAddr& source_addr) { + intptr_t fd = Create(addr); + if (fd < 0) { + return fd; + } + + intptr_t result = TEMP_FAILURE_RETRY( + bind(fd, &source_addr.addr, SocketAddress::GetAddrLength(source_addr))); + if (result != 0) { + FDUtils::SaveErrorAndClose(fd); + return -1; + } + + result = TEMP_FAILURE_RETRY(connect(fd, (struct sockaddr*)&addr.un, + SocketAddress::GetAddrLength(addr))); + if (result == 0 || errno == EAGAIN) { + return fd; + } + FDUtils::SaveErrorAndClose(fd); + return -1; +} + intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress, bool reusePort, @@ -189,6 +222,21 @@ intptr_t ServerSocket::CreateBindListen(const RawAddr& addr, return fd; } +intptr_t ServerSocket::CreateUnixDomainBindListen(const RawAddr& addr, + intptr_t backlog) { + intptr_t fd = Create(addr); + if (NO_RETRY_EXPECTED(bind(fd, (struct sockaddr*)&addr.un, + SocketAddress::GetAddrLength(addr))) < 0) { + FDUtils::SaveErrorAndClose(fd); + return -1; + } + if (NO_RETRY_EXPECTED(listen(fd, backlog > 0 ? backlog : SOMAXCONN)) != 0) { + FDUtils::SaveErrorAndClose(fd); + return -1; + } + return fd; +} + bool ServerSocket::StartAccept(intptr_t fd) { USE(fd); return true; diff --git a/runtime/bin/socket_base.cc b/runtime/bin/socket_base.cc index 8789c433825..6fb402cda52 100644 --- a/runtime/bin/socket_base.cc +++ b/runtime/bin/socket_base.cc @@ -9,6 +9,7 @@ #include "bin/isolate_data.h" #include "bin/lockers.h" #include "bin/thread.h" +#include "bin/typed_data_utils.h" #include "bin/utils.h" #include "include/dart_api.h" @@ -20,16 +21,33 @@ namespace dart { namespace bin { int SocketAddress::GetType() { - if (addr_.ss.ss_family == AF_INET6) { - return TYPE_IPV6; + switch (addr_.ss.ss_family) { + case AF_INET6: + return TYPE_IPV6; + case AF_INET: + return TYPE_IPV4; + case AF_UNIX: + return TYPE_UNIX; + default: + UNREACHABLE(); + return TYPE_ANY; } - return TYPE_IPV4; } intptr_t SocketAddress::GetAddrLength(const RawAddr& addr) { - ASSERT((addr.ss.ss_family == AF_INET) || (addr.ss.ss_family == AF_INET6)); - return (addr.ss.ss_family == AF_INET6) ? sizeof(struct sockaddr_in6) - : sizeof(struct sockaddr_in); + ASSERT((addr.ss.ss_family == AF_INET) || (addr.ss.ss_family == AF_INET6) || + (addr.ss.ss_family == AF_UNIX)); + switch (addr.ss.ss_family) { + case AF_INET6: + return sizeof(struct sockaddr_in6); + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_UNIX: + return sizeof(struct sockaddr_un); + default: + UNREACHABLE(); + return 0; + } } intptr_t SocketAddress::GetInAddrLength(const RawAddr& addr) { @@ -39,18 +57,24 @@ intptr_t SocketAddress::GetInAddrLength(const RawAddr& addr) { } bool SocketAddress::AreAddressesEqual(const RawAddr& a, const RawAddr& b) { + if (a.ss.ss_family != b.ss.ss_family) { + return false; + } if (a.ss.ss_family == AF_INET) { - if (b.ss.ss_family != AF_INET) { - return false; - } return memcmp(&a.in.sin_addr, &b.in.sin_addr, sizeof(a.in.sin_addr)) == 0; } else if (a.ss.ss_family == AF_INET6) { - if (b.ss.ss_family != AF_INET6) { - return false; - } return memcmp(&a.in6.sin6_addr, &b.in6.sin6_addr, sizeof(a.in6.sin6_addr)) == 0 && a.in6.sin6_scope_id == b.in6.sin6_scope_id; + } else if (a.ss.ss_family == AF_UNIX) { + // This is not used anywhere. The comparison of file path is done via + // File::AreIdentical(). + int len = sizeof(a.un.sun_path); + for (int i = 0; i < len; i++) { + if (a.un.sun_path[i] != b.un.sun_path[i]) return false; + if (a.un.sun_path[i] == '\0') return true; + } + return true; } else { UNREACHABLE(); return false; @@ -82,6 +106,25 @@ void SocketAddress::GetSockAddr(Dart_Handle obj, RawAddr* addr) { Dart_TypedDataReleaseData(obj); } +Dart_Handle SocketAddress::GetUnixDomainSockAddr(const char* path, + Namespace* namespc, + RawAddr* addr) { +#if defined(HOST_OS_LINUX) || defined(HOST_OS_ANDROID) + NamespaceScope ns(namespc, path); + path = ns.path(); +#endif // defined(HOST_OS_LINUX) || defined(HOST_OS_ANDROID) + if (sizeof(path) > sizeof(addr->un.sun_path)) { + OSError os_error(-1, + "The length of path exceeds the limit. " + "Check out man 7 unix page", + OSError::kUnknown); + return DartUtils::NewDartOSError(&os_error); + } + addr->un.sun_family = AF_UNIX; + Utils::SNPrint(addr->un.sun_path, sizeof(addr->un.sun_path), "%s", path); + return Dart_Null(); +} + int16_t SocketAddress::FromType(int type) { if (type == TYPE_ANY) { return AF_UNSPEC; @@ -89,6 +132,9 @@ int16_t SocketAddress::FromType(int type) { if (type == TYPE_IPV4) { return AF_INET; } + if (type == TYPE_UNIX) { + return AF_UNIX; + } ASSERT((type == TYPE_IPV6) && "Invalid type"); return AF_INET6; } @@ -96,16 +142,23 @@ int16_t SocketAddress::FromType(int type) { void SocketAddress::SetAddrPort(RawAddr* addr, intptr_t port) { if (addr->ss.ss_family == AF_INET) { addr->in.sin_port = htons(port); - } else { + } else if (addr->ss.ss_family == AF_INET6) { addr->in6.sin6_port = htons(port); + } else { + UNREACHABLE(); } } intptr_t SocketAddress::GetAddrPort(const RawAddr& addr) { if (addr.ss.ss_family == AF_INET) { return ntohs(addr.in.sin_port); - } else { + } else if (addr.ss.ss_family == AF_INET6) { return ntohs(addr.in6.sin6_port); + } else if (addr.ss.ss_family == AF_UNIX) { + return 0; + } else { + UNREACHABLE(); + return -1; } } @@ -176,16 +229,27 @@ void FUNCTION_NAME(InternetAddress_Parse)(Dart_NativeArguments args) { } } +void FUNCTION_NAME(InternetAddress_RawAddrToString)(Dart_NativeArguments args) { + RawAddr addr; + SocketAddress::GetSockAddr(Dart_GetNativeArgument(args, 0), &addr); + // INET6_ADDRSTRLEN is larger than INET_ADDRSTRLEN + char str[INET6_ADDRSTRLEN]; + bool ok = SocketBase::RawAddrToString(&addr, str); + if (!ok) { + str[0] = '\0'; + } + Dart_SetReturnValue(args, ThrowIfError(DartUtils::NewString(str))); +} + void FUNCTION_NAME(NetworkInterface_ListSupported)(Dart_NativeArguments args) { - Dart_SetReturnValue(args, - Dart_NewBoolean(SocketBase::ListInterfacesSupported())); + Dart_SetBooleanReturnValue(args, SocketBase::ListInterfacesSupported()); } void FUNCTION_NAME(SocketBase_IsBindError)(Dart_NativeArguments args) { intptr_t error_number = DartUtils::GetIntptrValue(Dart_GetNativeArgument(args, 1)); bool is_bind_error = SocketBase::IsBindError(error_number); - Dart_SetReturnValue(args, is_bind_error ? Dart_True() : Dart_False()); + Dart_SetBooleanReturnValue(args, is_bind_error ? true : false); } } // namespace bin diff --git a/runtime/bin/socket_base.h b/runtime/bin/socket_base.h index 404357b7320..8af4c593c19 100644 --- a/runtime/bin/socket_base.h +++ b/runtime/bin/socket_base.h @@ -23,6 +23,7 @@ #include "bin/builtin.h" #include "bin/dartutils.h" +#include "bin/file.h" #include "bin/thread.h" #include "bin/utils.h" #include "platform/allocation.h" @@ -34,6 +35,7 @@ namespace bin { union RawAddr { struct sockaddr_in in; struct sockaddr_in6 in6; + struct sockaddr_un un; struct sockaddr_storage ss; struct sockaddr addr; }; @@ -44,6 +46,7 @@ class SocketAddress { TYPE_ANY = -1, TYPE_IPV4, TYPE_IPV6, + TYPE_UNIX, }; enum { @@ -55,7 +58,9 @@ class SocketAddress { ADDRESS_LAST = ADDRESS_ANY_IP_V6, }; - explicit SocketAddress(struct sockaddr* sa); + // Unix domain socket may be unnamed. In this case addr_.un.sun_path contains + // garbage and should not be inspected. + explicit SocketAddress(struct sockaddr* sa, bool unnamed_unix_socket = false); ~SocketAddress() {} @@ -68,6 +73,9 @@ class SocketAddress { static intptr_t GetInAddrLength(const RawAddr& addr); static bool AreAddressesEqual(const RawAddr& a, const RawAddr& b); static void GetSockAddr(Dart_Handle obj, RawAddr* addr); + static Dart_Handle GetUnixDomainSockAddr(const char* path, + Namespace* namespc, + RawAddr* addr); static int16_t FromType(int type); static void SetAddrPort(RawAddr* addr, intptr_t port); static intptr_t GetAddrPort(const RawAddr& addr); @@ -77,7 +85,17 @@ class SocketAddress { static intptr_t GetAddrScope(const RawAddr& addr); private: +#if defined(HOST_OS_LINUX) || defined(HOST_OS_MACOS) || defined(HOST_OS_ANDROID) + // Unix domain address is only on Linux, Mac OS and Android now. + // unix(7) require sun_path to be 108 bytes on Linux and Android, 104 bytes on + // Mac OS. + static const intptr_t kMaxUnixPathLength = + sizeof(((struct sockaddr_un*)0)->sun_path); + char as_string_[kMaxUnixPathLength]; +#else char as_string_[INET6_ADDRSTRLEN]; +#endif // defined(HOST_OS_LINUX) || defined(HOST_OS_MACOS) || \ + // defined(HOST_OS_ANDROID) RawAddr addr_; DISALLOW_COPY_AND_ASSIGN(SocketAddress); @@ -214,6 +232,9 @@ class SocketBase : public AllStatic { OSError** os_error); static bool ParseAddress(int type, const char* address, RawAddr* addr); + + // Convert address from byte representation to human readable string. + static bool RawAddrToString(RawAddr* addr, char* str); static bool FormatNumericAddress(const RawAddr& addr, char* address, int len); // Whether ListInterfaces is supported. diff --git a/runtime/bin/socket_base_android.cc b/runtime/bin/socket_base_android.cc index c72c276a2ac..6cea30d3883 100644 --- a/runtime/bin/socket_base_android.cc +++ b/runtime/bin/socket_base_android.cc @@ -26,11 +26,19 @@ namespace dart { namespace bin { -SocketAddress::SocketAddress(struct sockaddr* sa) { - ASSERT(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); - if (!SocketBase::FormatNumericAddress(*reinterpret_cast(sa), - as_string_, INET6_ADDRSTRLEN)) { +SocketAddress::SocketAddress(struct sockaddr* sa, bool unnamed_unix_socket) { + if (unnamed_unix_socket) { + // This is an unnamed unix domain socket. as_string_[0] = 0; + } else if (sa->sa_family == AF_UNIX) { + struct sockaddr_un* un = ((struct sockaddr_un*)sa); + memmove(as_string_, un->sun_path, sizeof(un->sun_path)); + } else { + ASSERT(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); + if (!SocketBase::FormatNumericAddress(*reinterpret_cast(sa), + as_string_, INET6_ADDRSTRLEN)) { + as_string_[0] = 0; + } } socklen_t salen = GetAddrLength(*reinterpret_cast(sa)); memmove(reinterpret_cast(&addr_), sa, salen); @@ -140,6 +148,13 @@ SocketAddress* SocketBase::GetRemotePeer(intptr_t fd, intptr_t* port) { if (NO_RETRY_EXPECTED(getpeername(fd, &raw.addr, &size))) { return NULL; } + // sockaddr_un contains sa_family_t sun_familty and char[] sun_path. + // If size is the size of sa_familty_t, this is an unnamed socket and + // sun_path contains garbage. + if (size == sizeof(sa_family_t)) { + *port = 0; + return new SocketAddress(&raw.addr, true); + } *port = SocketAddress::GetAddrPort(raw); return new SocketAddress(&raw.addr); } @@ -244,6 +259,16 @@ bool SocketBase::ParseAddress(int type, const char* address, RawAddr* addr) { return (result == 1); } +bool SocketBase::RawAddrToString(RawAddr* addr, char* str) { + if (addr->addr.sa_family == AF_INET) { + return inet_ntop(AF_INET, &addr->in.sin_addr, str, INET_ADDRSTRLEN) != NULL; + } else { + ASSERT(addr->addr.sa_family == AF_INET6); + return inet_ntop(AF_INET6, &addr->in6.sin6_addr, str, INET6_ADDRSTRLEN) != + NULL; + } +} + static bool ShouldIncludeIfaAddrs(struct ifaddrs* ifa, int lookup_family) { if (ifa->ifa_addr == NULL) { // OpenVPN's virtual device tun0. diff --git a/runtime/bin/socket_base_android.h b/runtime/bin/socket_base_android.h index fb685be41a4..9b10162667b 100644 --- a/runtime/bin/socket_base_android.h +++ b/runtime/bin/socket_base_android.h @@ -12,5 +12,6 @@ #include #include #include +#include #endif // RUNTIME_BIN_SOCKET_BASE_ANDROID_H_ diff --git a/runtime/bin/socket_base_fuchsia.cc b/runtime/bin/socket_base_fuchsia.cc index ba90aa9e08c..d956dd09d2a 100644 --- a/runtime/bin/socket_base_fuchsia.cc +++ b/runtime/bin/socket_base_fuchsia.cc @@ -54,7 +54,11 @@ namespace dart { namespace bin { -SocketAddress::SocketAddress(struct sockaddr* sa) { +SocketAddress::SocketAddress(struct sockaddr* sa, bool unnamed_unix_socket) { + // Fuchsia does not support unix domain sockets. + if (unnamed_unix_socket) { + FATAL("Fuchsia does not support unix domain sockets."); + } ASSERT(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); if (!SocketBase::FormatNumericAddress(*reinterpret_cast(sa), as_string_, INET6_ADDRSTRLEN)) { @@ -274,6 +278,16 @@ bool SocketBase::ParseAddress(int type, const char* address, RawAddr* addr) { return (result == 1); } +bool SocketBase::RawAddrToString(RawAddr* addr, char* str) { + if (addr->addr.sa_family == AF_INET) { + return inet_ntop(AF_INET, &addr->in.sin_addr, str, INET_ADDRSTRLEN) != NULL; + } else { + ASSERT(addr->addr.sa_family == AF_INET6); + return inet_ntop(AF_INET6, &addr->in6.sin6_addr, str, INET6_ADDRSTRLEN) != + NULL; + } +} + bool SocketBase::ListInterfacesSupported() { return true; } diff --git a/runtime/bin/socket_base_linux.cc b/runtime/bin/socket_base_linux.cc index 9a52d96fb88..fb1e5550535 100644 --- a/runtime/bin/socket_base_linux.cc +++ b/runtime/bin/socket_base_linux.cc @@ -26,11 +26,19 @@ namespace dart { namespace bin { -SocketAddress::SocketAddress(struct sockaddr* sa) { - ASSERT(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); - if (!SocketBase::FormatNumericAddress(*reinterpret_cast(sa), - as_string_, INET6_ADDRSTRLEN)) { +SocketAddress::SocketAddress(struct sockaddr* sa, bool unnamed_unix_socket) { + if (unnamed_unix_socket) { + // This is an unnamed unix domain socket. as_string_[0] = 0; + } else if (sa->sa_family == AF_UNIX) { + struct sockaddr_un* un = ((struct sockaddr_un*)sa); + memmove(as_string_, un->sun_path, sizeof(un->sun_path)); + } else { + ASSERT(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); + if (!SocketBase::FormatNumericAddress(*reinterpret_cast(sa), + as_string_, INET6_ADDRSTRLEN)) { + as_string_[0] = 0; + } } socklen_t salen = GetAddrLength(*reinterpret_cast(sa)); memmove(reinterpret_cast(&addr_), sa, salen); @@ -140,6 +148,13 @@ SocketAddress* SocketBase::GetRemotePeer(intptr_t fd, intptr_t* port) { if (NO_RETRY_EXPECTED(getpeername(fd, &raw.addr, &size))) { return NULL; } + // sockaddr_un contains sa_family_t sun_familty and char[] sun_path. + // If size is the size of sa_familty_t, this is an unnamed socket and + // sun_path contains garbage. + if (size == sizeof(sa_family_t)) { + *port = 0; + return new SocketAddress(&raw.addr, true); + } *port = SocketAddress::GetAddrPort(raw); return new SocketAddress(&raw.addr); } @@ -246,6 +261,16 @@ bool SocketBase::ParseAddress(int type, const char* address, RawAddr* addr) { return (result == 1); } +bool SocketBase::RawAddrToString(RawAddr* addr, char* str) { + if (addr->addr.sa_family == AF_INET) { + return inet_ntop(AF_INET, &addr->in.sin_addr, str, INET_ADDRSTRLEN) != NULL; + } else { + ASSERT(addr->addr.sa_family == AF_INET6); + return inet_ntop(AF_INET6, &addr->in6.sin6_addr, str, INET6_ADDRSTRLEN) != + NULL; + } +} + static bool ShouldIncludeIfaAddrs(struct ifaddrs* ifa, int lookup_family) { if (ifa->ifa_addr == NULL) { // OpenVPN's virtual device tun0. diff --git a/runtime/bin/socket_base_linux.h b/runtime/bin/socket_base_linux.h index 0cca9bcae3c..42f5439d76f 100644 --- a/runtime/bin/socket_base_linux.h +++ b/runtime/bin/socket_base_linux.h @@ -12,5 +12,6 @@ #include #include #include +#include #endif // RUNTIME_BIN_SOCKET_BASE_LINUX_H_ diff --git a/runtime/bin/socket_base_macos.cc b/runtime/bin/socket_base_macos.cc index a44891166a9..e8bea831e2c 100644 --- a/runtime/bin/socket_base_macos.cc +++ b/runtime/bin/socket_base_macos.cc @@ -25,11 +25,19 @@ namespace dart { namespace bin { -SocketAddress::SocketAddress(struct sockaddr* sa) { - ASSERT(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); - if (!SocketBase::FormatNumericAddress(*reinterpret_cast(sa), - as_string_, INET6_ADDRSTRLEN)) { +SocketAddress::SocketAddress(struct sockaddr* sa, bool unnamed_unix_socket) { + if (unnamed_unix_socket) { + // This is an unnamed unix domain socket. as_string_[0] = 0; + } else if (sa->sa_family == AF_UNIX) { + struct sockaddr_un* un = ((struct sockaddr_un*)sa); + memmove(as_string_, un->sun_path, sizeof(un->sun_path)); + } else { + ASSERT(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); + if (!SocketBase::FormatNumericAddress(*reinterpret_cast(sa), + as_string_, INET6_ADDRSTRLEN)) { + as_string_[0] = 0; + } } socklen_t salen = GetAddrLength(*reinterpret_cast(sa)); memmove(reinterpret_cast(&addr_), sa, salen); @@ -139,6 +147,13 @@ SocketAddress* SocketBase::GetRemotePeer(intptr_t fd, intptr_t* port) { if (NO_RETRY_EXPECTED(getpeername(fd, &raw.addr, &size))) { return NULL; } + // sockaddr_un contains sa_family_t sun_familty and char[] sun_path. + // If size is the size of sa_familty_t, this is an unnamed socket and + // sun_path contains garbage. + if (size == sizeof(sa_family_t)) { + *port = 0; + return new SocketAddress(&raw.addr, true); + } *port = SocketAddress::GetAddrPort(raw); return new SocketAddress(&raw.addr); } @@ -236,6 +251,16 @@ bool SocketBase::ParseAddress(int type, const char* address, RawAddr* addr) { return (result == 1); } +bool SocketBase::RawAddrToString(RawAddr* addr, char* str) { + if (addr->addr.sa_family == AF_INET) { + return inet_ntop(AF_INET, &addr->in.sin_addr, str, INET_ADDRSTRLEN) != NULL; + } else { + ASSERT(addr->addr.sa_family == AF_INET6); + return inet_ntop(AF_INET6, &addr->in6.sin6_addr, str, INET6_ADDRSTRLEN) != + NULL; + } +} + static bool ShouldIncludeIfaAddrs(struct ifaddrs* ifa, int lookup_family) { if (ifa->ifa_addr == NULL) { // OpenVPN's virtual device tun0. diff --git a/runtime/bin/socket_base_macos.h b/runtime/bin/socket_base_macos.h index c382401dc75..d919063d21e 100644 --- a/runtime/bin/socket_base_macos.h +++ b/runtime/bin/socket_base_macos.h @@ -12,5 +12,6 @@ #include #include #include +#include #endif // RUNTIME_BIN_SOCKET_BASE_MACOS_H_ diff --git a/runtime/bin/socket_base_win.cc b/runtime/bin/socket_base_win.cc index b24dc054bc1..4ce3fc053d9 100644 --- a/runtime/bin/socket_base_win.cc +++ b/runtime/bin/socket_base_win.cc @@ -20,7 +20,10 @@ namespace dart { namespace bin { -SocketAddress::SocketAddress(struct sockaddr* sockaddr) { +SocketAddress::SocketAddress(struct sockaddr* sockaddr, + bool unnamed_unix_socket) { + // Unix domain sockets not supported on Win. Remove this assert if enabled. + ASSERT(!unnamed_unix_socket); ASSERT(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); RawAddr* raw = reinterpret_cast(sockaddr); @@ -246,6 +249,31 @@ bool SocketBase::ParseAddress(int type, const char* address, RawAddr* addr) { return result == 1; } +bool SocketBase::RawAddrToString(RawAddr* addr, char* str) { + // According to InetNtopW(), buffer should be large enough for at least 46 + // characters for IPv6 and 16 for IPv4. + COMPILE_ASSERT(INET6_ADDRSTRLEN >= 46); + wchar_t tmp_buffer[INET6_ADDRSTRLEN]; + if (addr->addr.sa_family == AF_INET) { + if (InetNtop(AF_INET, &addr->in.sin_addr, tmp_buffer, INET_ADDRSTRLEN) == + NULL) { + return false; + } + } else { + ASSERT(addr->addr.sa_family == AF_INET6); + if (InetNtop(AF_INET6, &addr->in6.sin6_addr, tmp_buffer, + INET6_ADDRSTRLEN) == NULL) { + return false; + } + } + WideToUtf8Scope wide_to_utf8_scope(tmp_buffer); + if (wide_to_utf8_scope.length() <= INET6_ADDRSTRLEN) { + strncpy(str, wide_to_utf8_scope.utf8(), INET6_ADDRSTRLEN); + return true; + } + return false; +} + bool SocketBase::ListInterfacesSupported() { return true; } diff --git a/runtime/bin/socket_base_win.h b/runtime/bin/socket_base_win.h index 55ab91d907e..9bfbe8c99c7 100644 --- a/runtime/bin/socket_base_win.h +++ b/runtime/bin/socket_base_win.h @@ -9,6 +9,7 @@ #error Do not include socket_base_win.h directly. Use socket_base.h. #endif +#include #include #include #include diff --git a/runtime/bin/socket_fuchsia.cc b/runtime/bin/socket_fuchsia.cc index b4c0b102ec8..fa8a95837f5 100644 --- a/runtime/bin/socket_fuchsia.cc +++ b/runtime/bin/socket_fuchsia.cc @@ -109,12 +109,25 @@ intptr_t Socket::CreateConnect(const RawAddr& addr) { return Connect(fd, addr); } +intptr_t Socket::CreateUnixDomainConnect(const RawAddr& addr) { + // Fuchsia does not support unix domain socket + errno = ENOSYS; + return -1; +} + intptr_t Socket::CreateBindConnect(const RawAddr& addr, const RawAddr& source_addr) { errno = ENOSYS; return -1; } +intptr_t Socket::CreateUnixDomainBindConnect(const RawAddr& addr, + const RawAddr& source_addr) { + // Fuchsia does not support unix domain socket + errno = ENOSYS; + return -1; +} + intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress, bool reusePort, @@ -192,6 +205,13 @@ intptr_t ServerSocket::CreateBindListen(const RawAddr& addr, return reinterpret_cast(io_handle); } +intptr_t ServerSocket::CreateUnixDomainBindListen(const RawAddr& addr, + intptr_t backlog) { + // Fuchsia does not support unix domain socket. + errno = ENOSYS; + return -1; +} + bool ServerSocket::StartAccept(intptr_t fd) { USE(fd); return true; diff --git a/runtime/bin/socket_linux.cc b/runtime/bin/socket_linux.cc index c993fcb8ee3..e6b7e5cb1e6 100644 --- a/runtime/bin/socket_linux.cc +++ b/runtime/bin/socket_linux.cc @@ -48,7 +48,7 @@ static intptr_t Connect(intptr_t fd, const RawAddr& addr) { if ((result == 0) || (errno == EINPROGRESS)) { return fd; } - FDUtils::FDUtils::SaveErrorAndClose(fd); + FDUtils::SaveErrorAndClose(fd); return -1; } @@ -60,6 +60,20 @@ intptr_t Socket::CreateConnect(const RawAddr& addr) { return Connect(fd, addr); } +intptr_t Socket::CreateUnixDomainConnect(const RawAddr& addr) { + intptr_t fd = Create(addr); + if (fd < 0) { + return fd; + } + intptr_t result = TEMP_FAILURE_RETRY(connect( + fd, (struct sockaddr*)&addr.un, SocketAddress::GetAddrLength(addr))); + if (result == 0 || errno == EAGAIN) { + return fd; + } + FDUtils::SaveErrorAndClose(fd); + return -1; +} + intptr_t Socket::CreateBindConnect(const RawAddr& addr, const RawAddr& source_addr) { intptr_t fd = Create(addr); @@ -69,7 +83,7 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, intptr_t result = TEMP_FAILURE_RETRY( bind(fd, &source_addr.addr, SocketAddress::GetAddrLength(source_addr))); - if ((result != 0) && (errno != EINPROGRESS)) { + if (result != 0) { FDUtils::SaveErrorAndClose(fd); return -1; } @@ -77,6 +91,29 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, return Connect(fd, addr); } +intptr_t Socket::CreateUnixDomainBindConnect(const RawAddr& addr, + const RawAddr& source_addr) { + intptr_t fd = Create(addr); + if (fd < 0) { + return fd; + } + + intptr_t result = TEMP_FAILURE_RETRY( + bind(fd, &source_addr.addr, SocketAddress::GetAddrLength(source_addr))); + if (result != 0) { + FDUtils::SaveErrorAndClose(fd); + return -1; + } + + result = TEMP_FAILURE_RETRY(connect(fd, (struct sockaddr*)&addr.un, + SocketAddress::GetAddrLength(addr))); + if (result == 0 || errno == EAGAIN) { + return fd; + } + FDUtils::SaveErrorAndClose(fd); + return -1; +} + intptr_t Socket::CreateBindDatagram(const RawAddr& addr, bool reuseAddress, bool reusePort, @@ -182,6 +219,21 @@ intptr_t ServerSocket::CreateBindListen(const RawAddr& addr, return fd; } +intptr_t ServerSocket::CreateUnixDomainBindListen(const RawAddr& addr, + intptr_t backlog) { + intptr_t fd = Create(addr); + if (NO_RETRY_EXPECTED(bind(fd, (struct sockaddr*)&addr.un, + sizeof(struct sockaddr_un))) < 0) { + FDUtils::SaveErrorAndClose(fd); + return -1; + } + if (NO_RETRY_EXPECTED(listen(fd, backlog > 0 ? backlog : SOMAXCONN)) != 0) { + FDUtils::SaveErrorAndClose(fd); + return -1; + } + return fd; +} + bool ServerSocket::StartAccept(intptr_t fd) { USE(fd); return true; diff --git a/runtime/bin/socket_macos.cc b/runtime/bin/socket_macos.cc index f87482e08ea..8c6be8bf54f 100644 --- a/runtime/bin/socket_macos.cc +++ b/runtime/bin/socket_macos.cc @@ -66,6 +66,14 @@ intptr_t Socket::CreateConnect(const RawAddr& addr) { return Connect(fd, addr); } +intptr_t Socket::CreateUnixDomainConnect(const RawAddr& addr) { + intptr_t fd = Create(addr); + if (fd < 0) { + return fd; + } + return Connect(fd, addr); +} + intptr_t Socket::CreateBindConnect(const RawAddr& addr, const RawAddr& source_addr) { intptr_t fd = Create(addr); @@ -75,7 +83,24 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, intptr_t result = TEMP_FAILURE_RETRY( bind(fd, &source_addr.addr, SocketAddress::GetAddrLength(source_addr))); - if ((result != 0) && (errno != EINPROGRESS)) { + if (result != 0) { + FDUtils::SaveErrorAndClose(fd); + return -1; + } + + return Connect(fd, addr); +} + +intptr_t Socket::CreateUnixDomainBindConnect(const RawAddr& addr, + const RawAddr& source_addr) { + intptr_t fd = Create(addr); + if (fd < 0) { + return fd; + } + + intptr_t result = TEMP_FAILURE_RETRY( + bind(fd, &source_addr.addr, SocketAddress::GetAddrLength(source_addr))); + if (result != 0) { FDUtils::SaveErrorAndClose(fd); return -1; } @@ -199,6 +224,37 @@ intptr_t ServerSocket::CreateBindListen(const RawAddr& addr, return fd; } +intptr_t ServerSocket::CreateUnixDomainBindListen(const RawAddr& addr, + intptr_t backlog) { + intptr_t fd; + fd = NO_RETRY_EXPECTED(socket(addr.ss.ss_family, SOCK_STREAM, 0)); + if (fd < 0) { + return -1; + } + + if (!FDUtils::SetCloseOnExec(fd)) { + FDUtils::SaveErrorAndClose(fd); + return -1; + } + + if (NO_RETRY_EXPECTED( + bind(fd, &addr.addr, SocketAddress::GetAddrLength(addr))) < 0) { + FDUtils::SaveErrorAndClose(fd); + return -1; + } + + if (NO_RETRY_EXPECTED(listen(fd, backlog > 0 ? backlog : SOMAXCONN)) != 0) { + FDUtils::SaveErrorAndClose(fd); + return -1; + } + + if (!FDUtils::SetNonBlocking(fd)) { + FDUtils::SaveErrorAndClose(fd); + return -1; + } + return fd; +} + bool ServerSocket::StartAccept(intptr_t fd) { USE(fd); return true; diff --git a/runtime/bin/socket_win.cc b/runtime/bin/socket_win.cc index 5d476e9ed2f..4e393dcb99a 100644 --- a/runtime/bin/socket_win.cc +++ b/runtime/bin/socket_win.cc @@ -131,6 +131,13 @@ intptr_t Socket::CreateConnect(const RawAddr& addr) { return Connect(fd, addr, bind_addr); } +intptr_t Socket::CreateUnixDomainConnect(const RawAddr& addr) { + // TODO(21403): Support unix domain socket on Windows + // https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ + SetLastError(ERROR_NOT_SUPPORTED); + return -1; +} + intptr_t Socket::CreateBindConnect(const RawAddr& addr, const RawAddr& source_addr) { intptr_t fd = Create(addr); @@ -141,6 +148,12 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, return Connect(fd, addr, source_addr); } +intptr_t Socket::CreateUnixDomainBindConnect(const RawAddr& addr, + const RawAddr& source_addr) { + SetLastError(ERROR_NOT_SUPPORTED); + return -1; +} + intptr_t ServerSocket::Accept(intptr_t fd) { ListenSocket* listen_socket = reinterpret_cast(fd); ClientSocket* client_socket = listen_socket->Accept(); @@ -271,6 +284,14 @@ intptr_t ServerSocket::CreateBindListen(const RawAddr& addr, return reinterpret_cast(listen_socket); } +intptr_t ServerSocket::CreateUnixDomainBindListen(const RawAddr& addr, + intptr_t backlog) { + // TODO(21403): Support unix domain socket on Windows + // https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ + SetLastError(ERROR_NOT_SUPPORTED); + return -1; +} + bool ServerSocket::StartAccept(intptr_t fd) { ListenSocket* listen_socket = reinterpret_cast(fd); listen_socket->EnsureInitialized(EventHandler::delegate()); diff --git a/runtime/bin/sync_socket_android.cc b/runtime/bin/sync_socket_android.cc index b051e3adb6c..80094f728e6 100644 --- a/runtime/bin/sync_socket_android.cc +++ b/runtime/bin/sync_socket_android.cc @@ -38,7 +38,7 @@ static intptr_t Connect(intptr_t fd, const RawAddr& addr) { return fd; } ASSERT(errno != EINPROGRESS); - FDUtils::FDUtils::SaveErrorAndClose(fd); + FDUtils::SaveErrorAndClose(fd); return -1; } diff --git a/runtime/bin/sync_socket_fuchsia.cc b/runtime/bin/sync_socket_fuchsia.cc index 3a6fe0d2540..23461f4ff4d 100644 --- a/runtime/bin/sync_socket_fuchsia.cc +++ b/runtime/bin/sync_socket_fuchsia.cc @@ -38,7 +38,7 @@ static intptr_t Connect(intptr_t fd, const RawAddr& addr) { return fd; } ASSERT(errno != EINPROGRESS); - FDUtils::FDUtils::SaveErrorAndClose(fd); + FDUtils::SaveErrorAndClose(fd); return -1; } diff --git a/runtime/bin/sync_socket_linux.cc b/runtime/bin/sync_socket_linux.cc index 1fb564bfaad..963787bf7b9 100644 --- a/runtime/bin/sync_socket_linux.cc +++ b/runtime/bin/sync_socket_linux.cc @@ -38,7 +38,7 @@ static intptr_t Connect(intptr_t fd, const RawAddr& addr) { return fd; } ASSERT(errno != EINPROGRESS); - FDUtils::FDUtils::SaveErrorAndClose(fd); + FDUtils::SaveErrorAndClose(fd); return -1; } diff --git a/runtime/bin/sync_socket_macos.cc b/runtime/bin/sync_socket_macos.cc index 5ee9b68c81f..e266341be87 100644 --- a/runtime/bin/sync_socket_macos.cc +++ b/runtime/bin/sync_socket_macos.cc @@ -41,7 +41,7 @@ static intptr_t Connect(intptr_t fd, const RawAddr& addr) { return fd; } ASSERT(errno != EINPROGRESS); - FDUtils::FDUtils::SaveErrorAndClose(fd); + FDUtils::SaveErrorAndClose(fd); return -1; } diff --git a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart index 514ffcb525b..48a0969e8c2 100644 --- a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart +++ b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart @@ -406,9 +406,16 @@ class InternetAddress { } @patch - factory InternetAddress(String address) { + factory InternetAddress(String address, {InternetAddressType type}) { throw UnsupportedError("InternetAddress"); } + + @patch + factory InternetAddress.fromRawAddress(Uint8List rawAddress, + {InternetAddressType type}) { + throw new UnsupportedError("InternetAddress.fromRawAddress"); + } + @patch static Future> lookup(String host, {InternetAddressType type = InternetAddressType.any}) { diff --git a/sdk/lib/_internal/js_runtime/lib/io_patch.dart b/sdk/lib/_internal/js_runtime/lib/io_patch.dart index 37aa15c0109..146776c1d14 100644 --- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart @@ -406,9 +406,16 @@ class InternetAddress { } @patch - factory InternetAddress(String address) { + factory InternetAddress(String address, {InternetAddressType type}) { throw new UnsupportedError("InternetAddress"); } + + @patch + factory InternetAddress.fromRawAddress(Uint8List rawAddress, + {InternetAddressType type}) { + throw new UnsupportedError("InternetAddress.fromRawAddress"); + } + @patch static Future> lookup(String host, {InternetAddressType type: InternetAddressType.any}) { diff --git a/sdk/lib/_internal/vm/bin/socket_patch.dart b/sdk/lib/_internal/vm/bin/socket_patch.dart index 8d5bbe37b77..3fe415d6307 100644 --- a/sdk/lib/_internal/vm/bin/socket_patch.dart +++ b/sdk/lib/_internal/vm/bin/socket_patch.dart @@ -71,8 +71,14 @@ class InternetAddress { } @patch - factory InternetAddress(String address) { - return new _InternetAddress.parse(address); + factory InternetAddress(String address, {InternetAddressType type}) { + return _InternetAddress.fromString(address, type: type); + } + + @patch + factory InternetAddress.fromRawAddress(Uint8List rawAddress, + {InternetAddressType type}) { + return _InternetAddress.fromRawAddress(rawAddress, type: type); } @patch @@ -140,10 +146,7 @@ class _InternetAddress implements InternetAddress { final String _host; final Uint8List _in_addr; final int _scope_id; - - InternetAddressType get type => _in_addr.length == _IPv4AddrLength - ? InternetAddressType.IPv4 - : InternetAddressType.IPv6; + final InternetAddressType type; String get host => _host != null ? _host : address; @@ -159,6 +162,9 @@ class _InternetAddress implements InternetAddress { if (_in_addr[i] != 0) return false; } return _in_addr[_IPv6AddrLength - 1] == 1; + + case InternetAddressType.unix: + return false; } } @@ -171,6 +177,9 @@ class _InternetAddress implements InternetAddress { case InternetAddressType.IPv6: // Checking for fe80::/10. return _in_addr[0] == 0xFE && (_in_addr[1] & 0xB0) == 0x80; + + case InternetAddressType.unix: + return false; } } @@ -183,57 +192,103 @@ class _InternetAddress implements InternetAddress { case InternetAddressType.IPv6: // Checking for ff00::/8. return _in_addr[0] == 0xFF; + + case InternetAddressType.unix: + return false; } } - Future reverse() => _NativeSocket.reverseLookup(this); + Future reverse() { + if (type == InternetAddressType.unix) { + return Future.value(this); + } + return _NativeSocket.reverseLookup(this); + } - _InternetAddress(this.address, this._host, this._in_addr, + _InternetAddress(this.type, this.address, this._host, this._in_addr, [this._scope_id = 0]); - factory _InternetAddress.parse(String address) { - if (address is! String) { - throw new ArgumentError("Invalid internet address $address"); + factory _InternetAddress.fromString(String address, + {InternetAddressType type}) { + if (type == InternetAddressType.unix) { + ArgumentError.checkNotNull(address, 'address'); + var rawAddress = FileSystemEntity._toUtf8Array(address); + return _InternetAddress( + InternetAddressType.unix, address, null, rawAddress); + } else { + if (address is! String) { + throw ArgumentError("Invalid internet address $address"); + } + var in_addr = _parse(address); + if (in_addr == null) { + throw ArgumentError("Invalid internet address $address"); + } + InternetAddressType type = in_addr.length == _IPv4AddrLength + ? InternetAddressType.IPv4 + : InternetAddressType.IPv6; + return _InternetAddress(type, address, null, in_addr); } - var in_addr = _parse(address); - if (in_addr == null) { - throw new ArgumentError("Invalid internet address $address"); + } + + factory _InternetAddress.fromRawAddress(Uint8List rawAddress, + {InternetAddressType type}) { + if (type == InternetAddressType.unix) { + ArgumentError.checkNotNull(rawAddress, 'rawAddress'); + var rawPath = FileSystemEntity._toNullTerminatedUtf8Array(rawAddress); + var address = FileSystemEntity._toStringFromUtf8Array(rawAddress); + return _InternetAddress(InternetAddressType.unix, address, null, rawPath); + } else { + int type = -1; + if (rawAddress.length == _IPv4AddrLength) { + type = 0; + } else { + if (rawAddress.length != _IPv6AddrLength) { + throw ArgumentError("Invalid internet address ${rawAddress}"); + } + type = 1; + } + var address = _rawAddrToString(rawAddress); + return _InternetAddress( + InternetAddressType._from(type), address, null, rawAddress); } - return new _InternetAddress(address, null, in_addr); } factory _InternetAddress.fixed(int id) { switch (id) { case _addressLoopbackIPv4: - var in_addr = new Uint8List(_IPv4AddrLength); + var in_addr = Uint8List(_IPv4AddrLength); in_addr[0] = 127; in_addr[_IPv4AddrLength - 1] = 1; - return new _InternetAddress("127.0.0.1", null, in_addr); + return _InternetAddress( + InternetAddressType.IPv4, "127.0.0.1", null, in_addr); case _addressLoopbackIPv6: - var in_addr = new Uint8List(_IPv6AddrLength); + var in_addr = Uint8List(_IPv6AddrLength); in_addr[_IPv6AddrLength - 1] = 1; - return new _InternetAddress("::1", null, in_addr); + return _InternetAddress(InternetAddressType.IPv6, "::1", null, in_addr); case _addressAnyIPv4: - var in_addr = new Uint8List(_IPv4AddrLength); - return new _InternetAddress("0.0.0.0", "0.0.0.0", in_addr); + var in_addr = Uint8List(_IPv4AddrLength); + return _InternetAddress( + InternetAddressType.IPv4, "0.0.0.0", "0.0.0.0", in_addr); case _addressAnyIPv6: - var in_addr = new Uint8List(_IPv6AddrLength); - return new _InternetAddress("::", "::", in_addr); + var in_addr = Uint8List(_IPv6AddrLength); + return _InternetAddress(InternetAddressType.IPv6, "::", "::", in_addr); default: assert(false); - throw new ArgumentError(); + throw ArgumentError(); } } // Create a clone of this _InternetAddress replacing the host. _InternetAddress _cloneWithNewHost(String host) { - return new _InternetAddress( - address, host, new Uint8List.fromList(_in_addr)); + return _InternetAddress(type, address, host, Uint8List.fromList(_in_addr)); } bool operator ==(other) { if (!(other is _InternetAddress)) return false; if (other.type != type) return false; + if (type == InternetAddressType.unix) { + return address == other.address; + } bool equals = true; for (int i = 0; i < _in_addr.length && equals; i++) { equals = other._in_addr[i] == _in_addr[i]; @@ -242,6 +297,9 @@ class _InternetAddress implements InternetAddress { } int get hashCode { + if (type == InternetAddressType.unix) { + return address.hashCode; + } int result = 1; for (int i = 0; i < _in_addr.length; i++) { result = (result * 31 + _in_addr[i]) & 0x3FFFFFFF; @@ -253,6 +311,9 @@ class _InternetAddress implements InternetAddress { return "InternetAddress('$address', ${type.name})"; } + static String _rawAddrToString(Uint8List address) + native "InternetAddress_RawAddrToString"; + static Uint8List _parse(String address) native "InternetAddress_Parse"; } @@ -394,8 +455,8 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { throw createError(response, "Failed host lookup: '$host'"); } else { return response.skip(1).map((result) { - var type = new InternetAddressType._from(result[0]); - return new _InternetAddress(result[1], host, result[2], result[3]); + var type = InternetAddressType._from(result[0]); + return _InternetAddress(type, result[1], host, result[2], result[3]); }).toList(); } }); @@ -423,10 +484,10 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { } else { var map = response.skip(1).fold(new Map(), (map, result) { - var type = new InternetAddressType._from(result[0]); + var type = InternetAddressType._from(result[0]); var name = result[3]; var index = result[4]; - var address = new _InternetAddress(result[1], "", result[2]); + var address = _InternetAddress(type, result[1], "", result[2]); if (!includeLinkLocal && address.isLinkLocal) return map; if (!includeLoopback && address.isLoopback) return map; map.putIfAbsent(name, () => new _NetworkInterface(name, index)); @@ -505,12 +566,23 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { socket.localAddress = address; var result; if (sourceAddress == null) { - result = socket.nativeCreateConnect( - address._in_addr, port, address._scope_id); + if (address.type == InternetAddressType.unix) { + result = socket.nativeCreateUnixDomainConnect( + address.address, _Namespace._namespace); + } else { + result = socket.nativeCreateConnect( + address._in_addr, port, address._scope_id); + } } else { assert(sourceAddress is _InternetAddress); - result = socket.nativeCreateBindConnect(address._in_addr, port, - sourceAddress._in_addr, address._scope_id); + if (address.type == InternetAddressType.unix) { + assert(sourceAddress.type == InternetAddressType.unix); + result = socket.nativeCreateUnixDomainBindConnect( + address.address, sourceAddress.address, _Namespace._namespace); + } else { + result = socket.nativeCreateBindConnect(address._in_addr, port, + sourceAddress._in_addr, address._scope_id); + } } if (result is OSError) { // Keep first error, if present. @@ -642,8 +714,18 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { var socket = new _NativeSocket.listen(); socket.localAddress = address; - var result = socket.nativeCreateBindListen( - address._in_addr, port, backlog, v6Only, shared, address._scope_id); + var result; + if (address.type == InternetAddressType.unix) { + var path = address.address; + if (FileSystemEntity.isLinkSync(path)) { + path = Link(path).targetSync(); + } + result = socket.nativeCreateUnixDomainBindListen( + path, backlog, shared, _Namespace._namespace); + } else { + result = socket.nativeCreateBindListen( + address._in_addr, port, backlog, v6Only, shared, address._scope_id); + } if (result is OSError) { throw new SocketException("Failed to create server socket", osError: result, address: address, port: port); @@ -885,12 +967,14 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { } int get port { + if (localAddress.type == InternetAddressType.unix) return 0; if (localPort != 0) return localPort; if (isClosing || isClosed) throw const SocketException.closed(); return localPort = nativeGetPort(); } int get remotePort { + if (localAddress.type == InternetAddressType.unix) return 0; if (isClosing || isClosed) throw const SocketException.closed(); return nativeGetRemotePeer()[1]; } @@ -902,7 +986,11 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { var result = nativeGetRemotePeer(); var addr = result[0]; var type = new InternetAddressType._from(addr[0]); - return new _InternetAddress(addr[1], null, addr[2]); + if (type == InternetAddressType.unix) { + return _InternetAddress.fromString(addr[1], + type: InternetAddressType.unix); + } + return _InternetAddress(type, addr[1], null, addr[2]); } void issueReadEvent() { @@ -1247,11 +1335,17 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { int port) native "Socket_SendTo"; nativeCreateConnect(Uint8List addr, int port, int scope_id) native "Socket_CreateConnect"; + nativeCreateUnixDomainConnect(String addr, _Namespace namespace) + native "Socket_CreateUnixDomainConnect"; nativeCreateBindConnect(Uint8List addr, int port, Uint8List sourceAddr, int scope_id) native "Socket_CreateBindConnect"; + nativeCreateUnixDomainBindConnect(String addr, String sourceAddr, + _Namespace namespace) native "Socket_CreateUnixDomainBindConnect"; bool isBindError(int errorNumber) native "SocketBase_IsBindError"; nativeCreateBindListen(Uint8List addr, int port, int backlog, bool v6Only, bool shared, int scope_id) native "ServerSocket_CreateBindListen"; + nativeCreateUnixDomainBindListen(String addr, int backlog, bool shared, + _Namespace namespace) native "ServerSocket_CreateUnixDomainBindListen"; nativeCreateBindDatagram(Uint8List addr, int port, bool reuseAddress, bool reusePort, int ttl) native "Socket_CreateBindDatagram"; bool nativeAccept(_NativeSocket socket) native "ServerSocket_Accept"; @@ -2090,6 +2184,9 @@ class _RawDatagramSocket extends Stream @pragma("vm:entry-point", "call") Datagram _makeDatagram( - Uint8List data, String address, Uint8List in_addr, int port) { - return new Datagram(data, new _InternetAddress(address, null, in_addr), port); + Uint8List data, String address, Uint8List in_addr, int port, int type) { + return new Datagram( + data, + _InternetAddress(InternetAddressType._from(type), address, null, in_addr), + port); } diff --git a/sdk/lib/_internal/vm/bin/sync_socket_patch.dart b/sdk/lib/_internal/vm/bin/sync_socket_patch.dart index d98649ed57a..bccdd4fce0a 100644 --- a/sdk/lib/_internal/vm/bin/sync_socket_patch.dart +++ b/sdk/lib/_internal/vm/bin/sync_socket_patch.dart @@ -144,7 +144,12 @@ class _NativeSynchronousSocket extends _NativeSynchronousSocketNativeWrapper { throw result; } var addr = result[0]; - return new _InternetAddress(addr[1], null, addr[2]); + var type = InternetAddressType._from(addr[0]); + if (type == InternetAddressType.unix) { + return _InternetAddress.fromString(addr[1], + type: InternetAddressType.unix); + } + return _InternetAddress(type, addr[1], null, addr[2]); } int get remotePort { @@ -188,7 +193,8 @@ class _NativeSynchronousSocket extends _NativeSynchronousSocketNativeWrapper { new List<_InternetAddress>(response.length); for (int i = 0; i < response.length; ++i) { var result = response[i]; - addresses[i] = new _InternetAddress(result[1], host, result[2]); + var type = InternetAddressType._from(result[0]); + addresses[i] = _InternetAddress(type, result[1], host, result[2]); } return addresses; } diff --git a/sdk/lib/io/socket.dart b/sdk/lib/io/socket.dart index 2b7edeb76bd..283a30a6ba3 100644 --- a/sdk/lib/io/socket.dart +++ b/sdk/lib/io/socket.dart @@ -8,11 +8,15 @@ part of dart.io; /** * [InternetAddressType] is the type an [InternetAddress]. Currently, - * IP version 4 (IPv4) and IP version 6 (IPv6) are supported. + * IP version 4 (IPv4), IP version 6 (IPv6) and Unix domain address are + * supported. Unix domain sockets are available only on Linux, MacOS and + * Android. */ class InternetAddressType { static const InternetAddressType IPv4 = const InternetAddressType._(0); static const InternetAddressType IPv6 = const InternetAddressType._(1); + @Since("2.8") + static const InternetAddressType unix = const InternetAddressType._(2); static const InternetAddressType any = const InternetAddressType._(-1); @Deprecated("Use IPv4 instead") @@ -27,8 +31,9 @@ class InternetAddressType { const InternetAddressType._(this._value); factory InternetAddressType._from(int value) { - if (value == 0) return IPv4; - if (value == 1) return IPv6; + if (value == IPv4._value) return IPv4; + if (value == IPv6._value) return IPv6; + if (value == unix._value) return unix; throw new ArgumentError("Invalid type: $value"); } @@ -43,6 +48,8 @@ class InternetAddressType { return "IPv4"; case 1: return "IPv6"; + case 2: + return "Unix"; default: throw new ArgumentError("Invalid InternetAddress"); } @@ -52,7 +59,7 @@ class InternetAddressType { } /** - * An internet address. + * An internet address or a Unix domain address. * * This object holds an internet address. If this internet address * is the result of a DNS lookup, the address also holds the hostname @@ -95,27 +102,35 @@ abstract class InternetAddress { external static InternetAddress get ANY_IP_V6; /** - * The [type] of the [InternetAddress] specified what IP protocol. + * The address family of the [InternetAddress]. */ InternetAddressType get type; /** - * The numeric address of the host. For IPv4 addresses this is using - * the dotted-decimal notation. For IPv6 it is using the - * hexadecimal representation. + * The numeric address of the host. + * + * For IPv4 addresses this is using the dotted-decimal notation. + * For IPv6 it is using the hexadecimal representation. + * For Unix domain addresses, this is a file path. */ String get address; /** - * The host used to lookup the address. If there is no host - * associated with the address this returns the numeric address. + * The host used to lookup the address. + * + * If there is no host associated with the address this returns the [address]. */ String get host; /** - * Get the raw address of this [InternetAddress]. The result is either a - * 4 or 16 byte long list. The returned list is a copy, making it possible - * to change the list without modifying the [InternetAddress]. + * The raw address of this [InternetAddress]. + * + * For an IP address, the result is either a 4 or 16 byte long list. + * For a Unix domain address, UTF-8 encoded byte sequences that represents + * [address] is returned. + * + * The returned list is a fresh copy, making it possible to change the list without + * modifying the [InternetAddress]. */ Uint8List get rawAddress; @@ -135,17 +150,48 @@ abstract class InternetAddress { bool get isMulticast; /** - * Creates a new [InternetAddress] from a numeric address. + * Creates a new [InternetAddress] from a numeric address or a file path. * - * If the address in [address] is not a numeric IPv4 - * (dotted-decimal notation) or IPv6 (hexadecimal representation). - * address [ArgumentError] is thrown. + * If [type] is [InternetAddressType.IPv4], [address] must be a numeric IPv4 + * address (dotted-decimal notation). + * If [type] is [InternetAddressType.IPv6], [address] must be a numeric IPv6 + * address (hexadecimal notation). + * If [type] is [InternetAddressType.unix], [address] must be a a valid file + * path. + * If [type] is omitted, [address] must be either a numeric IPv4 or IPv6 + * address and the type is inferred from the format. + * + * To create a Unix domain address, [type] should be + * [InternetAddressType.unix] and [address] should be a string. */ - external factory InternetAddress(String address); + external factory InternetAddress(String address, + {@Since("2.8") InternetAddressType type}); /** - * Perform a reverse dns lookup on the [address], creating a new - * [InternetAddress] where the host field set to the result. + * Creates a new [InternetAddress] from the provided raw address bytes. + * + * If the [type] is [InternetAddressType.IPv4], the [rawAddress] must have + * length 4. + * If the [type] is [InternetAddressType.IPv6], the [rawAddress] must have + * length 16. + * If the [type] is [InternetAddressType.IPv4], the [rawAddress] must be a + * valid UTF-8 encoded file path. + * + * If [type] is omitted, the [rawAddress] must have a length of either 4 or + * 16, in which case the type defaults to [InternetAddress.IPv4] or + * [InternetAddress.IPv6] respectively. + */ + external factory InternetAddress.fromRawAddress(Uint8List rawAddress, + {@Since("2.8") InternetAddressType type}); + + /** + * Perform a reverse DNS lookup on this [address] + * + * Returns a new [InternetAddress] with the same address, but where the [host] + * field set to the result of the lookup. + * + * If this address is Unix domain addresses, no lookup is performed and this + * address is returned directly. */ Future reverse(); @@ -826,28 +872,30 @@ abstract class Socket implements Stream, IOSink { void setRawOption(RawSocketOption option); /** - * Returns the port used by this socket. + * The port used by this socket. * * Throws a [SocketException] if the socket is closed. + * The port is 0 if the socket is a Unix domain socket. */ int get port; /** - * Returns the remote port connected to by this socket. + * The remote port connected to by this socket. * * Throws a [SocketException] if the socket is closed. + * The port is 0 if the socket is a Unix domain socket. */ int get remotePort; /** - * Returns the [InternetAddress] used to connect this socket. + * The [InternetAddress] used to connect this socket. * * Throws a [SocketException] if the socket is closed. */ InternetAddress get address; /** - * Returns the remote [InternetAddress] connected to by this socket. + * The remote [InternetAddress] connected to by this socket. * * Throws a [SocketException] if the socket is closed. */ diff --git a/sdk_nnbd/lib/_internal/js_dev_runtime/patch/io_patch.dart b/sdk_nnbd/lib/_internal/js_dev_runtime/patch/io_patch.dart index cd854ce1189..ceee3d471c7 100644 --- a/sdk_nnbd/lib/_internal/js_dev_runtime/patch/io_patch.dart +++ b/sdk_nnbd/lib/_internal/js_dev_runtime/patch/io_patch.dart @@ -404,9 +404,16 @@ class InternetAddress { } @patch - factory InternetAddress(String address) { + factory InternetAddress(String address, {InternetAddressType? type}) { throw UnsupportedError("InternetAddress"); } + + @patch + factory InternetAddress.fromRawAddress(Uint8List rawAddress, + {InternetAddressType? type}) { + throw new UnsupportedError("InternetAddress.fromRawAddress"); + } + @patch static Future> lookup(String host, {InternetAddressType type = InternetAddressType.any}) { diff --git a/sdk_nnbd/lib/_internal/js_runtime/lib/io_patch.dart b/sdk_nnbd/lib/_internal/js_runtime/lib/io_patch.dart index a6121c99b2c..da7ebc78dbd 100644 --- a/sdk_nnbd/lib/_internal/js_runtime/lib/io_patch.dart +++ b/sdk_nnbd/lib/_internal/js_runtime/lib/io_patch.dart @@ -404,9 +404,16 @@ class InternetAddress { } @patch - factory InternetAddress(String address) { + factory InternetAddress(String address, {InternetAddressType? type}) { throw new UnsupportedError("InternetAddress"); } + + @patch + factory InternetAddress.fromRawAddress(Uint8List rawAddress, + {InternetAddressType? type}) { + throw new UnsupportedError("InternetAddress.fromRawAddress"); + } + @patch static Future> lookup(String host, {InternetAddressType type: InternetAddressType.any}) { diff --git a/sdk_nnbd/lib/_internal/vm/bin/socket_patch.dart b/sdk_nnbd/lib/_internal/vm/bin/socket_patch.dart index 8b5638d8582..061d18e786e 100644 --- a/sdk_nnbd/lib/_internal/vm/bin/socket_patch.dart +++ b/sdk_nnbd/lib/_internal/vm/bin/socket_patch.dart @@ -68,8 +68,14 @@ class InternetAddress { } @patch - factory InternetAddress(String address) { - return new _InternetAddress.parse(address); + factory InternetAddress(String address, {InternetAddressType? type}) { + return _InternetAddress.fromString(address, type: type); + } + + @patch + factory InternetAddress.fromRawAddress(Uint8List rawAddress, + {InternetAddressType? type}) { + return _InternetAddress.fromRawAddress(rawAddress, type: type); } @patch @@ -141,10 +147,7 @@ class _InternetAddress implements InternetAddress { final String? _host; final Uint8List _in_addr; final int _scope_id; - - InternetAddressType get type => _in_addr.length == _IPv4AddrLength - ? InternetAddressType.IPv4 - : InternetAddressType.IPv6; + final InternetAddressType type; String get host => _host ?? address; @@ -160,6 +163,9 @@ class _InternetAddress implements InternetAddress { if (_in_addr[i] != 0) return false; } return _in_addr[_IPv6AddrLength - 1] == 1; + + case InternetAddressType.unix: + return false; } } @@ -172,6 +178,9 @@ class _InternetAddress implements InternetAddress { case InternetAddressType.IPv6: // Checking for fe80::/10. return _in_addr[0] == 0xFE && (_in_addr[1] & 0xB0) == 0x80; + + case InternetAddressType.unix: + return false; } } @@ -184,56 +193,101 @@ class _InternetAddress implements InternetAddress { case InternetAddressType.IPv6: // Checking for ff00::/8. return _in_addr[0] == 0xFF; + + case InternetAddressType.unix: + return false; } } - Future reverse() => _NativeSocket.reverseLookup(this); + Future reverse() { + if (type == InternetAddressType.unix) { + return Future.value(this); + } + return _NativeSocket.reverseLookup(this); + } - _InternetAddress(this.address, this._host, this._in_addr, + _InternetAddress(this.type, this.address, this._host, this._in_addr, [this._scope_id = 0]); - factory _InternetAddress.parse(String address) { + factory _InternetAddress.fromString(String address, + {InternetAddressType? type}) { // TODO: Remove once non-nullability is sound. - ArgumentError.checkNotNull(address, "address"); - var in_addr = _parse(address); - if (in_addr == null) { - throw new ArgumentError("Invalid internet address $address"); + ArgumentError.checkNotNull(address, 'address'); + if (type == InternetAddressType.unix) { + var rawAddress = FileSystemEntity._toUtf8Array(address); + return _InternetAddress( + InternetAddressType.unix, address, null, rawAddress); + } else { + var in_addr = _parse(address); + if (in_addr == null) { + throw ArgumentError("Invalid internet address $address"); + } + InternetAddressType type = in_addr.length == _IPv4AddrLength + ? InternetAddressType.IPv4 + : InternetAddressType.IPv6; + return _InternetAddress(type, address, null, in_addr); + } + } + + factory _InternetAddress.fromRawAddress(Uint8List rawAddress, + {InternetAddressType? type}) { + if (type == InternetAddressType.unix) { + ArgumentError.checkNotNull(rawAddress, 'rawAddress'); + var rawPath = FileSystemEntity._toNullTerminatedUtf8Array(rawAddress); + var address = FileSystemEntity._toStringFromUtf8Array(rawAddress); + return _InternetAddress(InternetAddressType.unix, address, null, rawPath); + } else { + int type = -1; + if (rawAddress.length == _IPv4AddrLength) { + type = 0; + } else { + if (rawAddress.length != _IPv6AddrLength) { + throw ArgumentError("Invalid internet address ${rawAddress}"); + } + type = 1; + } + var address = _rawAddrToString(rawAddress); + return _InternetAddress( + InternetAddressType._from(type), address, null, rawAddress); } - return new _InternetAddress(address, null, in_addr); } factory _InternetAddress.fixed(int id) { switch (id) { case _addressLoopbackIPv4: - var in_addr = new Uint8List(_IPv4AddrLength); + var in_addr = Uint8List(_IPv4AddrLength); in_addr[0] = 127; in_addr[_IPv4AddrLength - 1] = 1; - return new _InternetAddress("127.0.0.1", null, in_addr); + return _InternetAddress( + InternetAddressType.IPv4, "127.0.0.1", null, in_addr); case _addressLoopbackIPv6: - var in_addr = new Uint8List(_IPv6AddrLength); + var in_addr = Uint8List(_IPv6AddrLength); in_addr[_IPv6AddrLength - 1] = 1; - return new _InternetAddress("::1", null, in_addr); + return _InternetAddress(InternetAddressType.IPv6, "::1", null, in_addr); case _addressAnyIPv4: - var in_addr = new Uint8List(_IPv4AddrLength); - return new _InternetAddress("0.0.0.0", "0.0.0.0", in_addr); + var in_addr = Uint8List(_IPv4AddrLength); + return _InternetAddress( + InternetAddressType.IPv4, "0.0.0.0", "0.0.0.0", in_addr); case _addressAnyIPv6: - var in_addr = new Uint8List(_IPv6AddrLength); - return new _InternetAddress("::", "::", in_addr); + var in_addr = Uint8List(_IPv6AddrLength); + return _InternetAddress(InternetAddressType.IPv6, "::", "::", in_addr); default: assert(false); - throw new ArgumentError(); + throw ArgumentError(); } } // Create a clone of this _InternetAddress replacing the host. _InternetAddress _cloneWithNewHost(String host) { - return new _InternetAddress( - address, host, new Uint8List.fromList(_in_addr)); + return _InternetAddress(type, address, host, Uint8List.fromList(_in_addr)); } bool operator ==(other) { if (!(other is _InternetAddress)) return false; if (other.type != type) return false; + if (type == InternetAddressType.unix) { + return address == other.address; + } bool equals = true; for (int i = 0; i < _in_addr.length && equals; i++) { equals = other._in_addr[i] == _in_addr[i]; @@ -242,6 +296,9 @@ class _InternetAddress implements InternetAddress { } int get hashCode { + if (type == InternetAddressType.unix) { + return address.hashCode; + } int result = 1; for (int i = 0; i < _in_addr.length; i++) { result = (result * 31 + _in_addr[i]) & 0x3FFFFFFF; @@ -253,6 +310,9 @@ class _InternetAddress implements InternetAddress { return "InternetAddress('$address', ${type.name})"; } + static String _rawAddrToString(Uint8List address) + native "InternetAddress_RawAddrToString"; + static Uint8List? _parse(String address) native "InternetAddress_Parse"; } @@ -394,8 +454,8 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { throw createError(response, "Failed host lookup: '$host'"); } else { return response.skip(1).map((result) { - var type = new InternetAddressType._from(result[0]); - return new _InternetAddress(result[1], host, result[2], result[3]); + var type = InternetAddressType._from(result[0]); + return _InternetAddress(type, result[1], host, result[2], result[3]); }).toList(); } }); @@ -423,10 +483,10 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { } else { var map = response.skip(1).fold(new Map(), (map, result) { - var type = new InternetAddressType._from(result[0]); + var type = InternetAddressType._from(result[0]); var name = result[3]; var index = result[4]; - var address = new _InternetAddress(result[1], "", result[2]); + var address = _InternetAddress(type, result[1], "", result[2]); if (!includeLinkLocal && address.isLinkLocal) return map; if (!includeLoopback && address.isLoopback) return map; map.putIfAbsent(name, () => new _NetworkInterface(name, index)); @@ -505,12 +565,23 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { socket.localAddress = address; var result; if (sourceAddress == null) { - result = socket.nativeCreateConnect( - address._in_addr, port, address._scope_id); + if (address.type == InternetAddressType.unix) { + result = socket.nativeCreateUnixDomainConnect( + address.address, _Namespace._namespace); + } else { + result = socket.nativeCreateConnect( + address._in_addr, port, address._scope_id); + } } else { assert(sourceAddress is _InternetAddress); - result = socket.nativeCreateBindConnect(address._in_addr, port, - sourceAddress._in_addr, address._scope_id); + if (address.type == InternetAddressType.unix) { + assert(sourceAddress.type == InternetAddressType.unix); + result = socket.nativeCreateUnixDomainBindConnect( + address.address, sourceAddress.address, _Namespace._namespace); + } else { + result = socket.nativeCreateBindConnect(address._in_addr, port, + sourceAddress._in_addr, address._scope_id); + } } if (result is OSError) { // Keep first error, if present. @@ -641,8 +712,18 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { var socket = new _NativeSocket.listen(); socket.localAddress = address; - var result = socket.nativeCreateBindListen( - address._in_addr, port, backlog, v6Only, shared, address._scope_id); + var result; + if (address.type == InternetAddressType.unix) { + var path = address.address; + if (FileSystemEntity.isLinkSync(path)) { + path = Link(path).targetSync(); + } + result = socket.nativeCreateUnixDomainBindListen( + path, backlog, shared, _Namespace._namespace); + } else { + result = socket.nativeCreateBindListen( + address._in_addr, port, backlog, v6Only, shared, address._scope_id); + } if (result is OSError) { throw new SocketException("Failed to create server socket", osError: result, address: address, port: port); @@ -905,12 +986,14 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { } int get port { + if (localAddress.type == InternetAddressType.unix) return 0; if (localPort != 0) return localPort; if (isClosing || isClosed) throw const SocketException.closed(); return localPort = nativeGetPort(); } int get remotePort { + if (localAddress.type == InternetAddressType.unix) return 0; if (isClosing || isClosed) throw const SocketException.closed(); return nativeGetRemotePeer()[1]; } @@ -922,7 +1005,11 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { var result = nativeGetRemotePeer(); var addr = result[0]; var type = new InternetAddressType._from(addr[0]); - return new _InternetAddress(addr[1], null, addr[2]); + if (type == InternetAddressType.unix) { + return _InternetAddress.fromString(addr[1], + type: InternetAddressType.unix); + } + return _InternetAddress(type, addr[1], null, addr[2]); } void issueReadEvent() { @@ -1274,11 +1361,17 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { int port) native "Socket_SendTo"; nativeCreateConnect(Uint8List addr, int port, int scope_id) native "Socket_CreateConnect"; + nativeCreateUnixDomainConnect(String addr, _Namespace namespace) + native "Socket_CreateUnixDomainConnect"; nativeCreateBindConnect(Uint8List addr, int port, Uint8List sourceAddr, int scope_id) native "Socket_CreateBindConnect"; + nativeCreateUnixDomainBindConnect(String addr, String sourceAddr, + _Namespace namespace) native "Socket_CreateUnixDomainBindConnect"; bool isBindError(int errorNumber) native "SocketBase_IsBindError"; nativeCreateBindListen(Uint8List addr, int port, int backlog, bool v6Only, bool shared, int scope_id) native "ServerSocket_CreateBindListen"; + nativeCreateUnixDomainBindListen(String addr, int backlog, bool shared, + _Namespace namespace) native "ServerSocket_CreateUnixDomainBindListen"; nativeCreateBindDatagram(Uint8List addr, int port, bool reuseAddress, bool reusePort, int ttl) native "Socket_CreateBindDatagram"; bool nativeAccept(_NativeSocket socket) native "ServerSocket_Accept"; @@ -2122,6 +2215,9 @@ class _RawDatagramSocket extends Stream @pragma("vm:entry-point", "call") Datagram _makeDatagram( - Uint8List data, String address, Uint8List in_addr, int port) { - return new Datagram(data, new _InternetAddress(address, null, in_addr), port); + Uint8List data, String address, Uint8List in_addr, int port, int type) { + return new Datagram( + data, + _InternetAddress(InternetAddressType._from(type), address, null, in_addr), + port); } diff --git a/sdk_nnbd/lib/_internal/vm/bin/sync_socket_patch.dart b/sdk_nnbd/lib/_internal/vm/bin/sync_socket_patch.dart index cf16b8897bd..af9ce866cf8 100644 --- a/sdk_nnbd/lib/_internal/vm/bin/sync_socket_patch.dart +++ b/sdk_nnbd/lib/_internal/vm/bin/sync_socket_patch.dart @@ -142,7 +142,12 @@ class _NativeSynchronousSocket extends _NativeSynchronousSocketNativeWrapper { throw result; } var addr = result[0]; - return new _InternetAddress(addr[1], null, addr[2]); + var type = InternetAddressType._from(addr[0]); + if (type == InternetAddressType.unix) { + return _InternetAddress.fromString(addr[1], + type: InternetAddressType.unix); + } + return _InternetAddress(type, addr[1], null, addr[2]); } int get remotePort { @@ -184,7 +189,8 @@ class _NativeSynchronousSocket extends _NativeSynchronousSocketNativeWrapper { } return <_InternetAddress>[ for (int i = 0; i < response.length; ++i) - new _InternetAddress(response[i][1], host, response[i][2]), + new _InternetAddress(InternetAddressType._from(response[i][0]), + response[i][1], host, response[i][2]), ]; } diff --git a/sdk_nnbd/lib/io/socket.dart b/sdk_nnbd/lib/io/socket.dart index dc92d8bd774..bf1e2091ccf 100644 --- a/sdk_nnbd/lib/io/socket.dart +++ b/sdk_nnbd/lib/io/socket.dart @@ -6,11 +6,15 @@ part of dart.io; /** * [InternetAddressType] is the type an [InternetAddress]. Currently, - * IP version 4 (IPv4) and IP version 6 (IPv6) are supported. + * IP version 4 (IPv4), IP version 6 (IPv6) and Unix domain address are + * supported. Unix domain sockets are available only on Linux, MacOS and + * Android. */ class InternetAddressType { static const InternetAddressType IPv4 = const InternetAddressType._(0); static const InternetAddressType IPv6 = const InternetAddressType._(1); + @Since("2.8") + static const InternetAddressType unix = const InternetAddressType._(2); static const InternetAddressType any = const InternetAddressType._(-1); @Deprecated("Use IPv4 instead") @@ -25,8 +29,9 @@ class InternetAddressType { const InternetAddressType._(this._value); factory InternetAddressType._from(int value) { - if (value == 0) return IPv4; - if (value == 1) return IPv6; + if (value == IPv4._value) return IPv4; + if (value == IPv6._value) return IPv6; + if (value == unix._value) return unix; throw new ArgumentError("Invalid type: $value"); } @@ -41,6 +46,8 @@ class InternetAddressType { return "IPv4"; case 1: return "IPv6"; + case 2: + return "Unix"; default: throw new ArgumentError("Invalid InternetAddress"); } @@ -50,7 +57,7 @@ class InternetAddressType { } /** - * An internet address. + * An internet address or a Unix domain address. * * This object holds an internet address. If this internet address * is the result of a DNS lookup, the address also holds the hostname @@ -93,27 +100,35 @@ abstract class InternetAddress { external static InternetAddress get ANY_IP_V6; /** - * The [type] of the [InternetAddress] specified what IP protocol. + * The address family of the [InternetAddress]. */ InternetAddressType get type; /** - * The numeric address of the host. For IPv4 addresses this is using - * the dotted-decimal notation. For IPv6 it is using the - * hexadecimal representation. + * The numeric address of the host. + * + * For IPv4 addresses this is using the dotted-decimal notation. + * For IPv6 it is using the hexadecimal representation. + * For Unix domain addresses, this is a file path. */ String get address; /** - * The host used to lookup the address. If there is no host - * associated with the address this returns the numeric address. + * The host used to lookup the address. + * + * If there is no host associated with the address this returns the [address]. */ String get host; /** - * Get the raw address of this [InternetAddress]. The result is either a - * 4 or 16 byte long list. The returned list is a copy, making it possible - * to change the list without modifying the [InternetAddress]. + * The raw address of this [InternetAddress]. + * + * For an IP address, the result is either a 4 or 16 byte long list. + * For a Unix domain address, UTF-8 encoded byte sequences that represents + * [address] is returned. + * + * The returned list is a fresh copy, making it possible to change the list without + * modifying the [InternetAddress]. */ Uint8List get rawAddress; @@ -133,17 +148,48 @@ abstract class InternetAddress { bool get isMulticast; /** - * Creates a new [InternetAddress] from a numeric address. + * Creates a new [InternetAddress] from a numeric address or a file path. * - * If the address in [address] is not a numeric IPv4 - * (dotted-decimal notation) or IPv6 (hexadecimal representation). - * address [ArgumentError] is thrown. + * If [type] is [InternetAddressType.IPv4], [address] must be a numeric IPv4 + * address (dotted-decimal notation). + * If [type] is [InternetAddressType.IPv6], [address] must be a numeric IPv6 + * address (hexadecimal notation). + * If [type] is [InternetAddressType.unix], [address] must be a a valid file + * path. + * If [type] is omitted, [address] must be either a numeric IPv4 or IPv6 + * address and the type is inferred from the format. + * + * To create a Unix domain address, [type] should be + * [InternetAddressType.unix] and [address] should be a string. */ - external factory InternetAddress(String address); + external factory InternetAddress(String address, + {@Since("2.8") InternetAddressType? type}); /** - * Perform a reverse dns lookup on the [address], creating a new - * [InternetAddress] where the host field set to the result. + * Creates a new [InternetAddress] from the provided raw address bytes. + * + * If the [type] is [InternetAddressType.IPv4], the [rawAddress] must have + * length 4. + * If the [type] is [InternetAddressType.IPv6], the [rawAddress] must have + * length 16. + * If the [type] is [InternetAddressType.IPv4], the [rawAddress] must be a + * valid UTF-8 encoded file path. + * + * If [type] is omitted, the [rawAddress] must have a length of either 4 or + * 16, in which case the type defaults to [InternetAddress.IPv4] or + * [InternetAddress.IPv6] respectively. + */ + external factory InternetAddress.fromRawAddress(Uint8List rawAddress, + {@Since("2.8") InternetAddressType? type}); + + /** + * Perform a reverse DNS lookup on this [address] + * + * Returns a new [InternetAddress] with the same address, but where the [host] + * field set to the result of the lookup. + * + * If this address is Unix domain addresses, no lookup is performed and this + * address is returned directly. */ Future reverse(); @@ -817,28 +863,30 @@ abstract class Socket implements Stream, IOSink { void setRawOption(RawSocketOption option); /** - * Returns the port used by this socket. + * The port used by this socket. * * Throws a [SocketException] if the socket is closed. + * The port is 0 if the socket is a Unix domain socket. */ int get port; /** - * Returns the remote port connected to by this socket. + * The remote port connected to by this socket. * * Throws a [SocketException] if the socket is closed. + * The port is 0 if the socket is a Unix domain socket. */ int get remotePort; /** - * Returns the [InternetAddress] used to connect this socket. + * The [InternetAddress] used to connect this socket. * * Throws a [SocketException] if the socket is closed. */ InternetAddress get address; /** - * Returns the remote [InternetAddress] connected to by this socket. + * The remote [InternetAddress] connected to by this socket. * * Throws a [SocketException] if the socket is closed. */ diff --git a/tests/standalone/io/internet_address_test.dart b/tests/standalone/io/internet_address_test.dart index 1832207ca79..493957e8581 100644 --- a/tests/standalone/io/internet_address_test.dart +++ b/tests/standalone/io/internet_address_test.dart @@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:io'; +import 'dart:convert'; +import 'dart:typed_data'; import "package:expect/expect.dart"; @@ -131,10 +133,40 @@ void testReverseLookup() { }); } +void testRawAddress() { + Uint8List addr = Uint8List.fromList([127, 0, 0, 1]); + var address = InternetAddress.fromRawAddress(addr); + Expect.equals('127.0.0.1', address.address); + Expect.equals(address.address, address.host); + Expect.equals(InternetAddressType.IPv4, address.type); +} + +void testRawAddressIPv6() { + Uint8List addr = + Uint8List.fromList([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + var address = InternetAddress.fromRawAddress(addr); + Expect.equals('::1', address.address); + Expect.equals(address.address, address.host); + Expect.equals(InternetAddressType.IPv6, address.type); +} + +void testRawPath() { + var name = 'test_raw_path'; + Uint8List path = Uint8List.fromList(utf8.encode(name)); + var address = + InternetAddress.fromRawAddress(path, type: InternetAddressType.unix); + Expect.equals(name, address.address); + Expect.equals(address.address, address.host); + Expect.equals(InternetAddressType.unix, address.type); +} + void main() { testDefaultAddresses(); testConstructor(); testEquality(); testLookup(); testReverseLookup(); + testRawAddress(); + testRawAddressIPv6(); + testRawPath(); } diff --git a/tests/standalone/io/socket_from_raw_path_test.dart b/tests/standalone/io/socket_from_raw_path_test.dart new file mode 100644 index 00000000000..757bf37f513 --- /dev/null +++ b/tests/standalone/io/socket_from_raw_path_test.dart @@ -0,0 +1,73 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:expect/expect.dart'; + +Future testAddress(Uint8List name, String addr, + {InternetAddressType? type}) async { + var address = InternetAddress.fromRawAddress(name, type: type); + Expect.equals(address.address, addr); + var server = await ServerSocket.bind(address, 0); + var client = await Socket.connect(address, server.port); + var completer = Completer(); + server.listen((socket) async { + Expect.equals(socket.port, server.port); + Expect.equals(client.port, socket.remotePort); + Expect.equals(client.remotePort, socket.port); + + Expect.equals(client.remoteAddress, address); + socket.destroy(); + client.destroy(); + await server.close(); + completer.complete(); + }); + await completer.future; +} + +Future testUnixAddress() async { + Directory dir = Directory.systemTemp.createTempSync(); + var name = 'raw_path_test'; + try { + final file = File('${dir.path}/$name'); + Uint8List path = Uint8List.fromList(utf8.encode(file.path)); + var address = + InternetAddress.fromRawAddress(path, type: InternetAddressType.unix); + Expect.isTrue(address.address.toString().endsWith(name)); + + // Test socket + var server = await ServerSocket.bind(address, 0); + var client = await Socket.connect(address, server.port); + var completer = Completer(); + server.listen((socket) async { + Expect.equals(socket.port, server.port); + Expect.equals(client.port, socket.remotePort); + Expect.equals(client.remotePort, socket.port); + + Expect.equals(client.remoteAddress, address); + socket.destroy(); + client.destroy(); + await server.close(); + completer.complete(); + }); + await completer.future; + } finally { + dir.deleteSync(recursive: true); + } +} + +void main() async { + // Test for internet address ipv4 ('127.0.0.1'). + Uint8List addr = Uint8List.fromList([127, 0, 0, 1]); + await testAddress(addr, '127.0.0.1'); + + // Test unix socket + if (Platform.isMacOS || Platform.isLinux || Platform.isAndroid) { + await testUnixAddress(); + } +} diff --git a/tests/standalone/io/socket_info_ipv6_test.dart b/tests/standalone/io/socket_info_ipv6_test.dart index 11920fb0f67..7c15b7d2daa 100644 --- a/tests/standalone/io/socket_info_ipv6_test.dart +++ b/tests/standalone/io/socket_info_ipv6_test.dart @@ -2,8 +2,11 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import "package:expect/expect.dart"; +import "dart:async"; import "dart:io"; +import "dart:typed_data"; + +import "package:expect/expect.dart"; void testHostAndPort() { ServerSocket.bind("::1", 0).then((server) { @@ -29,6 +32,30 @@ void testHostAndPort() { }); } -void main() { - testHostAndPort(); +Future testRawAddress() async { + var list = + Uint8List.fromList([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + var addr = '::1'; + var address = InternetAddress.fromRawAddress(list); + Expect.equals(address.address, addr); + var server = await ServerSocket.bind(address, 0); + var client = await Socket.connect(address, server.port); + var completer = Completer(); + server.listen((socket) async { + Expect.equals(socket.port, server.port); + Expect.equals(client.port, socket.remotePort); + Expect.equals(client.remotePort, socket.port); + + Expect.equals(client.remoteAddress, address); + socket.destroy(); + client.destroy(); + await server.close(); + completer.complete(); + }); + await completer.future; +} + +void main() async { + testHostAndPort(); + await testRawAddress(); } diff --git a/tests/standalone/io/unix_socket_test.dart b/tests/standalone/io/unix_socket_test.dart new file mode 100644 index 00000000000..cc6ed79d072 --- /dev/null +++ b/tests/standalone/io/unix_socket_test.dart @@ -0,0 +1,174 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:expect/expect.dart'; + +Future testAddress(String name) async { + var address = InternetAddress('$name/sock', type: InternetAddressType.unix); + var server = await ServerSocket.bind(address, 0); + + var type = FileSystemEntity.typeSync(address.address); + + var client = await Socket.connect(address, server.port); + var completer = Completer(); + server.listen((socket) async { + Expect.equals(socket.port, 0); + Expect.equals(socket.port, server.port); + Expect.equals(client.port, socket.remotePort); + Expect.equals(client.remotePort, socket.port); + + // Client has not bound to a path. This is an unnamed socket. + Expect.equals(socket.remoteAddress.toString(), "InternetAddress('', Unix)"); + Expect.equals(client.remoteAddress.toString(), address.toString()); + socket.destroy(); + client.destroy(); + await server.close(); + completer.complete(); + }); + await completer.future; +} + +testBindShared(String name) async { + var address = InternetAddress('$name/sock', type: InternetAddressType.unix); + var socket = await ServerSocket.bind(address, 0, shared: true); + Expect.isTrue(socket.port == 0); + + // Same path + var socket2 = await ServerSocket.bind(address, 0, shared: true); + Expect.equals(socket.address.address, socket2.address.address); + Expect.equals(socket.port, socket2.port); + + // Test relative path + var path = name.substring(name.lastIndexOf('/') + 1); + address = InternetAddress('${name}/../${path}/sock', + type: InternetAddressType.unix); + + var socket3 = await ServerSocket.bind(address, 0, shared: true); + Expect.isTrue(FileSystemEntity.identicalSync( + socket.address.address, socket3.address.address)); + Expect.equals(socket.port, socket2.port); + await socket.close(); + await socket2.close(); + await socket3.close(); +} + +testBind(String name) async { + var address = InternetAddress('$name/sock', type: InternetAddressType.unix); + var server = await ServerSocket.bind(address, 0, shared: false); + Expect.isTrue(server.address.toString().contains(name)); + // Unix domain socket does not have a valid port number. + Expect.equals(server.port, 0); + + var type = FileSystemEntity.typeSync(address.address); + + var sub; + sub = server.listen((s) { + sub.cancel(); + server.close(); + }); + + var socket = await Socket.connect(address, server.port); + socket.write(" socket content"); + + await socket.destroy(); + await server.close(); +} + +Future testListenCloseListenClose(String name) async { + var address = InternetAddress('$name/sock', type: InternetAddressType.unix); + ServerSocket socket = await ServerSocket.bind(address, 0, shared: true); + ServerSocket socket2 = + await ServerSocket.bind(address, socket.port, shared: true); + + // The second socket should have kept the OS socket alive. We can therefore + // test if it is working correctly. + await socket.close(); + + var type = FileSystemEntity.typeSync(address.address); + + // For robustness we ignore any clients unrelated to this test. + List sendData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + socket2.listen((Socket client) async { + client.add(sendData); + await Future.wait([client.drain(), client.close()]); + }); + + final client = await Socket.connect(address, socket2.port); + List data = []; + var completer = Completer(); + client.listen(data.addAll, onDone: () { + Expect.listEquals(sendData, data); + completer.complete(); + }); + await completer.future; + await client.close(); + + // Close the second server socket. + await socket2.close(); +} + +Future testSourceAddressConnect(String name) async { + var address = InternetAddress('$name/sock', type: InternetAddressType.unix); + ServerSocket server = await ServerSocket.bind(address, 0); + + var completer = Completer(); + var localAddress = + InternetAddress('$name/local', type: InternetAddressType.unix); + server.listen((Socket socket) async { + Expect.equals(socket.address.address, address.address); + Expect.equals(socket.remoteAddress.address, localAddress.address); + socket.drain(); + socket.close(); + completer.complete(); + }); + + var type = FileSystemEntity.typeSync(address.address); + + Socket client = + await Socket.connect(address, server.port, sourceAddress: localAddress); + Expect.equals(client.remoteAddress.address, address.address); + await completer.future; + await client.close(); + await client.drain(); + await server.close(); +} + +// Create socket in temp directory +Future withTempDir(String prefix, void test(Directory dir)) async { + var tempDir = Directory.systemTemp.createTempSync(prefix); + try { + await test(tempDir); + } finally { + tempDir.deleteSync(recursive: true); + } +} + +void main() async { + try { + await withTempDir('unix_socket_test', (Directory dir) async { + await testAddress('${dir.path}'); + }); + await withTempDir('unix_socket_test', (Directory dir) async { + await testBind('${dir.path}'); + }); + await withTempDir('unix_socket_test', (Directory dir) async { + await testBindShared('${dir.path}'); + }); + await withTempDir('unix_socket_test', (Directory dir) async { + await testListenCloseListenClose('${dir.path}'); + }); + await withTempDir('unix_socket_test', (Directory dir) async { + await testSourceAddressConnect('${dir.path}'); + }); + } catch (e) { + if (Platform.isMacOS || Platform.isLinux || Platform.isAndroid) { + Expect.fail("Unexpected exceptions are thrown"); + } else { + Expect.isTrue(e is SocketException); + } + } +} diff --git a/tests/standalone_2/io/internet_address_test.dart b/tests/standalone_2/io/internet_address_test.dart index 1832207ca79..d35e91c90a6 100644 --- a/tests/standalone_2/io/internet_address_test.dart +++ b/tests/standalone_2/io/internet_address_test.dart @@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:io'; +import 'dart:convert'; +import 'dart:typed_data'; import "package:expect/expect.dart"; @@ -131,10 +133,39 @@ void testReverseLookup() { }); } +void testRawAddress() { + Uint8List addr = Uint8List.fromList([127, 0, 0, 1]); + var address = InternetAddress.fromRawAddress(addr); + Expect.equals('127.0.0.1', address.address); + Expect.equals(address.address, address.host); + Expect.equals(InternetAddressType.IPv4, address.type); +} + +void testRawAddressIPv6() { + Uint8List addr = + Uint8List.fromList([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + var address = InternetAddress.fromRawAddress(addr); + Expect.equals('::1', address.address); + Expect.equals(address.address, address.host); + Expect.equals(InternetAddressType.IPv6, address.type); +} + +void testRawPath() { + var name = 'test_raw_path'; + var address = InternetAddress.fromRawAddress(utf8.encode(name), + type: InternetAddressType.unix); + Expect.equals(name, address.address); + Expect.equals(address.address, address.host); + Expect.equals(InternetAddressType.unix, address.type); +} + void main() { testDefaultAddresses(); testConstructor(); testEquality(); testLookup(); testReverseLookup(); + testRawAddress(); + testRawAddressIPv6(); + testRawPath(); } diff --git a/tests/standalone_2/io/socket_from_raw_path_test.dart b/tests/standalone_2/io/socket_from_raw_path_test.dart new file mode 100644 index 00000000000..794f075e1e7 --- /dev/null +++ b/tests/standalone_2/io/socket_from_raw_path_test.dart @@ -0,0 +1,72 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:expect/expect.dart'; + +Future testAddress(Uint8List name, String addr, + {InternetAddressType type}) async { + var address = InternetAddress.fromRawAddress(name, type: type); + Expect.equals(address.address, addr); + var server = await ServerSocket.bind(address, 0); + var client = await Socket.connect(address, server.port); + var completer = Completer(); + server.listen((socket) async { + Expect.equals(socket.port, server.port); + Expect.equals(client.port, socket.remotePort); + Expect.equals(client.remotePort, socket.port); + + Expect.equals(client.remoteAddress, address); + socket.destroy(); + client.destroy(); + await server.close(); + completer.complete(); + }); + await completer.future; +} + +Future testUnixAddress() async { + Directory dir = Directory.systemTemp.createTempSync(); + var name = 'raw_path_test'; + try { + var file = File('${dir.path}/$name'); + var address = InternetAddress.fromRawAddress(utf8.encode(file.path), + type: InternetAddressType.unix); + Expect.isTrue(address.address.toString().endsWith(name)); + + // Test socket + var server = await ServerSocket.bind(address, 0); + var client = await Socket.connect(address, server.port); + var completer = Completer(); + server.listen((socket) async { + Expect.equals(socket.port, server.port); + Expect.equals(client.port, socket.remotePort); + Expect.equals(client.remotePort, socket.port); + + Expect.equals(client.remoteAddress, address); + socket.destroy(); + client.destroy(); + await server.close(); + completer.complete(); + }); + await completer.future; + } finally { + dir.deleteSync(recursive: true); + } +} + +void main() async { + // Test for internet address ipv4 ('127.0.0.1'). + Uint8List addr = Uint8List.fromList([127, 0, 0, 1]); + await testAddress(addr, '127.0.0.1'); + + // Test unix socket + if (Platform.isMacOS || Platform.isLinux || Platform.isAndroid) { + await testUnixAddress(); + } +} diff --git a/tests/standalone_2/io/socket_info_ipv6_test.dart b/tests/standalone_2/io/socket_info_ipv6_test.dart index 11920fb0f67..7c15b7d2daa 100644 --- a/tests/standalone_2/io/socket_info_ipv6_test.dart +++ b/tests/standalone_2/io/socket_info_ipv6_test.dart @@ -2,8 +2,11 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import "package:expect/expect.dart"; +import "dart:async"; import "dart:io"; +import "dart:typed_data"; + +import "package:expect/expect.dart"; void testHostAndPort() { ServerSocket.bind("::1", 0).then((server) { @@ -29,6 +32,30 @@ void testHostAndPort() { }); } -void main() { - testHostAndPort(); +Future testRawAddress() async { + var list = + Uint8List.fromList([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + var addr = '::1'; + var address = InternetAddress.fromRawAddress(list); + Expect.equals(address.address, addr); + var server = await ServerSocket.bind(address, 0); + var client = await Socket.connect(address, server.port); + var completer = Completer(); + server.listen((socket) async { + Expect.equals(socket.port, server.port); + Expect.equals(client.port, socket.remotePort); + Expect.equals(client.remotePort, socket.port); + + Expect.equals(client.remoteAddress, address); + socket.destroy(); + client.destroy(); + await server.close(); + completer.complete(); + }); + await completer.future; +} + +void main() async { + testHostAndPort(); + await testRawAddress(); } diff --git a/tests/standalone_2/io/unix_socket_test.dart b/tests/standalone_2/io/unix_socket_test.dart new file mode 100644 index 00000000000..cc6ed79d072 --- /dev/null +++ b/tests/standalone_2/io/unix_socket_test.dart @@ -0,0 +1,174 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:expect/expect.dart'; + +Future testAddress(String name) async { + var address = InternetAddress('$name/sock', type: InternetAddressType.unix); + var server = await ServerSocket.bind(address, 0); + + var type = FileSystemEntity.typeSync(address.address); + + var client = await Socket.connect(address, server.port); + var completer = Completer(); + server.listen((socket) async { + Expect.equals(socket.port, 0); + Expect.equals(socket.port, server.port); + Expect.equals(client.port, socket.remotePort); + Expect.equals(client.remotePort, socket.port); + + // Client has not bound to a path. This is an unnamed socket. + Expect.equals(socket.remoteAddress.toString(), "InternetAddress('', Unix)"); + Expect.equals(client.remoteAddress.toString(), address.toString()); + socket.destroy(); + client.destroy(); + await server.close(); + completer.complete(); + }); + await completer.future; +} + +testBindShared(String name) async { + var address = InternetAddress('$name/sock', type: InternetAddressType.unix); + var socket = await ServerSocket.bind(address, 0, shared: true); + Expect.isTrue(socket.port == 0); + + // Same path + var socket2 = await ServerSocket.bind(address, 0, shared: true); + Expect.equals(socket.address.address, socket2.address.address); + Expect.equals(socket.port, socket2.port); + + // Test relative path + var path = name.substring(name.lastIndexOf('/') + 1); + address = InternetAddress('${name}/../${path}/sock', + type: InternetAddressType.unix); + + var socket3 = await ServerSocket.bind(address, 0, shared: true); + Expect.isTrue(FileSystemEntity.identicalSync( + socket.address.address, socket3.address.address)); + Expect.equals(socket.port, socket2.port); + await socket.close(); + await socket2.close(); + await socket3.close(); +} + +testBind(String name) async { + var address = InternetAddress('$name/sock', type: InternetAddressType.unix); + var server = await ServerSocket.bind(address, 0, shared: false); + Expect.isTrue(server.address.toString().contains(name)); + // Unix domain socket does not have a valid port number. + Expect.equals(server.port, 0); + + var type = FileSystemEntity.typeSync(address.address); + + var sub; + sub = server.listen((s) { + sub.cancel(); + server.close(); + }); + + var socket = await Socket.connect(address, server.port); + socket.write(" socket content"); + + await socket.destroy(); + await server.close(); +} + +Future testListenCloseListenClose(String name) async { + var address = InternetAddress('$name/sock', type: InternetAddressType.unix); + ServerSocket socket = await ServerSocket.bind(address, 0, shared: true); + ServerSocket socket2 = + await ServerSocket.bind(address, socket.port, shared: true); + + // The second socket should have kept the OS socket alive. We can therefore + // test if it is working correctly. + await socket.close(); + + var type = FileSystemEntity.typeSync(address.address); + + // For robustness we ignore any clients unrelated to this test. + List sendData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + socket2.listen((Socket client) async { + client.add(sendData); + await Future.wait([client.drain(), client.close()]); + }); + + final client = await Socket.connect(address, socket2.port); + List data = []; + var completer = Completer(); + client.listen(data.addAll, onDone: () { + Expect.listEquals(sendData, data); + completer.complete(); + }); + await completer.future; + await client.close(); + + // Close the second server socket. + await socket2.close(); +} + +Future testSourceAddressConnect(String name) async { + var address = InternetAddress('$name/sock', type: InternetAddressType.unix); + ServerSocket server = await ServerSocket.bind(address, 0); + + var completer = Completer(); + var localAddress = + InternetAddress('$name/local', type: InternetAddressType.unix); + server.listen((Socket socket) async { + Expect.equals(socket.address.address, address.address); + Expect.equals(socket.remoteAddress.address, localAddress.address); + socket.drain(); + socket.close(); + completer.complete(); + }); + + var type = FileSystemEntity.typeSync(address.address); + + Socket client = + await Socket.connect(address, server.port, sourceAddress: localAddress); + Expect.equals(client.remoteAddress.address, address.address); + await completer.future; + await client.close(); + await client.drain(); + await server.close(); +} + +// Create socket in temp directory +Future withTempDir(String prefix, void test(Directory dir)) async { + var tempDir = Directory.systemTemp.createTempSync(prefix); + try { + await test(tempDir); + } finally { + tempDir.deleteSync(recursive: true); + } +} + +void main() async { + try { + await withTempDir('unix_socket_test', (Directory dir) async { + await testAddress('${dir.path}'); + }); + await withTempDir('unix_socket_test', (Directory dir) async { + await testBind('${dir.path}'); + }); + await withTempDir('unix_socket_test', (Directory dir) async { + await testBindShared('${dir.path}'); + }); + await withTempDir('unix_socket_test', (Directory dir) async { + await testListenCloseListenClose('${dir.path}'); + }); + await withTempDir('unix_socket_test', (Directory dir) async { + await testSourceAddressConnect('${dir.path}'); + }); + } catch (e) { + if (Platform.isMacOS || Platform.isLinux || Platform.isAndroid) { + Expect.fail("Unexpected exceptions are thrown"); + } else { + Expect.isTrue(e is SocketException); + } + } +}