From 4b250049af5b887a500937e32d06ae5a163592ff Mon Sep 17 00:00:00 2001 From: klzgrad Date: Sat, 24 Jun 2023 16:44:46 +0800 Subject: [PATCH] Refactor --- src/net/BUILD.gn | 10 +- ..._socket.cc => http_proxy_server_socket.cc} | 140 +++++---- ...xy_socket.h => http_proxy_server_socket.h} | 30 +- src/net/tools/naive/naive_connection.cc | 144 ++-------- src/net/tools/naive/naive_connection.h | 8 +- src/net/tools/naive/naive_padding_framer.cc | 117 ++++++++ src/net/tools/naive/naive_padding_framer.h | 72 +++++ src/net/tools/naive/naive_padding_socket.cc | 271 ++++++++++++++++++ src/net/tools/naive/naive_padding_socket.h | 105 +++++++ src/net/tools/naive/naive_protocol.cc | 44 +++ src/net/tools/naive/naive_protocol.h | 39 +++ src/net/tools/naive/naive_proxy.cc | 18 +- src/net/tools/naive/naive_proxy.h | 7 +- src/net/tools/naive/naive_proxy_bin.cc | 15 +- src/net/tools/naive/naive_proxy_delegate.cc | 128 +++++---- src/net/tools/naive/naive_proxy_delegate.h | 42 +-- tools/list-tls-sni-stream.sh | 10 + tools/parse-pcap-stream.py | 118 ++++++++ tools/sample-traffic.sh | 16 ++ 19 files changed, 1055 insertions(+), 279 deletions(-) rename src/net/tools/naive/{http_proxy_socket.cc => http_proxy_server_socket.cc} (68%) rename src/net/tools/naive/{http_proxy_socket.h => http_proxy_server_socket.h} (78%) create mode 100644 src/net/tools/naive/naive_padding_framer.cc create mode 100644 src/net/tools/naive/naive_padding_framer.h create mode 100644 src/net/tools/naive/naive_padding_socket.cc create mode 100644 src/net/tools/naive/naive_padding_socket.h create mode 100644 src/net/tools/naive/naive_protocol.cc create mode 100755 tools/list-tls-sni-stream.sh create mode 100755 tools/parse-pcap-stream.py create mode 100755 tools/sample-traffic.sh diff --git a/src/net/BUILD.gn b/src/net/BUILD.gn index 65f6324ca4..6144228ade 100644 --- a/src/net/BUILD.gn +++ b/src/net/BUILD.gn @@ -1767,13 +1767,19 @@ executable("naive") { sources = [ "tools/naive/naive_connection.cc", "tools/naive/naive_connection.h", + "tools/naive/naive_padding_framer.cc", + "tools/naive/naive_padding_framer.h", + "tools/naive/naive_padding_socket.cc", + "tools/naive/naive_padding_socket.h", + "tools/naive/naive_protocol.cc", + "tools/naive/naive_protocol.h", "tools/naive/naive_proxy.cc", "tools/naive/naive_proxy.h", "tools/naive/naive_proxy_bin.cc", "tools/naive/naive_proxy_delegate.h", "tools/naive/naive_proxy_delegate.cc", - "tools/naive/http_proxy_socket.cc", - "tools/naive/http_proxy_socket.h", + "tools/naive/http_proxy_server_socket.cc", + "tools/naive/http_proxy_server_socket.h", "tools/naive/redirect_resolver.h", "tools/naive/redirect_resolver.cc", "tools/naive/socks5_server_socket.cc", diff --git a/src/net/tools/naive/http_proxy_socket.cc b/src/net/tools/naive/http_proxy_server_socket.cc similarity index 68% rename from src/net/tools/naive/http_proxy_socket.cc rename to src/net/tools/naive/http_proxy_server_socket.cc index 1da4a935fa..cdbd9c3465 100644 --- a/src/net/tools/naive/http_proxy_socket.cc +++ b/src/net/tools/naive/http_proxy_server_socket.cc @@ -3,15 +3,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "net/tools/naive/http_proxy_socket.h" +#include "net/tools/naive/http_proxy_server_socket.h" +#include #include #include +#include #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/logging.h" #include "base/rand_util.h" +#include "base/strings/string_split.h" #include "base/sys_byteorder.h" #include "net/base/ip_address.h" #include "net/base/net_errors.h" @@ -19,6 +22,7 @@ #include "net/http/http_request_headers.h" #include "net/log/net_log.h" #include "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_constants.h" +#include "net/tools/naive/naive_protocol.h" #include "net/tools/naive/naive_proxy_delegate.h" namespace net { @@ -33,11 +37,12 @@ constexpr int kMinPaddingSize = 30; constexpr int kMaxPaddingSize = kMinPaddingSize + 32; } // namespace -HttpProxySocket::HttpProxySocket( +HttpProxyServerSocket::HttpProxyServerSocket( std::unique_ptr transport_socket, ClientPaddingDetectorDelegate* padding_detector_delegate, - const NetworkTrafficAnnotationTag& traffic_annotation) - : io_callback_(base::BindRepeating(&HttpProxySocket::OnIOComplete, + const NetworkTrafficAnnotationTag& traffic_annotation, + const std::vector& supported_padding_types) + : io_callback_(base::BindRepeating(&HttpProxyServerSocket::OnIOComplete, base::Unretained(this))), transport_(std::move(transport_socket)), padding_detector_delegate_(padding_detector_delegate), @@ -46,17 +51,18 @@ HttpProxySocket::HttpProxySocket( was_ever_used_(false), header_write_size_(-1), net_log_(transport_->NetLog()), - traffic_annotation_(traffic_annotation) {} + traffic_annotation_(traffic_annotation), + supported_padding_types_(supported_padding_types) {} -HttpProxySocket::~HttpProxySocket() { +HttpProxyServerSocket::~HttpProxyServerSocket() { Disconnect(); } -const HostPortPair& HttpProxySocket::request_endpoint() const { +const HostPortPair& HttpProxyServerSocket::request_endpoint() const { return request_endpoint_; } -int HttpProxySocket::Connect(CompletionOnceCallback callback) { +int HttpProxyServerSocket::Connect(CompletionOnceCallback callback) { DCHECK(transport_); DCHECK_EQ(STATE_NONE, next_state_); DCHECK(!user_callback_); @@ -75,7 +81,7 @@ int HttpProxySocket::Connect(CompletionOnceCallback callback) { return rv; } -void HttpProxySocket::Disconnect() { +void HttpProxyServerSocket::Disconnect() { completed_handshake_ = false; transport_->Disconnect(); @@ -85,23 +91,23 @@ void HttpProxySocket::Disconnect() { user_callback_.Reset(); } -bool HttpProxySocket::IsConnected() const { +bool HttpProxyServerSocket::IsConnected() const { return completed_handshake_ && transport_->IsConnected(); } -bool HttpProxySocket::IsConnectedAndIdle() const { +bool HttpProxyServerSocket::IsConnectedAndIdle() const { return completed_handshake_ && transport_->IsConnectedAndIdle(); } -const NetLogWithSource& HttpProxySocket::NetLog() const { +const NetLogWithSource& HttpProxyServerSocket::NetLog() const { return net_log_; } -bool HttpProxySocket::WasEverUsed() const { +bool HttpProxyServerSocket::WasEverUsed() const { return was_ever_used_; } -bool HttpProxySocket::WasAlpnNegotiated() const { +bool HttpProxyServerSocket::WasAlpnNegotiated() const { if (transport_) { return transport_->WasAlpnNegotiated(); } @@ -109,7 +115,7 @@ bool HttpProxySocket::WasAlpnNegotiated() const { return false; } -NextProto HttpProxySocket::GetNegotiatedProtocol() const { +NextProto HttpProxyServerSocket::GetNegotiatedProtocol() const { if (transport_) { return transport_->GetNegotiatedProtocol(); } @@ -117,7 +123,7 @@ NextProto HttpProxySocket::GetNegotiatedProtocol() const { return kProtoUnknown; } -bool HttpProxySocket::GetSSLInfo(SSLInfo* ssl_info) { +bool HttpProxyServerSocket::GetSSLInfo(SSLInfo* ssl_info) { if (transport_) { return transport_->GetSSLInfo(ssl_info); } @@ -125,19 +131,19 @@ bool HttpProxySocket::GetSSLInfo(SSLInfo* ssl_info) { return false; } -int64_t HttpProxySocket::GetTotalReceivedBytes() const { +int64_t HttpProxyServerSocket::GetTotalReceivedBytes() const { return transport_->GetTotalReceivedBytes(); } -void HttpProxySocket::ApplySocketTag(const SocketTag& tag) { +void HttpProxyServerSocket::ApplySocketTag(const SocketTag& tag) { return transport_->ApplySocketTag(tag); } // Read is called by the transport layer above to read. This can only be done // if the HTTP header is complete. -int HttpProxySocket::Read(IOBuffer* buf, - int buf_len, - CompletionOnceCallback callback) { +int HttpProxyServerSocket::Read(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback) { DCHECK(completed_handshake_); DCHECK_EQ(STATE_NONE, next_state_); DCHECK(!user_callback_); @@ -159,7 +165,7 @@ int HttpProxySocket::Read(IOBuffer* buf, int rv = transport_->Read( buf, buf_len, - base::BindOnce(&HttpProxySocket::OnReadWriteComplete, + base::BindOnce(&HttpProxyServerSocket::OnReadWriteComplete, base::Unretained(this), std::move(callback))); if (rv > 0) was_ever_used_ = true; @@ -167,8 +173,8 @@ int HttpProxySocket::Read(IOBuffer* buf, } // Write is called by the transport layer. This can only be done if the -// SOCKS handshake is complete. -int HttpProxySocket::Write( +// HTTP CONNECT request is complete. +int HttpProxyServerSocket::Write( IOBuffer* buf, int buf_len, CompletionOnceCallback callback, @@ -180,7 +186,7 @@ int HttpProxySocket::Write( int rv = transport_->Write( buf, buf_len, - base::BindOnce(&HttpProxySocket::OnReadWriteComplete, + base::BindOnce(&HttpProxyServerSocket::OnReadWriteComplete, base::Unretained(this), std::move(callback)), traffic_annotation); if (rv > 0) @@ -188,15 +194,15 @@ int HttpProxySocket::Write( return rv; } -int HttpProxySocket::SetReceiveBufferSize(int32_t size) { +int HttpProxyServerSocket::SetReceiveBufferSize(int32_t size) { return transport_->SetReceiveBufferSize(size); } -int HttpProxySocket::SetSendBufferSize(int32_t size) { +int HttpProxyServerSocket::SetSendBufferSize(int32_t size) { return transport_->SetSendBufferSize(size); } -void HttpProxySocket::DoCallback(int result) { +void HttpProxyServerSocket::DoCallback(int result) { DCHECK_NE(ERR_IO_PENDING, result); DCHECK(user_callback_); @@ -205,7 +211,7 @@ void HttpProxySocket::DoCallback(int result) { std::move(user_callback_).Run(result); } -void HttpProxySocket::OnIOComplete(int result) { +void HttpProxyServerSocket::OnIOComplete(int result) { DCHECK_NE(STATE_NONE, next_state_); int rv = DoLoop(result); if (rv != ERR_IO_PENDING) { @@ -213,8 +219,8 @@ void HttpProxySocket::OnIOComplete(int result) { } } -void HttpProxySocket::OnReadWriteComplete(CompletionOnceCallback callback, - int result) { +void HttpProxyServerSocket::OnReadWriteComplete(CompletionOnceCallback callback, + int result) { DCHECK_NE(ERR_IO_PENDING, result); DCHECK(callback); @@ -223,7 +229,7 @@ void HttpProxySocket::OnReadWriteComplete(CompletionOnceCallback callback, std::move(callback).Run(result); } -int HttpProxySocket::DoLoop(int last_io_result) { +int HttpProxyServerSocket::DoLoop(int last_io_result) { DCHECK_NE(next_state_, STATE_NONE); int rv = last_io_result; do { @@ -253,14 +259,50 @@ int HttpProxySocket::DoLoop(int last_io_result) { return rv; } -int HttpProxySocket::DoHeaderRead() { +int HttpProxyServerSocket::DoHeaderRead() { next_state_ = STATE_HEADER_READ_COMPLETE; handshake_buf_ = base::MakeRefCounted(kBufferSize); return transport_->Read(handshake_buf_.get(), kBufferSize, io_callback_); } -int HttpProxySocket::DoHeaderReadComplete(int result) { +std::optional HttpProxyServerSocket::ParsePaddingHeaders( + const HttpRequestHeaders& headers) { + bool has_padding = headers.HasHeader(kPaddingHeader); + std::string padding_type_request; + bool has_padding_type_request = + headers.GetHeader(kPaddingTypeRequestHeader, &padding_type_request); + + if (!has_padding_type_request) { + // Backward compatibility with before kVariant1 when the padding-version + // header does not exist. + if (has_padding) { + return PaddingType::kVariant1; + } else { + return PaddingType::kNone; + } + } + + std::vector padding_type_strs = base::SplitStringPiece( + padding_type_request, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + for (base::StringPiece padding_type_str : padding_type_strs) { + std::optional padding_type = + ParsePaddingType(padding_type_str); + if (!padding_type.has_value()) { + LOG(ERROR) << "Invalid padding type: " << padding_type_str; + return std::nullopt; + } + if (std::find(supported_padding_types_.begin(), + supported_padding_types_.end(), + *padding_type) != supported_padding_types_.end()) { + return padding_type; + } + } + LOG(ERROR) << "No padding type is supported: " << padding_type_request; + return std::nullopt; +} + +int HttpProxyServerSocket::DoHeaderReadComplete(int result) { if (result < 0) return result; @@ -273,23 +315,21 @@ int HttpProxySocket::DoHeaderReadComplete(int result) { return ERR_MSG_TOO_BIG; } - auto header_end = buffer_.find("\r\n\r\n"); + size_t header_end = buffer_.find("\r\n\r\n"); if (header_end == std::string::npos) { next_state_ = STATE_HEADER_READ; return OK; } - // HttpProxyClientSocket uses CONNECT for all endpoints. - // GET is also supported. - auto first_line_end = buffer_.find("\r\n"); - auto first_space = buffer_.find(' '); + size_t first_line_end = buffer_.find("\r\n"); + size_t first_space = buffer_.find(' '); bool is_http_1_0 = false; if (first_space == std::string::npos || first_space + 1 >= first_line_end) { return ERR_INVALID_ARGUMENT; } if (buffer_.compare(0, first_space, HttpRequestHeaders::kConnectMethod) == 0) { - auto second_space = buffer_.find(' ', first_space + 1); + size_t second_space = buffer_.find(' ', first_space + 1); if (second_space == std::string::npos || second_space >= first_line_end) { LOG(WARNING) << "Invalid request: " << buffer_.substr(0, first_line_end); return ERR_INVALID_ARGUMENT; @@ -301,7 +341,7 @@ int HttpProxySocket::DoHeaderReadComplete(int result) { is_http_1_0 = true; } - auto second_line = first_line_end + 2; + size_t second_line = first_line_end + 2; HttpRequestHeaders headers; std::string headers_str; if (second_line < header_end) { @@ -328,13 +368,11 @@ int HttpProxySocket::DoHeaderReadComplete(int result) { request_endpoint_.set_port(80); } - if (headers.HasHeader("padding")) { - padding_detector_delegate_->SetClientPaddingSupport( - PaddingSupport::kCapable); - } else { - padding_detector_delegate_->SetClientPaddingSupport( - PaddingSupport::kIncapable); + std::optional padding_type = ParsePaddingHeaders(headers); + if (!padding_type.has_value()) { + return ERR_INVALID_ARGUMENT; } + padding_detector_delegate_->SetClientPaddingType(*padding_type); if (is_http_1_0) { // Regerate http header to make sure don't leak them to end servers @@ -361,7 +399,7 @@ int HttpProxySocket::DoHeaderReadComplete(int result) { return OK; } -int HttpProxySocket::DoHeaderWrite() { +int HttpProxyServerSocket::DoHeaderWrite() { next_state_ = STATE_HEADER_WRITE_COMPLETE; // Adds padding. @@ -378,7 +416,7 @@ int HttpProxySocket::DoHeaderWrite() { io_callback_, traffic_annotation_); } -int HttpProxySocket::DoHeaderWriteComplete(int result) { +int HttpProxyServerSocket::DoHeaderWriteComplete(int result) { if (result < 0) return result; @@ -391,11 +429,11 @@ int HttpProxySocket::DoHeaderWriteComplete(int result) { return OK; } -int HttpProxySocket::GetPeerAddress(IPEndPoint* address) const { +int HttpProxyServerSocket::GetPeerAddress(IPEndPoint* address) const { return transport_->GetPeerAddress(address); } -int HttpProxySocket::GetLocalAddress(IPEndPoint* address) const { +int HttpProxyServerSocket::GetLocalAddress(IPEndPoint* address) const { return transport_->GetLocalAddress(address); } diff --git a/src/net/tools/naive/http_proxy_socket.h b/src/net/tools/naive/http_proxy_server_socket.h similarity index 78% rename from src/net/tools/naive/http_proxy_socket.h rename to src/net/tools/naive/http_proxy_server_socket.h index aa836b41fb..966e29c44b 100644 --- a/src/net/tools/naive/http_proxy_socket.h +++ b/src/net/tools/naive/http_proxy_server_socket.h @@ -3,13 +3,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef NET_TOOLS_NAIVE_HTTP_PROXY_SOCKET_H_ -#define NET_TOOLS_NAIVE_HTTP_PROXY_SOCKET_H_ +#ifndef NET_TOOLS_NAIVE_HTTP_PROXY_SERVER_SOCKET_H_ +#define NET_TOOLS_NAIVE_HTTP_PROXY_SERVER_SOCKET_H_ #include #include #include #include +#include #include "base/memory/scoped_refptr.h" #include "net/base/completion_once_callback.h" @@ -17,27 +18,31 @@ #include "net/base/host_port_pair.h" #include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" +#include "net/http/http_request_headers.h" #include "net/log/net_log_with_source.h" #include "net/socket/connection_attempts.h" #include "net/socket/next_proto.h" #include "net/socket/stream_socket.h" #include "net/ssl/ssl_info.h" +#include "net/tools/naive/naive_protocol.h" namespace net { struct NetworkTrafficAnnotationTag; class ClientPaddingDetectorDelegate; // This StreamSocket is used to setup a HTTP CONNECT tunnel. -class HttpProxySocket : public StreamSocket { +class HttpProxyServerSocket : public StreamSocket { public: - HttpProxySocket(std::unique_ptr transport_socket, - ClientPaddingDetectorDelegate* padding_detector_delegate, - const NetworkTrafficAnnotationTag& traffic_annotation); - HttpProxySocket(const HttpProxySocket&) = delete; - HttpProxySocket& operator=(const HttpProxySocket&) = delete; + HttpProxyServerSocket( + std::unique_ptr transport_socket, + ClientPaddingDetectorDelegate* padding_detector_delegate, + const NetworkTrafficAnnotationTag& traffic_annotation, + const std::vector& supported_padding_types); + HttpProxyServerSocket(const HttpProxyServerSocket&) = delete; + HttpProxyServerSocket& operator=(const HttpProxyServerSocket&) = delete; // On destruction Disconnect() is called. - ~HttpProxySocket() override; + ~HttpProxyServerSocket() override; const HostPortPair& request_endpoint() const; @@ -89,6 +94,9 @@ class HttpProxySocket : public StreamSocket { int DoHeaderRead(); int DoHeaderReadComplete(int result); + std::optional ParsePaddingHeaders( + const HttpRequestHeaders& headers); + CompletionRepeatingCallback io_callback_; // Stores the underlying socket. @@ -116,7 +124,9 @@ class HttpProxySocket : public StreamSocket { // Traffic annotation for socket control. const NetworkTrafficAnnotationTag& traffic_annotation_; + + std::vector supported_padding_types_; }; } // namespace net -#endif // NET_TOOLS_NAIVE_HTTP_PROXY_SOCKET_H_ +#endif // NET_TOOLS_NAIVE_HTTP_PROXY_SERVER_SOCKET_H_ diff --git a/src/net/tools/naive/naive_connection.cc b/src/net/tools/naive/naive_connection.cc index 149445401e..90f5776fa9 100644 --- a/src/net/tools/naive/naive_connection.cc +++ b/src/net/tools/naive/naive_connection.cc @@ -26,7 +26,8 @@ #include "net/socket/client_socket_pool_manager.h" #include "net/socket/stream_socket.h" #include "net/spdy/spdy_session.h" -#include "net/tools/naive/http_proxy_socket.h" +#include "net/tools/naive/http_proxy_server_socket.h" +#include "net/tools/naive/naive_padding_socket.h" #include "net/tools/naive/redirect_resolver.h" #include "net/tools/naive/socks5_server_socket.h" #include "url/scheme_host_port.h" @@ -45,9 +46,6 @@ namespace net { namespace { constexpr int kBufferSize = 64 * 1024; -constexpr int kFirstPaddings = 8; -constexpr int kPaddingHeaderSize = 3; -constexpr int kMaxPaddingSize = 255; } // namespace NaiveConnection::NaiveConnection( @@ -76,14 +74,12 @@ NaiveConnection::NaiveConnection( next_state_(STATE_NONE), client_socket_(std::move(accepted_socket)), server_socket_handle_(std::make_unique()), - sockets_{client_socket_.get(), nullptr}, + sockets_{nullptr, nullptr}, errors_{OK, OK}, write_pending_{false, false}, early_pull_pending_(false), can_push_to_server_(false), early_pull_result_(ERR_IO_PENDING), - num_paddings_{0, 0}, - read_padding_state_(STATE_READ_PAYLOAD_LENGTH_1), full_duplex_(false), time_func_(&base::TimeTicks::Now), traffic_annotation_(traffic_annotation) { @@ -181,11 +177,18 @@ int NaiveConnection::DoConnectClientComplete(int result) { if (result < 0) return result; + std::optional client_padding_type = + padding_detector_delegate_->GetClientPaddingType(); + CHECK(client_padding_type.has_value()); + + sockets_[kClient] = std::make_unique( + client_socket_.get(), *client_padding_type, kClient); + // For proxy client sockets, padding support detection is finished after the // first server response which means there will be one missed early pull. For - // proxy server sockets (HttpProxySocket), padding support detection is + // proxy server sockets (HttpProxyServerSocket), padding support detection is // done during client connect, so there shouldn't be any missed early pull. - if (!padding_detector_delegate_->IsPaddingSupportKnown()) { + if (!padding_detector_delegate_->GetServerPaddingType().has_value()) { early_pull_pending_ = false; early_pull_result_ = 0; next_state_ = STATE_CONNECT_SERVER; @@ -215,7 +218,7 @@ int NaiveConnection::DoConnectServer() { origin = socket->request_endpoint(); } else if (protocol_ == ClientProtocol::kHttp) { const auto* socket = - static_cast(client_socket_.get()); + static_cast(client_socket_.get()); origin = socket->request_endpoint(); } else if (protocol_ == ClientProtocol::kRedir) { #if BUILDFLAG(IS_LINUX) @@ -284,8 +287,12 @@ int NaiveConnection::DoConnectServerComplete(int result) { if (result < 0) return result; - DCHECK(server_socket_handle_->socket()); - sockets_[kServer] = server_socket_handle_->socket(); + std::optional server_padding_type = + padding_detector_delegate_->GetServerPaddingType(); + CHECK(server_padding_type.has_value()); + + sockets_[kServer] = std::make_unique( + server_socket_handle_->socket(), *server_padding_type, kServer); full_duplex_ = true; next_state_ = STATE_NONE; @@ -331,16 +338,7 @@ void NaiveConnection::Pull(Direction from, Direction to) { return; int read_size = kBufferSize; - auto padding_direction = padding_detector_delegate_->GetPaddingDirection(); - if (from == padding_direction && num_paddings_[from] < kFirstPaddings) { - auto buffer = base::MakeRefCounted(); - buffer->SetCapacity(kBufferSize); - buffer->set_offset(kPaddingHeaderSize); - read_buffers_[from] = buffer; - read_size = kBufferSize - kPaddingHeaderSize - kMaxPaddingSize; - } else { - read_buffers_[from] = base::MakeRefCounted(kBufferSize); - } + read_buffers_[from] = base::MakeRefCounted(kBufferSize); DCHECK(sockets_[from]); int rv = sockets_[from]->Read( @@ -356,108 +354,12 @@ void NaiveConnection::Pull(Direction from, Direction to) { } void NaiveConnection::Push(Direction from, Direction to, int size) { - int write_size = size; - int write_offset = 0; - auto padding_direction = padding_detector_delegate_->GetPaddingDirection(); - if (from == padding_direction && num_paddings_[from] < kFirstPaddings) { - // Adds padding. - ++num_paddings_[from]; - int padding_size = base::RandInt(0, kMaxPaddingSize); - auto* buffer = static_cast(read_buffers_[from].get()); - buffer->set_offset(0); - uint8_t* p = reinterpret_cast(buffer->data()); - p[0] = size / 256; - p[1] = size % 256; - p[2] = padding_size; - std::memset(p + kPaddingHeaderSize + size, 0, padding_size); - write_size = kPaddingHeaderSize + size + padding_size; - } else if (to == padding_direction && num_paddings_[from] < kFirstPaddings) { - // Removes padding. - const char* p = read_buffers_[from]->data(); - bool trivial_padding = false; - if (read_padding_state_ == STATE_READ_PAYLOAD_LENGTH_1 && - size >= kPaddingHeaderSize) { - int payload_size = - static_cast(p[0]) * 256 + static_cast(p[1]); - int padding_size = static_cast(p[2]); - if (size == kPaddingHeaderSize + payload_size + padding_size) { - write_size = payload_size; - write_offset = kPaddingHeaderSize; - ++num_paddings_[from]; - trivial_padding = true; - } - } - if (!trivial_padding) { - auto unpadded_buffer = base::MakeRefCounted(kBufferSize); - char* unpadded_ptr = unpadded_buffer->data(); - for (int i = 0; i < size;) { - if (num_paddings_[from] >= kFirstPaddings && - read_padding_state_ == STATE_READ_PAYLOAD_LENGTH_1) { - std::memcpy(unpadded_ptr, p + i, size - i); - unpadded_ptr += size - i; - break; - } - int copy_size; - switch (read_padding_state_) { - case STATE_READ_PAYLOAD_LENGTH_1: - payload_length_ = static_cast(p[i]); - ++i; - read_padding_state_ = STATE_READ_PAYLOAD_LENGTH_2; - break; - case STATE_READ_PAYLOAD_LENGTH_2: - payload_length_ = - payload_length_ * 256 + static_cast(p[i]); - ++i; - read_padding_state_ = STATE_READ_PADDING_LENGTH; - break; - case STATE_READ_PADDING_LENGTH: - padding_length_ = static_cast(p[i]); - ++i; - read_padding_state_ = STATE_READ_PAYLOAD; - break; - case STATE_READ_PAYLOAD: - if (payload_length_ <= size - i) { - copy_size = payload_length_; - read_padding_state_ = STATE_READ_PADDING; - } else { - copy_size = size - i; - } - std::memcpy(unpadded_ptr, p + i, copy_size); - unpadded_ptr += copy_size; - i += copy_size; - payload_length_ -= copy_size; - break; - case STATE_READ_PADDING: - if (padding_length_ <= size - i) { - copy_size = padding_length_; - read_padding_state_ = STATE_READ_PAYLOAD_LENGTH_1; - ++num_paddings_[from]; - } else { - copy_size = size - i; - } - i += copy_size; - padding_length_ -= copy_size; - break; - } - } - write_size = unpadded_ptr - unpadded_buffer->data(); - read_buffers_[from] = unpadded_buffer; - } - if (write_size == 0) { - OnPushComplete(from, to, OK); - return; - } - } - write_buffers_[to] = base::MakeRefCounted( - std::move(read_buffers_[from]), write_offset + write_size); - if (write_offset) { - write_buffers_[to]->DidConsume(write_offset); - } + std::move(read_buffers_[from]), size); write_pending_[to] = true; DCHECK(sockets_[to]); int rv = sockets_[to]->Write( - write_buffers_[to].get(), write_size, + write_buffers_[to].get(), write_buffers_[to]->BytesRemaining(), base::BindRepeating(&NaiveConnection::OnPushComplete, weak_ptr_factory_.GetWeakPtr(), from, to), traffic_annotation_); @@ -475,7 +377,7 @@ void NaiveConnection::Disconnect(Direction side) { } bool NaiveConnection::IsConnected(Direction side) { - return sockets_[side]; + return sockets_[side] != nullptr; } void NaiveConnection::OnBothDisconnected() { diff --git a/src/net/tools/naive/naive_connection.h b/src/net/tools/naive/naive_connection.h index 36f1ff4c06..6d4db6719c 100644 --- a/src/net/tools/naive/naive_connection.h +++ b/src/net/tools/naive/naive_connection.h @@ -14,6 +14,7 @@ #include "base/time/time.h" #include "net/base/completion_once_callback.h" #include "net/base/completion_repeating_callback.h" +#include "net/tools/naive/naive_padding_socket.h" #include "net/tools/naive/naive_protocol.h" #include "net/tools/naive/naive_proxy_delegate.h" @@ -111,7 +112,7 @@ class NaiveConnection { std::unique_ptr client_socket_; std::unique_ptr server_socket_handle_; - StreamSocket* sockets_[kNumDirections]; + std::unique_ptr sockets_[kNumDirections]; scoped_refptr read_buffers_[kNumDirections]; scoped_refptr write_buffers_[kNumDirections]; int errors_[kNumDirections]; @@ -123,11 +124,6 @@ class NaiveConnection { bool can_push_to_server_; int early_pull_result_; - int num_paddings_[kNumDirections]; - PaddingState read_padding_state_; - int payload_length_; - int padding_length_; - bool full_duplex_; TimeFunc time_func_; diff --git a/src/net/tools/naive/naive_padding_framer.cc b/src/net/tools/naive/naive_padding_framer.cc new file mode 100644 index 0000000000..34eb5a3b9a --- /dev/null +++ b/src/net/tools/naive/naive_padding_framer.cc @@ -0,0 +1,117 @@ +// Copyright 2023 klzgrad . All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "net/tools/naive/naive_padding_framer.h" + +#include +#include +#include +#include + +#include "base/check.h" +#include "base/check_op.h" + +namespace net { +NaivePaddingFramer::NaivePaddingFramer(std::optional max_read_frames) + : max_read_frames_(max_read_frames) { + if (max_read_frames.has_value()) { + CHECK_GE(*max_read_frames, 0); + } +} + +int NaivePaddingFramer::Read(const char* padded, + int padded_len, + char* payload_buf, + int payload_buf_capacity) { + // This check guarantees write_ptr does not overflow. + CHECK_GE(payload_buf_capacity, padded_len); + + char* write_ptr = payload_buf; + while (padded_len > 0) { + int copy_size; + switch (state_) { + case ReadState::kPayloadLength1: + if (max_read_frames_.has_value() && + num_read_frames_ >= *max_read_frames_) { + std::memcpy(write_ptr, padded, padded_len); + padded += padded_len; + write_ptr += padded_len; + padded_len = 0; + break; + } + read_payload_length_ = static_cast(padded[0]); + ++padded; + --padded_len; + state_ = ReadState::kPayloadLength2; + break; + case ReadState::kPayloadLength2: + read_payload_length_ = + read_payload_length_ * 256 + static_cast(padded[0]); + ++padded; + --padded_len; + state_ = ReadState::kPaddingLength1; + break; + case ReadState::kPaddingLength1: + read_padding_length_ = static_cast(padded[0]); + ++padded; + --padded_len; + state_ = ReadState::kPayload; + break; + case ReadState::kPayload: + copy_size = std::min(read_payload_length_, padded_len); + read_payload_length_ -= copy_size; + if (read_payload_length_ == 0) { + state_ = ReadState::kPadding; + } + + std::memcpy(write_ptr, padded, copy_size); + padded += copy_size; + write_ptr += copy_size; + padded_len -= copy_size; + break; + case ReadState::kPadding: + copy_size = std::min(read_padding_length_, padded_len); + read_padding_length_ -= copy_size; + if (read_padding_length_ == 0) { + if (num_read_frames_ < std::numeric_limits::max() - 1) { + ++num_read_frames_; + } + state_ = ReadState::kPayloadLength1; + } + + padded += copy_size; + padded_len -= copy_size; + break; + } + } + return write_ptr - payload_buf; +} + +int NaivePaddingFramer::Write(const char* payload_buf, + int payload_buf_len, + int padding_size, + char* padded, + int padded_capacity, + int& payload_consumed_len) { + CHECK_GE(payload_buf_len, 0); + CHECK_LE(padding_size, max_padding_size()); + CHECK_GE(padding_size, 0); + + payload_consumed_len = std::min( + payload_buf_len, padded_capacity - frame_header_size() - padding_size); + int padded_buf_len = + frame_header_size() + payload_consumed_len + padding_size; + + padded[0] = payload_consumed_len / 256; + padded[1] = payload_consumed_len % 256; + padded[2] = padding_size; + std::memcpy(padded + frame_header_size(), payload_buf, payload_consumed_len); + std::memset(padded + frame_header_size() + payload_consumed_len, '\0', + padding_size); + + if (num_written_frames_ < std::numeric_limits::max() - 1) { + ++num_written_frames_; + } + return padded_buf_len; +} +} // namespace net diff --git a/src/net/tools/naive/naive_padding_framer.h b/src/net/tools/naive/naive_padding_framer.h new file mode 100644 index 0000000000..097fdc5ed5 --- /dev/null +++ b/src/net/tools/naive/naive_padding_framer.h @@ -0,0 +1,72 @@ +// Copyright 2023 klzgrad . All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +namespace net { + +// struct PaddedFrame { +// uint16_t payload_size; // big-endian +// uint8_t padding_size; // big-endian +// uint8_t payload[payload_size]; +// uint8_t zeros[padding_size]; +// }; +class NaivePaddingFramer { + public: + // `max_read_frames`: Assumes the byte stream stops using the padding + // framing after `max_read_frames` frames. If -1, it means + // the byte stream always uses the padding framing. + explicit NaivePaddingFramer(std::optional max_read_frames); + + int max_payload_size() const { return std::numeric_limits::max(); } + + int max_padding_size() const { return std::numeric_limits::max(); } + + int frame_header_size() const { return 3; } + + int num_read_frames() const { return num_read_frames_; } + + int num_written_frames() const { return num_written_frames_; } + + // Reads `padded` for `padded_len` bytes and extracts unpadded payload to + // `payload_buf`. + // Returns the number of payload bytes extracted. + // Returning zero indicates a pure padding instead of EOF. + int Read(const char* padded, + int padded_len, + char* payload_buf, + int payload_buf_capacity); + + // Writes `payload_buf` for up to `payload_buf_len` bytes into `padded`. + // Returns the number of padded bytes written. + // If the padded bytes would exceed `padded_capacity`, the payload is + // truncated to `payload_consumed_len`. + int Write(const char* payload_buf, + int payload_buf_len, + int padding_size, + char* padded, + int padded_capacity, + int& payload_consumed_len); + + private: + enum class ReadState { + kPayloadLength1, + kPayloadLength2, + kPaddingLength1, + kPayload, + kPadding, + }; + + std::optional max_read_frames_; + + ReadState state_ = ReadState::kPayloadLength1; + int read_payload_length_ = 0; + int read_padding_length_ = 0; + int num_read_frames_ = 0; + + int num_written_frames_ = 0; +}; +} // namespace net diff --git a/src/net/tools/naive/naive_padding_socket.cc b/src/net/tools/naive/naive_padding_socket.cc new file mode 100644 index 0000000000..61f4b63871 --- /dev/null +++ b/src/net/tools/naive/naive_padding_socket.cc @@ -0,0 +1,271 @@ +// Copyright 2023 klzgrad . All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "net/tools/naive/naive_padding_socket.h" + +#include +#include +#include +#include +#include + +#include "base/rand_util.h" +#include "base/task/single_thread_task_runner.h" +#include "net/base/io_buffer.h" + +namespace net { + +namespace { +constexpr int kMaxBufferSize = 64 * 1024; +constexpr int kFirstPaddings = 8; +} // namespace + +NaivePaddingSocket::NaivePaddingSocket(StreamSocket* transport_socket, + PaddingType padding_type, + Direction direction) + : transport_socket_(transport_socket), + padding_type_(padding_type), + direction_(direction), + read_buf_(base::MakeRefCounted(kMaxBufferSize)), + framer_(kFirstPaddings) {} + +NaivePaddingSocket::~NaivePaddingSocket() { + Disconnect(); +} + +void NaivePaddingSocket::Disconnect() { + transport_socket_->Disconnect(); +} + +int NaivePaddingSocket::Read(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback) { + DCHECK(!callback.is_null()); + + switch (padding_type_) { + case PaddingType::kNone: + return ReadNoPadding(buf, buf_len, std::move(callback)); + case PaddingType::kVariant1: + if (framer_.num_read_frames() < kFirstPaddings) { + return ReadPaddingV1(buf, buf_len, std::move(callback)); + } else { + return ReadNoPadding(buf, buf_len, std::move(callback)); + } + default: + NOTREACHED(); + } +} + +int NaivePaddingSocket::ReadNoPadding(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback) { + int rv = transport_socket_->Read( + buf, buf_len, + base::BindOnce(&NaivePaddingSocket::OnReadNoPaddingComplete, + base::Unretained(this), std::move(callback))); + return rv; +} + +void NaivePaddingSocket::OnReadNoPaddingComplete( + CompletionOnceCallback callback, + int rv) { + DCHECK_NE(ERR_IO_PENDING, rv); + DCHECK(callback); + + std::move(callback).Run(rv); +} + +int NaivePaddingSocket::ReadPaddingV1(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback) { + DCHECK(!callback.is_null()); + DCHECK(read_user_buf_ == nullptr); + + // Truncates user requested buf len if it is too large for the padding + // buffer. + buf_len = std::min(buf_len, kMaxBufferSize); + read_user_buf_ = buf; + read_user_buf_len_ = buf_len; + + int rv = ReadPaddingV1Payload(); + + if (rv == ERR_IO_PENDING) { + read_callback_ = std::move(callback); + return rv; + } + + read_user_buf_ = nullptr; + + return rv; +} + +void NaivePaddingSocket::OnReadPaddingV1Complete(int rv) { + DCHECK_NE(ERR_IO_PENDING, rv); + DCHECK(read_callback_); + DCHECK(read_user_buf_ != nullptr); + + if (rv > 0) { + rv = framer_.Read(read_buf_->data(), rv, read_user_buf_->data(), + read_user_buf_len_); + if (rv == 0) { + rv = ReadPaddingV1Payload(); + if (rv == ERR_IO_PENDING) + return; + } + } + + // Must reset read_user_buf_ before invoking read_callback_, which may reenter + // Read(). + read_user_buf_ = nullptr; + + std::move(read_callback_).Run(rv); +} + +int NaivePaddingSocket::ReadPaddingV1Payload() { + for (;;) { + int rv = transport_socket_->Read( + read_buf_.get(), read_user_buf_len_, + base::BindOnce(&NaivePaddingSocket::OnReadPaddingV1Complete, + base::Unretained(this))); + if (rv <= 0) { + return rv; + } + rv = framer_.Read(read_buf_->data(), rv, read_user_buf_->data(), + read_user_buf_len_); + if (rv > 0) { + return rv; + } + } +} + +int NaivePaddingSocket::Write( + IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback, + const NetworkTrafficAnnotationTag& traffic_annotation) { + switch (padding_type_) { + case PaddingType::kNone: + return WriteNoPadding(buf, buf_len, std::move(callback), + traffic_annotation); + case PaddingType::kVariant1: + if (framer_.num_written_frames() < kFirstPaddings) { + return WritePaddingV1(buf, buf_len, std::move(callback), + traffic_annotation); + } else { + return WriteNoPadding(buf, buf_len, std::move(callback), + traffic_annotation); + } + default: + NOTREACHED(); + } +} + +int NaivePaddingSocket::WriteNoPadding( + IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback, + const NetworkTrafficAnnotationTag& traffic_annotation) { + return transport_socket_->Write( + buf, buf_len, + base::BindOnce(&NaivePaddingSocket::OnWriteNoPaddingComplete, + base::Unretained(this), std::move(callback), + traffic_annotation), + traffic_annotation); +} + +void NaivePaddingSocket::OnWriteNoPaddingComplete( + CompletionOnceCallback callback, + const NetworkTrafficAnnotationTag& traffic_annotation, + int rv) { + DCHECK_NE(ERR_IO_PENDING, rv); + DCHECK(callback); + + std::move(callback).Run(rv); +} + +int NaivePaddingSocket::WritePaddingV1( + IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback, + const NetworkTrafficAnnotationTag& traffic_annotation) { + DCHECK(write_buf_ == nullptr); + + auto padded = base::MakeRefCounted(kMaxBufferSize); + int padding_size; + if (direction_ == kServer) { + if (buf_len < 100) { + padding_size = base::RandInt(framer_.max_padding_size() - buf_len, + framer_.max_padding_size()); + } else { + padding_size = base::RandInt(0, framer_.max_padding_size()); + } + } else { + padding_size = base::RandInt(0, framer_.max_padding_size()); + } + int write_buf_len = + framer_.Write(buf->data(), buf_len, padding_size, padded->data(), + kMaxBufferSize, write_user_payload_len_); + // Using DrainableIOBuffer here because we do not want to + // repeatedly encode the padding frames when short writes happen. + write_buf_ = + base::MakeRefCounted(std::move(padded), write_buf_len); + + int rv = WritePaddingV1Drain(traffic_annotation); + if (rv == ERR_IO_PENDING) { + write_callback_ = std::move(callback); + return rv; + } + + write_buf_ = nullptr; + write_user_payload_len_ = 0; + + return rv; +} + +void NaivePaddingSocket::OnWritePaddingV1Complete( + const NetworkTrafficAnnotationTag& traffic_annotation, + int rv) { + DCHECK_NE(ERR_IO_PENDING, rv); + DCHECK(write_callback_); + DCHECK(write_buf_ != nullptr); + + if (rv > 0) { + write_buf_->DidConsume(rv); + rv = WritePaddingV1Drain(traffic_annotation); + if (rv == ERR_IO_PENDING) + return; + } + + // Must reset these before invoking write_callback_, which may reenter + // Write(). + write_buf_ = nullptr; + write_user_payload_len_ = 0; + + std::move(write_callback_).Run(rv); +} + +int NaivePaddingSocket::WritePaddingV1Drain( + const NetworkTrafficAnnotationTag& traffic_annotation) { + DCHECK(write_buf_ != nullptr); + + while (write_buf_->BytesRemaining() > 0) { + int remaining = write_buf_->BytesRemaining(); + if (direction_ == kServer && write_user_payload_len_ > 400 && + write_user_payload_len_ < 1024) { + remaining = std::min(remaining, base::RandInt(200, 300)); + } + int rv = transport_socket_->Write( + write_buf_.get(), remaining, + base::BindOnce(&NaivePaddingSocket::OnWritePaddingV1Complete, + base::Unretained(this), traffic_annotation), + traffic_annotation); + if (rv <= 0) { + return rv; + } + write_buf_->DidConsume(rv); + } + // Synchronously drained the buffer. + return write_user_payload_len_; +} + +} // namespace net diff --git a/src/net/tools/naive/naive_padding_socket.h b/src/net/tools/naive/naive_padding_socket.h new file mode 100644 index 0000000000..01a5720e32 --- /dev/null +++ b/src/net/tools/naive/naive_padding_socket.h @@ -0,0 +1,105 @@ +// Copyright 2023 klzgrad . All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef NET_TOOLS_NAIVE_NAIVE_PADDING_SOCKET_H_ +#define NET_TOOLS_NAIVE_NAIVE_PADDING_SOCKET_H_ + +#include +#include +#include +#include + +#include "base/memory/scoped_refptr.h" +#include "net/base/address_list.h" +#include "net/base/completion_once_callback.h" +#include "net/base/completion_repeating_callback.h" +#include "net/base/host_port_pair.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/net_export.h" +#include "net/socket/stream_socket.h" +#include "net/tools/naive/naive_padding_framer.h" +#include "net/tools/naive/naive_protocol.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "url/gurl.h" + +namespace net { + +class NaivePaddingSocket { + public: + NaivePaddingSocket(StreamSocket* transport_socket, + PaddingType padding_type, + Direction direction); + + NaivePaddingSocket(const NaivePaddingSocket&) = delete; + NaivePaddingSocket& operator=(const NaivePaddingSocket&) = delete; + + // On destruction Disconnect() is called. + ~NaivePaddingSocket(); + + void Disconnect(); + + int Read(IOBuffer* buf, int buf_len, CompletionOnceCallback callback); + + int Write(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback, + const NetworkTrafficAnnotationTag& traffic_annotation); + + private: + int ReadNoPadding(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback); + int WriteNoPadding(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback, + const NetworkTrafficAnnotationTag& traffic_annotation); + void OnReadNoPaddingComplete(CompletionOnceCallback callback, int rv); + void OnWriteNoPaddingComplete( + CompletionOnceCallback callback, + const NetworkTrafficAnnotationTag& traffic_annotation, + int rv); + + int ReadPaddingV1(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback); + int WritePaddingV1(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback, + const NetworkTrafficAnnotationTag& traffic_annotation); + void OnReadPaddingV1Complete(int rv); + void OnWritePaddingV1Complete( + const NetworkTrafficAnnotationTag& traffic_annotation, + int rv); + + // Exhausts synchronous reads if it is a pure padding + // so this does not return zero for non-EOF condition. + int ReadPaddingV1Payload(); + + int WritePaddingV1Drain( + const NetworkTrafficAnnotationTag& traffic_annotation); + + // Stores the underlying socket. + // Non-owning because this socket does not take part in the client socket pool + // handling and making it owning the transport socket may interfere badly + // with the client socket pool. + StreamSocket* transport_socket_; + + PaddingType padding_type_; + Direction direction_; + + IOBuffer* read_user_buf_ = nullptr; + int read_user_buf_len_ = 0; + CompletionOnceCallback read_callback_; + scoped_refptr read_buf_; + + int write_user_payload_len_ = 0; + CompletionOnceCallback write_callback_; + scoped_refptr write_buf_; + + NaivePaddingFramer framer_; +}; + +} // namespace net + +#endif // NET_TOOLS_NAIVE_NAIVE_PADDING_SOCKET_H_ diff --git a/src/net/tools/naive/naive_protocol.cc b/src/net/tools/naive/naive_protocol.cc new file mode 100644 index 0000000000..21e6515a48 --- /dev/null +++ b/src/net/tools/naive/naive_protocol.cc @@ -0,0 +1,44 @@ +// Copyright 2023 klzgrad . All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "net/tools/naive/naive_protocol.h" + +#include +#include + +#include "base/strings/string_piece.h" + +namespace net { +std::optional ParsePaddingType(base::StringPiece str) { + if (str == "0") { + return PaddingType::kNone; + } else if (str == "1") { + return PaddingType::kVariant1; + } else { + return std::nullopt; + } +} + +const char* ToString(PaddingType value) { + switch (value) { + case PaddingType::kNone: + return "0"; + case PaddingType::kVariant1: + return "1"; + default: + return ""; + } +} + +const char* ToReadableString(PaddingType value) { + switch (value) { + case PaddingType::kNone: + return "None"; + case PaddingType::kVariant1: + return "Variant1"; + default: + return ""; + } +} + +} // namespace net diff --git a/src/net/tools/naive/naive_protocol.h b/src/net/tools/naive/naive_protocol.h index e3e617fbfa..134dd90b6f 100644 --- a/src/net/tools/naive/naive_protocol.h +++ b/src/net/tools/naive/naive_protocol.h @@ -4,6 +4,11 @@ #ifndef NET_TOOLS_NAIVE_NAIVE_PROTOCOL_H_ #define NET_TOOLS_NAIVE_NAIVE_PROTOCOL_H_ +#include +#include + +#include "base/strings/string_piece_forward.h" + namespace net { enum class ClientProtocol { kSocks5, @@ -20,5 +25,39 @@ enum Direction { kNone = 2, }; +enum class PaddingType { + // Wire format: "0". + kNone = 0, + + // Pads the first 8 reads and writes with padding bytes of random size + // uniformly distributed in [0, 255]. + // struct PaddedFrame { + // uint8_t original_data_size_high; // original_data_size / 256 + // uint8_t original_data_size_low; // original_data_size % 256 + // uint8_t padding_size; + // uint8_t original_data[original_data_size]; + // uint8_t zeros[padding_size]; + // }; + // Wire format: "1". + kVariant1 = 1, +}; + +// Returns empty if `str` is invalid. +std::optional ParsePaddingType(base::StringPiece str); + +const char* ToString(PaddingType value); + +const char* ToReadableString(PaddingType value); + +constexpr const char* kPaddingHeader = "padding"; + +// Contains a comma separated list of requested padding types. +// Preferred types come first. +constexpr const char* kPaddingTypeRequestHeader = "padding-type-request"; + +// Contains a single number representing the negotiated padding type. +// Must be one of PaddingType. +constexpr const char* kPaddingTypeReplyHeader = "padding-type-reply"; + } // namespace net #endif // NET_TOOLS_NAIVE_NAIVE_PROTOCOL_H_ diff --git a/src/net/tools/naive/naive_proxy.cc b/src/net/tools/naive/naive_proxy.cc index 554af46bf8..59ca4eef4e 100644 --- a/src/net/tools/naive/naive_proxy.cc +++ b/src/net/tools/naive/naive_proxy.cc @@ -5,6 +5,7 @@ #include "net/tools/naive/naive_proxy.h" +#include #include #include "base/functional/bind.h" @@ -20,7 +21,7 @@ #include "net/socket/client_socket_pool_manager.h" #include "net/socket/server_socket.h" #include "net/socket/stream_socket.h" -#include "net/tools/naive/http_proxy_socket.h" +#include "net/tools/naive/http_proxy_server_socket.h" #include "net/tools/naive/naive_proxy_delegate.h" #include "net/tools/naive/socks5_server_socket.h" @@ -33,7 +34,8 @@ NaiveProxy::NaiveProxy(std::unique_ptr listen_socket, int concurrency, RedirectResolver* resolver, HttpNetworkSession* session, - const NetworkTrafficAnnotationTag& traffic_annotation) + const NetworkTrafficAnnotationTag& traffic_annotation, + const std::vector& supported_padding_types) : listen_socket_(std::move(listen_socket)), protocol_(protocol), listen_user_(listen_user), @@ -44,7 +46,8 @@ NaiveProxy::NaiveProxy(std::unique_ptr listen_socket, net_log_( NetLogWithSource::Make(session->net_log(), NetLogSourceType::NONE)), last_id_(0), - traffic_annotation_(traffic_annotation) { + traffic_annotation_(traffic_annotation), + supported_padding_types_(supported_padding_types) { const auto& proxy_config = static_cast( session_->proxy_resolution_service()) ->config(); @@ -125,9 +128,9 @@ void NaiveProxy::DoConnect() { listen_user_, listen_pass_, traffic_annotation_); } else if (protocol_ == ClientProtocol::kHttp) { - socket = std::make_unique(std::move(accepted_socket_), - padding_detector_delegate.get(), - traffic_annotation_); + socket = std::make_unique( + std::move(accepted_socket_), padding_detector_delegate.get(), + traffic_annotation_, supported_padding_types_); } else if (protocol_ == ClientProtocol::kRedir) { socket = std::move(accepted_socket_); } else { @@ -135,7 +138,8 @@ void NaiveProxy::DoConnect() { } last_id_++; - const auto& nak = network_anonymization_keys_[last_id_ % concurrency_]; + int tunnel_session_id = last_id_ % concurrency_; + const auto& nak = network_anonymization_keys_[tunnel_session_id]; auto connection_ptr = std::make_unique( last_id_, protocol_, std::move(padding_detector_delegate), proxy_info_, server_ssl_config_, proxy_ssl_config_, resolver_, session_, nak, net_log_, diff --git a/src/net/tools/naive/naive_proxy.h b/src/net/tools/naive/naive_proxy.h index 7f6f407d66..c5b6c99d67 100644 --- a/src/net/tools/naive/naive_proxy.h +++ b/src/net/tools/naive/naive_proxy.h @@ -2,12 +2,12 @@ // Copyright 2018 klzgrad . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - #ifndef NET_TOOLS_NAIVE_NAIVE_PROXY_H_ #define NET_TOOLS_NAIVE_NAIVE_PROXY_H_ #include #include +#include #include #include "base/memory/weak_ptr.h" @@ -38,7 +38,8 @@ class NaiveProxy { int concurrency, RedirectResolver* resolver, HttpNetworkSession* session, - const NetworkTrafficAnnotationTag& traffic_annotation); + const NetworkTrafficAnnotationTag& traffic_annotation, + const std::vector& supported_padding_types); ~NaiveProxy(); NaiveProxy(const NaiveProxy&) = delete; NaiveProxy& operator=(const NaiveProxy&) = delete; @@ -82,6 +83,8 @@ class NaiveProxy { const NetworkTrafficAnnotationTag& traffic_annotation_; + std::vector supported_padding_types_; + base::WeakPtrFactory weak_ptr_factory_{this}; }; diff --git a/src/net/tools/naive/naive_proxy_bin.cc b/src/net/tools/naive/naive_proxy_bin.cc index 909f2369f7..26c9001d7c 100644 --- a/src/net/tools/naive/naive_proxy_bin.cc +++ b/src/net/tools/naive/naive_proxy_bin.cc @@ -426,8 +426,9 @@ std::unique_ptr BuildURLRequestContext( builder.SetCertVerifier( CertVerifier::CreateDefault(std::move(cert_net_fetcher))); - builder.set_proxy_delegate( - std::make_unique(params.extra_headers)); + builder.set_proxy_delegate(std::make_unique( + params.extra_headers, + std::vector{PaddingType::kVariant1, PaddingType::kNone})); auto context = builder.Build(); @@ -584,10 +585,12 @@ int main(int argc, char* argv[]) { params.resolver_prefix); } - net::NaiveProxy naive_proxy(std::move(listen_socket), params.protocol, - params.listen_user, params.listen_pass, - params.concurrency, resolver.get(), session, - kTrafficAnnotation); + net::NaiveProxy naive_proxy( + std::move(listen_socket), params.protocol, params.listen_user, + params.listen_pass, params.concurrency, resolver.get(), session, + kTrafficAnnotation, + std::vector{net::PaddingType::kVariant1, + net::PaddingType::kNone}); base::RunLoop().Run(); diff --git a/src/net/tools/naive/naive_proxy_delegate.cc b/src/net/tools/naive/naive_proxy_delegate.cc index d18bf65e88..716201c8f6 100644 --- a/src/net/tools/naive/naive_proxy_delegate.cc +++ b/src/net/tools/naive/naive_proxy_delegate.cc @@ -3,10 +3,13 @@ // found in the LICENSE file. #include "net/tools/naive/naive_proxy_delegate.h" +#include #include #include "base/logging.h" #include "base/rand_util.h" +#include "base/strings/string_piece_forward.h" +#include "base/strings/string_util.h" #include "net/base/proxy_string_util.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" @@ -45,9 +48,18 @@ void FillNonindexHeaderValue(uint64_t unique_bits, char* buf, int len) { } } -NaiveProxyDelegate::NaiveProxyDelegate(const HttpRequestHeaders& extra_headers) +NaiveProxyDelegate::NaiveProxyDelegate( + const HttpRequestHeaders& extra_headers, + const std::vector& supported_padding_types) : extra_headers_(extra_headers) { InitializeNonindexCodes(); + + std::vector padding_type_strs; + for (PaddingType padding_type : supported_padding_types) { + padding_type_strs.push_back(ToString(padding_type)); + } + extra_headers_.SetHeader(kPaddingTypeRequestHeader, + base::JoinString(padding_type_strs, ", ")); } NaiveProxyDelegate::~NaiveProxyDelegate() = default; @@ -55,48 +67,81 @@ NaiveProxyDelegate::~NaiveProxyDelegate() = default; void NaiveProxyDelegate::OnBeforeTunnelRequest( const ProxyServer& proxy_server, HttpRequestHeaders* extra_headers) { + // Not possible to negotiate padding capability given the underlying + // protocols. if (proxy_server.is_direct() || proxy_server.is_socks()) return; // Sends client-side padding header regardless of server support std::string padding(base::RandInt(16, 32), '~'); FillNonindexHeaderValue(base::RandUint64(), &padding[0], padding.size()); - extra_headers->SetHeader("padding", padding); + extra_headers->SetHeader(kPaddingHeader, padding); // Enables Fast Open in H2/H3 proxy client socket once the state of server // padding support is known. - if (padding_state_by_server_[proxy_server] != PaddingSupport::kUnknown) { + if (padding_type_by_server_[proxy_server].has_value()) { extra_headers->SetHeader("fastopen", "1"); } extra_headers->MergeFrom(extra_headers_); } +std::optional NaiveProxyDelegate::ParsePaddingHeaders( + const HttpResponseHeaders& headers) { + bool has_padding = headers.HasHeader(kPaddingHeader); + std::string padding_type_reply; + bool has_padding_type_reply = + headers.GetNormalizedHeader(kPaddingTypeReplyHeader, &padding_type_reply); + + if (!has_padding_type_reply) { + // Backward compatibility with before kVariant1 when the padding-version + // header does not exist. + if (has_padding) { + return PaddingType::kVariant1; + } else { + return PaddingType::kNone; + } + } + std::optional padding_type = + ParsePaddingType(padding_type_reply); + if (!padding_type.has_value()) { + LOG(ERROR) << "Received invalid padding type: " << padding_type_reply; + } + return padding_type; +} + Error NaiveProxyDelegate::OnTunnelHeadersReceived( const ProxyServer& proxy_server, const HttpResponseHeaders& response_headers) { + // Not possible to negotiate padding capability given the underlying + // protocols. if (proxy_server.is_direct() || proxy_server.is_socks()) return OK; // Detects server padding support, even if it changes dynamically. - bool padding = response_headers.HasHeader("padding"); - auto new_state = - padding ? PaddingSupport::kCapable : PaddingSupport::kIncapable; - auto& padding_state = padding_state_by_server_[proxy_server]; - if (padding_state == PaddingSupport::kUnknown || padding_state != new_state) { - LOG(INFO) << "Padding capability of " << ProxyServerToProxyUri(proxy_server) - << (padding ? " detected" : " undetected"); + std::optional new_padding_type = + ParsePaddingHeaders(response_headers); + if (!new_padding_type.has_value()) { + return ERR_INVALID_RESPONSE; + } + std::optional& padding_type = + padding_type_by_server_[proxy_server]; + if (!padding_type.has_value() || padding_type != new_padding_type) { + LOG(INFO) << ProxyServerToProxyUri(proxy_server) + << " negotiated padding type: " + << ToReadableString(*new_padding_type); + padding_type = new_padding_type; } - padding_state = new_state; return OK; } -PaddingSupport NaiveProxyDelegate::GetProxyServerPaddingSupport( +std::optional NaiveProxyDelegate::GetProxyServerPaddingType( const ProxyServer& proxy_server) { - // Not possible to detect padding capability given underlying protocol. + // Not possible to negotiate padding capability given the underlying + // protocols. if (proxy_server.is_direct() || proxy_server.is_socks()) - return PaddingSupport::kIncapable; + return PaddingType::kNone; - return padding_state_by_server_[proxy_server]; + return padding_type_by_server_[proxy_server]; } PaddingDetectorDelegate::PaddingDetectorDelegate( @@ -105,55 +150,32 @@ PaddingDetectorDelegate::PaddingDetectorDelegate( ClientProtocol client_protocol) : naive_proxy_delegate_(naive_proxy_delegate), proxy_server_(proxy_server), - client_protocol_(client_protocol), - detected_client_padding_support_(PaddingSupport::kUnknown), - cached_server_padding_support_(PaddingSupport::kUnknown) {} + client_protocol_(client_protocol) {} PaddingDetectorDelegate::~PaddingDetectorDelegate() = default; -bool PaddingDetectorDelegate::IsPaddingSupportKnown() { - auto c = GetClientPaddingSupport(); - auto s = GetServerPaddingSupport(); - return c != PaddingSupport::kUnknown && s != PaddingSupport::kUnknown; +void PaddingDetectorDelegate::SetClientPaddingType(PaddingType padding_type) { + detected_client_padding_type_ = padding_type; } -Direction PaddingDetectorDelegate::GetPaddingDirection() { - auto c = GetClientPaddingSupport(); - auto s = GetServerPaddingSupport(); - // Padding support must be already detected at this point. - CHECK_NE(c, PaddingSupport::kUnknown); - CHECK_NE(s, PaddingSupport::kUnknown); - if (c == PaddingSupport::kCapable && s == PaddingSupport::kIncapable) { - return kServer; - } - if (c == PaddingSupport::kIncapable && s == PaddingSupport::kCapable) { - return kClient; - } - return kNone; -} - -void PaddingDetectorDelegate::SetClientPaddingSupport( - PaddingSupport padding_support) { - detected_client_padding_support_ = padding_support; -} - -PaddingSupport PaddingDetectorDelegate::GetClientPaddingSupport() { - // Not possible to detect padding capability given underlying protocol. +std::optional PaddingDetectorDelegate::GetClientPaddingType() { + // Not possible to negotiate padding capability given the underlying + // protocols. if (client_protocol_ == ClientProtocol::kSocks5) { - return PaddingSupport::kIncapable; + return PaddingType::kNone; } else if (client_protocol_ == ClientProtocol::kRedir) { - return PaddingSupport::kIncapable; + return PaddingType::kNone; } - return detected_client_padding_support_; + return detected_client_padding_type_; } -PaddingSupport PaddingDetectorDelegate::GetServerPaddingSupport() { - if (cached_server_padding_support_ != PaddingSupport::kUnknown) - return cached_server_padding_support_; - cached_server_padding_support_ = - naive_proxy_delegate_->GetProxyServerPaddingSupport(proxy_server_); - return cached_server_padding_support_; +std::optional PaddingDetectorDelegate::GetServerPaddingType() { + if (cached_server_padding_type_.has_value()) + return cached_server_padding_type_; + cached_server_padding_type_ = + naive_proxy_delegate_->GetProxyServerPaddingType(proxy_server_); + return cached_server_padding_type_; } } // namespace net diff --git a/src/net/tools/naive/naive_proxy_delegate.h b/src/net/tools/naive/naive_proxy_delegate.h index 43cdeeadc2..4a6e6ed8c8 100644 --- a/src/net/tools/naive/naive_proxy_delegate.h +++ b/src/net/tools/naive/naive_proxy_delegate.h @@ -6,12 +6,15 @@ #include #include +#include #include +#include #include "base/strings/string_piece.h" #include "net/base/net_errors.h" #include "net/base/proxy_delegate.h" #include "net/base/proxy_server.h" +#include "net/http/http_request_headers.h" #include "net/proxy_resolution/proxy_retry_info.h" #include "net/tools/naive/naive_protocol.h" #include "url/gurl.h" @@ -23,18 +26,11 @@ void InitializeNonindexCodes(); void FillNonindexHeaderValue(uint64_t unique_bits, char* buf, int len); class ProxyInfo; -class HttpRequestHeaders; -class HttpResponseHeaders; - -enum class PaddingSupport { - kUnknown = 0, - kCapable, - kIncapable, -}; class NaiveProxyDelegate : public ProxyDelegate { public: - explicit NaiveProxyDelegate(const HttpRequestHeaders& extra_headers); + NaiveProxyDelegate(const HttpRequestHeaders& extra_headers, + const std::vector& supported_padding_types); ~NaiveProxyDelegate() override; void OnResolveProxy(const GURL& url, @@ -51,18 +47,25 @@ class NaiveProxyDelegate : public ProxyDelegate { const ProxyServer& proxy_server, const HttpResponseHeaders& response_headers) override; - PaddingSupport GetProxyServerPaddingSupport(const ProxyServer& proxy_server); + // Returns empty if the padding type has not been negotiated. + std::optional GetProxyServerPaddingType( + const ProxyServer& proxy_server); private: - const HttpRequestHeaders& extra_headers_; - std::map padding_state_by_server_; + std::optional ParsePaddingHeaders( + const HttpResponseHeaders& headers); + + HttpRequestHeaders extra_headers_; + + // Empty value means padding type has not been negotiated. + std::map> padding_type_by_server_; }; class ClientPaddingDetectorDelegate { public: virtual ~ClientPaddingDetectorDelegate() = default; - virtual void SetClientPaddingSupport(PaddingSupport padding_support) = 0; + virtual void SetClientPaddingType(PaddingType padding_type) = 0; }; class PaddingDetectorDelegate : public ClientPaddingDetectorDelegate { @@ -72,22 +75,19 @@ class PaddingDetectorDelegate : public ClientPaddingDetectorDelegate { ClientProtocol client_protocol); ~PaddingDetectorDelegate() override; - bool IsPaddingSupportKnown(); - Direction GetPaddingDirection(); - void SetClientPaddingSupport(PaddingSupport padding_support) override; + std::optional GetClientPaddingType(); + std::optional GetServerPaddingType(); + void SetClientPaddingType(PaddingType padding_type) override; private: - PaddingSupport GetClientPaddingSupport(); - PaddingSupport GetServerPaddingSupport(); - NaiveProxyDelegate* naive_proxy_delegate_; const ProxyServer& proxy_server_; ClientProtocol client_protocol_; - PaddingSupport detected_client_padding_support_; + std::optional detected_client_padding_type_; // The result is only cached during one connection, so it's still dynamically // updated in the following connections after server changes support. - PaddingSupport cached_server_padding_support_; + std::optional cached_server_padding_type_; }; } // namespace net diff --git a/tools/list-tls-sni-stream.sh b/tools/list-tls-sni-stream.sh new file mode 100755 index 0000000000..03ba5d6b81 --- /dev/null +++ b/tools/list-tls-sni-stream.sh @@ -0,0 +1,10 @@ +#!/bin/sh +if [ ! "$1" ]; then + echo "Usage: $0 PCAP_FILE" + exit 1 +fi + +file="$1" +# Remember to disable segmentation offload so pcap files won't capture packets larger than MTU: +# sudo ethtool --offload eth0 gso off gro off +tshark -2 -r "$file" -R tls.handshake.extensions_server_name -T fields -e tls.handshake.extensions_server_name -e tcp.stream diff --git a/tools/parse-pcap-stream.py b/tools/parse-pcap-stream.py new file mode 100755 index 0000000000..b05ac533e1 --- /dev/null +++ b/tools/parse-pcap-stream.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +import os +import sys +import subprocess +import yaml + + +class TlsStreamParser: + STATE_CONTENT_TYPE = 0 + STATE_VERSION_BYTE0 = 1 + STATE_VERSION_BYTE1 = 2 + STATE_LENGTH_BYTE0 = 3 + STATE_LENGTH_BYTE1 = 4 + STATE_DATA = 5 + + TLS_HEADER_SIZE = 5 + + def __init__(self): + self.state = self.STATE_CONTENT_TYPE + self.current_length = None + self.current_remaining = None + + def read(self, data): + record_parts = [] + i = 0 + tls_consumed_bytes = 0 + while i < len(data): + if self.state == self.STATE_CONTENT_TYPE: + # TODO: add content type description + content_type = data[i] + self.state = self.STATE_VERSION_BYTE0 + i += 1 + tls_consumed_bytes += 1 + elif self.state == self.STATE_VERSION_BYTE0: + self.state = self.STATE_VERSION_BYTE1 + i += 1 + tls_consumed_bytes += 1 + elif self.state == self.STATE_VERSION_BYTE1: + self.state = self.STATE_LENGTH_BYTE0 + i += 1 + tls_consumed_bytes += 1 + elif self.state == self.STATE_LENGTH_BYTE0: + self.current_length = data[i] + self.state = self.STATE_LENGTH_BYTE1 + i += 1 + tls_consumed_bytes += 1 + elif self.state == self.STATE_LENGTH_BYTE1: + self.current_length = self.current_length * 256 + data[i] + self.current_remaining = self.current_length + self.state = self.STATE_DATA + i += 1 + tls_consumed_bytes += 1 + elif self.state == self.STATE_DATA: + consume_data = min(self.current_remaining, len(data) - i) + self.current_remaining -= consume_data + i += consume_data + tls_consumed_bytes += consume_data + if self.current_remaining == 0: + record_parts.append( + (tls_consumed_bytes, self.TLS_HEADER_SIZE + self.current_length)) + tls_consumed_bytes = 0 + self.current_length = None + self.state = self.STATE_CONTENT_TYPE + if tls_consumed_bytes: + if self.current_length is None: + record_parts.append((tls_consumed_bytes, '?')) + else: + record_parts.append( + (tls_consumed_bytes, self.TLS_HEADER_SIZE + self.current_length)) + return record_parts + + +if len(sys.argv) != 3: + print(f'Usage: {sys.argv[0]} PCAP_FILE STREAM_ID') + os.exit(1) + +file = sys.argv[1] +stream_id = sys.argv[2] +result = subprocess.run(['tshark', '-2', '-r', file, '-q', '-z', + f'follow,tcp,yaml,{stream_id}'], capture_output=True, check=True, text=True) + +follow_result = yaml.safe_load(result.stdout) +LOCAL_PEER = 0 +REMOTE_PEER = 1 +assert follow_result['peers'][REMOTE_PEER][ + 'port'] == 443, f"assuming the remote peer is the TLS server: {follow_result['peers']}" +packets = follow_result['packets'] + +upload_stream = TlsStreamParser() +download_stream = TlsStreamParser() +rtt = packets[1]['timestamp'] - packets[0]['timestamp'] +time_unit = rtt / 2 +local_timestamp_first = packets[0]['timestamp'] +mitm_timestamp_first = local_timestamp_first + rtt / 4 +min_mitm_timestamp_up = packets[0]['timestamp'] +min_mitm_timestamp_down = packets[0]['timestamp'] +for packet in packets: + local_timestamp = packet['timestamp'] + + data = packet['data'] + if packet['peer'] == LOCAL_PEER: + mitm_timestamp = local_timestamp + time_unit / 2 + mitm_timestamp = max(mitm_timestamp, min_mitm_timestamp_up) + min_mitm_timestamp_up = mitm_timestamp + + timestamp = (mitm_timestamp - mitm_timestamp_first) / time_unit + record_parts = upload_stream.read(data) + print('%.3f' % timestamp, len(data), ','.join( + f'{i}/{j}' for i, j in record_parts)) + elif packet['peer'] == REMOTE_PEER: + mitm_timestamp = local_timestamp - time_unit / 2 + mitm_timestamp = max(mitm_timestamp, min_mitm_timestamp_down) + min_mitm_timestamp_down = mitm_timestamp + + timestamp = (mitm_timestamp - mitm_timestamp_first) / time_unit + record_parts = download_stream.read(data) + print('%.3f' % timestamp, -len(data), + ','.join(f'{i}/{j}' for i, j in record_parts)) diff --git a/tools/sample-traffic.sh b/tools/sample-traffic.sh new file mode 100755 index 0000000000..d352a9760e --- /dev/null +++ b/tools/sample-traffic.sh @@ -0,0 +1,16 @@ +#!/bin/sh +if [ ! "$1" ]; then + echo "Usage: $0 IFACE" + exit 1 +fi + +iface="$1" +sudo echo + +sudo tcpdump -i "$1" -s0 -w microsoft-direct.pcap & +chromium --user-data-dir=$(mktemp -d) https://www.microsoft.com/en-us/ +sudo pkill tcpdump + +sudo tcpdump -i "$1" -s0 -w microsoft-proxy.pcap & +chromium --proxy-server=socks5://127.0.0.1:1080 --user-data-dir=$(mktemp -d) https://www.microsoft.com/en-us/ +sudo pkill tcpdump &