diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index f1acfa64b4ad13..bfa2b082b4ef35 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -702,13 +702,7 @@ void Url(const FunctionCallbackInfo& args) { if (ids.empty()) return; - std::string url = "ws://"; - url += io->host(); - url += ":"; - url += std::to_string(io->port()); - url += "/"; - url += ids[0]; - + std::string url = FormatWsAddress(io->host(), io->port(), ids[0], true); args.GetReturnValue().Set(OneByteString(env->isolate(), url.c_str())); } diff --git a/src/inspector_io.cc b/src/inspector_io.cc index 69eed62ab4729a..a7d69263edad6d 100644 --- a/src/inspector_io.cc +++ b/src/inspector_io.cc @@ -313,7 +313,7 @@ void InspectorIo::ThreadMain() { uv_sem_post(&thread_start_sem_); return; } - port_ = server.port(); // Safe, main thread is waiting on semaphore. + port_ = server.Port(); // Safe, main thread is waiting on semaphore. if (!wait_for_connect_) { uv_sem_post(&thread_start_sem_); } diff --git a/src/inspector_io.h b/src/inspector_io.h index 6ef2ea54c4745d..7c15466eed91ff 100644 --- a/src/inspector_io.h +++ b/src/inspector_io.h @@ -28,6 +28,10 @@ class StringView; namespace node { namespace inspector { +std::string FormatWsAddress(const std::string& host, int port, + const std::string& target_id, + bool include_protocol); + class InspectorIoDelegate; enum class InspectorAction { diff --git a/src/inspector_socket.h b/src/inspector_socket.h index 7cd8254fb3f4d2..ee4bd7835c75ff 100644 --- a/src/inspector_socket.h +++ b/src/inspector_socket.h @@ -63,6 +63,7 @@ class InspectorSocket { bool ws_mode; bool shutting_down; bool connection_eof; + private: DISALLOW_COPY_AND_ASSIGN(InspectorSocket); }; diff --git a/src/inspector_socket_server.cc b/src/inspector_socket_server.cc index f3e56b6ceb53e0..cdc907ee9b263b 100644 --- a/src/inspector_socket_server.cc +++ b/src/inspector_socket_server.cc @@ -12,6 +12,28 @@ namespace node { namespace inspector { +// Function is declared in inspector_io.h so the rest of the node does not +// depend on inspector_socket_server.h +std::string FormatWsAddress(const std::string& host, int port, + const std::string& target_id, + bool include_protocol) { + // Host is valid (socket was bound) so colon means it's a v6 IP address + bool v6 = host.find(':') != std::string::npos; + std::ostringstream url; + if (include_protocol) + url << "ws://"; + if (v6) { + url << '['; + } + url << host; + if (v6) { + url << ']'; + } + url << ':' << port << '/' << target_id; + return url.str(); +} + + namespace { static const uint8_t PROTOCOL_JSON[] = { @@ -24,12 +46,6 @@ void Escape(std::string* string) { } } -std::string GetWsUrl(const std::string& host, int port, const std::string& id) { - char buf[1024]; - snprintf(buf, sizeof(buf), "%s:%d/%s", host.c_str(), port, id.c_str()); - return buf; -} - std::string MapToString(const std::map& object) { bool first = true; std::ostringstream json; @@ -82,8 +98,8 @@ void PrintDebuggerReadyMessage(const std::string& host, return; } for (const std::string& id : ids) { - fprintf(out, "Debugger listening on ws://%s\n", - GetWsUrl(host, port, id).c_str()); + fprintf(out, "Debugger listening on %s\n", + FormatWsAddress(host, port, id, true).c_str()); } fprintf(out, "For help see %s\n", "https://nodejs.org/en/docs/inspector"); @@ -151,24 +167,6 @@ int GetSocketHost(uv_tcp_t* socket, std::string* out_host) { *out_host = ip; return err; } - -int GetPort(uv_tcp_t* socket, int* out_port) { - sockaddr_storage addr; - int len = sizeof(addr); - int err = uv_tcp_getsockname(socket, - reinterpret_cast(&addr), - &len); - if (err != 0) - return err; - int port; - if (addr.ss_family == AF_INET6) - port = reinterpret_cast(&addr)->sin6_port; - else - port = reinterpret_cast(&addr)->sin_port; - *out_port = ntohs(port); - return err; -} - } // namespace @@ -211,34 +209,77 @@ class Closer { class SocketSession { public: - SocketSession(InspectorSocketServer* server, int id); + static int Accept(InspectorSocketServer* server, int server_port, + uv_stream_t* server_socket); + void Send(const std::string& message); void Close(); - void Declined() { state_ = State::kDeclined; } + + int id() const { return id_; } + bool IsForTarget(const std::string& target_id) const { + return target_id_ == target_id; + } + static int ServerPortForClient(InspectorSocket* client) { + return From(client)->server_port_; + } + + private: + SocketSession(InspectorSocketServer* server, int server_port); static SocketSession* From(InspectorSocket* socket) { return node::ContainerOf(&SocketSession::socket_, socket); } + + enum class State { kHttp, kWebSocket, kClosing, kEOF, kDeclined }; + static bool HandshakeCallback(InspectorSocket* socket, + enum inspector_handshake_event state, + const std::string& path); + static void ReadCallback(uv_stream_t* stream, ssize_t read, + const uv_buf_t* buf); + static void CloseCallback(InspectorSocket* socket, int code); + void FrontendConnected(); - InspectorSocketServer* GetServer() { return server_; } - int Id() { return id_; } - void Send(const std::string& message); + void SetDeclined() { state_ = State::kDeclined; } void SetTargetId(const std::string& target_id) { CHECK(target_id_.empty()); target_id_ = target_id; } - InspectorSocket* Socket() { return &socket_; } - const std::string TargetId() { return target_id_; } - private: - enum class State { kHttp, kWebSocket, kClosing, kEOF, kDeclined }; - static void CloseCallback(InspectorSocket* socket, int code); - static void ReadCallback(uv_stream_t* stream, ssize_t read, - const uv_buf_t* buf); - void OnRemoteDataIO(ssize_t read, const uv_buf_t* buf); const int id_; InspectorSocket socket_; InspectorSocketServer* server_; std::string target_id_; State state_; + const int server_port_; +}; + +class ServerSocket { + public: + static int Listen(InspectorSocketServer* inspector_server, + sockaddr* addr, uv_loop_t* loop); + void Close() { + uv_close(reinterpret_cast(&tcp_socket_), + SocketClosedCallback); + } + int port() const { return port_; } + + private: + explicit ServerSocket(InspectorSocketServer* server) + : tcp_socket_(uv_tcp_t()), server_(server), port_(-1) {} + template + static ServerSocket* FromTcpSocket(UvHandle* socket) { + return node::ContainerOf(&ServerSocket::tcp_socket_, + reinterpret_cast(socket)); + } + + static void SocketConnectedCallback(uv_stream_t* tcp_socket, int status); + static void SocketClosedCallback(uv_handle_t* tcp_socket); + static void FreeOnCloseCallback(uv_handle_t* tcp_socket_) { + delete FromTcpSocket(tcp_socket_); + } + int DetectPort(); + + uv_tcp_t tcp_socket_; + InspectorSocketServer* server_; + int port_; }; InspectorSocketServer::InspectorSocketServer(SocketServerDelegate* delegate, @@ -249,58 +290,29 @@ InspectorSocketServer::InspectorSocketServer(SocketServerDelegate* delegate, delegate_(delegate), host_(host), port_(port), - server_(uv_tcp_t()), closer_(nullptr), next_session_id_(0), out_(out) { state_ = ServerState::kNew; } -// static -bool InspectorSocketServer::HandshakeCallback(InspectorSocket* socket, - inspector_handshake_event event, - const std::string& path) { - InspectorSocketServer* server = SocketSession::From(socket)->GetServer(); - const std::string& id = path.empty() ? path : path.substr(1); - switch (event) { - case kInspectorHandshakeHttpGet: - return server->RespondToGet(socket, path); - case kInspectorHandshakeUpgrading: - return server->SessionStarted(SocketSession::From(socket), id); - case kInspectorHandshakeUpgraded: - SocketSession::From(socket)->FrontendConnected(); - return true; - case kInspectorHandshakeFailed: - server->SessionTerminated(SocketSession::From(socket)); - return false; - default: - UNREACHABLE(); - return false; - } -} - bool InspectorSocketServer::SessionStarted(SocketSession* session, const std::string& id) { - bool connected = false; - if (TargetExists(id)) { - connected = delegate_->StartSession(session->Id(), id); - } - if (connected) { - connected_sessions_[session->Id()] = session; - session->SetTargetId(id); + if (TargetExists(id) && delegate_->StartSession(session->id(), id)) { + connected_sessions_[session->id()] = session; + return true; } else { - session->Declined(); + return false; } - return connected; } void InspectorSocketServer::SessionTerminated(SocketSession* session) { - int id = session->Id(); + int id = session->id(); if (connected_sessions_.erase(id) != 0) { delegate_->EndSession(id); if (connected_sessions_.empty()) { - if (state_ == ServerState::kRunning) { - PrintDebuggerReadyMessage(host_, port_, + if (state_ == ServerState::kRunning && !server_sockets_.empty()) { + PrintDebuggerReadyMessage(host_, server_sockets_[0]->port(), delegate_->GetTargetIds(), out_); } if (state_ == ServerState::kStopped) { @@ -311,8 +323,8 @@ void InspectorSocketServer::SessionTerminated(SocketSession* session) { delete session; } -bool InspectorSocketServer::RespondToGet(InspectorSocket* socket, - const std::string& path) { +bool InspectorSocketServer::HandleGetRequest(InspectorSocket* socket, + const std::string& path) { const char* command = MatchPathSegment(path.c_str(), "/json"); if (command == nullptr) return false; @@ -354,21 +366,22 @@ void InspectorSocketServer::SendListResponse(InspectorSocket* socket) { bool connected = false; for (const auto& session : connected_sessions_) { - if (session.second->TargetId() == id) { + if (session.second->IsForTarget(id)) { connected = true; break; } } if (!connected) { std::string host; + int port = SocketSession::ServerPortForClient(socket); GetSocketHost(&socket->tcp, &host); - std::string address = GetWsUrl(host, port_, id); std::ostringstream frontend_url; frontend_url << "chrome-devtools://devtools/bundled"; frontend_url << "/inspector.html?experiments=true&v8only=true&ws="; - frontend_url << address; + frontend_url << FormatWsAddress(host, port, id, false); target_map["devtoolsFrontendUrl"] += frontend_url.str(); - target_map["webSocketDebuggerUrl"] = "ws://" + address; + target_map["webSocketDebuggerUrl"] = + FormatWsAddress(host, port, id, true); } } SendHttpResponse(socket, MapsToString(response)); @@ -376,30 +389,44 @@ void InspectorSocketServer::SendListResponse(InspectorSocket* socket) { bool InspectorSocketServer::Start() { CHECK_EQ(state_, ServerState::kNew); - sockaddr_in addr; - uv_tcp_init(loop_, &server_); - uv_ip4_addr(host_.c_str(), port_, &addr); - int err = uv_tcp_bind(&server_, - reinterpret_cast(&addr), 0); - if (err == 0) - err = GetPort(&server_, &port_); - if (err == 0) { - err = uv_listen(reinterpret_cast(&server_), 1, - SocketConnectedCallback); + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICSERV; + hints.ai_socktype = SOCK_STREAM; + uv_getaddrinfo_t req; + const std::string port_string = std::to_string(port_); + int err = uv_getaddrinfo(loop_, &req, nullptr, host_.c_str(), + port_string.c_str(), &hints); + if (err < 0) { + if (out_ != NULL) { + fprintf(out_, "Unable to resolve \"%s\": %s\n", host_.c_str(), + uv_strerror(err)); + } + return false; } - if (err == 0 && connected_sessions_.empty()) { - state_ = ServerState::kRunning; - PrintDebuggerReadyMessage(host_, port_, delegate_->GetTargetIds(), out_); + for (addrinfo* address = req.addrinfo; address != nullptr; + address = address->ai_next) { + err = ServerSocket::Listen(this, address->ai_addr, loop_); } - if (err != 0 && connected_sessions_.empty()) { + uv_freeaddrinfo(req.addrinfo); + + if (!connected_sessions_.empty()) { + return true; + } + // We only show error if we failed to start server on all addresses. We only + // show one error, for the last address. + if (server_sockets_.empty()) { if (out_ != NULL) { fprintf(out_, "Starting inspector on %s:%d failed: %s\n", host_.c_str(), port_, uv_strerror(err)); fflush(out_); } - uv_close(reinterpret_cast(&server_), nullptr); return false; } + state_ = ServerState::kRunning; + // getaddrinfo sorts the addresses, so the first port is most relevant. + PrintDebuggerReadyMessage(host_, server_sockets_[0]->port(), + delegate_->GetTargetIds(), out_); return true; } @@ -411,7 +438,8 @@ void InspectorSocketServer::Stop(ServerCallback cb) { closer_->AddCallback(cb); closer_->IncreaseExpectedCount(); state_ = ServerState::kStopping; - uv_close(reinterpret_cast(&server_), ServerClosedCallback); + for (ServerSocket* server_socket : server_sockets_) + server_socket->Close(); closer_->NotifyIfDone(); } @@ -434,37 +462,41 @@ void InspectorSocketServer::Send(int session_id, const std::string& message) { } } -// static -void InspectorSocketServer::ServerClosedCallback(uv_handle_t* server) { - InspectorSocketServer* socket_server = InspectorSocketServer::From(server); - CHECK_EQ(socket_server->state_, ServerState::kStopping); - if (socket_server->closer_) { - socket_server->closer_->DecreaseExpectedCount(); +void InspectorSocketServer::ServerSocketListening(ServerSocket* server_socket) { + server_sockets_.push_back(server_socket); +} + +void InspectorSocketServer::ServerSocketClosed(ServerSocket* server_socket) { + CHECK_EQ(state_, ServerState::kStopping); + + server_sockets_.erase(std::remove(server_sockets_.begin(), + server_sockets_.end(), server_socket), + server_sockets_.end()); + if (!server_sockets_.empty()) + return; + + if (closer_ != nullptr) { + closer_->DecreaseExpectedCount(); } - if (socket_server->connected_sessions_.empty()) { - socket_server->delegate_->ServerDone(); + if (connected_sessions_.empty()) { + delegate_->ServerDone(); } - socket_server->state_ = ServerState::kStopped; + state_ = ServerState::kStopped; } -// static -void InspectorSocketServer::SocketConnectedCallback(uv_stream_t* server, - int status) { - if (status == 0) { - InspectorSocketServer* socket_server = InspectorSocketServer::From(server); - // Memory is freed when the socket closes. - SocketSession* session = - new SocketSession(socket_server, socket_server->next_session_id_++); - if (inspector_accept(server, session->Socket(), HandshakeCallback) != 0) { - delete session; - } +int InspectorSocketServer::Port() const { + if (!server_sockets_.empty()) { + return server_sockets_[0]->port(); } + return port_; } // InspectorSession tracking -SocketSession::SocketSession(InspectorSocketServer* server, int id) - : id_(id), server_(server), - state_(State::kHttp) { } +SocketSession::SocketSession(InspectorSocketServer* server, int server_port) + : id_(server->GenerateSessionId()), + server_(server), + state_(State::kHttp), + server_port_(server_port) { } void SocketSession::Close() { CHECK_NE(state_, State::kClosing); @@ -472,6 +504,49 @@ void SocketSession::Close() { inspector_close(&socket_, CloseCallback); } +// static +int SocketSession::Accept(InspectorSocketServer* server, int server_port, + uv_stream_t* server_socket) { + // Memory is freed when the socket closes. + SocketSession* session = new SocketSession(server, server_port); + int err = inspector_accept(server_socket, &session->socket_, + HandshakeCallback); + if (err != 0) { + delete session; + } + return err; +} + +// static +bool SocketSession::HandshakeCallback(InspectorSocket* socket, + inspector_handshake_event event, + const std::string& path) { + SocketSession* session = SocketSession::From(socket); + InspectorSocketServer* server = session->server_; + const std::string& id = path.empty() ? path : path.substr(1); + switch (event) { + case kInspectorHandshakeHttpGet: + return server->HandleGetRequest(socket, path); + case kInspectorHandshakeUpgrading: + if (server->SessionStarted(session, id)) { + session->SetTargetId(id); + return true; + } else { + session->SetDeclined(); + return false; + } + case kInspectorHandshakeUpgraded: + session->FrontendConnected(); + return true; + case kInspectorHandshakeFailed: + server->SessionTerminated(session); + return false; + default: + UNREACHABLE(); + return false; + } +} + // static void SocketSession::CloseCallback(InspectorSocket* socket, int code) { SocketSession* session = SocketSession::From(socket); @@ -489,14 +564,12 @@ void SocketSession::FrontendConnected() { void SocketSession::ReadCallback(uv_stream_t* stream, ssize_t read, const uv_buf_t* buf) { InspectorSocket* socket = inspector_from_stream(stream); - SocketSession::From(socket)->OnRemoteDataIO(read, buf); -} - -void SocketSession::OnRemoteDataIO(ssize_t read, const uv_buf_t* buf) { + SocketSession* session = SocketSession::From(socket); if (read > 0) { - server_->Delegate()->MessageReceived(id_, std::string(buf->base, read)); + session->server_->MessageReceived(session->id_, + std::string(buf->base, read)); } else { - Close(); + session->Close(); } if (buf != nullptr && buf->base != nullptr) delete[] buf->base; @@ -506,5 +579,62 @@ void SocketSession::Send(const std::string& message) { inspector_write(&socket_, message.data(), message.length()); } +// ServerSocket implementation +int ServerSocket::DetectPort() { + sockaddr_storage addr; + int len = sizeof(addr); + int err = uv_tcp_getsockname(&tcp_socket_, + reinterpret_cast(&addr), &len); + if (err != 0) + return err; + int port; + if (addr.ss_family == AF_INET6) + port = reinterpret_cast(&addr)->sin6_port; + else + port = reinterpret_cast(&addr)->sin_port; + port_ = ntohs(port); + return err; +} + +// static +int ServerSocket::Listen(InspectorSocketServer* inspector_server, + sockaddr* addr, uv_loop_t* loop) { + ServerSocket* server_socket = new ServerSocket(inspector_server); + uv_tcp_t* server = &server_socket->tcp_socket_; + CHECK_EQ(0, uv_tcp_init(loop, server)); + int err = uv_tcp_bind(server, addr, 0); + if (err == 0) { + err = uv_listen(reinterpret_cast(server), 1, + ServerSocket::SocketConnectedCallback); + } + if (err == 0) { + err = server_socket->DetectPort(); + } + if (err == 0) { + inspector_server->ServerSocketListening(server_socket); + } else { + uv_close(reinterpret_cast(server), FreeOnCloseCallback); + } + return err; +} + +// static +void ServerSocket::SocketConnectedCallback(uv_stream_t* tcp_socket, + int status) { + if (status == 0) { + ServerSocket* server_socket = ServerSocket::FromTcpSocket(tcp_socket); + // Memory is freed when the socket closes. + SocketSession::Accept(server_socket->server_, server_socket->port_, + tcp_socket); + } +} + +// static +void ServerSocket::SocketClosedCallback(uv_handle_t* tcp_socket) { + ServerSocket* server_socket = ServerSocket::FromTcpSocket(tcp_socket); + server_socket->server_->ServerSocketClosed(server_socket); + delete server_socket; +} + } // namespace inspector } // namespace node diff --git a/src/inspector_socket_server.h b/src/inspector_socket_server.h index 4c606ee77a15f3..16b047da333f68 100644 --- a/src/inspector_socket_server.h +++ b/src/inspector_socket_server.h @@ -18,6 +18,7 @@ namespace inspector { class Closer; class SocketSession; +class ServerSocket; class SocketServerDelegate { public: @@ -54,28 +55,27 @@ class InspectorSocketServer { // kKill void TerminateConnections(); - int port() { - return port_; + int Port() const; + + // Server socket lifecycle. There may be multiple sockets + void ServerSocketListening(ServerSocket* server_socket); + void ServerSocketClosed(ServerSocket* server_socket); + + // Session connection lifecycle + bool HandleGetRequest(InspectorSocket* socket, const std::string& path); + bool SessionStarted(SocketSession* session, const std::string& id); + void SessionTerminated(SocketSession* session); + void MessageReceived(int session_id, const std::string& message) { + delegate_->MessageReceived(session_id, message); } - private: - static bool HandshakeCallback(InspectorSocket* socket, - enum inspector_handshake_event state, - const std::string& path); - static void SocketConnectedCallback(uv_stream_t* server, int status); - static void ServerClosedCallback(uv_handle_t* server); - template - static InspectorSocketServer* From(SomeUvStruct* server) { - return node::ContainerOf(&InspectorSocketServer::server_, - reinterpret_cast(server)); + int GenerateSessionId() { + return next_session_id_++; } - bool RespondToGet(InspectorSocket* socket, const std::string& path); + + private: void SendListResponse(InspectorSocket* socket); - void ReadCallback(InspectorSocket* socket, ssize_t read, const uv_buf_t* buf); - bool SessionStarted(SocketSession* session, const std::string& id); - void SessionTerminated(SocketSession* session); bool TargetExists(const std::string& id); - SocketServerDelegate* Delegate() { return delegate_; } enum class ServerState {kNew, kRunning, kStopping, kStopped}; uv_loop_t* loop_; @@ -83,14 +83,13 @@ class InspectorSocketServer { const std::string host_; int port_; std::string path_; - uv_tcp_t server_; + std::vector server_sockets_; Closer* closer_; std::map connected_sessions_; int next_session_id_; FILE* out_; ServerState state_; - friend class SocketSession; friend class Closer; }; diff --git a/test/cctest/test_inspector_socket_server.cc b/test/cctest/test_inspector_socket_server.cc index 7224ad9e31b3a1..cd9e8f1cfcbb89 100644 --- a/test/cctest/test_inspector_socket_server.cc +++ b/test/cctest/test_inspector_socket_server.cc @@ -26,7 +26,7 @@ static const char WS_HANDSHAKE_RESPONSE[] = { \ Timeout timeout(&loop); \ while ((condition) && !timeout.timed_out) { \ - uv_run(&loop, UV_RUN_NOWAIT); \ + uv_run(&loop, UV_RUN_ONCE); \ } \ ASSERT_FALSE((condition)); \ } @@ -41,6 +41,7 @@ class Timeout { explicit Timeout(uv_loop_t* loop) : timed_out(false), done_(false) { uv_timer_init(loop, &timer_); uv_timer_start(&timer_, Timeout::set_flag, 5000, 0); + uv_unref(reinterpret_cast(&timer_)); } ~Timeout() { @@ -163,18 +164,20 @@ class SocketWrapper { connected_(false), sending_(false) { } - void Connect(std::string host, int port) { + void Connect(std::string host, int port, bool v6 = false) { closed_ = false; connection_failed_ = false; connected_ = false; eof_ = false; contents_.clear(); uv_tcp_init(loop_, &socket_); - sockaddr_in addr; - uv_ip4_addr(host.c_str(), port, &addr); - int err = uv_tcp_connect(&connect_, &socket_, - reinterpret_cast(&addr), - Connected_); + union {sockaddr generic; sockaddr_in v4; sockaddr_in6 v6;} addr; + if (v6) { + uv_ip6_addr(host.c_str(), port, &addr.v6); + } else { + uv_ip4_addr(host.c_str(), port, &addr.v4); + } + int err = uv_tcp_connect(&connect_, &socket_, &addr.generic, Connected_); ASSERT_EQ(0, err); SPIN_WHILE(!connected_) uv_read_start(reinterpret_cast(&socket_), AllocCallback, @@ -306,9 +309,14 @@ class SocketWrapper { class ServerHolder { public: template - ServerHolder(Delegate* delegate, uv_loop_t* loop, int port, FILE* out = NULL) + ServerHolder(Delegate* delegate, uv_loop_t* loop, int port) + : ServerHolder(delegate, loop, HOST, port, NULL) { } + + template + ServerHolder(Delegate* delegate, uv_loop_t* loop, const std::string host, + int port, FILE* out) : closed(false), paused(false), - server_(delegate, loop, HOST, port, out) { + server_(delegate, loop, host, port, out) { delegate->Connect(&server_); } @@ -317,7 +325,7 @@ class ServerHolder { } int port() { - return server_.port(); + return server_.Port(); } static void CloseCallback(InspectorSocketServer* server) { @@ -575,3 +583,47 @@ TEST_F(InspectorSocketServerTest, TerminatingSessionReportsDone) { socket1.ExpectEOF(); SPIN_WHILE(!delegate.done); } + +TEST_F(InspectorSocketServerTest, FailsToBindToNodejsHost) { + TestInspectorServerDelegate delegate; + ServerHolder server(&delegate, &loop, "nodejs.org", 0, nullptr); + ASSERT_FALSE(server->Start()); + SPIN_WHILE(uv_loop_alive(&loop)); +} + +bool has_ipv6_address() { + uv_interface_address_s* addresses = nullptr; + int address_count = 0; + int err = uv_interface_addresses(&addresses, &address_count); + if (err != 0) { + return false; + } + bool has_address = false; + for (int i = 0; i < address_count; i++) { + if (addresses[i].address.address6.sin6_family == AF_INET6) { + has_address = true; + } + } + uv_free_interface_addresses(addresses, address_count); + return has_address; +} + +TEST_F(InspectorSocketServerTest, BindsToIpV6) { + if (!has_ipv6_address()) { + fprintf(stderr, "No IPv6 network detected\n"); + return; + } + TestInspectorServerDelegate delegate; + ServerHolder server(&delegate, &loop, "::", 0, NULL); + ASSERT_TRUE(server->Start()); + + SocketWrapper socket1(&loop); + socket1.Connect("[::]", server.port(), true); + socket1.Write(WsHandshakeRequest(MAIN_TARGET_ID)); + socket1.Expect(WS_HANDSHAKE_RESPONSE); + server->Stop(ServerHolder::CloseCallback); + SPIN_WHILE(!server.closed); + ASSERT_FALSE(delegate.done); + socket1.Close(); + SPIN_WHILE(!delegate.done); +}