Negotiate padding capability automatically

Client: On the first connection does a full Open and detects if the
server supports padding by checking for "Padding" header in the
response. Applies padding if the server does. In the following
connections it's back to Fast Open.

Server: Detects if the client supports padding by checking for "Padding"
header in the CONNECT request. Applies padding if the client does.

Both client and server always send "Padding" headers to somewhat protect
the request and response headers' packet lengths, even if the other side
may not acknowledge padding negotiation, either due to old version or
"Padding" headers being dropped by the frontend.

The manual option --padding is removed.
This commit is contained in:
klzgrad 2020-05-25 00:30:52 +08:00
parent 9ca803e3bb
commit 5e7d36ef98
11 changed files with 384 additions and 155 deletions

View File

@ -2371,6 +2371,8 @@ if (!is_ios && !is_android) {
"tools/naive/naive_proxy.cc", "tools/naive/naive_proxy.cc",
"tools/naive/naive_proxy.h", "tools/naive/naive_proxy.h",
"tools/naive/naive_proxy_bin.cc", "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.cc",
"tools/naive/http_proxy_socket.h", "tools/naive/http_proxy_socket.h",
"tools/naive/redirect_resolver.h", "tools/naive/redirect_resolver.h",

View File

@ -15,8 +15,10 @@
#include "base/sys_byteorder.h" #include "base/sys_byteorder.h"
#include "net/base/ip_address.h" #include "net/base/ip_address.h"
#include "net/base/net_errors.h" #include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "net/log/net_log.h" #include "net/log/net_log.h"
#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" #include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
#include "net/tools/naive/naive_proxy_delegate.h"
namespace net { namespace net {
@ -28,40 +30,22 @@ constexpr int kResponseHeaderSize = sizeof(kResponseHeader) - 1;
// A plain 200 is 10 bytes. Expected 48 bytes. "Padding" uses up 7 bytes. // A plain 200 is 10 bytes. Expected 48 bytes. "Padding" uses up 7 bytes.
constexpr int kMinPaddingSize = 30; constexpr int kMinPaddingSize = 30;
constexpr int kMaxPaddingSize = kMinPaddingSize + 32; constexpr int kMaxPaddingSize = kMinPaddingSize + 32;
bool g_nonindex_codes_initialized;
uint8_t g_nonindex_codes[17];
void InitializeNonindexCodes() {
if (g_nonindex_codes_initialized)
return;
g_nonindex_codes_initialized = true;
unsigned i = 0;
for (const auto& symbol : spdy::HpackHuffmanCodeVector()) {
if (symbol.id >= 0x20 && symbol.id <= 0x7f && symbol.length >= 8) {
g_nonindex_codes[i++] = symbol.id;
if (i >= sizeof(g_nonindex_codes))
break;
}
}
CHECK(i == sizeof(g_nonindex_codes));
}
} // namespace } // namespace
HttpProxySocket::HttpProxySocket( HttpProxySocket::HttpProxySocket(
std::unique_ptr<StreamSocket> transport_socket, std::unique_ptr<StreamSocket> transport_socket,
ClientPaddingDetectorDelegate* padding_detector_delegate,
const NetworkTrafficAnnotationTag& traffic_annotation) const NetworkTrafficAnnotationTag& traffic_annotation)
: io_callback_(base::BindRepeating(&HttpProxySocket::OnIOComplete, : io_callback_(base::BindRepeating(&HttpProxySocket::OnIOComplete,
base::Unretained(this))), base::Unretained(this))),
transport_(std::move(transport_socket)), transport_(std::move(transport_socket)),
padding_detector_delegate_(padding_detector_delegate),
next_state_(STATE_NONE), next_state_(STATE_NONE),
completed_handshake_(false), completed_handshake_(false),
was_ever_used_(false), was_ever_used_(false),
header_write_size_(-1), header_write_size_(-1),
net_log_(transport_->NetLog()), net_log_(transport_->NetLog()),
traffic_annotation_(traffic_annotation) { traffic_annotation_(traffic_annotation) {}
InitializeNonindexCodes();
}
HttpProxySocket::~HttpProxySocket() { HttpProxySocket::~HttpProxySocket() {
Disconnect(); Disconnect();
@ -314,6 +298,21 @@ int HttpProxySocket::DoHeaderReadComplete(int result) {
request_endpoint_ = HostPortPair::FromString( request_endpoint_ = HostPortPair::FromString(
buffer_.substr(first_space + 1, second_space - (first_space + 1))); buffer_.substr(first_space + 1, second_space - (first_space + 1)));
auto second_line = first_line_end + 2;
HttpRequestHeaders headers;
std::string headers_str;
if (second_line < header_end) {
headers_str = buffer_.substr(second_line, header_end - second_line);
headers.AddHeadersFromString(headers_str);
}
if (headers.HasHeader("padding")) {
padding_detector_delegate_->SetClientPaddingSupport(
PaddingSupport::kCapable);
} else {
padding_detector_delegate_->SetClientPaddingSupport(
PaddingSupport::kIncapable);
}
buffer_ = buffer_.substr(header_end + 4); buffer_ = buffer_.substr(header_end + 4);
next_state_ = STATE_HEADER_WRITE; next_state_ = STATE_HEADER_WRITE;
@ -329,13 +328,8 @@ int HttpProxySocket::DoHeaderWrite() {
handshake_buf_ = base::MakeRefCounted<IOBuffer>(header_write_size_); handshake_buf_ = base::MakeRefCounted<IOBuffer>(header_write_size_);
char* p = handshake_buf_->data(); char* p = handshake_buf_->data();
std::memcpy(p, kResponseHeader, kResponseHeaderSize); std::memcpy(p, kResponseHeader, kResponseHeaderSize);
std::memset(p + kResponseHeaderSize, g_nonindex_codes[16], padding_size); FillNonindexHeaderValue(base::RandUint64(), p + kResponseHeaderSize,
// Prevents index reuse padding_size);
uint64_t bits = base::RandUint64();
for (int i = 0; i < 16; i++) {
p[kResponseHeaderSize + i] = g_nonindex_codes[bits & 0b1111];
bits >>= 4;
}
std::memcpy(p + kResponseHeaderSize + padding_size, "\r\n\r\n", 4); std::memcpy(p + kResponseHeaderSize + padding_size, "\r\n\r\n", 4);
return transport_->Write(handshake_buf_.get(), header_write_size_, return transport_->Write(handshake_buf_.get(), header_write_size_,

View File

@ -26,11 +26,13 @@
namespace net { namespace net {
struct NetworkTrafficAnnotationTag; struct NetworkTrafficAnnotationTag;
class ClientPaddingDetectorDelegate;
// This StreamSocket is used to setup a HTTP CONNECT tunnel. // This StreamSocket is used to setup a HTTP CONNECT tunnel.
class HttpProxySocket : public StreamSocket { class HttpProxySocket : public StreamSocket {
public: public:
HttpProxySocket(std::unique_ptr<StreamSocket> transport_socket, HttpProxySocket(std::unique_ptr<StreamSocket> transport_socket,
ClientPaddingDetectorDelegate* padding_detector_delegate,
const NetworkTrafficAnnotationTag& traffic_annotation); const NetworkTrafficAnnotationTag& traffic_annotation);
// On destruction Disconnect() is called. // On destruction Disconnect() is called.
@ -93,6 +95,7 @@ class HttpProxySocket : public StreamSocket {
// Stores the underlying socket. // Stores the underlying socket.
std::unique_ptr<StreamSocket> transport_; std::unique_ptr<StreamSocket> transport_;
ClientPaddingDetectorDelegate* padding_detector_delegate_;
State next_state_; State next_state_;

View File

@ -48,8 +48,8 @@ constexpr int kMaxPaddingSize = 255;
NaiveConnection::NaiveConnection( NaiveConnection::NaiveConnection(
unsigned int id, unsigned int id,
Protocol protocol, ClientProtocol protocol,
Direction pad_direction, std::unique_ptr<PaddingDetectorDelegate> padding_detector_delegate,
const ProxyInfo& proxy_info, const ProxyInfo& proxy_info,
const SSLConfig& server_ssl_config, const SSLConfig& server_ssl_config,
const SSLConfig& proxy_ssl_config, const SSLConfig& proxy_ssl_config,
@ -61,7 +61,7 @@ NaiveConnection::NaiveConnection(
const NetworkTrafficAnnotationTag& traffic_annotation) const NetworkTrafficAnnotationTag& traffic_annotation)
: id_(id), : id_(id),
protocol_(protocol), protocol_(protocol),
pad_direction_(pad_direction), padding_detector_delegate_(std::move(padding_detector_delegate)),
proxy_info_(proxy_info), proxy_info_(proxy_info),
server_ssl_config_(server_ssl_config), server_ssl_config_(server_ssl_config),
proxy_ssl_config_(proxy_ssl_config), proxy_ssl_config_(proxy_ssl_config),
@ -177,6 +177,17 @@ int NaiveConnection::DoConnectClientComplete(int result) {
if (result < 0) if (result < 0)
return result; return result;
// 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
// done during client connect, so there shouldn't be any missed early pull.
if (!padding_detector_delegate_->IsPaddingSupportKnown()) {
early_pull_pending_ = false;
early_pull_result_ = 0;
next_state_ = STATE_CONNECT_SERVER;
return OK;
}
early_pull_pending_ = true; early_pull_pending_ = true;
Pull(kClient, kServer); Pull(kClient, kServer);
if (early_pull_result_ != ERR_IO_PENDING) { if (early_pull_result_ != ERR_IO_PENDING) {
@ -194,15 +205,15 @@ int NaiveConnection::DoConnectServer() {
next_state_ = STATE_CONNECT_SERVER_COMPLETE; next_state_ = STATE_CONNECT_SERVER_COMPLETE;
HostPortPair origin; HostPortPair origin;
if (protocol_ == kSocks5) { if (protocol_ == ClientProtocol::kSocks5) {
const auto* socket = const auto* socket =
static_cast<const Socks5ServerSocket*>(client_socket_.get()); static_cast<const Socks5ServerSocket*>(client_socket_.get());
origin = socket->request_endpoint(); origin = socket->request_endpoint();
} else if (protocol_ == kHttp) { } else if (protocol_ == ClientProtocol::kHttp) {
const auto* socket = const auto* socket =
static_cast<const HttpProxySocket*>(client_socket_.get()); static_cast<const HttpProxySocket*>(client_socket_.get());
origin = socket->request_endpoint(); origin = socket->request_endpoint();
} else if (protocol_ == kRedir) { } else if (protocol_ == ClientProtocol::kRedir) {
#if defined(OS_LINUX) #if defined(OS_LINUX)
const auto* socket = const auto* socket =
static_cast<const TCPClientSocket*>(client_socket_.get()); static_cast<const TCPClientSocket*>(client_socket_.get());
@ -280,7 +291,11 @@ int NaiveConnection::Run(CompletionOnceCallback callback) {
yield_after_time_[kServer] = yield_after_time_[kClient]; yield_after_time_[kServer] = yield_after_time_[kClient];
can_push_to_server_ = true; can_push_to_server_ = true;
if (!early_pull_pending_) { // early_pull_result_ == 0 means the early pull was not started because
// padding support was not yet known.
if (!early_pull_pending_ && early_pull_result_ == 0) {
Pull(kClient, kServer);
} else if (!early_pull_pending_) {
DCHECK_GT(early_pull_result_, 0); DCHECK_GT(early_pull_result_, 0);
Push(kClient, kServer, early_pull_result_); Push(kClient, kServer, early_pull_result_);
} }
@ -294,7 +309,8 @@ void NaiveConnection::Pull(Direction from, Direction to) {
return; return;
int read_size = kBufferSize; int read_size = kBufferSize;
if (from == pad_direction_ && num_paddings_[from] < kFirstPaddings) { auto padding_direction = padding_detector_delegate_->GetPaddingDirection();
if (from == padding_direction && num_paddings_[from] < kFirstPaddings) {
auto buffer = base::MakeRefCounted<GrowableIOBuffer>(); auto buffer = base::MakeRefCounted<GrowableIOBuffer>();
buffer->SetCapacity(kBufferSize); buffer->SetCapacity(kBufferSize);
buffer->set_offset(kPaddingHeaderSize); buffer->set_offset(kPaddingHeaderSize);
@ -320,7 +336,8 @@ void NaiveConnection::Pull(Direction from, Direction to) {
void NaiveConnection::Push(Direction from, Direction to, int size) { void NaiveConnection::Push(Direction from, Direction to, int size) {
int write_size = size; int write_size = size;
int write_offset = 0; int write_offset = 0;
if (from == pad_direction_ && num_paddings_[from] < kFirstPaddings) { auto padding_direction = padding_detector_delegate_->GetPaddingDirection();
if (from == padding_direction && num_paddings_[from] < kFirstPaddings) {
// Adds padding. // Adds padding.
++num_paddings_[from]; ++num_paddings_[from];
int padding_size = base::RandInt(0, kMaxPaddingSize); int padding_size = base::RandInt(0, kMaxPaddingSize);
@ -332,7 +349,7 @@ void NaiveConnection::Push(Direction from, Direction to, int size) {
p[2] = padding_size; p[2] = padding_size;
std::memset(p + kPaddingHeaderSize + size, 0, padding_size); std::memset(p + kPaddingHeaderSize + size, 0, padding_size);
write_size = kPaddingHeaderSize + size + padding_size; write_size = kPaddingHeaderSize + size + padding_size;
} else if (to == pad_direction_ && num_paddings_[from] < kFirstPaddings) { } else if (to == padding_direction && num_paddings_[from] < kFirstPaddings) {
// Removes padding. // Removes padding.
const char* p = read_buffers_[from]->data(); const char* p = read_buffers_[from]->data();
bool trivial_padding = false; bool trivial_padding = false;
@ -482,7 +499,7 @@ void NaiveConnection::OnPushError(Direction from, Direction to, int error) {
void NaiveConnection::OnPullComplete(Direction from, Direction to, int result) { void NaiveConnection::OnPullComplete(Direction from, Direction to, int result) {
if (from == kClient && early_pull_pending_) { if (from == kClient && early_pull_pending_) {
early_pull_pending_ = false; early_pull_pending_ = false;
early_pull_result_ = result; early_pull_result_ = result ? result : ERR_CONNECTION_CLOSED;
} }
if (result <= 0) { if (result <= 0) {

View File

@ -15,6 +15,8 @@
#include "base/time/time.h" #include "base/time/time.h"
#include "net/base/completion_once_callback.h" #include "net/base/completion_once_callback.h"
#include "net/base/completion_repeating_callback.h" #include "net/base/completion_repeating_callback.h"
#include "net/tools/naive/naive_protocol.h"
#include "net/tools/naive/naive_proxy_delegate.h"
namespace net { namespace net {
@ -34,23 +36,10 @@ class NaiveConnection {
public: public:
using TimeFunc = base::TimeTicks (*)(); using TimeFunc = base::TimeTicks (*)();
enum Protocol { NaiveConnection(
kSocks5, unsigned int id,
kHttp, ClientProtocol protocol,
kRedir, std::unique_ptr<PaddingDetectorDelegate> padding_detector_delegate,
};
// From this direction.
enum Direction {
kClient = 0,
kServer = 1,
kNumDirections = 2,
kNone = 2,
};
NaiveConnection(unsigned int id,
Protocol protocol,
Direction pad_direction,
const ProxyInfo& proxy_info, const ProxyInfo& proxy_info,
const SSLConfig& server_ssl_config, const SSLConfig& server_ssl_config,
const SSLConfig& proxy_ssl_config, const SSLConfig& proxy_ssl_config,
@ -102,8 +91,8 @@ class NaiveConnection {
void OnPushComplete(Direction from, Direction to, int result); void OnPushComplete(Direction from, Direction to, int result);
unsigned int id_; unsigned int id_;
Protocol protocol_; ClientProtocol protocol_;
Direction pad_direction_; std::unique_ptr<PaddingDetectorDelegate> padding_detector_delegate_;
const ProxyInfo& proxy_info_; const ProxyInfo& proxy_info_;
const SSLConfig& server_ssl_config_; const SSLConfig& server_ssl_config_;
const SSLConfig& proxy_ssl_config_; const SSLConfig& proxy_ssl_config_;

View File

@ -0,0 +1,24 @@
// Copyright 2020 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_PROTOCOL_H_
#define NET_TOOLS_NAIVE_NAIVE_PROTOCOL_H_
namespace net {
enum class ClientProtocol {
kSocks5,
kHttp,
kRedir,
};
// Adds padding for traffic from this direction.
// Removes padding for traffic from the opposite direction.
enum Direction {
kClient = 0,
kServer = 1,
kNumDirections = 2,
kNone = 2,
};
} // namespace net
#endif // NET_TOOLS_NAIVE_NAIVE_PROTOCOL_H_

View File

@ -22,20 +22,19 @@
#include "net/socket/server_socket.h" #include "net/socket/server_socket.h"
#include "net/socket/stream_socket.h" #include "net/socket/stream_socket.h"
#include "net/tools/naive/http_proxy_socket.h" #include "net/tools/naive/http_proxy_socket.h"
#include "net/tools/naive/naive_proxy_delegate.h"
#include "net/tools/naive/socks5_server_socket.h" #include "net/tools/naive/socks5_server_socket.h"
namespace net { namespace net {
NaiveProxy::NaiveProxy(std::unique_ptr<ServerSocket> listen_socket, NaiveProxy::NaiveProxy(std::unique_ptr<ServerSocket> listen_socket,
NaiveConnection::Protocol protocol, ClientProtocol protocol,
bool use_padding,
int concurrency, int concurrency,
RedirectResolver* resolver, RedirectResolver* resolver,
HttpNetworkSession* session, HttpNetworkSession* session,
const NetworkTrafficAnnotationTag& traffic_annotation) const NetworkTrafficAnnotationTag& traffic_annotation)
: listen_socket_(std::move(listen_socket)), : listen_socket_(std::move(listen_socket)),
protocol_(protocol), protocol_(protocol),
use_padding_(use_padding),
concurrency_(std::min(4, std::max(1, concurrency))), concurrency_(std::min(4, std::max(1, concurrency))),
resolver_(resolver), resolver_(resolver),
session_(session), session_(session),
@ -99,30 +98,33 @@ void NaiveProxy::HandleAcceptResult(int result) {
void NaiveProxy::DoConnect() { void NaiveProxy::DoConnect() {
std::unique_ptr<StreamSocket> socket; std::unique_ptr<StreamSocket> socket;
NaiveConnection::Direction pad_direction; auto* proxy_delegate =
if (protocol_ == NaiveConnection::kSocks5) { static_cast<NaiveProxyDelegate*>(session_->context().proxy_delegate);
DCHECK(proxy_delegate);
DCHECK(!proxy_info_.is_empty());
const auto& proxy_server = proxy_info_.proxy_server();
auto padding_detector_delegate = std::make_unique<PaddingDetectorDelegate>(
proxy_delegate, proxy_server, protocol_);
if (protocol_ == ClientProtocol::kSocks5) {
socket = std::make_unique<Socks5ServerSocket>(std::move(accepted_socket_), socket = std::make_unique<Socks5ServerSocket>(std::move(accepted_socket_),
traffic_annotation_); traffic_annotation_);
pad_direction = NaiveConnection::kClient; } else if (protocol_ == ClientProtocol::kHttp) {
} else if (protocol_ == NaiveConnection::kHttp) {
socket = std::make_unique<HttpProxySocket>(std::move(accepted_socket_), socket = std::make_unique<HttpProxySocket>(std::move(accepted_socket_),
padding_detector_delegate.get(),
traffic_annotation_); traffic_annotation_);
pad_direction = NaiveConnection::kServer; } else if (protocol_ == ClientProtocol::kRedir) {
} else if (protocol_ == NaiveConnection::kRedir) {
socket = std::move(accepted_socket_); socket = std::move(accepted_socket_);
pad_direction = NaiveConnection::kClient;
} else { } else {
return; return;
} }
if (!use_padding_) {
pad_direction = NaiveConnection::kNone;
}
last_id_++; last_id_++;
const auto& nik = network_isolation_keys_[last_id_ % concurrency_]; const auto& nik = network_isolation_keys_[last_id_ % concurrency_];
auto connection_ptr = std::make_unique<NaiveConnection>( auto connection_ptr = std::make_unique<NaiveConnection>(
last_id_, protocol_, pad_direction, proxy_info_, server_ssl_config_, last_id_, protocol_, std::move(padding_detector_delegate), proxy_info_,
proxy_ssl_config_, resolver_, session_, nik, net_log_, std::move(socket), server_ssl_config_, proxy_ssl_config_, resolver_, session_, nik, net_log_,
traffic_annotation_); std::move(socket), traffic_annotation_);
auto* connection = connection_ptr.get(); auto* connection = connection_ptr.get();
connection_by_id_[connection->id()] = std::move(connection_ptr); connection_by_id_[connection->id()] = std::move(connection_ptr);
int result = connection->Connect( int result = connection->Connect(

View File

@ -18,6 +18,7 @@
#include "net/proxy_resolution/proxy_info.h" #include "net/proxy_resolution/proxy_info.h"
#include "net/ssl/ssl_config.h" #include "net/ssl/ssl_config.h"
#include "net/tools/naive/naive_connection.h" #include "net/tools/naive/naive_connection.h"
#include "net/tools/naive/naive_protocol.h"
namespace net { namespace net {
@ -32,8 +33,7 @@ class RedirectResolver;
class NaiveProxy { class NaiveProxy {
public: public:
NaiveProxy(std::unique_ptr<ServerSocket> server_socket, NaiveProxy(std::unique_ptr<ServerSocket> server_socket,
NaiveConnection::Protocol protocol, ClientProtocol protocol,
bool use_padding,
int concurrency, int concurrency,
RedirectResolver* resolver, RedirectResolver* resolver,
HttpNetworkSession* session, HttpNetworkSession* session,
@ -58,8 +58,7 @@ class NaiveProxy {
NaiveConnection* FindConnection(unsigned int connection_id); NaiveConnection* FindConnection(unsigned int connection_id);
std::unique_ptr<ServerSocket> listen_socket_; std::unique_ptr<ServerSocket> listen_socket_;
NaiveConnection::Protocol protocol_; ClientProtocol protocol_;
bool use_padding_;
int concurrency_; int concurrency_;
ProxyInfo proxy_info_; ProxyInfo proxy_info_;
SSLConfig server_ssl_config_; SSLConfig server_ssl_config_;

View File

@ -40,7 +40,6 @@
#include "net/http/http_auth_cache.h" #include "net/http/http_auth_cache.h"
#include "net/http/http_network_session.h" #include "net/http/http_network_session.h"
#include "net/http/http_request_headers.h" #include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_transaction_factory.h" #include "net/http/http_transaction_factory.h"
#include "net/log/file_net_log_observer.h" #include "net/log/file_net_log_observer.h"
#include "net/log/net_log.h" #include "net/log/net_log.h"
@ -58,8 +57,9 @@
#include "net/socket/udp_server_socket.h" #include "net/socket/udp_server_socket.h"
#include "net/ssl/ssl_key_logger_impl.h" #include "net/ssl/ssl_key_logger_impl.h"
#include "net/third_party/quiche/src/quic/core/quic_versions.h" #include "net/third_party/quiche/src/quic/core/quic_versions.h"
#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" #include "net/tools/naive/naive_protocol.h"
#include "net/tools/naive/naive_proxy.h" #include "net/tools/naive/naive_proxy.h"
#include "net/tools/naive/naive_proxy_delegate.h"
#include "net/tools/naive/redirect_resolver.h" #include "net/tools/naive/redirect_resolver.h"
#include "net/traffic_annotation/network_traffic_annotation.h" #include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context.h"
@ -84,7 +84,6 @@ constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
struct CommandLine { struct CommandLine {
std::string listen; std::string listen;
std::string proxy; std::string proxy;
bool padding;
std::string concurrency; std::string concurrency;
std::string extra_headers; std::string extra_headers;
std::string host_resolver_rules; std::string host_resolver_rules;
@ -96,10 +95,9 @@ struct CommandLine {
}; };
struct Params { struct Params {
net::NaiveConnection::Protocol protocol; net::ClientProtocol protocol;
std::string listen_addr; std::string listen_addr;
int listen_port; int listen_port;
bool use_padding;
int concurrency; int concurrency;
net::HttpRequestHeaders extra_headers; net::HttpRequestHeaders extra_headers;
std::string proxy_url; std::string proxy_url;
@ -139,7 +137,6 @@ void GetCommandLine(const base::CommandLine& proc, CommandLine* cmdline) {
" redir (Linux only)\n" " redir (Linux only)\n"
"--proxy=<proto>://[<user>:<pass>@]<hostname>[:<port>]\n" "--proxy=<proto>://[<user>:<pass>@]<hostname>[:<port>]\n"
" proto: https, quic\n" " proto: https, quic\n"
"--padding Use padding\n"
"--concurrency=<N> Use N connections, less secure\n" "--concurrency=<N> Use N connections, less secure\n"
"--extra-headers=... Extra headers split by CRLF\n" "--extra-headers=... Extra headers split by CRLF\n"
"--host-resolver-rules=... Resolver rules\n" "--host-resolver-rules=... Resolver rules\n"
@ -158,7 +155,6 @@ void GetCommandLine(const base::CommandLine& proc, CommandLine* cmdline) {
cmdline->listen = proc.GetSwitchValueASCII("listen"); cmdline->listen = proc.GetSwitchValueASCII("listen");
cmdline->proxy = proc.GetSwitchValueASCII("proxy"); cmdline->proxy = proc.GetSwitchValueASCII("proxy");
cmdline->padding = proc.HasSwitch("padding");
cmdline->concurrency = proc.GetSwitchValueASCII("concurrency"); cmdline->concurrency = proc.GetSwitchValueASCII("concurrency");
cmdline->extra_headers = proc.GetSwitchValueASCII("extra-headers"); cmdline->extra_headers = proc.GetSwitchValueASCII("extra-headers");
cmdline->host_resolver_rules = cmdline->host_resolver_rules =
@ -193,7 +189,6 @@ void GetCommandLineFromConfig(const base::FilePath& config_path,
if (proxy) { if (proxy) {
cmdline->proxy = *proxy; cmdline->proxy = *proxy;
} }
cmdline->padding = value->FindBoolKey("padding").value_or(false);
const auto* concurrency = value->FindStringKey("concurrency"); const auto* concurrency = value->FindStringKey("concurrency");
if (concurrency) { if (concurrency) {
cmdline->concurrency = *concurrency; cmdline->concurrency = *concurrency;
@ -236,7 +231,7 @@ std::string GetProxyFromURL(const GURL& url) {
} }
bool ParseCommandLine(const CommandLine& cmdline, Params* params) { bool ParseCommandLine(const CommandLine& cmdline, Params* params) {
params->protocol = net::NaiveConnection::kSocks5; params->protocol = net::ClientProtocol::kSocks5;
params->listen_addr = "0.0.0.0"; params->listen_addr = "0.0.0.0";
params->listen_port = 1080; params->listen_port = 1080;
url::AddStandardScheme("socks", url::SCHEME_WITH_HOST_AND_PORT); url::AddStandardScheme("socks", url::SCHEME_WITH_HOST_AND_PORT);
@ -244,14 +239,14 @@ bool ParseCommandLine(const CommandLine& cmdline, Params* params) {
if (!cmdline.listen.empty()) { if (!cmdline.listen.empty()) {
GURL url(cmdline.listen); GURL url(cmdline.listen);
if (url.scheme() == "socks") { if (url.scheme() == "socks") {
params->protocol = net::NaiveConnection::kSocks5; params->protocol = net::ClientProtocol::kSocks5;
params->listen_port = 1080; params->listen_port = 1080;
} else if (url.scheme() == "http") { } else if (url.scheme() == "http") {
params->protocol = net::NaiveConnection::kHttp; params->protocol = net::ClientProtocol::kHttp;
params->listen_port = 8080; params->listen_port = 8080;
} else if (url.scheme() == "redir") { } else if (url.scheme() == "redir") {
#if defined(OS_LINUX) #if defined(OS_LINUX)
params->protocol = net::NaiveConnection::kRedir; params->protocol = net::ClientProtocol::kRedir;
params->listen_port = 1080; params->listen_port = 1080;
#else #else
std::cerr << "Redir protocol only supports Linux." << std::endl; std::cerr << "Redir protocol only supports Linux." << std::endl;
@ -277,8 +272,6 @@ bool ParseCommandLine(const CommandLine& cmdline, Params* params) {
} }
} }
url::AddStandardScheme("quic",
url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION);
params->proxy_url = "direct://"; params->proxy_url = "direct://";
GURL url(cmdline.proxy); GURL url(cmdline.proxy);
GURL::Replacements remove_auth; GURL::Replacements remove_auth;
@ -294,8 +287,6 @@ bool ParseCommandLine(const CommandLine& cmdline, Params* params) {
net::GetIdentityFromURL(url, &params->proxy_user, &params->proxy_pass); net::GetIdentityFromURL(url, &params->proxy_user, &params->proxy_pass);
} }
params->use_padding = cmdline.padding;
if (!cmdline.concurrency.empty()) { if (!cmdline.concurrency.empty()) {
if (!base::StringToInt(cmdline.concurrency, &params->concurrency) || if (!base::StringToInt(cmdline.concurrency, &params->concurrency) ||
params->concurrency < 1 || params->concurrency > 4) { params->concurrency < 1 || params->concurrency > 4) {
@ -310,7 +301,7 @@ bool ParseCommandLine(const CommandLine& cmdline, Params* params) {
params->host_resolver_rules = cmdline.host_resolver_rules; params->host_resolver_rules = cmdline.host_resolver_rules;
if (params->protocol == net::NaiveConnection::kRedir) { if (params->protocol == net::ClientProtocol::kRedir) {
std::string range = "100.64.0.0/10"; std::string range = "100.64.0.0/10";
if (!cmdline.resolver_range.empty()) if (!cmdline.resolver_range.empty())
range = cmdline.resolver_range; range = cmdline.resolver_range;
@ -387,54 +378,7 @@ class PrintingLogObserver : public NetLog::ThreadSafeObserver {
}; };
} // namespace } // namespace
class ProxyInfo;
class ProxyServer;
namespace { namespace {
class NaiveProxyDelegate : public ProxyDelegate {
public:
NaiveProxyDelegate(const Params& params) : params_(params) {
unsigned i = 0;
for (const auto& symbol : spdy::HpackHuffmanCodeVector()) {
if (symbol.id >= 0x20 && symbol.id <= 0x7f && symbol.length >= 8) {
nonindex_codes_[i++] = symbol.id;
if (i >= sizeof(nonindex_codes_))
break;
}
}
CHECK(i == sizeof(nonindex_codes_));
}
void OnResolveProxy(const GURL& url,
const std::string& method,
const ProxyRetryInfoMap& proxy_retry_info,
ProxyInfo* result) override {}
void OnFallback(const ProxyServer& bad_proxy, int net_error) override {}
void OnBeforeTunnelRequest(const ProxyServer& proxy_server,
HttpRequestHeaders* extra_headers) override {
std::string padding(base::RandInt(16, 32), nonindex_codes_[16]);
// Prevents index reuse
uint64_t bits = base::RandUint64();
for (int i = 0; i < 16; i++) {
padding[i] = nonindex_codes_[bits & 0b1111];
bits >>= 4;
}
extra_headers->SetHeader("Padding", padding);
extra_headers->MergeFrom(params_.extra_headers);
}
Error OnTunnelHeadersReceived(
const ProxyServer& proxy_server,
const HttpResponseHeaders& response_headers) override {
return OK;
}
private:
const Params& params_;
uint8_t nonindex_codes_[17];
};
std::unique_ptr<URLRequestContext> BuildCertURLRequestContext(NetLog* net_log) { std::unique_ptr<URLRequestContext> BuildCertURLRequestContext(NetLog* net_log) {
URLRequestContextBuilder builder; URLRequestContextBuilder builder;
@ -481,7 +425,8 @@ std::unique_ptr<URLRequestContext> BuildURLRequestContext(
builder.SetCertVerifier( builder.SetCertVerifier(
CertVerifier::CreateDefault(std::move(cert_net_fetcher))); CertVerifier::CreateDefault(std::move(cert_net_fetcher)));
builder.set_proxy_delegate(std::make_unique<NaiveProxyDelegate>(params)); builder.set_proxy_delegate(
std::make_unique<NaiveProxyDelegate>(params.extra_headers));
auto context = builder.Build(); auto context = builder.Build();
@ -511,6 +456,8 @@ std::unique_ptr<URLRequestContext> BuildURLRequestContext(
} // namespace net } // namespace net
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
url::AddStandardScheme("quic",
url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION);
base::FeatureList::InitializeInstance( base::FeatureList::InitializeInstance(
"PartitionConnectionsByNetworkIsolationKey", std::string()); "PartitionConnectionsByNetworkIsolationKey", std::string());
base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO); base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
@ -605,7 +552,7 @@ int main(int argc, char* argv[]) {
<< params.listen_port; << params.listen_port;
std::unique_ptr<net::RedirectResolver> resolver; std::unique_ptr<net::RedirectResolver> resolver;
if (params.protocol == net::NaiveConnection::kRedir) { if (params.protocol == net::ClientProtocol::kRedir) {
auto resolver_socket = auto resolver_socket =
std::make_unique<net::UDPServerSocket>(net_log, net::NetLogSource()); std::make_unique<net::UDPServerSocket>(net_log, net::NetLogSource());
resolver_socket->AllowAddressReuse(); resolver_socket->AllowAddressReuse();
@ -628,8 +575,8 @@ int main(int argc, char* argv[]) {
} }
net::NaiveProxy naive_proxy(std::move(listen_socket), params.protocol, net::NaiveProxy naive_proxy(std::move(listen_socket), params.protocol,
params.use_padding, params.concurrency, params.concurrency, resolver.get(), session,
resolver.get(), session, kTrafficAnnotation); kTrafficAnnotation);
base::RunLoop().Run(); base::RunLoop().Run();

View File

@ -0,0 +1,158 @@
// Copyright 2020 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_proxy_delegate.h"
#include <string>
#include "base/logging.h"
#include "base/rand_util.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
namespace net {
namespace {
bool g_nonindex_codes_initialized;
uint8_t g_nonindex_codes[17];
} // namespace
void InitializeNonindexCodes() {
if (g_nonindex_codes_initialized)
return;
g_nonindex_codes_initialized = true;
unsigned i = 0;
for (const auto& symbol : spdy::HpackHuffmanCodeVector()) {
if (symbol.id >= 0x20 && symbol.id <= 0x7f && symbol.length >= 8) {
g_nonindex_codes[i++] = symbol.id;
if (i >= sizeof(g_nonindex_codes))
break;
}
}
CHECK(i == sizeof(g_nonindex_codes));
}
void FillNonindexHeaderValue(uint64_t unique_bits, char* buf, int len) {
DCHECK(g_nonindex_codes_initialized);
int first = len < 16 ? len : 16;
for (int i = 0; i < first; i++) {
buf[i] = g_nonindex_codes[unique_bits & 0b1111];
unique_bits >>= 4;
}
for (int i = first; i < len; i++) {
buf[i] = g_nonindex_codes[16];
}
}
NaiveProxyDelegate::NaiveProxyDelegate(const HttpRequestHeaders& extra_headers)
: extra_headers_(extra_headers) {
InitializeNonindexCodes();
}
NaiveProxyDelegate::~NaiveProxyDelegate() = default;
void NaiveProxyDelegate::OnBeforeTunnelRequest(
const ProxyServer& proxy_server,
HttpRequestHeaders* extra_headers) {
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);
// 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) {
extra_headers->SetHeader("fastopen", "1");
}
extra_headers->MergeFrom(extra_headers_);
}
Error NaiveProxyDelegate::OnTunnelHeadersReceived(
const ProxyServer& proxy_server,
const HttpResponseHeaders& response_headers) {
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 " << proxy_server.ToURI()
<< (padding ? " detected" : " undetected");
}
padding_state = new_state;
return OK;
}
PaddingSupport NaiveProxyDelegate::GetProxyServerPaddingSupport(
const ProxyServer& proxy_server) {
// Not possible to detect padding capability given underlying protocol.
if (proxy_server.is_direct() || proxy_server.is_socks())
return PaddingSupport::kIncapable;
return padding_state_by_server_[proxy_server];
}
PaddingDetectorDelegate::PaddingDetectorDelegate(
NaiveProxyDelegate* naive_proxy_delegate,
const ProxyServer& proxy_server,
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) {}
PaddingDetectorDelegate::~PaddingDetectorDelegate() = default;
bool PaddingDetectorDelegate::IsPaddingSupportKnown() {
auto c = GetClientPaddingSupport();
auto s = GetServerPaddingSupport();
return c != PaddingSupport::kUnknown && s != PaddingSupport::kUnknown;
}
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.
if (client_protocol_ == ClientProtocol::kSocks5) {
return PaddingSupport::kIncapable;
} else if (client_protocol_ == ClientProtocol::kRedir) {
return PaddingSupport::kIncapable;
}
return detected_client_padding_support_;
}
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_;
}
} // namespace net

View File

@ -0,0 +1,94 @@
// Copyright 2020 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_DELEGATE_H_
#define NET_TOOLS_NAIVE_NAIVE_PROXY_DELEGATE_H_
#include <cstdint>
#include <map>
#include <string>
#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/proxy_resolution/proxy_retry_info.h"
#include "net/tools/naive/naive_protocol.h"
#include "url/gurl.h"
namespace net {
void InitializeNonindexCodes();
// |unique_bits| SHOULD have relatively unique values.
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() override;
void OnResolveProxy(const GURL& url,
const std::string& method,
const ProxyRetryInfoMap& proxy_retry_info,
ProxyInfo* result) override {}
void OnFallback(const ProxyServer& bad_proxy, int net_error) override {}
// This only affects h2 proxy client socket.
void OnBeforeTunnelRequest(const ProxyServer& proxy_server,
HttpRequestHeaders* extra_headers) override;
Error OnTunnelHeadersReceived(
const ProxyServer& proxy_server,
const HttpResponseHeaders& response_headers) override;
PaddingSupport GetProxyServerPaddingSupport(const ProxyServer& proxy_server);
private:
const HttpRequestHeaders& extra_headers_;
std::map<ProxyServer, PaddingSupport> padding_state_by_server_;
};
class ClientPaddingDetectorDelegate {
public:
virtual ~ClientPaddingDetectorDelegate() = default;
virtual void SetClientPaddingSupport(PaddingSupport padding_support) = 0;
};
class PaddingDetectorDelegate : public ClientPaddingDetectorDelegate {
public:
PaddingDetectorDelegate(NaiveProxyDelegate* naive_proxy_delegate,
const ProxyServer& proxy_server,
ClientProtocol client_protocol);
~PaddingDetectorDelegate() override;
bool IsPaddingSupportKnown();
Direction GetPaddingDirection();
void SetClientPaddingSupport(PaddingSupport padding_support) override;
private:
PaddingSupport GetClientPaddingSupport();
PaddingSupport GetServerPaddingSupport();
NaiveProxyDelegate* naive_proxy_delegate_;
const ProxyServer& proxy_server_;
ClientProtocol client_protocol_;
PaddingSupport detected_client_padding_support_;
// 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_;
};
} // namespace net
#endif // NET_TOOLS_NAIVE_NAIVE_PROXY_DELEGATE_H_