This commit is contained in:
klzgrad 2023-06-24 16:44:46 +08:00
parent c53143ba5c
commit 4b250049af
19 changed files with 1055 additions and 279 deletions

View File

@ -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",

View File

@ -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 <algorithm>
#include <cstring>
#include <utility>
#include <vector>
#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<StreamSocket> transport_socket,
ClientPaddingDetectorDelegate* padding_detector_delegate,
const NetworkTrafficAnnotationTag& traffic_annotation)
: io_callback_(base::BindRepeating(&HttpProxySocket::OnIOComplete,
const NetworkTrafficAnnotationTag& traffic_annotation,
const std::vector<PaddingType>& 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,17 +131,17 @@ 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 HttpProxyServerSocket::Read(IOBuffer* buf,
int buf_len,
CompletionOnceCallback callback) {
DCHECK(completed_handshake_);
@ -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,7 +219,7 @@ void HttpProxySocket::OnIOComplete(int result) {
}
}
void HttpProxySocket::OnReadWriteComplete(CompletionOnceCallback callback,
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<IOBuffer>(kBufferSize);
return transport_->Read(handshake_buf_.get(), kBufferSize, io_callback_);
}
int HttpProxySocket::DoHeaderReadComplete(int result) {
std::optional<PaddingType> 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<base::StringPiece> 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<PaddingType> 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<PaddingType> 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);
}

View File

@ -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 <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#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<StreamSocket> transport_socket,
HttpProxyServerSocket(
std::unique_ptr<StreamSocket> transport_socket,
ClientPaddingDetectorDelegate* padding_detector_delegate,
const NetworkTrafficAnnotationTag& traffic_annotation);
HttpProxySocket(const HttpProxySocket&) = delete;
HttpProxySocket& operator=(const HttpProxySocket&) = delete;
const NetworkTrafficAnnotationTag& traffic_annotation,
const std::vector<PaddingType>& 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<PaddingType> 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<PaddingType> supported_padding_types_;
};
} // namespace net
#endif // NET_TOOLS_NAIVE_HTTP_PROXY_SOCKET_H_
#endif // NET_TOOLS_NAIVE_HTTP_PROXY_SERVER_SOCKET_H_

View File

@ -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<ClientSocketHandle>()),
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<PaddingType> client_padding_type =
padding_detector_delegate_->GetClientPaddingType();
CHECK(client_padding_type.has_value());
sockets_[kClient] = std::make_unique<NaivePaddingSocket>(
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<const HttpProxySocket*>(client_socket_.get());
static_cast<const HttpProxyServerSocket*>(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<PaddingType> server_padding_type =
padding_detector_delegate_->GetServerPaddingType();
CHECK(server_padding_type.has_value());
sockets_[kServer] = std::make_unique<NaivePaddingSocket>(
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<GrowableIOBuffer>();
buffer->SetCapacity(kBufferSize);
buffer->set_offset(kPaddingHeaderSize);
read_buffers_[from] = buffer;
read_size = kBufferSize - kPaddingHeaderSize - kMaxPaddingSize;
} else {
read_buffers_[from] = base::MakeRefCounted<IOBuffer>(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<GrowableIOBuffer*>(read_buffers_[from].get());
buffer->set_offset(0);
uint8_t* p = reinterpret_cast<uint8_t*>(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<uint8_t>(p[0]) * 256 + static_cast<uint8_t>(p[1]);
int padding_size = static_cast<uint8_t>(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<IOBuffer>(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<uint8_t>(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<uint8_t>(p[i]);
++i;
read_padding_state_ = STATE_READ_PADDING_LENGTH;
break;
case STATE_READ_PADDING_LENGTH:
padding_length_ = static_cast<uint8_t>(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<DrainableIOBuffer>(
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() {

View File

@ -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<StreamSocket> client_socket_;
std::unique_ptr<ClientSocketHandle> server_socket_handle_;
StreamSocket* sockets_[kNumDirections];
std::unique_ptr<NaivePaddingSocket> sockets_[kNumDirections];
scoped_refptr<IOBuffer> read_buffers_[kNumDirections];
scoped_refptr<DrainableIOBuffer> 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_;

View File

@ -0,0 +1,117 @@
// Copyright 2023 klzgrad <kizdiv@gmail.com>. 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 <cstdint>
#include <cstring>
#include <limits>
#include <optional>
#include "base/check.h"
#include "base/check_op.h"
namespace net {
NaivePaddingFramer::NaivePaddingFramer(std::optional<int> 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<uint8_t>(padded[0]);
++padded;
--padded_len;
state_ = ReadState::kPayloadLength2;
break;
case ReadState::kPayloadLength2:
read_payload_length_ =
read_payload_length_ * 256 + static_cast<uint8_t>(padded[0]);
++padded;
--padded_len;
state_ = ReadState::kPaddingLength1;
break;
case ReadState::kPaddingLength1:
read_padding_length_ = static_cast<uint8_t>(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<int>::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<int>::max() - 1) {
++num_written_frames_;
}
return padded_buf_len;
}
} // namespace net

View File

@ -0,0 +1,72 @@
// Copyright 2023 klzgrad <kizdiv@gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdint>
#include <limits>
#include <optional>
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<int> max_read_frames);
int max_payload_size() const { return std::numeric_limits<uint16_t>::max(); }
int max_padding_size() const { return std::numeric_limits<uint8_t>::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<int> 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

View File

@ -0,0 +1,271 @@
// Copyright 2023 klzgrad <kizdiv@gmail.com>. 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 <algorithm>
#include <cstring>
#include <optional>
#include <tuple>
#include <utility>
#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<IOBuffer>(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<IOBuffer>(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<DrainableIOBuffer>(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

View File

@ -0,0 +1,105 @@
// Copyright 2023 klzgrad <kizdiv@gmail.com>. 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 <cstddef>
#include <memory>
#include <optional>
#include <string>
#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<IOBuffer> read_buf_;
int write_user_payload_len_ = 0;
CompletionOnceCallback write_callback_;
scoped_refptr<DrainableIOBuffer> write_buf_;
NaivePaddingFramer framer_;
};
} // namespace net
#endif // NET_TOOLS_NAIVE_NAIVE_PADDING_SOCKET_H_

View File

@ -0,0 +1,44 @@
// Copyright 2023 klzgrad <kizdiv@gmail.com>. 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 <optional>
#include <string>
#include "base/strings/string_piece.h"
namespace net {
std::optional<PaddingType> 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

View File

@ -4,6 +4,11 @@
#ifndef NET_TOOLS_NAIVE_NAIVE_PROTOCOL_H_
#define NET_TOOLS_NAIVE_NAIVE_PROTOCOL_H_
#include <optional>
#include <string>
#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<PaddingType> 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_

View File

@ -5,6 +5,7 @@
#include "net/tools/naive/naive_proxy.h"
#include <string>
#include <utility>
#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<ServerSocket> listen_socket,
int concurrency,
RedirectResolver* resolver,
HttpNetworkSession* session,
const NetworkTrafficAnnotationTag& traffic_annotation)
const NetworkTrafficAnnotationTag& traffic_annotation,
const std::vector<PaddingType>& supported_padding_types)
: listen_socket_(std::move(listen_socket)),
protocol_(protocol),
listen_user_(listen_user),
@ -44,7 +46,8 @@ NaiveProxy::NaiveProxy(std::unique_ptr<ServerSocket> 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<ConfiguredProxyResolutionService*>(
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<HttpProxySocket>(std::move(accepted_socket_),
padding_detector_delegate.get(),
traffic_annotation_);
socket = std::make_unique<HttpProxyServerSocket>(
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<NaiveConnection>(
last_id_, protocol_, std::move(padding_detector_delegate), proxy_info_,
server_ssl_config_, proxy_ssl_config_, resolver_, session_, nak, net_log_,

View File

@ -2,12 +2,12 @@
// Copyright 2018 klzgrad <kizdiv@gmail.com>. 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 <map>
#include <memory>
#include <string>
#include <vector>
#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<PaddingType>& 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<PaddingType> supported_padding_types_;
base::WeakPtrFactory<NaiveProxy> weak_ptr_factory_{this};
};

View File

@ -426,8 +426,9 @@ std::unique_ptr<URLRequestContext> BuildURLRequestContext(
builder.SetCertVerifier(
CertVerifier::CreateDefault(std::move(cert_net_fetcher)));
builder.set_proxy_delegate(
std::make_unique<NaiveProxyDelegate>(params.extra_headers));
builder.set_proxy_delegate(std::make_unique<NaiveProxyDelegate>(
params.extra_headers,
std::vector<PaddingType>{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>{net::PaddingType::kVariant1,
net::PaddingType::kNone});
base::RunLoop().Run();

View File

@ -3,10 +3,13 @@
// found in the LICENSE file.
#include "net/tools/naive/naive_proxy_delegate.h"
#include <optional>
#include <string>
#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<PaddingType>& supported_padding_types)
: extra_headers_(extra_headers) {
InitializeNonindexCodes();
std::vector<base::StringPiece> 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<PaddingType> 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<PaddingType> 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<PaddingType> new_padding_type =
ParsePaddingHeaders(response_headers);
if (!new_padding_type.has_value()) {
return ERR_INVALID_RESPONSE;
}
std::optional<PaddingType>& 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<PaddingType> 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<PaddingType> 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<PaddingType> 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

View File

@ -6,12 +6,15 @@
#include <cstdint>
#include <map>
#include <optional>
#include <string>
#include <vector>
#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<PaddingType>& 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<PaddingType> GetProxyServerPaddingType(
const ProxyServer& proxy_server);
private:
const HttpRequestHeaders& extra_headers_;
std::map<ProxyServer, PaddingSupport> padding_state_by_server_;
std::optional<PaddingType> ParsePaddingHeaders(
const HttpResponseHeaders& headers);
HttpRequestHeaders extra_headers_;
// Empty value means padding type has not been negotiated.
std::map<ProxyServer, std::optional<PaddingType>> 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<PaddingType> GetClientPaddingType();
std::optional<PaddingType> 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<PaddingType> 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<PaddingType> cached_server_padding_type_;
};
} // namespace net

10
tools/list-tls-sni-stream.sh Executable file
View File

@ -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

118
tools/parse-pcap-stream.py Executable file
View File

@ -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))

16
tools/sample-traffic.sh Executable file
View File

@ -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 &