From ceb33b8b32824c0b93d714ea7d262390bb04e630 Mon Sep 17 00:00:00 2001 From: liuweili Date: Sat, 11 Jan 2025 15:10:17 +0800 Subject: [PATCH 1/4] add inspector_socket Signed-off-by: liuweili --- src/inspector/inspector_socket.cpp | 873 +++++++++++++++++++++++++++++ src/inspector/inspector_socket.h | 69 +++ 2 files changed, 942 insertions(+) create mode 100644 src/inspector/inspector_socket.cpp create mode 100644 src/inspector/inspector_socket.h diff --git a/src/inspector/inspector_socket.cpp b/src/inspector/inspector_socket.cpp new file mode 100644 index 0000000..85f4bb7 --- /dev/null +++ b/src/inspector/inspector_socket.cpp @@ -0,0 +1,873 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "inspector_socket.h" + +#include +#include +#include + +#include "inspector/inspector_utils.h" +#include "llhttp.h" +#include "openssl/sha.h" // Sha-1 hash + +#define ACCEPT_KEY_LENGTH Base64EncodeSize(20) + +#define DUMP_READS 0 +#define DUMP_WRITES 0 + +namespace jsvm { +namespace inspector { + +class TcpHolder { +public: + static void DisconnectAndDispose(TcpHolder* holder); + using Pointer = DeleteFnPtr; + + static Pointer Accept(uv_stream_t* server, InspectorSocket::DelegatePointer delegate); + void SetHandler(ProtocolHandler* handler); + int WriteRaw(const std::vector& buffer, uv_write_cb writeCb); + uv_tcp_t* GetTcp() + { + return &tcp; + } + InspectorSocket::Delegate* GetDelegate(); + +private: + static TcpHolder* From(void* handle) + { + return jsvm::inspector::ContainerOf(&TcpHolder::tcp, reinterpret_cast(handle)); + } + static void OnClosed(uv_handle_t* handle); + static void OnDataReceivedCb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf); + explicit TcpHolder(InspectorSocket::DelegatePointer delegate); + ~TcpHolder() = default; + void ReclaimUvBuf(const uv_buf_t* buf, ssize_t read); + + uv_tcp_t tcp; + const InspectorSocket::DelegatePointer delegate; + ProtocolHandler* handler; + std::vector buffer; +}; + +class ProtocolHandler { +public: + ProtocolHandler(InspectorSocket* inspector, TcpHolder::Pointer tcp); + + virtual void AcceptUpgrade(const std::string& acceptKey) = 0; + virtual void OnData(std::vector* data) = 0; + virtual void OnEof() = 0; + virtual void Write(const std::vector data) = 0; + virtual void CancelHandshake() = 0; + + std::string GetHost() const; + + InspectorSocket* GetInspectorSocket() + { + return inspector; + } + virtual void Shutdown() = 0; + +protected: + virtual ~ProtocolHandler() = default; + int WriteRaw(const std::vector& buffer, uv_write_cb writeCb); + InspectorSocket::Delegate* GetDelegate(); + + InspectorSocket* const inspector; + TcpHolder::Pointer tcp; +}; + +namespace { + +#if DUMP_READS || DUMP_WRITES +static void dump_hex(const char* buf, size_t len) +{ + const char* ptr = buf; + const char* end = ptr + len; + const char* cptr; + char c; + int i; + + while (ptr < end) { + cptr = ptr; + constexpr size_t byte1 = 16; + for (i = 0; i < byte1 && ptr < end; i++) { + printf("%2.2X ", static_cast(*(ptr++))); + } + constexpr size_t byte2 = 72; + for (i = byte2 - (i * 4); i > 0; i--) { + printf(" "); + } + constexpr size_t byte3 = 16; + for (i = 0; i < byte3 && cptr < end; i++) { + c = *(cptr++); + printf("%c", (c > 0x19) ? c : '.'); + } + printf("\n"); + } + printf("\n\n"); +} +#endif + +class WriteRequest { +public: + WriteRequest(ProtocolHandler* handler, const std::vector& buffer) + : handler(handler), storage(buffer), req(uv_write_t()), buf(uv_buf_init(storage.data(), storage.size())) + {} + + static WriteRequest* from_write_req(uv_write_t* req) + { + return jsvm::inspector::ContainerOf(&WriteRequest::req, req); + } + + static void Cleanup(uv_write_t* req, int status) + { + delete WriteRequest::from_write_req(req); + } + + ProtocolHandler* const handler; + std::vector storage; + uv_write_t req; + uv_buf_t buf; +}; + +void allocate_buffer(uv_handle_t* stream, size_t len, uv_buf_t* buf) +{ + *buf = uv_buf_init(new char[len], len); +} + +static void remove_from_beginning(std::vector* buffer, size_t count) +{ + buffer->erase(buffer->begin(), buffer->begin() + count); +} + +static const char CLOSE_FRAME[] = { '\x88', '\x00' }; + +enum WsDecodeResult { FRAME_OK, FRAME_INCOMPLETE, FRAME_CLOSE, FRAME_ERROR }; + +static void generate_accept_string(const std::string& clientKey, char (*buffer)[ACCEPT_KEY_LENGTH]) +{ + // Magic string from websockets spec. + static constexpr char WS_MAGIC[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + std::string input(clientKey + WS_MAGIC); + char hash[SHA_DIGEST_LENGTH]; + USE(SHA1(reinterpret_cast(input.data()), input.size(), + reinterpret_cast(hash))); + jsvm::inspector::Base64Encode(hash, sizeof(hash), *buffer, sizeof(*buffer)); +} + +static std::string TrimPort(const std::string& host) +{ + size_t lastColonPos = host.rfind(':'); + if (lastColonPos == std::string::npos) { + return host; + } + size_t bracket = host.rfind(']'); + if (bracket == std::string::npos || lastColonPos > bracket) { + return host.substr(0, lastColonPos); + } + return host; +} + +static bool IsIPAddress(const std::string& host) +{ + // To avoid DNS rebinding attacks, we are aware of the following requirements: + // * the host name must be an IP address (CVE-2018-7160, CVE-2022-32212), + // * the IP address must be routable (hackerone.com/reports/1632921), and + // * the IP address must be formatted unambiguously (CVE-2022-43548). + + // The logic below assumes that the string is null-terminated, so ensure that + // we did not somehow end up with null characters within the string. + if (host.find('\0') != std::string::npos) { + return false; + } + + // All IPv6 addresses must be enclosed in square brackets, and anything + // enclosed in square brackets must be an IPv6 address. + if (host.length() >= 4 && host.front() == '[' && host.back() == ']') { + // INET6_ADDRSTRLEN is the maximum length of the dual format (including the + // terminating null character), which is the longest possible representation + // of an IPv6 address: xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:ddd.ddd.ddd.ddd + if (host.length() - 2 >= INET6_ADDRSTRLEN) { + return false; + } + + // Annoyingly, libuv's implementation of inet_pton() deviates from other + // implementations of the function in that it allows '%' in IPv6 addresses. + if (host.find('%') != std::string::npos) { + return false; + } + + // Parse the IPv6 address to ensure it is syntactically valid. + char ipv6Str[INET6_ADDRSTRLEN]; + std::copy(host.begin() + 1, host.end() - 1, ipv6Str); + ipv6Str[host.length()] = '\0'; + unsigned char ipv6[sizeof(struct in6_addr)]; + if (uv_inet_pton(AF_INET6, ipv6Str, ipv6) != 0) { + return false; + } + + // The only non-routable IPv6 address is ::/128. It should not be necessary + // to explicitly reject it because it will still be enclosed in square + // brackets and not even macOS should make DNS requests in that case, but + // history has taught us that we cannot be careful enough. + // Note that RFC 4291 defines both "IPv4-Compatible IPv6 Addresses" and + // "IPv4-Mapped IPv6 Addresses", which means that there are IPv6 addresses + // (other than ::/128) that represent non-routable IPv4 addresses. However, + // this translation assumes that the host is interpreted as an IPv6 address + // in the first place, at which point DNS rebinding should not be an issue. + if (std::all_of(ipv6, ipv6 + sizeof(ipv6), [](auto b) { return b == 0; })) { + return false; + } + + // It is a syntactically valid and routable IPv6 address enclosed in square + // brackets. No client should be able to misinterpret this. + return true; + } + + // Anything not enclosed in square brackets must be an IPv4 address. It is + // important here that inet_pton() accepts only the so-called dotted-decimal + // notation, which is a strict subset of the so-called numbers-and-dots + // notation that is allowed by inet_aton() and inet_addr(). This subset does + // not allow hexadecimal or octal number formats. + unsigned char ipv4[sizeof(struct in_addr)]; + if (uv_inet_pton(AF_INET, host.c_str(), ipv4) != 0) { + return false; + } + + // The only strictly non-routable IPv4 address is 0.0.0.0, and macOS will make + // DNS requests for this IP address, so we need to explicitly reject it. In + // fact, we can safely reject all of 0.0.0.0/8 (see Section 3.2 of RFC 791 and + // Section 3.2.1.3 of RFC 1122). + // Note that inet_pton() stores the IPv4 address in network byte order. + if (ipv4[0] == 0) { + return false; + } + + // It is a routable IPv4 address in dotted-decimal notation. + return true; +} + +// Constants for hybi-10 frame format. + +typedef int OpCode; + +const OpCode K_OP_CODE_CONTINUATION = 0x0; +const OpCode K_OP_CODE_TEXT = 0x1; +const OpCode K_OP_CODE_BINARY = 0x2; +const OpCode K_OP_CODE_CLOSE = 0x8; +const OpCode K_OP_CODE_PING = 0x9; +const OpCode K_OP_CODE_PONG = 0xA; + +const unsigned char K_FINAL_BIT = 0x80; +const unsigned char K_RESERVED_1_BIT = 0x40; +const unsigned char K_RESERVED_2_BIT = 0x20; +const unsigned char K_RESERVED_3_BIT = 0x10; +const unsigned char K_OP_CODE_MASK = 0xF; +const unsigned char K_MASK_BIT = 0x80; +const unsigned char K_PAYLOAD_LENGTH_MASK = 0x7F; + +const size_t K_MAX_SINGLE_BYTE_PAYLOAD_LENGTH = 125; +const size_t K_TWO_BYTE_PAYLOAD_LENGTH_FIELD = 126; +const size_t K_EIGHT_BYTE_PAYLOAD_LENGTH_FIELD = 127; +const size_t K_MASKING_KEY_WIDTH_IN_BYTES = 4; + +static std::vector encode_frame_hybi17(const std::vector& message) +{ + std::vector frame; + OpCode opCode = K_OP_CODE_TEXT; + frame.push_back(K_FINAL_BIT | opCode); + const size_t dataLength = message.size(); + if (dataLength <= K_MAX_SINGLE_BYTE_PAYLOAD_LENGTH) { + frame.push_back(static_cast(dataLength)); + } else if (dataLength <= 0xFFFF) { + frame.push_back(K_TWO_BYTE_PAYLOAD_LENGTH_FIELD); + frame.push_back((dataLength & 0xFF00) >> 8); + frame.push_back(dataLength & 0xFF); + } else { + frame.push_back(K_EIGHT_BYTE_PAYLOAD_LENGTH_FIELD); + char extendedPayloadLength[8]; + size_t remaining = dataLength; + // Fill the length into extendedPayloadLength in the network byte order. + constexpr size_t byteCount = 8; + for (int i = 0; i < byteCount; ++i) { + extendedPayloadLength[7 - i] = remaining & 0xFF; + remaining >>= 8; + } + frame.insert(frame.end(), extendedPayloadLength, extendedPayloadLength + 8); + CHECK_EQ(0, remaining); + } + frame.insert(frame.end(), message.begin(), message.end()); + return frame; +} + +static WsDecodeResult DecodeFrameHybi17(const std::vector& buffer, + bool clientFrame, + int* bytesConsumed, + std::vector* output, + bool* compressed) +{ + *bytesConsumed = 0; + if (buffer.size() < 2) { + return FRAME_INCOMPLETE; + } + + auto it = buffer.begin(); + + unsigned char firstByte = *it++; + unsigned char secondByte = *it++; + + bool final = (firstByte & K_FINAL_BIT) != 0; + bool reserved1 = (firstByte & K_RESERVED_1_BIT) != 0; + bool reserved2 = (firstByte & K_RESERVED_2_BIT) != 0; + bool reserved3 = (firstByte & K_RESERVED_3_BIT) != 0; + int opCode = firstByte & K_OP_CODE_MASK; + bool masked = (secondByte & K_MASK_BIT) != 0; + *compressed = reserved1; + if (!final || reserved2 || reserved3) { + return FRAME_ERROR; // Only compression extension is supported. + } + + bool closed = false; + switch (opCode) { + case K_OP_CODE_CLOSE: + closed = true; + break; + case K_OP_CODE_TEXT: + break; + case K_OP_CODE_BINARY: // We don't support binary frames yet. + case K_OP_CODE_CONTINUATION: // We don't support binary frames yet. + case K_OP_CODE_PING: // We don't support binary frames yet. + case K_OP_CODE_PONG: // We don't support binary frames yet. + default: + return FRAME_ERROR; + } + + // In Hybi-17 spec client MUST mask its frame. + if (clientFrame && !masked) { + return FRAME_ERROR; + } + + uint64_t payloadLength64 = secondByte & K_PAYLOAD_LENGTH_MASK; + if (payloadLength64 > K_MAX_SINGLE_BYTE_PAYLOAD_LENGTH) { + int extendedPayloadLengthSize; + if (payloadLength64 == K_TWO_BYTE_PAYLOAD_LENGTH_FIELD) { + extendedPayloadLengthSize = 2; + } else if (payloadLength64 == K_EIGHT_BYTE_PAYLOAD_LENGTH_FIELD) { + extendedPayloadLengthSize = 8; + } else { + return FRAME_ERROR; + } + if ((buffer.end() - it) < extendedPayloadLengthSize) { + return FRAME_INCOMPLETE; + } + payloadLength64 = 0; + for (int i = 0; i < extendedPayloadLengthSize; ++i) { + payloadLength64 <<= 8; + payloadLength64 |= static_cast(*it++); + } + } + + static const uint64_t maxPayloadLength = 0x7FFFFFFFFFFFFFFF; + static const size_t maxLength = SIZE_MAX; + if (payloadLength64 > maxPayloadLength || payloadLength64 > maxLength - K_MASKING_KEY_WIDTH_IN_BYTES) { + // WebSocket frame length too large. + return FRAME_ERROR; + } + size_t payloadLength = static_cast(payloadLength64); + + if (buffer.size() - K_MASKING_KEY_WIDTH_IN_BYTES < payloadLength) { + return FRAME_INCOMPLETE; + } + + std::vector::const_iterator maskingKey = it; + std::vector::const_iterator payload = it + K_MASKING_KEY_WIDTH_IN_BYTES; + for (size_t i = 0; i < payloadLength; ++i) { // Unmask the payload. + output->insert(output->end(), payload[i] ^ maskingKey[i % K_MASKING_KEY_WIDTH_IN_BYTES]); + } + + size_t pos = it + K_MASKING_KEY_WIDTH_IN_BYTES + payloadLength - buffer.begin(); + *bytesConsumed = pos; + return closed ? FRAME_CLOSE : FRAME_OK; +} + +// WS protocol +class WsHandler : public ProtocolHandler { +public: + WsHandler(InspectorSocket* inspector, TcpHolder::Pointer tcp) + : ProtocolHandler(inspector, std::move(tcp)), onCloseSent(&WsHandler::WaitForCloseReply), + onCloseReceived(&WsHandler::CloseFrameReceived), dispose(false) + {} + + void AcceptUpgrade(const std::string& acceptKey) override {} + void CancelHandshake() override {} + + void OnEof() override + { + tcp.reset(); + if (dispose) { + delete this; + } + } + + void OnData(std::vector* data) override + { + // 1. Parse. + int processed = 0; + do { + processed = ParseWsFrames(*data); + // 2. Fix the data size & length + if (processed > 0) { + remove_from_beginning(data, processed); + } + } while (processed > 0 && !data->empty()); + } + + void Write(const std::vector data) override + { + std::vector output = encode_frame_hybi17(data); + WriteRaw(output, WriteRequest::Cleanup); + } + +protected: + void Shutdown() override + { + if (tcp) { + dispose = true; + SendClose(); + } else { + delete this; + } + } + +private: + using Callback = void (WsHandler::*)(); + + static void OnCloseFrameWritten(uv_write_t* req, int status) + { + WriteRequest* wr = WriteRequest::from_write_req(req); + WsHandler* handler = static_cast(wr->handler); + delete wr; + Callback cb = handler->onCloseSent; + (handler->*cb)(); + } + + void WaitForCloseReply() + { + onCloseReceived = &WsHandler::OnEof; + } + + void SendClose() + { + WriteRaw(std::vector(CLOSE_FRAME, CLOSE_FRAME + sizeof(CLOSE_FRAME)), OnCloseFrameWritten); + } + + void CloseFrameReceived() + { + onCloseSent = &WsHandler::OnEof; + SendClose(); + } + + int ParseWsFrames(const std::vector& buffer) + { + int bytesConsumed = 0; + std::vector output; + bool compressed = false; + + WsDecodeResult r = DecodeFrameHybi17(buffer, true /* clientFrame */, &bytesConsumed, &output, &compressed); + // Compressed frame means client is ignoring the headers and misbehaves + if (compressed || r == FRAME_ERROR) { + OnEof(); + bytesConsumed = 0; + } else if (r == FRAME_CLOSE) { + (this->*onCloseReceived)(); + bytesConsumed = 0; + } else if (r == FRAME_OK) { + GetDelegate()->OnWsFrame(output); + } + return bytesConsumed; + } + + Callback onCloseSent; + Callback onCloseReceived; + bool dispose; +}; + +// HTTP protocol +class HttpEvent { +public: + HttpEvent(const std::string& path, bool upgrade, bool isGET, const std::string& wsKey, const std::string& host) + : path(path), upgrade(upgrade), isGET(isGET), wsKey(wsKey), host(host) + {} + + std::string path; + bool upgrade; + bool isGET; + std::string wsKey; + std::string host; +}; + +class HttpHandler : public ProtocolHandler { +public: + explicit HttpHandler(InspectorSocket* inspector, TcpHolder::Pointer tcp) + : ProtocolHandler(inspector, std::move(tcp)), parsingValue(false) + { + llhttp_init(&parser, HTTP_REQUEST, &parserSettings); + llhttp_settings_init(&parserSettings); + parserSettings.on_header_field = OnHeaderField; + parserSettings.on_header_value = OnHeaderValue; + parserSettings.on_message_complete = OnMessageComplete; + parserSettings.on_url = OnPath; + } + + void AcceptUpgrade(const std::string& acceptKey) override + { + char acceptString[ACCEPT_KEY_LENGTH]; + generate_accept_string(acceptKey, &acceptString); + const char acceptWsPrefix[] = "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: "; + const char acceptWsSuffix[] = "\r\n\r\n"; + std::vector reply(acceptWsPrefix, acceptWsPrefix + sizeof(acceptWsPrefix) - 1); + reply.insert(reply.end(), acceptString, acceptString + sizeof(acceptString)); + reply.insert(reply.end(), acceptWsSuffix, acceptWsSuffix + sizeof(acceptWsSuffix) - 1); + if (WriteRaw(reply, WriteRequest::Cleanup) >= 0) { + inspector->SwitchProtocol(new WsHandler(inspector, std::move(tcp))); + } else { + tcp.reset(); + } + } + + void CancelHandshake() override + { + const char handshakeFailedResponse[] = "HTTP/1.0 400 Bad Request\r\n" + "Content-Type: text/html; charset=UTF-8\r\n\r\n" + "WebSockets request was expected\r\n"; + WriteRaw(std::vector(handshakeFailedResponse, + handshakeFailedResponse + sizeof(handshakeFailedResponse) - 1), + ThenCloseAndReportFailure); + } + + void OnEof() override + { + tcp.reset(); + } + + void OnData(std::vector* data) override + { + llhttp_errno_t err = llhttp_execute(&parser, data->data(), data->size()); + if (err == HPE_PAUSED_UPGRADE) { + err = HPE_OK; + llhttp_resume_after_upgrade(&parser); + } + data->clear(); + if (err != HPE_OK) { + CancelHandshake(); + } + // Event handling may delete *this + std::vector httpEvents; + std::swap(httpEvents, events); + for (const HttpEvent& event : httpEvents) { + if (!IsAllowedHost(event.host) || !event.isGET) { + CancelHandshake(); + return; + } else if (!event.upgrade) { + GetDelegate()->OnHttpGet(event.host, event.path); + } else if (event.wsKey.empty()) { + CancelHandshake(); + return; + } else { + GetDelegate()->OnSocketUpgrade(event.host, event.path, event.wsKey); + } + } + } + + void Write(const std::vector data) override + { + WriteRaw(data, WriteRequest::Cleanup); + } + +protected: + void Shutdown() override + { + delete this; + } + +private: + static void ThenCloseAndReportFailure(uv_write_t* req, int status) + { + ProtocolHandler* handler = WriteRequest::from_write_req(req)->handler; + WriteRequest::Cleanup(req, status); + handler->GetInspectorSocket()->SwitchProtocol(nullptr); + } + + static int OnHeaderValue(llhttp_t* parser, const char* at, size_t length) + { + HttpHandler* handler = From(parser); + handler->parsingValue = true; + handler->headers[handler->currentHeader].append(at, length); + return 0; + } + + static int OnHeaderField(llhttp_t* parser, const char* at, size_t length) + { + HttpHandler* handler = From(parser); + if (handler->parsingValue) { + handler->parsingValue = false; + handler->currentHeader.clear(); + } + handler->currentHeader.append(at, length); + return 0; + } + + static int OnPath(llhttp_t* parser, const char* at, size_t length) + { + HttpHandler* handler = From(parser); + handler->path.append(at, length); + return 0; + } + + static HttpHandler* From(llhttp_t* parser) + { + return jsvm::inspector::ContainerOf(&HttpHandler::parser, parser); + } + + static int OnMessageComplete(llhttp_t* parser) + { + // Event needs to be fired after the parser is done. + HttpHandler* handler = From(parser); + handler->events.emplace_back(handler->path, parser->upgrade, parser->method == HTTP_GET, + handler->HeaderValue("Sec-WebSocket-Key"), handler->HeaderValue("Host")); + handler->path = ""; + handler->parsingValue = false; + handler->headers.clear(); + handler->currentHeader = ""; + return 0; + } + + std::string HeaderValue(const std::string& header) const + { + bool headerFound = false; + std::string value; + for (const auto& header_value : headers) { + if (jsvm::inspector::StringEqualNoCaseN(header_value.first.data(), header.data(), header.length())) { + if (headerFound) { + return ""; + } + value = header_value.second; + headerFound = true; + } + } + return value; + } + + bool IsAllowedHost(const std::string& hostWithPort) const + { + std::string host = TrimPort(hostWithPort); + return host.empty() || IsIPAddress(host) || jsvm::inspector::StringEqualNoCase(host.data(), "localhost"); + } + + bool parsingValue; + llhttp_t parser; + llhttp_settings_t parserSettings; + std::vector events; + std::string currentHeader; + std::map headers; + std::string path; +}; + +} // namespace + +// Any protocol +ProtocolHandler::ProtocolHandler(InspectorSocket* inspector, TcpHolder::Pointer tcpParam) + : inspector(inspector), tcp(std::move(tcpParam)) +{ + CHECK_NOT_NULL(tcp); + tcp->SetHandler(this); +} + +int ProtocolHandler::WriteRaw(const std::vector& buffer, uv_write_cb writeCb) +{ + return tcp->WriteRaw(buffer, writeCb); +} + +InspectorSocket::Delegate* ProtocolHandler::GetDelegate() +{ + return tcp->GetDelegate(); +} + +std::string ProtocolHandler::GetHost() const +{ + char ip[INET6_ADDRSTRLEN]; + sockaddr_storage addr; + int len = sizeof(addr); + int err = uv_tcp_getsockname(tcp->GetTcp(), reinterpret_cast(&addr), &len); + if (err != 0) { + return ""; + } + if (addr.ss_family == AF_INET6) { + const sockaddr_in6* v6 = reinterpret_cast(&addr); + err = uv_ip6_name(v6, ip, sizeof(ip)); + } else { + const sockaddr_in* v4 = reinterpret_cast(&addr); + err = uv_ip4_name(v4, ip, sizeof(ip)); + } + if (err != 0) { + return ""; + } + return ip; +} + +// RAII uv_tcp_t wrapper +TcpHolder::TcpHolder(InspectorSocket::DelegatePointer delegate) : tcp(), delegate(std::move(delegate)), handler(nullptr) +{} + +// static +TcpHolder::Pointer TcpHolder::Accept(uv_stream_t* server, InspectorSocket::DelegatePointer delegate) +{ + TcpHolder* result = new TcpHolder(std::move(delegate)); + uv_stream_t* tcp = reinterpret_cast(&result->tcp); + int err = uv_tcp_init(server->loop, &result->tcp); + if (err == 0) { + err = uv_accept(server, tcp); + } + if (err == 0) { + err = uv_read_start(tcp, allocate_buffer, OnDataReceivedCb); + } + if (err == 0) { + return TcpHolder::Pointer(result); + } else { + delete result; + return nullptr; + } +} + +void TcpHolder::SetHandler(ProtocolHandler* protocalHandler) +{ + handler = protocalHandler; +} + +int TcpHolder::WriteRaw(const std::vector& buffer, uv_write_cb writeCb) +{ +#if DUMP_WRITES + printf("%s (%ld bytes):\n", __FUNCTION__, buffer.size()); + dump_hex(buffer.data(), buffer.size()); + printf("\n"); +#endif + + // Freed in write_request_cleanup + WriteRequest* wr = new WriteRequest(handler, buffer); + uv_stream_t* stream = reinterpret_cast(&tcp); + int err = uv_write(&wr->req, stream, &wr->buf, 1, writeCb); + if (err < 0) { + delete wr; + } + return err < 0; +} + +InspectorSocket::Delegate* TcpHolder::GetDelegate() +{ + return delegate.get(); +} + +// static +void TcpHolder::OnClosed(uv_handle_t* handle) +{ + delete From(handle); +} + +void TcpHolder::OnDataReceivedCb(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) +{ +#if DUMP_READS + if (nread >= 0) { + printf("%s (%ld bytes)\n", __FUNCTION__, nread); + dump_hex(buf->base, nread); + } else { + printf("[%s:%d] %s\n", __FUNCTION__, __LINE__, uv_err_name(nread)); + } +#endif + TcpHolder* holder = From(tcp); + holder->ReclaimUvBuf(buf, nread); + if (nread < 0 || nread == UV_EOF) { + holder->handler->OnEof(); + } else { + holder->handler->OnData(&holder->buffer); + } +} + +// static +void TcpHolder::DisconnectAndDispose(TcpHolder* holder) +{ + uv_handle_t* handle = reinterpret_cast(&holder->tcp); + uv_close(handle, OnClosed); +} + +void TcpHolder::ReclaimUvBuf(const uv_buf_t* buf, ssize_t read) +{ + if (read > 0) { + buffer.insert(buffer.end(), buf->base, buf->base + read); + } + delete[] buf->base; +} + +InspectorSocket::~InspectorSocket() = default; + +// static +void InspectorSocket::Shutdown(ProtocolHandler* handler) +{ + handler->Shutdown(); +} + +// static +InspectorSocket::Pointer InspectorSocket::Accept(uv_stream_t* server, DelegatePointer delegate) +{ + auto tcp = TcpHolder::Accept(server, std::move(delegate)); + if (tcp) { + InspectorSocket* inspector = new InspectorSocket(); + inspector->SwitchProtocol(new HttpHandler(inspector, std::move(tcp))); + return InspectorSocket::Pointer(inspector); + } else { + return InspectorSocket::Pointer(nullptr); + } +} + +void InspectorSocket::AcceptUpgrade(const std::string& acceptKey) +{ + protocolHandler->AcceptUpgrade(acceptKey); +} + +void InspectorSocket::CancelHandshake() +{ + protocolHandler->CancelHandshake(); +} + +std::string InspectorSocket::GetHost() +{ + return protocolHandler->GetHost(); +} + +void InspectorSocket::SwitchProtocol(ProtocolHandler* handler) +{ + protocolHandler.reset(std::move(handler)); +} + +void InspectorSocket::Write(const char* data, size_t len) +{ + protocolHandler->Write(std::vector(data, data + len)); +} + +} // namespace inspector +} // namespace jsvm diff --git a/src/inspector/inspector_socket.h b/src/inspector/inspector_socket.h new file mode 100644 index 0000000..de03227 --- /dev/null +++ b/src/inspector/inspector_socket.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_INSPECTOR_SOCKET_H_ +#define SRC_INSPECTOR_SOCKET_H_ + +#include +#include + +#include "inspector_utils.h" +#include "uv.h" + +namespace jsvm { +namespace inspector { + +class ProtocolHandler; + +// HTTP Wrapper around a uv_tcp_t +class InspectorSocket { +public: + class Delegate { + public: + virtual void OnHttpGet(const std::string& host, const std::string& path) = 0; + virtual void OnSocketUpgrade(const std::string& host, + const std::string& path, + const std::string& acceptKey) = 0; + virtual void OnWsFrame(const std::vector& frame) = 0; + virtual ~Delegate() = default; + }; + + using DelegatePointer = std::unique_ptr; + using Pointer = std::unique_ptr; + + static Pointer Accept(uv_stream_t* server, DelegatePointer delegate); + + ~InspectorSocket(); + + void AcceptUpgrade(const std::string& acceptKey); + void CancelHandshake(); + void Write(const char* data, size_t len); + void SwitchProtocol(ProtocolHandler* handler); + std::string GetHost(); + + InspectorSocket(const InspectorSocket&) = delete; + InspectorSocket& operator=(const InspectorSocket&) = delete; + +private: + static void Shutdown(ProtocolHandler* handler); + InspectorSocket() = default; + + DeleteFnPtr protocolHandler; +}; + +} // namespace inspector +} // namespace jsvm + +#endif // SRC_INSPECTOR_SOCKET_H_ -- Gitee From eb06e858c9d10cef259e21aeed883cc69d8a8ae7 Mon Sep 17 00:00:00 2001 From: liuweili Date: Sat, 11 Jan 2025 15:40:11 +0800 Subject: [PATCH 2/4] fix code check Signed-off-by: liuweili --- src/inspector/inspector_socket.cpp | 115 +++++++++-------------------- src/inspector/inspector_socket.h | 29 +++++--- 2 files changed, 52 insertions(+), 92 deletions(-) diff --git a/src/inspector/inspector_socket.cpp b/src/inspector/inspector_socket.cpp index 85f4bb7..47527a7 100644 --- a/src/inspector/inspector_socket.cpp +++ b/src/inspector/inspector_socket.cpp @@ -25,9 +25,6 @@ #define ACCEPT_KEY_LENGTH Base64EncodeSize(20) -#define DUMP_READS 0 -#define DUMP_WRITES 0 - namespace jsvm { namespace inspector { @@ -90,51 +87,20 @@ protected: }; namespace { - -#if DUMP_READS || DUMP_WRITES -static void dump_hex(const char* buf, size_t len) -{ - const char* ptr = buf; - const char* end = ptr + len; - const char* cptr; - char c; - int i; - - while (ptr < end) { - cptr = ptr; - constexpr size_t byte1 = 16; - for (i = 0; i < byte1 && ptr < end; i++) { - printf("%2.2X ", static_cast(*(ptr++))); - } - constexpr size_t byte2 = 72; - for (i = byte2 - (i * 4); i > 0; i--) { - printf(" "); - } - constexpr size_t byte3 = 16; - for (i = 0; i < byte3 && cptr < end; i++) { - c = *(cptr++); - printf("%c", (c > 0x19) ? c : '.'); - } - printf("\n"); - } - printf("\n\n"); -} -#endif - class WriteRequest { public: WriteRequest(ProtocolHandler* handler, const std::vector& buffer) : handler(handler), storage(buffer), req(uv_write_t()), buf(uv_buf_init(storage.data(), storage.size())) {} - static WriteRequest* from_write_req(uv_write_t* req) + static WriteRequest* FromWriteReq(uv_write_t* req) { return jsvm::inspector::ContainerOf(&WriteRequest::req, req); } static void Cleanup(uv_write_t* req, int status) { - delete WriteRequest::from_write_req(req); + delete WriteRequest::FromWriteReq(req); } ProtocolHandler* const handler; @@ -143,12 +109,13 @@ public: uv_buf_t buf; }; -void allocate_buffer(uv_handle_t* stream, size_t len, uv_buf_t* buf) +void AllocateBuffer(uv_handle_t* stream, size_t len, uv_buf_t* buf) { + CHECK(len > 0); *buf = uv_buf_init(new char[len], len); } -static void remove_from_beginning(std::vector* buffer, size_t count) +static void RemoveFromBeginning(std::vector* buffer, size_t count) { buffer->erase(buffer->begin(), buffer->begin() + count); } @@ -157,11 +124,11 @@ static const char CLOSE_FRAME[] = { '\x88', '\x00' }; enum WsDecodeResult { FRAME_OK, FRAME_INCOMPLETE, FRAME_CLOSE, FRAME_ERROR }; -static void generate_accept_string(const std::string& clientKey, char (*buffer)[ACCEPT_KEY_LENGTH]) +static void GenerateAcceptString(const std::string& clientKey, char (*buffer)[ACCEPT_KEY_LENGTH]) { // Magic string from websockets spec. - static constexpr char WS_MAGIC[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - std::string input(clientKey + WS_MAGIC); + static constexpr char wsMagic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + std::string input(clientKey + wsMagic); char hash[SHA_DIGEST_LENGTH]; USE(SHA1(reinterpret_cast(input.data()), input.size(), reinterpret_cast(hash))); @@ -247,11 +214,6 @@ static bool IsIPAddress(const std::string& host) return false; } - // The only strictly non-routable IPv4 address is 0.0.0.0, and macOS will make - // DNS requests for this IP address, so we need to explicitly reject it. In - // fact, we can safely reject all of 0.0.0.0/8 (see Section 3.2 of RFC 791 and - // Section 3.2.1.3 of RFC 1122). - // Note that inet_pton() stores the IPv4 address in network byte order. if (ipv4[0] == 0) { return false; } @@ -422,6 +384,12 @@ public: } } + void Write(const std::vector data) override + { + std::vector output = encode_frame_hybi17(data); + WriteRaw(output, WriteRequest::Cleanup); + } + void OnData(std::vector* data) override { // 1. Parse. @@ -430,17 +398,11 @@ public: processed = ParseWsFrames(*data); // 2. Fix the data size & length if (processed > 0) { - remove_from_beginning(data, processed); + RemoveFromBeginning(data, processed); } } while (processed > 0 && !data->empty()); } - void Write(const std::vector data) override - { - std::vector output = encode_frame_hybi17(data); - WriteRaw(output, WriteRequest::Cleanup); - } - protected: void Shutdown() override { @@ -448,6 +410,7 @@ protected: dispose = true; SendClose(); } else { + // if tcp is null, delete this delete this; } } @@ -457,7 +420,7 @@ private: static void OnCloseFrameWritten(uv_write_t* req, int status) { - WriteRequest* wr = WriteRequest::from_write_req(req); + WriteRequest* wr = WriteRequest::FromWriteReq(req); WsHandler* handler = static_cast(wr->handler); delete wr; Callback cb = handler->onCloseSent; @@ -535,7 +498,7 @@ public: void AcceptUpgrade(const std::string& acceptKey) override { char acceptString[ACCEPT_KEY_LENGTH]; - generate_accept_string(acceptKey, &acceptString); + GenerateAcceptString(acceptKey, &acceptString); const char acceptWsPrefix[] = "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" @@ -566,6 +529,11 @@ public: tcp.reset(); } + void Write(const std::vector data) override + { + WriteRaw(data, WriteRequest::Cleanup); + } + void OnData(std::vector* data) override { llhttp_errno_t err = llhttp_execute(&parser, data->data(), data->size()); @@ -595,11 +563,6 @@ public: } } - void Write(const std::vector data) override - { - WriteRaw(data, WriteRequest::Cleanup); - } - protected: void Shutdown() override { @@ -609,7 +572,7 @@ protected: private: static void ThenCloseAndReportFailure(uv_write_t* req, int status) { - ProtocolHandler* handler = WriteRequest::from_write_req(req)->handler; + ProtocolHandler* handler = WriteRequest::FromWriteReq(req)->handler; WriteRequest::Cleanup(req, status); handler->GetInspectorSocket()->SwitchProtocol(nullptr); } @@ -715,17 +678,19 @@ std::string ProtocolHandler::GetHost() const sockaddr_storage addr; int len = sizeof(addr); int err = uv_tcp_getsockname(tcp->GetTcp(), reinterpret_cast(&addr), &len); - if (err != 0) { + if (err) { return ""; } if (addr.ss_family == AF_INET6) { + // using ipv6 const sockaddr_in6* v6 = reinterpret_cast(&addr); err = uv_ip6_name(v6, ip, sizeof(ip)); } else { + // using ipv4 const sockaddr_in* v4 = reinterpret_cast(&addr); err = uv_ip4_name(v4, ip, sizeof(ip)); } - if (err != 0) { + if (err) { return ""; } return ip; @@ -745,7 +710,7 @@ TcpHolder::Pointer TcpHolder::Accept(uv_stream_t* server, InspectorSocket::Deleg err = uv_accept(server, tcp); } if (err == 0) { - err = uv_read_start(tcp, allocate_buffer, OnDataReceivedCb); + err = uv_read_start(tcp, AllocateBuffer, OnDataReceivedCb); } if (err == 0) { return TcpHolder::Pointer(result); @@ -762,12 +727,6 @@ void TcpHolder::SetHandler(ProtocolHandler* protocalHandler) int TcpHolder::WriteRaw(const std::vector& buffer, uv_write_cb writeCb) { -#if DUMP_WRITES - printf("%s (%ld bytes):\n", __FUNCTION__, buffer.size()); - dump_hex(buffer.data(), buffer.size()); - printf("\n"); -#endif - // Freed in write_request_cleanup WriteRequest* wr = new WriteRequest(handler, buffer); uv_stream_t* stream = reinterpret_cast(&tcp); @@ -791,14 +750,6 @@ void TcpHolder::OnClosed(uv_handle_t* handle) void TcpHolder::OnDataReceivedCb(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) { -#if DUMP_READS - if (nread >= 0) { - printf("%s (%ld bytes)\n", __FUNCTION__, nread); - dump_hex(buf->base, nread); - } else { - printf("[%s:%d] %s\n", __FUNCTION__, __LINE__, uv_err_name(nread)); - } -#endif TcpHolder* holder = From(tcp); holder->ReclaimUvBuf(buf, nread); if (nread < 0 || nread == UV_EOF) { @@ -818,6 +769,7 @@ void TcpHolder::DisconnectAndDispose(TcpHolder* holder) void TcpHolder::ReclaimUvBuf(const uv_buf_t* buf, ssize_t read) { if (read > 0) { + // insert buffer buffer.insert(buffer.end(), buf->base, buf->base + read); } delete[] buf->base; @@ -835,13 +787,14 @@ void InspectorSocket::Shutdown(ProtocolHandler* handler) InspectorSocket::Pointer InspectorSocket::Accept(uv_stream_t* server, DelegatePointer delegate) { auto tcp = TcpHolder::Accept(server, std::move(delegate)); + InspectorSocket* inspector = nullptr; if (tcp) { - InspectorSocket* inspector = new InspectorSocket(); + // If accept tcp, create new inspector socket + inspector = new InspectorSocket(); inspector->SwitchProtocol(new HttpHandler(inspector, std::move(tcp))); return InspectorSocket::Pointer(inspector); - } else { - return InspectorSocket::Pointer(nullptr); } + return InspectorSocket::Pointer(nullptr); } void InspectorSocket::AcceptUpgrade(const std::string& acceptKey) diff --git a/src/inspector/inspector_socket.h b/src/inspector/inspector_socket.h index de03227..55080a7 100644 --- a/src/inspector/inspector_socket.h +++ b/src/inspector/inspector_socket.h @@ -29,36 +29,43 @@ class ProtocolHandler; // HTTP Wrapper around a uv_tcp_t class InspectorSocket { +private: + InspectorSocket() = default; public: + using Pointer = std::unique_ptr; + + InspectorSocket(const InspectorSocket&) = delete; + + InspectorSocket& operator=(const InspectorSocket&) = delete; + + ~InspectorSocket(); + class Delegate { public: + virtual void OnWsFrame(const std::vector& frame) = 0; + virtual ~Delegate() = default; virtual void OnHttpGet(const std::string& host, const std::string& path) = 0; virtual void OnSocketUpgrade(const std::string& host, const std::string& path, const std::string& acceptKey) = 0; - virtual void OnWsFrame(const std::vector& frame) = 0; - virtual ~Delegate() = default; }; using DelegatePointer = std::unique_ptr; - using Pointer = std::unique_ptr; static Pointer Accept(uv_stream_t* server, DelegatePointer delegate); - ~InspectorSocket(); - void AcceptUpgrade(const std::string& acceptKey); - void CancelHandshake(); - void Write(const char* data, size_t len); + void SwitchProtocol(ProtocolHandler* handler); - std::string GetHost(); - InspectorSocket(const InspectorSocket&) = delete; - InspectorSocket& operator=(const InspectorSocket&) = delete; + void Write(const char* data, size_t len); + + void CancelHandshake(); + + std::string GetHost(); private: static void Shutdown(ProtocolHandler* handler); - InspectorSocket() = default; DeleteFnPtr protocolHandler; }; -- Gitee From 20d5a6f8c2efd3970759c9fcac79c3a8ef8b50f4 Mon Sep 17 00:00:00 2001 From: wangyimin Date: Sat, 11 Jan 2025 17:39:59 +0800 Subject: [PATCH 3/4] fix codecheck Signed-off-by: wangyimin --- src/inspector/inspector_socket.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/inspector/inspector_socket.cpp b/src/inspector/inspector_socket.cpp index 47527a7..ab6108f 100644 --- a/src/inspector/inspector_socket.cpp +++ b/src/inspector/inspector_socket.cpp @@ -163,11 +163,11 @@ static bool IsIPAddress(const std::string& host) // All IPv6 addresses must be enclosed in square brackets, and anything // enclosed in square brackets must be an IPv6 address. - if (host.length() >= 4 && host.front() == '[' && host.back() == ']') { + if (host.length() >= ByteSize::SIZE_4_BYTES && host.front() == '[' && host.back() == ']') { // INET6_ADDRSTRLEN is the maximum length of the dual format (including the // terminating null character), which is the longest possible representation // of an IPv6 address: xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:ddd.ddd.ddd.ddd - if (host.length() - 2 >= INET6_ADDRSTRLEN) { + if (host.length() - ByteSize::SIZE_2_BYTES >= INET6_ADDRSTRLEN) { return false; } @@ -260,15 +260,15 @@ static std::vector encode_frame_hybi17(const std::vector& message) frame.push_back(dataLength & 0xFF); } else { frame.push_back(K_EIGHT_BYTE_PAYLOAD_LENGTH_FIELD); - char extendedPayloadLength[8]; + constexpr size_t byteCount = 8; + char extendedPayloadLength[byteCount]; size_t remaining = dataLength; // Fill the length into extendedPayloadLength in the network byte order. - constexpr size_t byteCount = 8; for (int i = 0; i < byteCount; ++i) { - extendedPayloadLength[7 - i] = remaining & 0xFF; - remaining >>= 8; + extendedPayloadLength[byteCount - 1 - i] = remaining & 0xFF; + remaining >>= byteCount; } - frame.insert(frame.end(), extendedPayloadLength, extendedPayloadLength + 8); + frame.insert(frame.end(), extendedPayloadLength, extendedPayloadLength + byteCount); CHECK_EQ(0, remaining); } frame.insert(frame.end(), message.begin(), message.end()); @@ -326,9 +326,9 @@ static WsDecodeResult DecodeFrameHybi17(const std::vector& buffer, if (payloadLength64 > K_MAX_SINGLE_BYTE_PAYLOAD_LENGTH) { int extendedPayloadLengthSize; if (payloadLength64 == K_TWO_BYTE_PAYLOAD_LENGTH_FIELD) { - extendedPayloadLengthSize = 2; + extendedPayloadLengthSize = ByteSize::SIZE_2_BYTES; } else if (payloadLength64 == K_EIGHT_BYTE_PAYLOAD_LENGTH_FIELD) { - extendedPayloadLengthSize = 8; + extendedPayloadLengthSize = ByteSize::SIZE_8_BYTES; } else { return FRAME_ERROR; } @@ -787,7 +787,7 @@ void InspectorSocket::Shutdown(ProtocolHandler* handler) InspectorSocket::Pointer InspectorSocket::Accept(uv_stream_t* server, DelegatePointer delegate) { auto tcp = TcpHolder::Accept(server, std::move(delegate)); - InspectorSocket* inspector = nullptr; + InspectorSocket* inspector = nullptr; if (tcp) { // If accept tcp, create new inspector socket inspector = new InspectorSocket(); -- Gitee From 79d825a1970338b63a6673f3865d065d80e0cae7 Mon Sep 17 00:00:00 2001 From: wangyimin Date: Mon, 13 Jan 2025 09:59:13 +0800 Subject: [PATCH 4/4] fix codecheck Signed-off-by: wangyimin --- src/inspector/inspector_socket.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/inspector/inspector_socket.cpp b/src/inspector/inspector_socket.cpp index ab6108f..9c5b6da 100644 --- a/src/inspector/inspector_socket.cpp +++ b/src/inspector/inspector_socket.cpp @@ -256,7 +256,7 @@ static std::vector encode_frame_hybi17(const std::vector& message) frame.push_back(static_cast(dataLength)); } else if (dataLength <= 0xFFFF) { frame.push_back(K_TWO_BYTE_PAYLOAD_LENGTH_FIELD); - frame.push_back((dataLength & 0xFF00) >> 8); + frame.push_back((dataLength & 0xFF00) >> ByteOffset::BIT_8); frame.push_back(dataLength & 0xFF); } else { frame.push_back(K_EIGHT_BYTE_PAYLOAD_LENGTH_FIELD); @@ -282,7 +282,7 @@ static WsDecodeResult DecodeFrameHybi17(const std::vector& buffer, bool* compressed) { *bytesConsumed = 0; - if (buffer.size() < 2) { + if (buffer.size() < ByteSize::SIZE_2_BYTES) { return FRAME_INCOMPLETE; } @@ -337,7 +337,7 @@ static WsDecodeResult DecodeFrameHybi17(const std::vector& buffer, } payloadLength64 = 0; for (int i = 0; i < extendedPayloadLengthSize; ++i) { - payloadLength64 <<= 8; + payloadLength64 <<= ByteOffset::BIT_8; payloadLength64 |= static_cast(*it++); } } -- Gitee