mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-21 12:56:13 +03:00
Add Naive
This commit is contained in:
parent
1aada03f08
commit
5a07a5ee07
@ -1850,3 +1850,43 @@ static_library("preload_decoder") {
|
||||
]
|
||||
deps = [ "//base" ]
|
||||
}
|
||||
|
||||
executable("naive") {
|
||||
sources = [
|
||||
"tools/naive/http_proxy_server_socket.cc",
|
||||
"tools/naive/http_proxy_server_socket.h",
|
||||
"tools/naive/naive_command_line.cc",
|
||||
"tools/naive/naive_command_line.h",
|
||||
"tools/naive/naive_config.cc",
|
||||
"tools/naive/naive_config.h",
|
||||
"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_bin.cc",
|
||||
"tools/naive/naive_proxy_delegate.cc",
|
||||
"tools/naive/naive_proxy_delegate.h",
|
||||
"tools/naive/naive_proxy.cc",
|
||||
"tools/naive/naive_proxy.h",
|
||||
"tools/naive/redirect_resolver.cc",
|
||||
"tools/naive/redirect_resolver.h",
|
||||
"tools/naive/socks5_server_socket.cc",
|
||||
"tools/naive/socks5_server_socket.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":net",
|
||||
"//base",
|
||||
"//build/win:default_exe_manifest",
|
||||
"//components/version_info:version_info",
|
||||
"//url",
|
||||
]
|
||||
|
||||
if (is_apple) {
|
||||
deps += [ "//base/allocator:early_zone_registration_apple" ]
|
||||
}
|
||||
}
|
||||
|
@ -558,6 +558,11 @@ EVENT_TYPE(SOCKS_HOSTNAME_TOO_BIG)
|
||||
EVENT_TYPE(SOCKS_UNEXPECTEDLY_CLOSED_DURING_GREETING)
|
||||
EVENT_TYPE(SOCKS_UNEXPECTEDLY_CLOSED_DURING_HANDSHAKE)
|
||||
|
||||
EVENT_TYPE(SOCKS_NO_REQUESTED_AUTH)
|
||||
EVENT_TYPE(SOCKS_NO_ACCEPTABLE_AUTH)
|
||||
EVENT_TYPE(SOCKS_ZERO_LENGTH_DOMAIN)
|
||||
EVENT_TYPE(SOCKS_UNEXPECTED_COMMAND)
|
||||
|
||||
// This event indicates that a bad version number was received in the
|
||||
// proxy server's response. The extra parameters show its value:
|
||||
// {
|
||||
|
481
src/net/tools/naive/http_proxy_server_socket.cc
Normal file
481
src/net/tools/naive/http_proxy_server_socket.cc
Normal file
@ -0,0 +1,481 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// 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.
|
||||
#ifdef UNSAFE_BUFFERS_BUILD
|
||||
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
|
||||
#pragma allow_unsafe_buffers
|
||||
#endif
|
||||
|
||||
#include "net/tools/naive/http_proxy_server_socket.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/base64.h"
|
||||
#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"
|
||||
#include "net/base/url_util.h"
|
||||
#include "net/http/http_request_headers.h"
|
||||
#include "net/log/net_log.h"
|
||||
#include "net/third_party/quiche/src/quiche/http2/hpack/hpack_constants.h"
|
||||
#include "net/tools/naive/naive_protocol.h"
|
||||
#include "net/tools/naive/naive_proxy_delegate.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
namespace net {
|
||||
|
||||
namespace {
|
||||
constexpr int kBufferSize = 64 * 1024;
|
||||
constexpr size_t kMaxHeaderSize = 64 * 1024;
|
||||
constexpr char kResponseHeader[] = "HTTP/1.1 200 OK\r\nPadding: ";
|
||||
constexpr int kResponseHeaderSize = sizeof(kResponseHeader) - 1;
|
||||
// A plain 200 is 10 bytes. Expected 48 bytes. "Padding" uses up 7 bytes.
|
||||
constexpr int kMinPaddingSize = 30;
|
||||
constexpr int kMaxPaddingSize = kMinPaddingSize + 32;
|
||||
} // namespace
|
||||
|
||||
HttpProxyServerSocket::HttpProxyServerSocket(
|
||||
std::unique_ptr<StreamSocket> transport_socket,
|
||||
const std::string& user,
|
||||
const std::string& pass,
|
||||
ClientPaddingDetectorDelegate* padding_detector_delegate,
|
||||
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),
|
||||
next_state_(STATE_NONE),
|
||||
completed_handshake_(false),
|
||||
was_ever_used_(false),
|
||||
header_write_size_(-1),
|
||||
net_log_(transport_->NetLog()),
|
||||
traffic_annotation_(traffic_annotation),
|
||||
supported_padding_types_(supported_padding_types) {
|
||||
if (!user.empty() || !pass.empty()) {
|
||||
basic_auth_ =
|
||||
std::string("Basic ").append(base::Base64Encode(user + ":" + pass));
|
||||
}
|
||||
}
|
||||
|
||||
HttpProxyServerSocket::~HttpProxyServerSocket() {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
const HostPortPair& HttpProxyServerSocket::request_endpoint() const {
|
||||
return request_endpoint_;
|
||||
}
|
||||
|
||||
int HttpProxyServerSocket::Connect(CompletionOnceCallback callback) {
|
||||
DCHECK(transport_);
|
||||
DCHECK_EQ(STATE_NONE, next_state_);
|
||||
DCHECK(!user_callback_);
|
||||
|
||||
// If already connected, then just return OK.
|
||||
if (completed_handshake_)
|
||||
return OK;
|
||||
|
||||
next_state_ = STATE_HEADER_READ;
|
||||
buffer_.clear();
|
||||
|
||||
int rv = DoLoop(OK);
|
||||
if (rv == ERR_IO_PENDING) {
|
||||
user_callback_ = std::move(callback);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void HttpProxyServerSocket::Disconnect() {
|
||||
completed_handshake_ = false;
|
||||
transport_->Disconnect();
|
||||
|
||||
// Reset other states to make sure they aren't mistakenly used later.
|
||||
// These are the states initialized by Connect().
|
||||
next_state_ = STATE_NONE;
|
||||
user_callback_.Reset();
|
||||
}
|
||||
|
||||
bool HttpProxyServerSocket::IsConnected() const {
|
||||
return completed_handshake_ && transport_->IsConnected();
|
||||
}
|
||||
|
||||
bool HttpProxyServerSocket::IsConnectedAndIdle() const {
|
||||
return completed_handshake_ && transport_->IsConnectedAndIdle();
|
||||
}
|
||||
|
||||
const NetLogWithSource& HttpProxyServerSocket::NetLog() const {
|
||||
return net_log_;
|
||||
}
|
||||
|
||||
bool HttpProxyServerSocket::WasEverUsed() const {
|
||||
return was_ever_used_;
|
||||
}
|
||||
|
||||
NextProto HttpProxyServerSocket::GetNegotiatedProtocol() const {
|
||||
if (transport_) {
|
||||
return transport_->GetNegotiatedProtocol();
|
||||
}
|
||||
NOTREACHED();
|
||||
return kProtoUnknown;
|
||||
}
|
||||
|
||||
bool HttpProxyServerSocket::GetSSLInfo(SSLInfo* ssl_info) {
|
||||
if (transport_) {
|
||||
return transport_->GetSSLInfo(ssl_info);
|
||||
}
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t HttpProxyServerSocket::GetTotalReceivedBytes() const {
|
||||
return transport_->GetTotalReceivedBytes();
|
||||
}
|
||||
|
||||
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 HttpProxyServerSocket::Read(IOBuffer* buf,
|
||||
int buf_len,
|
||||
CompletionOnceCallback callback) {
|
||||
DCHECK(completed_handshake_);
|
||||
DCHECK_EQ(STATE_NONE, next_state_);
|
||||
DCHECK(!user_callback_);
|
||||
DCHECK(callback);
|
||||
|
||||
if (!buffer_.empty()) {
|
||||
was_ever_used_ = true;
|
||||
int data_len = buffer_.size();
|
||||
if (data_len <= buf_len) {
|
||||
std::memcpy(buf->data(), buffer_.data(), data_len);
|
||||
buffer_.clear();
|
||||
return data_len;
|
||||
} else {
|
||||
std::memcpy(buf->data(), buffer_.data(), buf_len);
|
||||
buffer_ = buffer_.substr(buf_len);
|
||||
return buf_len;
|
||||
}
|
||||
}
|
||||
|
||||
int rv = transport_->Read(
|
||||
buf, buf_len,
|
||||
base::BindOnce(&HttpProxyServerSocket::OnReadWriteComplete,
|
||||
base::Unretained(this), std::move(callback)));
|
||||
if (rv > 0)
|
||||
was_ever_used_ = true;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Write is called by the transport layer. This can only be done if the
|
||||
// HTTP CONNECT request is complete.
|
||||
int HttpProxyServerSocket::Write(
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
CompletionOnceCallback callback,
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation) {
|
||||
DCHECK(completed_handshake_);
|
||||
DCHECK_EQ(STATE_NONE, next_state_);
|
||||
DCHECK(!user_callback_);
|
||||
DCHECK(callback);
|
||||
|
||||
int rv = transport_->Write(
|
||||
buf, buf_len,
|
||||
base::BindOnce(&HttpProxyServerSocket::OnReadWriteComplete,
|
||||
base::Unretained(this), std::move(callback)),
|
||||
traffic_annotation);
|
||||
if (rv > 0)
|
||||
was_ever_used_ = true;
|
||||
return rv;
|
||||
}
|
||||
|
||||
int HttpProxyServerSocket::SetReceiveBufferSize(int32_t size) {
|
||||
return transport_->SetReceiveBufferSize(size);
|
||||
}
|
||||
|
||||
int HttpProxyServerSocket::SetSendBufferSize(int32_t size) {
|
||||
return transport_->SetSendBufferSize(size);
|
||||
}
|
||||
|
||||
void HttpProxyServerSocket::DoCallback(int result) {
|
||||
DCHECK_NE(ERR_IO_PENDING, result);
|
||||
DCHECK(user_callback_);
|
||||
|
||||
// Since Run() may result in Read being called,
|
||||
// clear user_callback_ up front.
|
||||
std::move(user_callback_).Run(result);
|
||||
}
|
||||
|
||||
void HttpProxyServerSocket::OnIOComplete(int result) {
|
||||
DCHECK_NE(STATE_NONE, next_state_);
|
||||
int rv = DoLoop(result);
|
||||
if (rv != ERR_IO_PENDING) {
|
||||
DoCallback(rv);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpProxyServerSocket::OnReadWriteComplete(CompletionOnceCallback callback,
|
||||
int result) {
|
||||
DCHECK_NE(ERR_IO_PENDING, result);
|
||||
DCHECK(callback);
|
||||
|
||||
if (result > 0)
|
||||
was_ever_used_ = true;
|
||||
std::move(callback).Run(result);
|
||||
}
|
||||
|
||||
int HttpProxyServerSocket::DoLoop(int last_io_result) {
|
||||
DCHECK_NE(next_state_, STATE_NONE);
|
||||
int rv = last_io_result;
|
||||
do {
|
||||
State state = next_state_;
|
||||
next_state_ = STATE_NONE;
|
||||
switch (state) {
|
||||
case STATE_HEADER_READ:
|
||||
DCHECK_EQ(OK, rv);
|
||||
rv = DoHeaderRead();
|
||||
break;
|
||||
case STATE_HEADER_READ_COMPLETE:
|
||||
rv = DoHeaderReadComplete(rv);
|
||||
break;
|
||||
case STATE_HEADER_WRITE:
|
||||
DCHECK_EQ(OK, rv);
|
||||
rv = DoHeaderWrite();
|
||||
break;
|
||||
case STATE_HEADER_WRITE_COMPLETE:
|
||||
rv = DoHeaderWriteComplete(rv);
|
||||
break;
|
||||
default:
|
||||
NOTREACHED() << "bad state";
|
||||
rv = ERR_UNEXPECTED;
|
||||
break;
|
||||
}
|
||||
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int HttpProxyServerSocket::DoHeaderRead() {
|
||||
next_state_ = STATE_HEADER_READ_COMPLETE;
|
||||
|
||||
handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(kBufferSize);
|
||||
return transport_->Read(handshake_buf_.get(), kBufferSize, io_callback_);
|
||||
}
|
||||
|
||||
std::optional<PaddingType> HttpProxyServerSocket::ParsePaddingHeaders(
|
||||
const HttpRequestHeaders& headers) {
|
||||
bool has_padding = headers.HasHeader(kPaddingHeader);
|
||||
std::optional<std::string> padding_type_request =
|
||||
headers.GetHeader(kPaddingTypeRequestHeader);
|
||||
|
||||
if (!padding_type_request.has_value()) {
|
||||
// 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<std::string_view> padding_type_strs = base::SplitStringPiece(
|
||||
*padding_type_request, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||||
for (std::string_view 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;
|
||||
|
||||
if (result == 0) {
|
||||
return ERR_CONNECTION_CLOSED;
|
||||
}
|
||||
|
||||
buffer_.append(handshake_buf_->data(), result);
|
||||
if (buffer_.size() > kMaxHeaderSize) {
|
||||
return ERR_MSG_TOO_BIG;
|
||||
}
|
||||
|
||||
size_t header_end = buffer_.find("\r\n\r\n");
|
||||
if (header_end == std::string::npos) {
|
||||
next_state_ = STATE_HEADER_READ;
|
||||
return OK;
|
||||
}
|
||||
|
||||
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) {
|
||||
LOG(WARNING) << "Invalid request: " << buffer_.substr(0, first_line_end);
|
||||
return ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
std::string method = buffer_.substr(0, first_space);
|
||||
std::string uri =
|
||||
buffer_.substr(first_space + 1, second_space - (first_space + 1));
|
||||
std::string version =
|
||||
buffer_.substr(second_space + 1, first_line_end - (second_space + 1));
|
||||
if (method == HttpRequestHeaders::kConnectMethod) {
|
||||
request_endpoint_ = HostPortPair::FromString(uri);
|
||||
} else {
|
||||
// postprobe endpoint handling
|
||||
is_http_1_0 = true;
|
||||
}
|
||||
|
||||
size_t 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 (!basic_auth_.empty()) {
|
||||
std::optional<std::string> proxy_auth;
|
||||
proxy_auth = headers.GetHeader(HttpRequestHeaders::kProxyAuthorization);
|
||||
if (proxy_auth != basic_auth_) {
|
||||
LOG(WARNING) << "Invalid Proxy-Authorization: " << proxy_auth.value_or("");
|
||||
return ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_http_1_0) {
|
||||
GURL url(uri);
|
||||
if (!url.is_valid()) {
|
||||
LOG(WARNING) << "Invalid URI: " << uri;
|
||||
return ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
std::string host;
|
||||
int port;
|
||||
|
||||
std::optional<std::string> host_str = headers.GetHeader(HttpRequestHeaders::kHost);
|
||||
if (host_str.has_value()) {
|
||||
if (!ParseHostAndPort(*host_str, &host, &port)) {
|
||||
LOG(WARNING) << "Invalid Host: " << *host_str;
|
||||
return ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (port == -1) {
|
||||
port = 80;
|
||||
}
|
||||
} else {
|
||||
if (!url.has_host()) {
|
||||
LOG(WARNING) << "Missing host: " << uri;
|
||||
return ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
host = url.host();
|
||||
port = url.EffectiveIntPort();
|
||||
|
||||
*host_str = url.host();
|
||||
if (url.has_port()) {
|
||||
host_str->append(":").append(url.port());
|
||||
}
|
||||
headers.SetHeader(HttpRequestHeaders::kHost, *host_str);
|
||||
}
|
||||
// Host is already known. Converts any absolute URI to relative.
|
||||
uri = url.path();
|
||||
if (url.has_query()) {
|
||||
uri.append("?").append(url.query());
|
||||
}
|
||||
|
||||
request_endpoint_.set_host(host);
|
||||
request_endpoint_.set_port(port);
|
||||
}
|
||||
|
||||
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) {
|
||||
// Regenerates http header to make sure don't leak them to end servers
|
||||
HttpRequestHeaders sanitized_headers = headers;
|
||||
sanitized_headers.RemoveHeader(HttpRequestHeaders::kProxyConnection);
|
||||
sanitized_headers.RemoveHeader(HttpRequestHeaders::kProxyAuthorization);
|
||||
std::ostringstream ss;
|
||||
ss << method << " " << uri << " " << version << "\r\n"
|
||||
<< sanitized_headers.ToString();
|
||||
if (buffer_.size() > header_end + 4) {
|
||||
ss << buffer_.substr(header_end + 4);
|
||||
}
|
||||
buffer_ = ss.str();
|
||||
// Skips padding write for raw http proxy
|
||||
completed_handshake_ = true;
|
||||
next_state_ = STATE_NONE;
|
||||
return OK;
|
||||
}
|
||||
|
||||
buffer_ = buffer_.substr(header_end + 4);
|
||||
|
||||
next_state_ = STATE_HEADER_WRITE;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int HttpProxyServerSocket::DoHeaderWrite() {
|
||||
next_state_ = STATE_HEADER_WRITE_COMPLETE;
|
||||
|
||||
// Adds padding.
|
||||
int padding_size = base::RandInt(kMinPaddingSize, kMaxPaddingSize);
|
||||
header_write_size_ = kResponseHeaderSize + padding_size + 4;
|
||||
handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(header_write_size_);
|
||||
char* p = handshake_buf_->data();
|
||||
std::memcpy(p, kResponseHeader, kResponseHeaderSize);
|
||||
FillNonindexHeaderValue(base::RandUint64(), p + kResponseHeaderSize,
|
||||
padding_size);
|
||||
std::memcpy(p + kResponseHeaderSize + padding_size, "\r\n\r\n", 4);
|
||||
|
||||
return transport_->Write(handshake_buf_.get(), header_write_size_,
|
||||
io_callback_, traffic_annotation_);
|
||||
}
|
||||
|
||||
int HttpProxyServerSocket::DoHeaderWriteComplete(int result) {
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
if (result != header_write_size_) {
|
||||
return ERR_FAILED;
|
||||
}
|
||||
|
||||
completed_handshake_ = true;
|
||||
next_state_ = STATE_NONE;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int HttpProxyServerSocket::GetPeerAddress(IPEndPoint* address) const {
|
||||
return transport_->GetPeerAddress(address);
|
||||
}
|
||||
|
||||
int HttpProxyServerSocket::GetLocalAddress(IPEndPoint* address) const {
|
||||
return transport_->GetLocalAddress(address);
|
||||
}
|
||||
|
||||
} // namespace net
|
135
src/net/tools/naive/http_proxy_server_socket.h
Normal file
135
src/net/tools/naive/http_proxy_server_socket.h
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// 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_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"
|
||||
#include "net/base/completion_repeating_callback.h"
|
||||
#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 HttpProxyServerSocket : public StreamSocket {
|
||||
public:
|
||||
HttpProxyServerSocket(
|
||||
std::unique_ptr<StreamSocket> transport_socket,
|
||||
const std::string& user,
|
||||
const std::string& pass,
|
||||
ClientPaddingDetectorDelegate* padding_detector_delegate,
|
||||
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.
|
||||
~HttpProxyServerSocket() override;
|
||||
|
||||
const HostPortPair& request_endpoint() const;
|
||||
|
||||
// StreamSocket implementation.
|
||||
|
||||
int Connect(CompletionOnceCallback callback) override;
|
||||
void Disconnect() override;
|
||||
bool IsConnected() const override;
|
||||
bool IsConnectedAndIdle() const override;
|
||||
const NetLogWithSource& NetLog() const override;
|
||||
bool WasEverUsed() const override;
|
||||
NextProto GetNegotiatedProtocol() const override;
|
||||
bool GetSSLInfo(SSLInfo* ssl_info) override;
|
||||
int64_t GetTotalReceivedBytes() const override;
|
||||
void ApplySocketTag(const SocketTag& tag) override;
|
||||
|
||||
// Socket implementation.
|
||||
int Read(IOBuffer* buf,
|
||||
int buf_len,
|
||||
CompletionOnceCallback callback) override;
|
||||
int Write(IOBuffer* buf,
|
||||
int buf_len,
|
||||
CompletionOnceCallback callback,
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation) override;
|
||||
|
||||
int SetReceiveBufferSize(int32_t size) override;
|
||||
int SetSendBufferSize(int32_t size) override;
|
||||
|
||||
int GetPeerAddress(IPEndPoint* address) const override;
|
||||
int GetLocalAddress(IPEndPoint* address) const override;
|
||||
|
||||
private:
|
||||
enum State {
|
||||
STATE_HEADER_READ,
|
||||
STATE_HEADER_READ_COMPLETE,
|
||||
STATE_HEADER_WRITE,
|
||||
STATE_HEADER_WRITE_COMPLETE,
|
||||
STATE_NONE,
|
||||
};
|
||||
|
||||
void DoCallback(int result);
|
||||
void OnIOComplete(int result);
|
||||
void OnReadWriteComplete(CompletionOnceCallback callback, int result);
|
||||
|
||||
int DoLoop(int last_io_result);
|
||||
int DoHeaderWrite();
|
||||
int DoHeaderWriteComplete(int result);
|
||||
int DoHeaderRead();
|
||||
int DoHeaderReadComplete(int result);
|
||||
|
||||
std::optional<PaddingType> ParsePaddingHeaders(
|
||||
const HttpRequestHeaders& headers);
|
||||
|
||||
CompletionRepeatingCallback io_callback_;
|
||||
|
||||
// Stores the underlying socket.
|
||||
std::unique_ptr<StreamSocket> transport_;
|
||||
ClientPaddingDetectorDelegate* padding_detector_delegate_;
|
||||
|
||||
State next_state_;
|
||||
|
||||
// Stores the callback to the layer above, called on completing Connect().
|
||||
CompletionOnceCallback user_callback_;
|
||||
|
||||
// This IOBuffer is used by the class to read and write
|
||||
// SOCKS handshake data. The length contains the expected size to
|
||||
// read or write.
|
||||
scoped_refptr<IOBuffer> handshake_buf_;
|
||||
|
||||
std::string buffer_;
|
||||
bool completed_handshake_;
|
||||
bool was_ever_used_;
|
||||
int header_write_size_;
|
||||
|
||||
std::string basic_auth_;
|
||||
|
||||
HostPortPair request_endpoint_;
|
||||
|
||||
NetLogWithSource net_log_;
|
||||
|
||||
// Traffic annotation for socket control.
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation_;
|
||||
|
||||
std::vector<PaddingType> supported_padding_types_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
#endif // NET_TOOLS_NAIVE_HTTP_PROXY_SERVER_SOCKET_H_
|
74
src/net/tools/naive/naive_command_line.cc
Normal file
74
src/net/tools/naive/naive_command_line.cc
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2024 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_command_line.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#include "base/strings/string_util_win.h"
|
||||
#endif
|
||||
|
||||
DuplicateSwitchCollector::DuplicateSwitchCollector() = default;
|
||||
DuplicateSwitchCollector::~DuplicateSwitchCollector() = default;
|
||||
|
||||
void DuplicateSwitchCollector::ResolveDuplicate(
|
||||
std::string_view key,
|
||||
base::CommandLine::StringViewType new_value,
|
||||
base::CommandLine::StringType& out_value) {
|
||||
out_value = new_value;
|
||||
values_by_key_[std::string(key)].push_back(
|
||||
base::CommandLine::StringType(new_value));
|
||||
}
|
||||
|
||||
const std::vector<base::CommandLine::StringType>&
|
||||
DuplicateSwitchCollector::GetValuesByKey(std::string_view key) {
|
||||
return values_by_key_[std::string(key)];
|
||||
}
|
||||
|
||||
namespace {
|
||||
DuplicateSwitchCollector* g_duplicate_switch_collector;
|
||||
}
|
||||
|
||||
void DuplicateSwitchCollector::InitInstance() {
|
||||
auto new_duplicate_switch_collector =
|
||||
std::make_unique<DuplicateSwitchCollector>();
|
||||
g_duplicate_switch_collector = new_duplicate_switch_collector.get();
|
||||
base::CommandLine::SetDuplicateSwitchHandler(
|
||||
std::move(new_duplicate_switch_collector));
|
||||
}
|
||||
|
||||
DuplicateSwitchCollector& DuplicateSwitchCollector::GetInstance() {
|
||||
CHECK(g_duplicate_switch_collector != nullptr);
|
||||
return *g_duplicate_switch_collector;
|
||||
}
|
||||
|
||||
base::Value::Dict GetSwitchesAsValue(const base::CommandLine& cmdline) {
|
||||
base::Value::Dict dict;
|
||||
for (const auto& [key, value] : cmdline.GetSwitches()) {
|
||||
const std::vector<base::CommandLine::StringType>& values =
|
||||
DuplicateSwitchCollector::GetInstance().GetValuesByKey(key);
|
||||
if (values.size() > 1) {
|
||||
base::Value::List list;
|
||||
for (const base::CommandLine::StringType& v : values) {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
list.Append(base::AsStringPiece16(v));
|
||||
#else
|
||||
list.Append(v);
|
||||
#endif
|
||||
}
|
||||
dict.Set(key, std::move(list));
|
||||
} else {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
dict.Set(key, base::AsStringPiece16(value));
|
||||
#else
|
||||
dict.Set(key, value);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
}
|
36
src/net/tools/naive/naive_command_line.h
Normal file
36
src/net/tools/naive/naive_command_line.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2024 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_COMMAND_LINE_H_
|
||||
#define NET_TOOLS_NAIVE_NAIVE_COMMAND_LINE_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/values.h"
|
||||
|
||||
class DuplicateSwitchCollector : public base::DuplicateSwitchHandler {
|
||||
public:
|
||||
DuplicateSwitchCollector();
|
||||
~DuplicateSwitchCollector() override;
|
||||
|
||||
void ResolveDuplicate(std::string_view key,
|
||||
base::CommandLine::StringViewType new_value,
|
||||
base::CommandLine::StringType& out_value) override;
|
||||
|
||||
const std::vector<base::CommandLine::StringType>& GetValuesByKey(
|
||||
std::string_view key);
|
||||
|
||||
static void InitInstance();
|
||||
static DuplicateSwitchCollector& GetInstance();
|
||||
|
||||
private:
|
||||
std::map<std::string, std::vector<base::CommandLine::StringType>>
|
||||
values_by_key_;
|
||||
};
|
||||
|
||||
base::Value::Dict GetSwitchesAsValue(const base::CommandLine& cmdline);
|
||||
#endif // NET_TOOLS_NAIVE_NAIVE_COMMAND_LINE_H_
|
272
src/net/tools/naive/naive_config.cc
Normal file
272
src/net/tools/naive/naive_config.cc
Normal file
@ -0,0 +1,272 @@
|
||||
// Copyright 2024 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_config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "base/strings/escape.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_tokenizer.h"
|
||||
#include "net/base/proxy_server.h"
|
||||
#include "net/base/proxy_string_util.h"
|
||||
#include "net/base/url_util.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
namespace net {
|
||||
namespace {
|
||||
ProxyServer MyProxyUriToProxyServer(std::string_view uri) {
|
||||
if (uri.compare(0, 7, "quic://") == 0) {
|
||||
return ProxySchemeHostAndPortToProxyServer(ProxyServer::SCHEME_QUIC,
|
||||
uri.substr(7));
|
||||
}
|
||||
return ProxyUriToProxyServer(uri, ProxyServer::SCHEME_INVALID);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
NaiveListenConfig::NaiveListenConfig() = default;
|
||||
NaiveListenConfig::NaiveListenConfig(const NaiveListenConfig&) = default;
|
||||
NaiveListenConfig::~NaiveListenConfig() = default;
|
||||
|
||||
bool NaiveListenConfig::Parse(const std::string& str) {
|
||||
GURL url(str);
|
||||
if (url.scheme() == "socks") {
|
||||
protocol = ClientProtocol::kSocks5;
|
||||
} else if (url.scheme() == "http") {
|
||||
protocol = ClientProtocol::kHttp;
|
||||
} else if (url.scheme() == "redir") {
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
protocol = ClientProtocol::kRedir;
|
||||
#else
|
||||
std::cerr << "Redir protocol only supports Linux." << std::endl;
|
||||
return false;
|
||||
#endif
|
||||
} else {
|
||||
std::cerr << "Invalid scheme in " << str << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!url.username().empty()) {
|
||||
user = base::UnescapeBinaryURLComponent(url.username());
|
||||
}
|
||||
if (!url.password().empty()) {
|
||||
pass = base::UnescapeBinaryURLComponent(url.password());
|
||||
}
|
||||
|
||||
if (!url.host().empty()) {
|
||||
addr = url.HostNoBrackets();
|
||||
}
|
||||
|
||||
int effective_port = url.EffectiveIntPort();
|
||||
if (effective_port == url::PORT_INVALID) {
|
||||
std::cerr << "Invalid port in " << str << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (effective_port != url::PORT_UNSPECIFIED) {
|
||||
port = effective_port;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NaiveConfig::NaiveConfig() = default;
|
||||
NaiveConfig::NaiveConfig(const NaiveConfig&) = default;
|
||||
NaiveConfig::~NaiveConfig() = default;
|
||||
|
||||
bool NaiveConfig::Parse(const base::Value::Dict& value) {
|
||||
if (const base::Value* v = value.Find("listen")) {
|
||||
listen.clear();
|
||||
if (const std::string* str = v->GetIfString()) {
|
||||
if (!listen.emplace_back().Parse(*str)) {
|
||||
return false;
|
||||
}
|
||||
} else if (const base::Value::List* strs = v->GetIfList()) {
|
||||
for (const auto& str_e : *strs) {
|
||||
if (const std::string* s = str_e.GetIfString()) {
|
||||
if (!listen.emplace_back().Parse(*s)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Invalid listen element" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Invalid listen" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const base::Value* v = value.Find("insecure-concurrency")) {
|
||||
if (std::optional<int> i = v->GetIfInt()) {
|
||||
insecure_concurrency = *i;
|
||||
} else if (const std::string* str = v->GetIfString()) {
|
||||
if (!base::StringToInt(*str, &insecure_concurrency)) {
|
||||
std::cerr << "Invalid concurrency" << std::endl;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Invalid concurrency" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (insecure_concurrency < 1) {
|
||||
std::cerr << "Invalid concurrency" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const base::Value* v = value.Find("extra-headers")) {
|
||||
if (const std::string* str = v->GetIfString()) {
|
||||
extra_headers.AddHeadersFromString(*str);
|
||||
} else {
|
||||
std::cerr << "Invalid extra-headers" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const base::Value* v = value.Find("proxy")) {
|
||||
if (const std::string* str = v->GetIfString(); str && !str->empty()) {
|
||||
base::StringTokenizer proxy_uri_list(*str, ",");
|
||||
std::vector<ProxyServer> proxy_servers;
|
||||
bool seen_tcp = false;
|
||||
while (proxy_uri_list.GetNext()) {
|
||||
std::string token(proxy_uri_list.token());
|
||||
GURL url(token);
|
||||
|
||||
std::u16string proxy_user;
|
||||
std::u16string proxy_pass;
|
||||
net::GetIdentityFromURL(url, &proxy_user, &proxy_pass);
|
||||
GURL::Replacements remove_auth;
|
||||
remove_auth.ClearUsername();
|
||||
remove_auth.ClearPassword();
|
||||
GURL url_no_auth = url.ReplaceComponents(remove_auth);
|
||||
std::string proxy_uri = url_no_auth.GetWithEmptyPath().spec();
|
||||
if (!proxy_uri.empty() && proxy_uri.back() == '/') {
|
||||
proxy_uri.pop_back();
|
||||
}
|
||||
|
||||
proxy_servers.emplace_back(MyProxyUriToProxyServer(proxy_uri));
|
||||
const ProxyServer& last = proxy_servers.back();
|
||||
if (last.is_quic()) {
|
||||
if (seen_tcp) {
|
||||
std::cerr << "QUIC proxy cannot follow TCP-based proxies"
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
origins_to_force_quic_on.insert(HostPortPair::FromURL(url));
|
||||
} else if (last.is_https() || last.is_http() || last.is_socks()) {
|
||||
seen_tcp = true;
|
||||
} else {
|
||||
std::cerr << "Invalid proxy scheme" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
AuthCredentials auth(proxy_user, proxy_pass);
|
||||
if (!auth.Empty()) {
|
||||
if (last.is_socks()) {
|
||||
std::cerr << "SOCKS proxy with auth is not supported" << std::endl;
|
||||
} else {
|
||||
std::string proxy_url(token);
|
||||
if (proxy_url.compare(0, 7, "quic://") == 0) {
|
||||
proxy_url.replace(0, 4, "https");
|
||||
}
|
||||
auth_store[url::SchemeHostPort{GURL{proxy_url}}] = auth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (proxy_servers.size() > 1 &&
|
||||
std::any_of(proxy_servers.begin(), proxy_servers.end(),
|
||||
[](const ProxyServer& s) { return s.is_socks(); })) {
|
||||
// See net/socket/connect_job_params_factory.cc
|
||||
// DCHECK(proxy_server.is_socks());
|
||||
// DCHECK_EQ(1u, proxy_chain.length());
|
||||
std::cerr
|
||||
<< "Multi-proxy chain containing SOCKS proxies is not supported."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
if (std::any_of(proxy_servers.begin(), proxy_servers.end(),
|
||||
[](const ProxyServer& s) { return s.is_quic(); })) {
|
||||
proxy_chain = ProxyChain::ForIpProtection(proxy_servers);
|
||||
} else {
|
||||
proxy_chain = ProxyChain(proxy_servers);
|
||||
}
|
||||
|
||||
if (!proxy_chain.IsValid()) {
|
||||
std::cerr << "Invalid proxy chain" << std::endl;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Invalid proxy argument" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const base::Value* v = value.Find("host-resolver-rules")) {
|
||||
if (const std::string* str = v->GetIfString()) {
|
||||
host_resolver_rules = *str;
|
||||
} else {
|
||||
std::cerr << "Invalid host-resolver-rules" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const base::Value* v = value.Find("resolver-range")) {
|
||||
if (const std::string* str = v->GetIfString(); str && !str->empty()) {
|
||||
if (!net::ParseCIDRBlock(*str, &resolver_range, &resolver_prefix)) {
|
||||
std::cerr << "Invalid resolver-range" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (resolver_range.IsIPv6()) {
|
||||
std::cerr << "IPv6 resolver range not supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Invalid resolver-range" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const base::Value* v = value.Find("log")) {
|
||||
if (const std::string* str = v->GetIfString()) {
|
||||
if (!str->empty()) {
|
||||
log.logging_dest = logging::LOG_TO_FILE;
|
||||
log_file = base::FilePath::FromUTF8Unsafe(*str);
|
||||
log.log_file_path = log_file.value().c_str();
|
||||
} else {
|
||||
log.logging_dest = logging::LOG_TO_STDERR;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Invalid log" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const base::Value* v = value.Find("log-net-log")) {
|
||||
if (const std::string* str = v->GetIfString(); str && !str->empty()) {
|
||||
log_net_log = base::FilePath::FromUTF8Unsafe(*str);
|
||||
} else {
|
||||
std::cerr << "Invalid log-net-log" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const base::Value* v = value.Find("ssl-key-log-file")) {
|
||||
if (const std::string* str = v->GetIfString(); str && !str->empty()) {
|
||||
ssl_key_log_file = base::FilePath::FromUTF8Unsafe(*str);
|
||||
} else {
|
||||
std::cerr << "Invalid ssl-key-log-file" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (value.contains("no-post-quantum")) {
|
||||
no_post_quantum = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace net
|
72
src/net/tools/naive/naive_config.h
Normal file
72
src/net/tools/naive/naive_config.h
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright 2024 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_CONFIG_H_
|
||||
#define NET_TOOLS_NAIVE_NAIVE_CONFIG_H_
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/values.h"
|
||||
#include "net/base/auth.h"
|
||||
#include "net/base/host_port_pair.h"
|
||||
#include "net/base/ip_address.h"
|
||||
#include "net/base/proxy_chain.h"
|
||||
#include "net/http/http_request_headers.h"
|
||||
#include "net/tools/naive/naive_protocol.h"
|
||||
#include "url/scheme_host_port.h"
|
||||
|
||||
namespace net {
|
||||
|
||||
struct NaiveListenConfig {
|
||||
ClientProtocol protocol = ClientProtocol::kSocks5;
|
||||
std::string user;
|
||||
std::string pass;
|
||||
std::string addr = "0.0.0.0";
|
||||
int port = 1080;
|
||||
|
||||
NaiveListenConfig();
|
||||
NaiveListenConfig(const NaiveListenConfig&);
|
||||
~NaiveListenConfig();
|
||||
bool Parse(const std::string& str);
|
||||
};
|
||||
|
||||
struct NaiveConfig {
|
||||
std::vector<NaiveListenConfig> listen = {NaiveListenConfig()};
|
||||
|
||||
int insecure_concurrency = 1;
|
||||
|
||||
HttpRequestHeaders extra_headers;
|
||||
|
||||
// The last server is assumed to be Naive.
|
||||
ProxyChain proxy_chain = ProxyChain::Direct();
|
||||
std::set<HostPortPair> origins_to_force_quic_on;
|
||||
std::map<url::SchemeHostPort, AuthCredentials> auth_store;
|
||||
|
||||
std::string host_resolver_rules;
|
||||
|
||||
IPAddress resolver_range = {100, 64, 0, 0};
|
||||
size_t resolver_prefix = 10;
|
||||
|
||||
logging::LoggingSettings log = {.logging_dest = logging::LOG_NONE};
|
||||
base::FilePath log_file;
|
||||
|
||||
base::FilePath log_net_log;
|
||||
|
||||
base::FilePath ssl_key_log_file;
|
||||
|
||||
std::optional<bool> no_post_quantum;
|
||||
|
||||
NaiveConfig();
|
||||
NaiveConfig(const NaiveConfig&);
|
||||
~NaiveConfig();
|
||||
bool Parse(const base::Value::Dict& value);
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
#endif // NET_TOOLS_NAIVE_NAIVE_CONFIG_H_
|
478
src/net/tools/naive/naive_connection.cc
Normal file
478
src/net/tools/naive/naive_connection.cc
Normal file
@ -0,0 +1,478 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// 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.
|
||||
#ifdef UNSAFE_BUFFERS_BUILD
|
||||
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
|
||||
#pragma allow_unsafe_buffers
|
||||
#endif
|
||||
|
||||
#include "net/tools/naive/naive_connection.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/functional/callback_helpers.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/rand_util.h"
|
||||
#include "base/strings/strcat.h"
|
||||
#include "base/task/single_thread_task_runner.h"
|
||||
#include "base/time/time.h"
|
||||
#include "build/build_config.h"
|
||||
#include "net/base/io_buffer.h"
|
||||
#include "net/base/load_flags.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/base/privacy_mode.h"
|
||||
#include "net/base/url_util.h"
|
||||
#include "net/proxy_resolution/proxy_info.h"
|
||||
#include "net/socket/client_socket_handle.h"
|
||||
#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_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"
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
#include <linux/netfilter_ipv4.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "net/base/ip_endpoint.h"
|
||||
#include "net/base/sockaddr_storage.h"
|
||||
#include "net/socket/tcp_client_socket.h"
|
||||
#endif
|
||||
|
||||
namespace net {
|
||||
|
||||
namespace {
|
||||
constexpr int kBufferSize = 64 * 1024;
|
||||
} // namespace
|
||||
|
||||
NaiveConnection::NaiveConnection(
|
||||
unsigned int id,
|
||||
ClientProtocol protocol,
|
||||
std::unique_ptr<PaddingDetectorDelegate> padding_detector_delegate,
|
||||
const ProxyInfo& proxy_info,
|
||||
RedirectResolver* resolver,
|
||||
HttpNetworkSession* session,
|
||||
const NetworkAnonymizationKey& network_anonymization_key,
|
||||
const NetLogWithSource& net_log,
|
||||
std::unique_ptr<StreamSocket> accepted_socket,
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation)
|
||||
: id_(id),
|
||||
protocol_(protocol),
|
||||
padding_detector_delegate_(std::move(padding_detector_delegate)),
|
||||
proxy_info_(proxy_info),
|
||||
resolver_(resolver),
|
||||
session_(session),
|
||||
network_anonymization_key_(network_anonymization_key),
|
||||
net_log_(net_log),
|
||||
next_state_(STATE_NONE),
|
||||
client_socket_(std::move(accepted_socket)),
|
||||
server_socket_handle_(std::make_unique<ClientSocketHandle>()),
|
||||
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),
|
||||
full_duplex_(false),
|
||||
time_func_(&base::TimeTicks::Now),
|
||||
traffic_annotation_(traffic_annotation) {
|
||||
io_callback_ = base::BindRepeating(&NaiveConnection::OnIOComplete,
|
||||
weak_ptr_factory_.GetWeakPtr());
|
||||
}
|
||||
|
||||
NaiveConnection::~NaiveConnection() {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
int NaiveConnection::Connect(CompletionOnceCallback callback) {
|
||||
DCHECK(client_socket_);
|
||||
DCHECK_EQ(next_state_, STATE_NONE);
|
||||
DCHECK(!connect_callback_);
|
||||
|
||||
if (full_duplex_)
|
||||
return OK;
|
||||
|
||||
next_state_ = STATE_CONNECT_CLIENT;
|
||||
|
||||
int rv = DoLoop(OK);
|
||||
if (rv == ERR_IO_PENDING) {
|
||||
connect_callback_ = std::move(callback);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void NaiveConnection::Disconnect() {
|
||||
full_duplex_ = false;
|
||||
// Closes server side first because latency is higher.
|
||||
if (server_socket_handle_->socket())
|
||||
server_socket_handle_->socket()->Disconnect();
|
||||
client_socket_->Disconnect();
|
||||
|
||||
next_state_ = STATE_NONE;
|
||||
connect_callback_.Reset();
|
||||
run_callback_.Reset();
|
||||
}
|
||||
|
||||
void NaiveConnection::DoCallback(int result) {
|
||||
DCHECK_NE(result, ERR_IO_PENDING);
|
||||
DCHECK(connect_callback_);
|
||||
|
||||
// Since Run() may result in Read being called,
|
||||
// clear connect_callback_ up front.
|
||||
std::move(connect_callback_).Run(result);
|
||||
}
|
||||
|
||||
void NaiveConnection::OnIOComplete(int result) {
|
||||
DCHECK_NE(next_state_, STATE_NONE);
|
||||
int rv = DoLoop(result);
|
||||
if (rv != ERR_IO_PENDING) {
|
||||
DoCallback(rv);
|
||||
}
|
||||
}
|
||||
|
||||
int NaiveConnection::DoLoop(int last_io_result) {
|
||||
DCHECK_NE(next_state_, STATE_NONE);
|
||||
int rv = last_io_result;
|
||||
do {
|
||||
State state = next_state_;
|
||||
next_state_ = STATE_NONE;
|
||||
switch (state) {
|
||||
case STATE_CONNECT_CLIENT:
|
||||
DCHECK_EQ(rv, OK);
|
||||
rv = DoConnectClient();
|
||||
break;
|
||||
case STATE_CONNECT_CLIENT_COMPLETE:
|
||||
rv = DoConnectClientComplete(rv);
|
||||
break;
|
||||
case STATE_CONNECT_SERVER:
|
||||
DCHECK_EQ(rv, OK);
|
||||
rv = DoConnectServer();
|
||||
break;
|
||||
case STATE_CONNECT_SERVER_COMPLETE:
|
||||
rv = DoConnectServerComplete(rv);
|
||||
break;
|
||||
default:
|
||||
NOTREACHED() << "bad state";
|
||||
rv = ERR_UNEXPECTED;
|
||||
break;
|
||||
}
|
||||
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int NaiveConnection::DoConnectClient() {
|
||||
next_state_ = STATE_CONNECT_CLIENT_COMPLETE;
|
||||
|
||||
return client_socket_->Connect(io_callback_);
|
||||
}
|
||||
|
||||
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 (HttpProxyServerSocket), padding support detection is
|
||||
// done during client connect, so there shouldn't be any missed early pull.
|
||||
if (!padding_detector_delegate_->GetServerPaddingType().has_value()) {
|
||||
early_pull_pending_ = false;
|
||||
early_pull_result_ = 0;
|
||||
next_state_ = STATE_CONNECT_SERVER;
|
||||
return OK;
|
||||
}
|
||||
|
||||
early_pull_pending_ = true;
|
||||
Pull(kClient, kServer);
|
||||
if (early_pull_result_ != ERR_IO_PENDING) {
|
||||
// Pull has completed synchronously.
|
||||
if (early_pull_result_ <= 0) {
|
||||
return early_pull_result_ ? early_pull_result_ : ERR_CONNECTION_CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
next_state_ = STATE_CONNECT_SERVER;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int NaiveConnection::DoConnectServer() {
|
||||
next_state_ = STATE_CONNECT_SERVER_COMPLETE;
|
||||
|
||||
HostPortPair origin;
|
||||
if (protocol_ == ClientProtocol::kSocks5) {
|
||||
const auto* socket =
|
||||
static_cast<const Socks5ServerSocket*>(client_socket_.get());
|
||||
origin = socket->request_endpoint();
|
||||
} else if (protocol_ == ClientProtocol::kHttp) {
|
||||
const auto* socket =
|
||||
static_cast<const HttpProxyServerSocket*>(client_socket_.get());
|
||||
origin = socket->request_endpoint();
|
||||
} else if (protocol_ == ClientProtocol::kRedir) {
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
const auto* socket =
|
||||
static_cast<const TCPClientSocket*>(client_socket_.get());
|
||||
IPEndPoint peer_endpoint;
|
||||
int rv;
|
||||
rv = socket->GetPeerAddress(&peer_endpoint);
|
||||
if (rv != OK) {
|
||||
LOG(ERROR) << "Connection " << id_
|
||||
<< " cannot get peer address: " << ErrorToShortString(rv);
|
||||
return rv;
|
||||
}
|
||||
int sd = socket->SocketDescriptorForTesting();
|
||||
SockaddrStorage dst;
|
||||
if (peer_endpoint.GetFamily() == ADDRESS_FAMILY_IPV4 ||
|
||||
peer_endpoint.address().IsIPv4MappedIPv6()) {
|
||||
rv = getsockopt(sd, SOL_IP, SO_ORIGINAL_DST, dst.addr, &dst.addr_len);
|
||||
} else {
|
||||
rv = getsockopt(sd, SOL_IPV6, SO_ORIGINAL_DST, dst.addr, &dst.addr_len);
|
||||
}
|
||||
if (rv == 0) {
|
||||
IPEndPoint ipe;
|
||||
if (ipe.FromSockAddr(dst.addr, dst.addr_len)) {
|
||||
const auto& addr = ipe.address();
|
||||
auto name = resolver_->FindNameByAddress(addr);
|
||||
if (!name.empty()) {
|
||||
origin = HostPortPair(name, ipe.port());
|
||||
} else if (!resolver_->IsInResolvedRange(addr)) {
|
||||
origin = HostPortPair::FromIPEndPoint(ipe);
|
||||
} else {
|
||||
LOG(ERROR) << "Connection " << id_ << " to unresolved name for "
|
||||
<< addr.ToString();
|
||||
return ERR_ADDRESS_INVALID;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG(ERROR) << "Failed to get original destination address";
|
||||
return ERR_ADDRESS_INVALID;
|
||||
}
|
||||
#else
|
||||
static_cast<void>(resolver_);
|
||||
#endif
|
||||
}
|
||||
|
||||
url::CanonHostInfo host_info;
|
||||
url::SchemeHostPort endpoint(
|
||||
"http", CanonicalizeHost(origin.HostForURL(), &host_info), origin.port(),
|
||||
url::SchemeHostPort::ALREADY_CANONICALIZED);
|
||||
if (!endpoint.IsValid()) {
|
||||
LOG(ERROR) << "Connection " << id_ << " to invalid origin "
|
||||
<< origin.ToString();
|
||||
return ERR_ADDRESS_INVALID;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Connection " << id_ << " to " << origin.ToString();
|
||||
|
||||
// Ignores socket limit set by socket pool for this type of socket.
|
||||
return InitSocketHandleForHttpRequest(
|
||||
std::move(endpoint), LOAD_IGNORE_LIMITS, MAXIMUM_PRIORITY, session_,
|
||||
proxy_info_, {}, PRIVACY_MODE_DISABLED,
|
||||
network_anonymization_key_, SecureDnsPolicy::kDisable, SocketTag(),
|
||||
net_log_, server_socket_handle_.get(), io_callback_,
|
||||
ClientSocketPool::ProxyAuthCallback());
|
||||
}
|
||||
|
||||
int NaiveConnection::DoConnectServerComplete(int result) {
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
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;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int NaiveConnection::Run(CompletionOnceCallback callback) {
|
||||
DCHECK(sockets_[kServer]);
|
||||
DCHECK_EQ(next_state_, STATE_NONE);
|
||||
DCHECK(!connect_callback_);
|
||||
|
||||
// The client-side socket may be closed before the server-side
|
||||
// socket is connected.
|
||||
if (errors_[kClient] != OK || sockets_[kClient] == nullptr)
|
||||
return errors_[kClient];
|
||||
if (errors_[kServer] != OK)
|
||||
return errors_[kServer];
|
||||
|
||||
run_callback_ = std::move(callback);
|
||||
|
||||
bytes_passed_without_yielding_[kClient] = 0;
|
||||
bytes_passed_without_yielding_[kServer] = 0;
|
||||
|
||||
yield_after_time_[kClient] =
|
||||
time_func_() + base::Milliseconds(kYieldAfterDurationMilliseconds);
|
||||
yield_after_time_[kServer] = yield_after_time_[kClient];
|
||||
|
||||
can_push_to_server_ = true;
|
||||
// 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);
|
||||
Push(kClient, kServer, early_pull_result_);
|
||||
}
|
||||
Pull(kServer, kClient);
|
||||
|
||||
return ERR_IO_PENDING;
|
||||
}
|
||||
|
||||
void NaiveConnection::Pull(Direction from, Direction to) {
|
||||
if (errors_[kClient] < 0 || errors_[kServer] < 0)
|
||||
return;
|
||||
|
||||
int read_size = kBufferSize;
|
||||
read_buffers_[from] = base::MakeRefCounted<IOBufferWithSize>(kBufferSize);
|
||||
|
||||
DCHECK(sockets_[from]);
|
||||
int rv = sockets_[from]->Read(
|
||||
read_buffers_[from].get(), read_size,
|
||||
base::BindRepeating(&NaiveConnection::OnPullComplete,
|
||||
weak_ptr_factory_.GetWeakPtr(), from, to));
|
||||
|
||||
if (from == kClient && early_pull_pending_)
|
||||
early_pull_result_ = rv;
|
||||
|
||||
if (rv != ERR_IO_PENDING)
|
||||
OnPullComplete(from, to, rv);
|
||||
}
|
||||
|
||||
void NaiveConnection::Push(Direction from, Direction to, int size) {
|
||||
write_buffers_[to] = base::MakeRefCounted<DrainableIOBuffer>(
|
||||
std::move(read_buffers_[from]), size);
|
||||
write_pending_[to] = true;
|
||||
DCHECK(sockets_[to]);
|
||||
int rv = sockets_[to]->Write(
|
||||
write_buffers_[to].get(), write_buffers_[to]->BytesRemaining(),
|
||||
base::BindRepeating(&NaiveConnection::OnPushComplete,
|
||||
weak_ptr_factory_.GetWeakPtr(), from, to),
|
||||
traffic_annotation_);
|
||||
|
||||
if (rv != ERR_IO_PENDING)
|
||||
OnPushComplete(from, to, rv);
|
||||
}
|
||||
|
||||
void NaiveConnection::Disconnect(Direction side) {
|
||||
if (sockets_[side]) {
|
||||
sockets_[side]->Disconnect();
|
||||
sockets_[side] = nullptr;
|
||||
write_pending_[side] = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NaiveConnection::IsConnected(Direction side) {
|
||||
return sockets_[side] != nullptr;
|
||||
}
|
||||
|
||||
void NaiveConnection::OnBothDisconnected() {
|
||||
if (run_callback_) {
|
||||
int error = OK;
|
||||
if (errors_[kClient] != ERR_CONNECTION_CLOSED && errors_[kClient] < 0)
|
||||
error = errors_[kClient];
|
||||
if (errors_[kServer] != ERR_CONNECTION_CLOSED && errors_[kClient] < 0)
|
||||
error = errors_[kServer];
|
||||
std::move(run_callback_).Run(error);
|
||||
}
|
||||
}
|
||||
|
||||
void NaiveConnection::OnPullError(Direction from, Direction to, int error) {
|
||||
DCHECK_LT(error, 0);
|
||||
|
||||
errors_[from] = error;
|
||||
Disconnect(from);
|
||||
|
||||
if (!write_pending_[to])
|
||||
Disconnect(to);
|
||||
|
||||
if (!IsConnected(from) && !IsConnected(to))
|
||||
OnBothDisconnected();
|
||||
}
|
||||
|
||||
void NaiveConnection::OnPushError(Direction from, Direction to, int error) {
|
||||
DCHECK_LE(error, 0);
|
||||
DCHECK(!write_pending_[to]);
|
||||
|
||||
if (error < 0) {
|
||||
errors_[to] = error;
|
||||
Disconnect(kServer);
|
||||
Disconnect(kClient);
|
||||
} else if (!IsConnected(from)) {
|
||||
Disconnect(to);
|
||||
}
|
||||
|
||||
if (!IsConnected(from) && !IsConnected(to))
|
||||
OnBothDisconnected();
|
||||
}
|
||||
|
||||
void NaiveConnection::OnPullComplete(Direction from, Direction to, int result) {
|
||||
if (from == kClient && early_pull_pending_) {
|
||||
early_pull_pending_ = false;
|
||||
early_pull_result_ = result ? result : ERR_CONNECTION_CLOSED;
|
||||
}
|
||||
|
||||
if (result <= 0) {
|
||||
OnPullError(from, to, result ? result : ERR_CONNECTION_CLOSED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (from == kClient && !can_push_to_server_)
|
||||
return;
|
||||
|
||||
Push(from, to, result);
|
||||
}
|
||||
|
||||
void NaiveConnection::OnPushComplete(Direction from, Direction to, int result) {
|
||||
if (result >= 0 && write_buffers_[to] != nullptr) {
|
||||
bytes_passed_without_yielding_[from] += result;
|
||||
write_buffers_[to]->DidConsume(result);
|
||||
int size = write_buffers_[to]->BytesRemaining();
|
||||
if (size > 0) {
|
||||
int rv = sockets_[to]->Write(
|
||||
write_buffers_[to].get(), size,
|
||||
base::BindRepeating(&NaiveConnection::OnPushComplete,
|
||||
weak_ptr_factory_.GetWeakPtr(), from, to),
|
||||
traffic_annotation_);
|
||||
if (rv != ERR_IO_PENDING)
|
||||
OnPushComplete(from, to, rv);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
write_pending_[to] = false;
|
||||
// Checks for termination even if result is OK.
|
||||
OnPushError(from, to, result >= 0 ? OK : result);
|
||||
|
||||
if (bytes_passed_without_yielding_[from] > kYieldAfterBytesRead ||
|
||||
time_func_() > yield_after_time_[from]) {
|
||||
bytes_passed_without_yielding_[from] = 0;
|
||||
yield_after_time_[from] =
|
||||
time_func_() + base::Milliseconds(kYieldAfterDurationMilliseconds);
|
||||
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindRepeating(&NaiveConnection::Pull,
|
||||
weak_ptr_factory_.GetWeakPtr(), from, to));
|
||||
} else {
|
||||
Pull(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace net
|
134
src/net/tools/naive/naive_connection.h
Normal file
134
src/net/tools/naive/naive_connection.h
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// 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_CONNECTION_H_
|
||||
#define NET_TOOLS_NAIVE_NAIVE_CONNECTION_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#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"
|
||||
|
||||
namespace net {
|
||||
|
||||
class ClientSocketHandle;
|
||||
class DrainableIOBuffer;
|
||||
class HttpNetworkSession;
|
||||
class IOBuffer;
|
||||
class NetLogWithSource;
|
||||
class ProxyInfo;
|
||||
class StreamSocket;
|
||||
struct NetworkTrafficAnnotationTag;
|
||||
struct SSLConfig;
|
||||
class RedirectResolver;
|
||||
class NetworkAnonymizationKey;
|
||||
|
||||
class NaiveConnection {
|
||||
public:
|
||||
using TimeFunc = base::TimeTicks (*)();
|
||||
|
||||
NaiveConnection(
|
||||
unsigned int id,
|
||||
ClientProtocol protocol,
|
||||
std::unique_ptr<PaddingDetectorDelegate> padding_detector_delegate,
|
||||
const ProxyInfo& proxy_info,
|
||||
RedirectResolver* resolver,
|
||||
HttpNetworkSession* session,
|
||||
const NetworkAnonymizationKey& network_anonymization_key,
|
||||
const NetLogWithSource& net_log,
|
||||
std::unique_ptr<StreamSocket> accepted_socket,
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation);
|
||||
~NaiveConnection();
|
||||
NaiveConnection(const NaiveConnection&) = delete;
|
||||
NaiveConnection& operator=(const NaiveConnection&) = delete;
|
||||
|
||||
unsigned int id() const { return id_; }
|
||||
int Connect(CompletionOnceCallback callback);
|
||||
void Disconnect();
|
||||
int Run(CompletionOnceCallback callback);
|
||||
|
||||
private:
|
||||
enum State {
|
||||
STATE_CONNECT_CLIENT,
|
||||
STATE_CONNECT_CLIENT_COMPLETE,
|
||||
STATE_CONNECT_SERVER,
|
||||
STATE_CONNECT_SERVER_COMPLETE,
|
||||
STATE_NONE,
|
||||
};
|
||||
|
||||
enum PaddingState {
|
||||
STATE_READ_PAYLOAD_LENGTH_1,
|
||||
STATE_READ_PAYLOAD_LENGTH_2,
|
||||
STATE_READ_PADDING_LENGTH,
|
||||
STATE_READ_PAYLOAD,
|
||||
STATE_READ_PADDING,
|
||||
};
|
||||
|
||||
void DoCallback(int result);
|
||||
void OnIOComplete(int result);
|
||||
int DoLoop(int last_io_result);
|
||||
int DoConnectClient();
|
||||
int DoConnectClientComplete(int result);
|
||||
int DoConnectServer();
|
||||
int DoConnectServerComplete(int result);
|
||||
void Pull(Direction from, Direction to);
|
||||
void Push(Direction from, Direction to, int size);
|
||||
void Disconnect(Direction side);
|
||||
bool IsConnected(Direction side);
|
||||
void OnBothDisconnected();
|
||||
void OnPullError(Direction from, Direction to, int error);
|
||||
void OnPushError(Direction from, Direction to, int error);
|
||||
void OnPullComplete(Direction from, Direction to, int result);
|
||||
void OnPushComplete(Direction from, Direction to, int result);
|
||||
|
||||
unsigned int id_;
|
||||
ClientProtocol protocol_;
|
||||
std::unique_ptr<PaddingDetectorDelegate> padding_detector_delegate_;
|
||||
const ProxyInfo& proxy_info_;
|
||||
RedirectResolver* resolver_;
|
||||
HttpNetworkSession* session_;
|
||||
const NetworkAnonymizationKey& network_anonymization_key_;
|
||||
const NetLogWithSource& net_log_;
|
||||
|
||||
CompletionRepeatingCallback io_callback_;
|
||||
CompletionOnceCallback connect_callback_;
|
||||
CompletionOnceCallback run_callback_;
|
||||
|
||||
State next_state_;
|
||||
|
||||
std::unique_ptr<StreamSocket> client_socket_;
|
||||
std::unique_ptr<ClientSocketHandle> server_socket_handle_;
|
||||
|
||||
std::unique_ptr<NaivePaddingSocket> sockets_[kNumDirections];
|
||||
scoped_refptr<IOBuffer> read_buffers_[kNumDirections];
|
||||
scoped_refptr<DrainableIOBuffer> write_buffers_[kNumDirections];
|
||||
int errors_[kNumDirections];
|
||||
bool write_pending_[kNumDirections];
|
||||
int bytes_passed_without_yielding_[kNumDirections];
|
||||
base::TimeTicks yield_after_time_[kNumDirections];
|
||||
|
||||
bool early_pull_pending_;
|
||||
bool can_push_to_server_;
|
||||
int early_pull_result_;
|
||||
|
||||
bool full_duplex_;
|
||||
|
||||
TimeFunc time_func_;
|
||||
|
||||
// Traffic annotation for socket control.
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation_;
|
||||
|
||||
base::WeakPtrFactory<NaiveConnection> weak_ptr_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
#endif // NET_TOOLS_NAIVE_NAIVE_CONNECTION_H_
|
123
src/net/tools/naive/naive_padding_framer.cc
Normal file
123
src/net/tools/naive/naive_padding_framer.cc
Normal file
@ -0,0 +1,123 @@
|
||||
// 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.
|
||||
#ifdef UNSAFE_BUFFERS_BUILD
|
||||
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
|
||||
#pragma allow_unsafe_buffers
|
||||
#endif
|
||||
|
||||
#include "net/tools/naive/naive_padding_framer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#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
|
76
src/net/tools/naive/naive_padding_framer.h
Normal file
76
src/net/tools/naive/naive_padding_framer.h
Normal file
@ -0,0 +1,76 @@
|
||||
// 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_FRAMER_H_
|
||||
#define NET_TOOLS_NAIVE_NAIVE_PADDING_FRAMER_H_
|
||||
|
||||
#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
|
||||
#endif // NET_TOOLS_NAIVE_NAIVE_PADDING_FRAMER_H_
|
271
src/net/tools/naive/naive_padding_socket.cc
Normal file
271
src/net/tools/naive/naive_padding_socket.cc
Normal 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<IOBufferWithSize>(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<IOBufferWithSize>(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
|
105
src/net/tools/naive/naive_padding_socket.h
Normal file
105
src/net/tools/naive/naive_padding_socket.h
Normal 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_
|
56
src/net/tools/naive/naive_protocol.cc
Normal file
56
src/net/tools/naive/naive_protocol.cc
Normal file
@ -0,0 +1,56 @@
|
||||
// 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 <string_view>
|
||||
|
||||
namespace net {
|
||||
const char* ToString(ClientProtocol value) {
|
||||
switch (value) {
|
||||
case ClientProtocol::kSocks5:
|
||||
return "socks";
|
||||
case ClientProtocol::kHttp:
|
||||
return "http";
|
||||
case ClientProtocol::kRedir:
|
||||
return "redir";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<PaddingType> ParsePaddingType(std::string_view 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
|
64
src/net/tools/naive/naive_protocol.h
Normal file
64
src/net/tools/naive/naive_protocol.h
Normal file
@ -0,0 +1,64 @@
|
||||
// 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_
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace net {
|
||||
enum class ClientProtocol {
|
||||
kSocks5,
|
||||
kHttp,
|
||||
kRedir,
|
||||
};
|
||||
|
||||
const char* ToString(ClientProtocol value);
|
||||
|
||||
// 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,
|
||||
};
|
||||
|
||||
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(std::string_view 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_
|
204
src/net/tools/naive/naive_proxy.cc
Normal file
204
src/net/tools/naive/naive_proxy.cc
Normal file
@ -0,0 +1,204 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// 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.
|
||||
|
||||
#include "net/tools/naive/naive_proxy.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/location.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/task/single_thread_task_runner.h"
|
||||
#include "net/base/load_flags.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/http/http_network_session.h"
|
||||
#include "net/proxy_resolution/configured_proxy_resolution_service.h"
|
||||
#include "net/proxy_resolution/proxy_config.h"
|
||||
#include "net/proxy_resolution/proxy_list.h"
|
||||
#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_server_socket.h"
|
||||
#include "net/tools/naive/naive_proxy_delegate.h"
|
||||
#include "net/tools/naive/socks5_server_socket.h"
|
||||
|
||||
namespace net {
|
||||
|
||||
NaiveProxy::NaiveProxy(std::unique_ptr<ServerSocket> listen_socket,
|
||||
ClientProtocol protocol,
|
||||
const std::string& listen_user,
|
||||
const std::string& listen_pass,
|
||||
int concurrency,
|
||||
RedirectResolver* resolver,
|
||||
HttpNetworkSession* session,
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation,
|
||||
const std::vector<PaddingType>& supported_padding_types)
|
||||
: listen_socket_(std::move(listen_socket)),
|
||||
protocol_(protocol),
|
||||
listen_user_(listen_user),
|
||||
listen_pass_(listen_pass),
|
||||
concurrency_(concurrency),
|
||||
resolver_(resolver),
|
||||
session_(session),
|
||||
net_log_(
|
||||
NetLogWithSource::Make(session->net_log(), NetLogSourceType::NONE)),
|
||||
last_id_(0),
|
||||
traffic_annotation_(traffic_annotation),
|
||||
supported_padding_types_(supported_padding_types) {
|
||||
const auto& proxy_config = static_cast<ConfiguredProxyResolutionService*>(
|
||||
session_->proxy_resolution_service())
|
||||
->config();
|
||||
DCHECK(proxy_config);
|
||||
const ProxyList& proxy_list =
|
||||
proxy_config.value().value().proxy_rules().single_proxies;
|
||||
DCHECK(!proxy_list.IsEmpty());
|
||||
proxy_info_.UseProxyList(proxy_list);
|
||||
proxy_info_.set_traffic_annotation(
|
||||
net::MutableNetworkTrafficAnnotationTag(traffic_annotation_));
|
||||
|
||||
for (int i = 0; i < concurrency_; i++) {
|
||||
network_anonymization_keys_.push_back(
|
||||
NetworkAnonymizationKey::CreateTransient());
|
||||
}
|
||||
|
||||
DCHECK(listen_socket_);
|
||||
// Start accepting connections in next run loop in case when delegate is not
|
||||
// ready to get callbacks.
|
||||
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||||
FROM_HERE, base::BindOnce(&NaiveProxy::DoAcceptLoop,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
NaiveProxy::~NaiveProxy() = default;
|
||||
|
||||
void NaiveProxy::DoAcceptLoop() {
|
||||
int result;
|
||||
do {
|
||||
result = listen_socket_->Accept(
|
||||
&accepted_socket_, base::BindRepeating(&NaiveProxy::OnAcceptComplete,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
if (result == ERR_IO_PENDING)
|
||||
return;
|
||||
HandleAcceptResult(result);
|
||||
} while (result == OK);
|
||||
}
|
||||
|
||||
void NaiveProxy::OnAcceptComplete(int result) {
|
||||
HandleAcceptResult(result);
|
||||
if (result == OK)
|
||||
DoAcceptLoop();
|
||||
}
|
||||
|
||||
void NaiveProxy::HandleAcceptResult(int result) {
|
||||
if (result != OK) {
|
||||
LOG(ERROR) << "Accept error: " << ErrorToShortString(result);
|
||||
return;
|
||||
}
|
||||
DoConnect();
|
||||
}
|
||||
|
||||
void NaiveProxy::DoConnect() {
|
||||
std::unique_ptr<StreamSocket> socket;
|
||||
auto* proxy_delegate =
|
||||
static_cast<NaiveProxyDelegate*>(session_->context().proxy_delegate);
|
||||
DCHECK(proxy_delegate);
|
||||
DCHECK(!proxy_info_.is_empty());
|
||||
const ProxyChain& proxy_server = proxy_info_.proxy_chain();
|
||||
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_),
|
||||
listen_user_, listen_pass_,
|
||||
traffic_annotation_);
|
||||
} else if (protocol_ == ClientProtocol::kHttp) {
|
||||
socket = std::make_unique<HttpProxyServerSocket>(
|
||||
std::move(accepted_socket_), listen_user_, listen_pass_,
|
||||
padding_detector_delegate.get(), traffic_annotation_,
|
||||
supported_padding_types_);
|
||||
} else if (protocol_ == ClientProtocol::kRedir) {
|
||||
socket = std::move(accepted_socket_);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
last_id_++;
|
||||
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_,
|
||||
resolver_, session_, nak, net_log_, std::move(socket),
|
||||
traffic_annotation_);
|
||||
auto* connection = connection_ptr.get();
|
||||
connection_by_id_[connection->id()] = std::move(connection_ptr);
|
||||
int result = connection->Connect(
|
||||
base::BindRepeating(&NaiveProxy::OnConnectComplete,
|
||||
weak_ptr_factory_.GetWeakPtr(), connection->id()));
|
||||
if (result == ERR_IO_PENDING)
|
||||
return;
|
||||
HandleConnectResult(connection, result);
|
||||
}
|
||||
|
||||
void NaiveProxy::OnConnectComplete(unsigned int connection_id, int result) {
|
||||
auto* connection = FindConnection(connection_id);
|
||||
if (!connection)
|
||||
return;
|
||||
HandleConnectResult(connection, result);
|
||||
}
|
||||
|
||||
void NaiveProxy::HandleConnectResult(NaiveConnection* connection, int result) {
|
||||
if (result != OK) {
|
||||
Close(connection->id(), result);
|
||||
return;
|
||||
}
|
||||
DoRun(connection);
|
||||
}
|
||||
|
||||
void NaiveProxy::DoRun(NaiveConnection* connection) {
|
||||
int result = connection->Run(
|
||||
base::BindRepeating(&NaiveProxy::OnRunComplete,
|
||||
weak_ptr_factory_.GetWeakPtr(), connection->id()));
|
||||
if (result == ERR_IO_PENDING)
|
||||
return;
|
||||
HandleRunResult(connection, result);
|
||||
}
|
||||
|
||||
void NaiveProxy::OnRunComplete(unsigned int connection_id, int result) {
|
||||
auto* connection = FindConnection(connection_id);
|
||||
if (!connection)
|
||||
return;
|
||||
HandleRunResult(connection, result);
|
||||
}
|
||||
|
||||
void NaiveProxy::HandleRunResult(NaiveConnection* connection, int result) {
|
||||
Close(connection->id(), result);
|
||||
}
|
||||
|
||||
void NaiveProxy::Close(unsigned int connection_id, int reason) {
|
||||
auto it = connection_by_id_.find(connection_id);
|
||||
if (it == connection_by_id_.end())
|
||||
return;
|
||||
|
||||
LOG(INFO) << "Connection " << connection_id
|
||||
<< " closed: " << ErrorToShortString(reason);
|
||||
|
||||
// The call stack might have callbacks which still have the pointer of
|
||||
// connection. Instead of referencing connection with ID all the time,
|
||||
// destroys the connection in next run loop to make sure any pending
|
||||
// callbacks in the call stack return.
|
||||
base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(
|
||||
FROM_HERE, std::move(it->second));
|
||||
connection_by_id_.erase(it);
|
||||
}
|
||||
|
||||
NaiveConnection* NaiveProxy::FindConnection(unsigned int connection_id) {
|
||||
auto it = connection_by_id_.find(connection_id);
|
||||
if (it == connection_by_id_.end())
|
||||
return nullptr;
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
} // namespace net
|
90
src/net/tools/naive/naive_proxy.h
Normal file
90
src/net/tools/naive/naive_proxy.h
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// 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"
|
||||
#include "net/base/completion_repeating_callback.h"
|
||||
#include "net/base/network_isolation_key.h"
|
||||
#include "net/log/net_log_with_source.h"
|
||||
#include "net/proxy_resolution/proxy_info.h"
|
||||
#include "net/ssl/ssl_config.h"
|
||||
#include "net/tools/naive/naive_connection.h"
|
||||
#include "net/tools/naive/naive_protocol.h"
|
||||
|
||||
namespace net {
|
||||
|
||||
class ClientSocketHandle;
|
||||
class HttpNetworkSession;
|
||||
class NaiveConnection;
|
||||
class ServerSocket;
|
||||
class StreamSocket;
|
||||
struct NetworkTrafficAnnotationTag;
|
||||
class RedirectResolver;
|
||||
|
||||
class NaiveProxy {
|
||||
public:
|
||||
NaiveProxy(std::unique_ptr<ServerSocket> server_socket,
|
||||
ClientProtocol protocol,
|
||||
const std::string& listen_user,
|
||||
const std::string& listen_pass,
|
||||
int concurrency,
|
||||
RedirectResolver* resolver,
|
||||
HttpNetworkSession* session,
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation,
|
||||
const std::vector<PaddingType>& supported_padding_types);
|
||||
~NaiveProxy();
|
||||
NaiveProxy(const NaiveProxy&) = delete;
|
||||
NaiveProxy& operator=(const NaiveProxy&) = delete;
|
||||
|
||||
private:
|
||||
void DoAcceptLoop();
|
||||
void OnAcceptComplete(int result);
|
||||
void HandleAcceptResult(int result);
|
||||
|
||||
void DoConnect();
|
||||
void OnConnectComplete(unsigned int connection_id, int result);
|
||||
void HandleConnectResult(NaiveConnection* connection, int result);
|
||||
|
||||
void DoRun(NaiveConnection* connection);
|
||||
void OnRunComplete(unsigned int connection_id, int result);
|
||||
void HandleRunResult(NaiveConnection* connection, int result);
|
||||
|
||||
void Close(unsigned int connection_id, int reason);
|
||||
|
||||
NaiveConnection* FindConnection(unsigned int connection_id);
|
||||
|
||||
std::unique_ptr<ServerSocket> listen_socket_;
|
||||
ClientProtocol protocol_;
|
||||
std::string listen_user_;
|
||||
std::string listen_pass_;
|
||||
int concurrency_;
|
||||
ProxyInfo proxy_info_;
|
||||
RedirectResolver* resolver_;
|
||||
HttpNetworkSession* session_;
|
||||
NetLogWithSource net_log_;
|
||||
|
||||
unsigned int last_id_;
|
||||
|
||||
std::unique_ptr<StreamSocket> accepted_socket_;
|
||||
|
||||
std::vector<NetworkAnonymizationKey> network_anonymization_keys_;
|
||||
|
||||
std::map<unsigned int, std::unique_ptr<NaiveConnection>> connection_by_id_;
|
||||
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation_;
|
||||
|
||||
std::vector<PaddingType> supported_padding_types_;
|
||||
|
||||
base::WeakPtrFactory<NaiveProxy> weak_ptr_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
#endif // NET_TOOLS_NAIVE_NAIVE_PROXY_H_
|
518
src/net/tools/naive/naive_proxy_bin.cc
Normal file
518
src/net/tools/naive/naive_proxy_bin.cc
Normal file
@ -0,0 +1,518 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// 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.
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "base/at_exit.h"
|
||||
#include "base/check.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/feature_list.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/json/json_file_value_serializer.h"
|
||||
#include "base/json/json_writer.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/process/memory.h"
|
||||
#include "base/rand_util.h"
|
||||
#include "base/run_loop.h"
|
||||
#include "base/strings/escape.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/system/sys_info.h"
|
||||
#include "base/task/single_thread_task_executor.h"
|
||||
#include "base/task/thread_pool/thread_pool_instance.h"
|
||||
#include "base/values.h"
|
||||
#include "build/build_config.h"
|
||||
#include "components/version_info/version_info.h"
|
||||
#include "net/base/auth.h"
|
||||
#include "net/base/network_isolation_key.h"
|
||||
#include "net/base/url_util.h"
|
||||
#include "net/cert/cert_verifier.h"
|
||||
#include "net/cert_net/cert_net_fetcher_url_request.h"
|
||||
#include "net/dns/host_resolver.h"
|
||||
#include "net/dns/mapped_host_resolver.h"
|
||||
#include "net/http/http_auth.h"
|
||||
#include "net/http/http_auth_cache.h"
|
||||
#include "net/http/http_network_session.h"
|
||||
#include "net/http/http_request_headers.h"
|
||||
#include "net/http/http_transaction_factory.h"
|
||||
#include "net/log/file_net_log_observer.h"
|
||||
#include "net/log/net_log.h"
|
||||
#include "net/log/net_log_capture_mode.h"
|
||||
#include "net/log/net_log_entry.h"
|
||||
#include "net/log/net_log_event_type.h"
|
||||
#include "net/log/net_log_source.h"
|
||||
#include "net/log/net_log_util.h"
|
||||
#include "net/proxy_resolution/configured_proxy_resolution_service.h"
|
||||
#include "net/proxy_resolution/proxy_config.h"
|
||||
#include "net/proxy_resolution/proxy_config_service_fixed.h"
|
||||
#include "net/proxy_resolution/proxy_config_with_annotation.h"
|
||||
#include "net/socket/client_socket_pool_manager.h"
|
||||
#include "net/socket/ssl_client_socket.h"
|
||||
#include "net/socket/tcp_server_socket.h"
|
||||
#include "net/socket/udp_server_socket.h"
|
||||
#include "net/ssl/ssl_config_service.h"
|
||||
#include "net/ssl/ssl_key_logger_impl.h"
|
||||
#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
|
||||
#include "net/tools/naive/naive_command_line.h"
|
||||
#include "net/tools/naive/naive_config.h"
|
||||
#include "net/tools/naive/naive_protocol.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/traffic_annotation/network_traffic_annotation.h"
|
||||
#include "net/url_request/url_request_context.h"
|
||||
#include "net/url_request/url_request_context_builder.h"
|
||||
#include "url/gurl.h"
|
||||
#include "url/scheme_host_port.h"
|
||||
#include "url/url_util.h"
|
||||
|
||||
#if BUILDFLAG(IS_APPLE)
|
||||
#include "base/allocator/early_zone_registration_apple.h"
|
||||
#include "base/apple/scoped_nsautorelease_pool.h"
|
||||
#endif
|
||||
|
||||
#if PA_BUILDFLAG(USE_PARTITION_ALLOC)
|
||||
#include "base/allocator/allocator_check.h"
|
||||
#include "base/allocator/partition_alloc_support.h"
|
||||
#include "base/allocator/partition_allocator/src/partition_alloc/shim/allocator_shim.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kListenBackLog = 512;
|
||||
constexpr int kDefaultMaxSocketsPerPool = 256;
|
||||
constexpr int kDefaultMaxSocketsPerGroup = 255;
|
||||
constexpr int kExpectedMaxUsers = 8;
|
||||
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
|
||||
net::DefineNetworkTrafficAnnotation("naive", "");
|
||||
|
||||
std::unique_ptr<base::Value::Dict> GetConstants() {
|
||||
base::Value::Dict constants_dict = net::GetNetConstants();
|
||||
base::Value::Dict dict;
|
||||
std::string os_type = base::StringPrintf(
|
||||
"%s: %s (%s)", base::SysInfo::OperatingSystemName().c_str(),
|
||||
base::SysInfo::OperatingSystemVersion().c_str(),
|
||||
base::SysInfo::OperatingSystemArchitecture().c_str());
|
||||
dict.Set("os_type", os_type);
|
||||
constants_dict.Set("clientInfo", std::move(dict));
|
||||
return std::make_unique<base::Value::Dict>(std::move(constants_dict));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace net {
|
||||
namespace {
|
||||
// NetLog::ThreadSafeObserver implementation that simply prints events
|
||||
// to the logs.
|
||||
class PrintingLogObserver : public NetLog::ThreadSafeObserver {
|
||||
public:
|
||||
PrintingLogObserver() = default;
|
||||
PrintingLogObserver(const PrintingLogObserver&) = delete;
|
||||
PrintingLogObserver& operator=(const PrintingLogObserver&) = delete;
|
||||
|
||||
~PrintingLogObserver() override {
|
||||
// This is guaranteed to be safe as this program is single threaded.
|
||||
net_log()->RemoveObserver(this);
|
||||
}
|
||||
|
||||
// NetLog::ThreadSafeObserver implementation:
|
||||
void OnAddEntry(const NetLogEntry& entry) override {
|
||||
switch (entry.type) {
|
||||
case NetLogEventType::SOCKET_POOL_STALLED_MAX_SOCKETS:
|
||||
case NetLogEventType::SOCKET_POOL_STALLED_MAX_SOCKETS_PER_GROUP:
|
||||
case NetLogEventType::HTTP2_SESSION_STREAM_STALLED_BY_SESSION_SEND_WINDOW:
|
||||
case NetLogEventType::HTTP2_SESSION_STREAM_STALLED_BY_STREAM_SEND_WINDOW:
|
||||
case NetLogEventType::HTTP2_SESSION_STALLED_MAX_STREAMS:
|
||||
case NetLogEventType::HTTP2_STREAM_FLOW_CONTROL_UNSTALLED:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
const char* source_type = NetLog::SourceTypeToString(entry.source.type);
|
||||
const char* event_type = NetLogEventTypeToString(entry.type);
|
||||
const char* event_phase = NetLog::EventPhaseToString(entry.phase);
|
||||
base::Value params(entry.ToDict());
|
||||
std::string params_str;
|
||||
base::JSONWriter::Write(params, ¶ms_str);
|
||||
params_str.insert(0, ": ");
|
||||
|
||||
VLOG(1) << source_type << "(" << entry.source.id << "): " << event_type
|
||||
<< ": " << event_phase << params_str;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
std::unique_ptr<URLRequestContext> BuildCertURLRequestContext(NetLog* net_log) {
|
||||
URLRequestContextBuilder builder;
|
||||
|
||||
builder.DisableHttpCache();
|
||||
builder.set_net_log(net_log);
|
||||
|
||||
ProxyConfig proxy_config;
|
||||
auto proxy_service =
|
||||
ConfiguredProxyResolutionService::CreateWithoutProxyResolver(
|
||||
std::make_unique<ProxyConfigServiceFixed>(
|
||||
ProxyConfigWithAnnotation(proxy_config, kTrafficAnnotation)),
|
||||
net_log);
|
||||
proxy_service->ForceReloadProxyConfig();
|
||||
builder.set_proxy_resolution_service(std::move(proxy_service));
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
// Builds a URLRequestContext assuming there's only a single loop.
|
||||
std::unique_ptr<URLRequestContext> BuildURLRequestContext(
|
||||
const NaiveConfig& config,
|
||||
scoped_refptr<CertNetFetcherURLRequest> cert_net_fetcher,
|
||||
NetLog* net_log) {
|
||||
URLRequestContextBuilder builder;
|
||||
|
||||
builder.DisableHttpCache();
|
||||
|
||||
// Overrides HTTP/2 initial window size default values to accommodate
|
||||
// high BDP links.
|
||||
// See net/http/http_network_session.cc for the default values.
|
||||
// Alternative implementations than fixed large windows:
|
||||
// (1) Dynamic window scaling, see
|
||||
// https://github.com/dotnet/runtime/pull/54755
|
||||
// and https://grpc.io/blog/grpc-go-perf-improvements/
|
||||
// This approach estimates throughput and RTT in userspace
|
||||
// and incurs big architectural complexity.
|
||||
// (2) Obtains TCP receive windows from Linux-specific TCP_INFO.
|
||||
// This approach is not portable.
|
||||
// Security impact:
|
||||
// This use of non-default settings creates a fingerprinting feature
|
||||
// that is visible to proxy servers, though this is only exploitable
|
||||
// if the proxy servers can be MITM'd.
|
||||
|
||||
constexpr int kMaxBandwidthMBps = 125;
|
||||
constexpr double kTypicalRttSecond = 0.256;
|
||||
constexpr int kMaxBdpMB = kMaxBandwidthMBps * kTypicalRttSecond;
|
||||
|
||||
// The windows size should be twice the BDP because WINDOW_UPDATEs
|
||||
// are sent after half the window is unacknowledged.
|
||||
constexpr int kTypicalWindow = kMaxBdpMB * 2 * 1024 * 1024;
|
||||
HttpNetworkSessionParams http_network_session_params;
|
||||
http_network_session_params.spdy_session_max_recv_window_size =
|
||||
kTypicalWindow * 2;
|
||||
http_network_session_params
|
||||
.http2_settings[spdy::SETTINGS_INITIAL_WINDOW_SIZE] = kTypicalWindow;
|
||||
builder.set_http_network_session_params(http_network_session_params);
|
||||
|
||||
builder.set_net_log(net_log);
|
||||
|
||||
ProxyConfig proxy_config;
|
||||
proxy_config.proxy_rules().type =
|
||||
net::ProxyConfig::ProxyRules::Type::PROXY_LIST;
|
||||
proxy_config.proxy_rules().single_proxies.SetSingleProxyChain(
|
||||
config.proxy_chain);
|
||||
LOG(INFO) << "Proxying via "
|
||||
<< proxy_config.proxy_rules().single_proxies.ToDebugString();
|
||||
auto proxy_service =
|
||||
ConfiguredProxyResolutionService::CreateWithoutProxyResolver(
|
||||
std::make_unique<ProxyConfigServiceFixed>(
|
||||
ProxyConfigWithAnnotation(proxy_config, kTrafficAnnotation)),
|
||||
net_log);
|
||||
proxy_service->ForceReloadProxyConfig();
|
||||
builder.set_proxy_resolution_service(std::move(proxy_service));
|
||||
|
||||
if (!config.host_resolver_rules.empty()) {
|
||||
builder.set_host_mapping_rules(config.host_resolver_rules);
|
||||
}
|
||||
|
||||
builder.SetCertVerifier(
|
||||
CertVerifier::CreateDefault(std::move(cert_net_fetcher)));
|
||||
|
||||
builder.set_proxy_delegate(std::make_unique<NaiveProxyDelegate>(
|
||||
config.extra_headers,
|
||||
std::vector<PaddingType>{PaddingType::kVariant1, PaddingType::kNone}));
|
||||
|
||||
if (config.no_post_quantum == true) {
|
||||
struct NoPostQuantum : public SSLConfigService {
|
||||
SSLContextConfig GetSSLContextConfig() override {
|
||||
SSLContextConfig config;
|
||||
config.post_quantum_override = false;
|
||||
return config;
|
||||
}
|
||||
|
||||
bool CanShareConnectionWithClientCerts(std::string_view) const override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
builder.set_ssl_config_service(std::make_unique<NoPostQuantum>());
|
||||
}
|
||||
|
||||
auto context = builder.Build();
|
||||
|
||||
if (!config.origins_to_force_quic_on.empty()) {
|
||||
auto* quic = context->quic_context()->params();
|
||||
quic->supported_versions = {quic::ParsedQuicVersion::RFCv1()};
|
||||
quic->origins_to_force_quic_on.insert(
|
||||
config.origins_to_force_quic_on.begin(),
|
||||
config.origins_to_force_quic_on.end());
|
||||
}
|
||||
|
||||
for (const auto& [k, v] : config.auth_store) {
|
||||
auto* session = context->http_transaction_factory()->GetSession();
|
||||
auto* auth_cache = session->http_auth_cache();
|
||||
auth_cache->Add(k, HttpAuth::AUTH_PROXY,
|
||||
/*realm=*/{}, HttpAuth::AUTH_SCHEME_BASIC, {},
|
||||
/*challenge=*/"Basic", v, /*path=*/"/");
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
} // namespace
|
||||
} // namespace net
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// chrome/app/chrome_exe_main_mac.cc: main()
|
||||
#if BUILDFLAG(IS_APPLE)
|
||||
partition_alloc::EarlyMallocZoneRegistration();
|
||||
#endif
|
||||
|
||||
// content/app/content_main.cc: RunContentProcess()
|
||||
#if BUILDFLAG(IS_APPLE)
|
||||
base::apple::ScopedNSAutoreleasePool pool;
|
||||
#endif
|
||||
|
||||
// content/app/content_main.cc: RunContentProcess()
|
||||
#if BUILDFLAG(IS_APPLE) && PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
|
||||
// The static initializer function for initializing PartitionAlloc
|
||||
// InitializeDefaultMallocZoneWithPartitionAlloc() would be removed by the
|
||||
// linker if allocator_shim.o is not referenced by the following call,
|
||||
// resulting in undefined behavior of accessing uninitialized TLS
|
||||
// data in PurgeCurrentThread() when PA is enabled.
|
||||
allocator_shim::InitializeAllocatorShim();
|
||||
#endif
|
||||
|
||||
// content/app/content_main.cc: RunContentProcess()
|
||||
base::EnableTerminationOnOutOfMemory();
|
||||
|
||||
DuplicateSwitchCollector::InitInstance();
|
||||
|
||||
// content/app/content_main.cc: RunContentProcess()
|
||||
base::CommandLine::Init(argc, argv);
|
||||
|
||||
// content/app/content_main.cc: RunContentProcess()
|
||||
base::EnableTerminationOnHeapCorruption();
|
||||
|
||||
// content/app/content_main.cc: RunContentProcess()
|
||||
// content/app/content_main_runner_impl.cc: Initialize()
|
||||
base::AtExitManager exit_manager;
|
||||
|
||||
#if PA_BUILDFLAG(USE_PARTITION_ALLOC)
|
||||
std::string process_type = "";
|
||||
base::allocator::PartitionAllocSupport::Get()->ReconfigureEarlyish(
|
||||
process_type);
|
||||
#endif
|
||||
|
||||
// content/app/content_main.cc: RunContentProcess()
|
||||
// content/app/content_main_runner_impl.cc: Initialize()
|
||||
// If we are on a platform where the default allocator is overridden (e.g.
|
||||
// with PartitionAlloc on most platforms) smoke-tests that the overriding
|
||||
// logic is working correctly. If not causes a hard crash, as its unexpected
|
||||
// absence has security implications.
|
||||
#if PA_BUILDFLAG(USE_PARTITION_ALLOC)
|
||||
CHECK(base::allocator::IsAllocatorInitialized());
|
||||
#endif
|
||||
|
||||
// content/app/content_main.cc: RunContentProcess()
|
||||
// content/app/content_main_runner_impl.cc: Run()
|
||||
base::FeatureList::InitInstance("PartitionConnectionsByNetworkIsolationKey",
|
||||
std::string());
|
||||
|
||||
#if PA_BUILDFLAG(USE_PARTITION_ALLOC)
|
||||
base::allocator::PartitionAllocSupport::Get()
|
||||
->ReconfigureAfterFeatureListInit(/*process_type=*/"");
|
||||
#endif
|
||||
|
||||
base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
|
||||
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("naive");
|
||||
|
||||
#if PA_BUILDFLAG(USE_PARTITION_ALLOC)
|
||||
base::allocator::PartitionAllocSupport::Get()->ReconfigureAfterTaskRunnerInit(
|
||||
process_type);
|
||||
#endif
|
||||
|
||||
url::AddStandardScheme("quic",
|
||||
url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION);
|
||||
url::AddStandardScheme("socks",
|
||||
url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION);
|
||||
url::AddStandardScheme("redir", url::SCHEME_WITH_HOST_AND_PORT);
|
||||
net::ClientSocketPoolManager::set_max_sockets_per_pool(
|
||||
net::HttpNetworkSession::NORMAL_SOCKET_POOL,
|
||||
kDefaultMaxSocketsPerPool * kExpectedMaxUsers);
|
||||
net::ClientSocketPoolManager::set_max_sockets_per_proxy_chain(
|
||||
net::HttpNetworkSession::NORMAL_SOCKET_POOL,
|
||||
kDefaultMaxSocketsPerPool * kExpectedMaxUsers);
|
||||
net::ClientSocketPoolManager::set_max_sockets_per_group(
|
||||
net::HttpNetworkSession::NORMAL_SOCKET_POOL,
|
||||
kDefaultMaxSocketsPerGroup * kExpectedMaxUsers);
|
||||
|
||||
const auto& proc = *base::CommandLine::ForCurrentProcess();
|
||||
const auto& args = proc.GetArgs();
|
||||
base::Value::Dict config_dict;
|
||||
if (args.empty() && proc.argv().size() >= 2) {
|
||||
config_dict = GetSwitchesAsValue(proc);
|
||||
} else {
|
||||
base::FilePath config_file;
|
||||
if (!args.empty()) {
|
||||
config_file = base::FilePath(args[0]);
|
||||
} else {
|
||||
config_file = base::FilePath::FromUTF8Unsafe("config.json");
|
||||
}
|
||||
JSONFileValueDeserializer reader(config_file);
|
||||
int error_code;
|
||||
std::string error_message;
|
||||
std::unique_ptr<base::Value> value =
|
||||
reader.Deserialize(&error_code, &error_message);
|
||||
if (value == nullptr) {
|
||||
std::cerr << "Error reading " << config_file << ": (" << error_code
|
||||
<< ") " << error_message << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (const base::Value::Dict* dict = value->GetIfDict()) {
|
||||
config_dict = dict->Clone();
|
||||
}
|
||||
}
|
||||
|
||||
if (config_dict.contains("h") || config_dict.contains("help")) {
|
||||
std::cout << "Usage: naive { OPTIONS | config.json }\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
"-h, --help Show this message\n"
|
||||
"--version Print version\n"
|
||||
"--listen=<proto>://[addr][:port] [--listen=...]\n"
|
||||
" proto: socks, http\n"
|
||||
" redir (Linux only)\n"
|
||||
"--proxy=<proto>://[<user>:<pass>@]<hostname>[:<port>]\n"
|
||||
" proto: https, quic\n"
|
||||
"--insecure-concurrency=<N> Use N connections, insecure\n"
|
||||
"--extra-headers=... Extra headers split by CRLF\n"
|
||||
"--host-resolver-rules=... Resolver rules\n"
|
||||
"--resolver-range=... Redirect resolver range\n"
|
||||
"--log[=<path>] Log to stderr, or file\n"
|
||||
"--log-net-log=<path> Save NetLog\n"
|
||||
"--ssl-key-log-file=<path> Save SSL keys for Wireshark\n"
|
||||
"--no-post-quantum No post-quantum key agreement\n"
|
||||
<< std::endl;
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
if (config_dict.contains("version")) {
|
||||
std::cout << "naive " << version_info::GetVersionNumber() << std::endl;
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
net::NaiveConfig config;
|
||||
if (!config.Parse(config_dict)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
CHECK(logging::InitLogging(config.log));
|
||||
|
||||
if (!config.ssl_key_log_file.empty()) {
|
||||
net::SSLClientSocket::SetSSLKeyLogger(
|
||||
std::make_unique<net::SSLKeyLoggerImpl>(config.ssl_key_log_file));
|
||||
}
|
||||
|
||||
// The declaration order for net_log and printing_log_observer is
|
||||
// important. The destructor of PrintingLogObserver removes itself
|
||||
// from net_log, so net_log must be available for entire lifetime of
|
||||
// printing_log_observer.
|
||||
net::NetLog* net_log = net::NetLog::Get();
|
||||
std::unique_ptr<net::FileNetLogObserver> observer;
|
||||
if (!config.log_net_log.empty()) {
|
||||
observer = net::FileNetLogObserver::CreateUnbounded(
|
||||
config.log_net_log, net::NetLogCaptureMode::kDefault, GetConstants());
|
||||
observer->StartObserving(net_log);
|
||||
}
|
||||
|
||||
// Avoids net log overhead if verbose logging is disabled.
|
||||
std::unique_ptr<net::PrintingLogObserver> printing_log_observer;
|
||||
if (config.log.logging_dest != logging::LOG_NONE && VLOG_IS_ON(1)) {
|
||||
printing_log_observer = std::make_unique<net::PrintingLogObserver>();
|
||||
net_log->AddObserver(printing_log_observer.get(),
|
||||
net::NetLogCaptureMode::kDefault);
|
||||
}
|
||||
|
||||
auto cert_context = net::BuildCertURLRequestContext(net_log);
|
||||
scoped_refptr<net::CertNetFetcherURLRequest> cert_net_fetcher;
|
||||
// The builtin verifier is supported but not enabled by default on Mac,
|
||||
// falling back to CreateSystemVerifyProc() which drops the net fetcher,
|
||||
// causing a DCHECK in ~CertNetFetcherURLRequest().
|
||||
// See CertVerifier::CreateDefaultWithoutCaching() and
|
||||
// CertVerifyProc::CreateSystemVerifyProc() for the build flags.
|
||||
#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED) || BUILDFLAG(IS_FUCHSIA) || \
|
||||
BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
|
||||
cert_net_fetcher = base::MakeRefCounted<net::CertNetFetcherURLRequest>();
|
||||
cert_net_fetcher->SetURLRequestContext(cert_context.get());
|
||||
#endif
|
||||
auto context =
|
||||
net::BuildURLRequestContext(config, std::move(cert_net_fetcher), net_log);
|
||||
auto* session = context->http_transaction_factory()->GetSession();
|
||||
|
||||
std::vector<std::unique_ptr<net::NaiveProxy>> naive_proxies;
|
||||
std::unique_ptr<net::RedirectResolver> resolver;
|
||||
|
||||
for (const net::NaiveListenConfig& listen_config : config.listen) {
|
||||
auto listen_socket =
|
||||
std::make_unique<net::TCPServerSocket>(net_log, net::NetLogSource());
|
||||
|
||||
int result = listen_socket->ListenWithAddressAndPort(
|
||||
listen_config.addr, listen_config.port, kListenBackLog);
|
||||
if (result != net::OK) {
|
||||
LOG(ERROR) << "Failed to listen on "
|
||||
<< net::ToString(listen_config.protocol) << "://"
|
||||
<< listen_config.addr << " " << listen_config.port << ": "
|
||||
<< net::ErrorToShortString(result);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
LOG(INFO) << "Listening on " << net::ToString(listen_config.protocol)
|
||||
<< "://" << listen_config.addr << ":" << listen_config.port;
|
||||
|
||||
if (resolver == nullptr &&
|
||||
listen_config.protocol == net::ClientProtocol::kRedir) {
|
||||
auto resolver_socket =
|
||||
std::make_unique<net::UDPServerSocket>(net_log, net::NetLogSource());
|
||||
resolver_socket->AllowAddressReuse();
|
||||
net::IPAddress listen_addr;
|
||||
if (!listen_addr.AssignFromIPLiteral(listen_config.addr)) {
|
||||
LOG(ERROR) << "Failed to open resolver: " << listen_config.addr;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
result = resolver_socket->Listen(
|
||||
net::IPEndPoint(listen_addr, listen_config.port));
|
||||
if (result != net::OK) {
|
||||
LOG(ERROR) << "Failed to open resolver: "
|
||||
<< net::ErrorToShortString(result);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
resolver = std::make_unique<net::RedirectResolver>(
|
||||
std::move(resolver_socket), config.resolver_range,
|
||||
config.resolver_prefix);
|
||||
}
|
||||
|
||||
auto naive_proxy = std::make_unique<net::NaiveProxy>(
|
||||
std::move(listen_socket), listen_config.protocol, listen_config.user,
|
||||
listen_config.pass, config.insecure_concurrency, resolver.get(),
|
||||
session, kTrafficAnnotation,
|
||||
std::vector<net::PaddingType>{net::PaddingType::kVariant1,
|
||||
net::PaddingType::kNone});
|
||||
naive_proxies.push_back(std::move(naive_proxy));
|
||||
}
|
||||
|
||||
base::RunLoop().Run();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
207
src/net/tools/naive/naive_proxy_delegate.cc
Normal file
207
src/net/tools/naive/naive_proxy_delegate.cc
Normal file
@ -0,0 +1,207 @@
|
||||
// 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.
|
||||
#ifdef UNSAFE_BUFFERS_BUILD
|
||||
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
|
||||
#pragma allow_unsafe_buffers
|
||||
#endif
|
||||
|
||||
#include "net/tools/naive/naive_proxy_delegate.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/rand_util.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"
|
||||
#include "net/third_party/quiche/src/quiche/http2/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,
|
||||
const std::vector<PaddingType>& supported_padding_types)
|
||||
: extra_headers_(extra_headers) {
|
||||
InitializeNonindexCodes();
|
||||
|
||||
std::vector<std::string_view> 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;
|
||||
|
||||
Error NaiveProxyDelegate::OnBeforeTunnelRequest(
|
||||
const ProxyChain& proxy_chain,
|
||||
size_t chain_index,
|
||||
HttpRequestHeaders* extra_headers) {
|
||||
// Not possible to negotiate padding capability given the underlying
|
||||
// protocols.
|
||||
if (proxy_chain.is_direct())
|
||||
return OK;
|
||||
const ProxyServer& proxy_server = proxy_chain.GetProxyServer(chain_index);
|
||||
if (proxy_server.is_socks())
|
||||
return OK;
|
||||
|
||||
// Only the last server is attempted for padding
|
||||
// because proxy chaining will corrupt the padding.
|
||||
if (chain_index != proxy_chain.length() - 1)
|
||||
return OK;
|
||||
|
||||
// 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(kPaddingHeader, padding);
|
||||
|
||||
// Enables Fast Open in H2/H3 proxy client socket once the state of server
|
||||
// padding support is known.
|
||||
if (padding_type_by_server_[proxy_server].has_value()) {
|
||||
extra_headers->SetHeader("fastopen", "1");
|
||||
}
|
||||
extra_headers->MergeFrom(extra_headers_);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
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 ProxyChain& proxy_chain,
|
||||
size_t chain_index,
|
||||
const HttpResponseHeaders& response_headers) {
|
||||
// Not possible to negotiate padding capability given the underlying
|
||||
// protocols.
|
||||
if (proxy_chain.is_direct())
|
||||
return OK;
|
||||
const ProxyServer& proxy_server = proxy_chain.GetProxyServer(chain_index);
|
||||
if (proxy_server.is_socks())
|
||||
return OK;
|
||||
|
||||
// Only the last server is attempted for padding
|
||||
// because proxy chaining will corrupt the padding.
|
||||
if (chain_index != proxy_chain.length() - 1)
|
||||
return OK;
|
||||
|
||||
// Detects server padding support, even if it changes dynamically.
|
||||
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;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
std::optional<PaddingType> NaiveProxyDelegate::GetProxyChainPaddingType(
|
||||
const ProxyChain& proxy_chain) {
|
||||
// Not possible to negotiate padding capability given the underlying
|
||||
// protocols.
|
||||
if (proxy_chain.is_direct())
|
||||
return PaddingType::kNone;
|
||||
if (proxy_chain.Last().is_socks())
|
||||
return PaddingType::kNone;
|
||||
return padding_type_by_server_[proxy_chain.Last()];
|
||||
}
|
||||
|
||||
PaddingDetectorDelegate::PaddingDetectorDelegate(
|
||||
NaiveProxyDelegate* naive_proxy_delegate,
|
||||
const ProxyChain& proxy_chain,
|
||||
ClientProtocol client_protocol)
|
||||
: naive_proxy_delegate_(naive_proxy_delegate),
|
||||
proxy_chain_(proxy_chain),
|
||||
client_protocol_(client_protocol) {}
|
||||
|
||||
PaddingDetectorDelegate::~PaddingDetectorDelegate() = default;
|
||||
|
||||
void PaddingDetectorDelegate::SetClientPaddingType(PaddingType padding_type) {
|
||||
detected_client_padding_type_ = padding_type;
|
||||
}
|
||||
|
||||
std::optional<PaddingType> PaddingDetectorDelegate::GetClientPaddingType() {
|
||||
// Not possible to negotiate padding capability given the underlying
|
||||
// protocols.
|
||||
if (client_protocol_ == ClientProtocol::kSocks5) {
|
||||
return PaddingType::kNone;
|
||||
} else if (client_protocol_ == ClientProtocol::kRedir) {
|
||||
return PaddingType::kNone;
|
||||
}
|
||||
|
||||
return detected_client_padding_type_;
|
||||
}
|
||||
|
||||
std::optional<PaddingType> PaddingDetectorDelegate::GetServerPaddingType() {
|
||||
if (cached_server_padding_type_.has_value())
|
||||
return cached_server_padding_type_;
|
||||
cached_server_padding_type_ =
|
||||
naive_proxy_delegate_->GetProxyChainPaddingType(proxy_chain_);
|
||||
return cached_server_padding_type_;
|
||||
}
|
||||
|
||||
} // namespace net
|
101
src/net/tools/naive/naive_proxy_delegate.h
Normal file
101
src/net/tools/naive/naive_proxy_delegate.h
Normal file
@ -0,0 +1,101 @@
|
||||
// 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 <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/base/proxy_chain.h"
|
||||
#include "net/base/proxy_delegate.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"
|
||||
|
||||
namespace net {
|
||||
|
||||
void InitializeNonindexCodes();
|
||||
// |unique_bits| SHOULD have relatively unique values.
|
||||
void FillNonindexHeaderValue(uint64_t unique_bits, char* buf, int len);
|
||||
|
||||
class ProxyInfo;
|
||||
|
||||
class NaiveProxyDelegate : public ProxyDelegate {
|
||||
public:
|
||||
NaiveProxyDelegate(const HttpRequestHeaders& extra_headers,
|
||||
const std::vector<PaddingType>& supported_padding_types);
|
||||
~NaiveProxyDelegate() override;
|
||||
|
||||
void OnResolveProxy(const GURL& url,
|
||||
const NetworkAnonymizationKey& network_anonymization_key,
|
||||
const std::string& method,
|
||||
const ProxyRetryInfoMap& proxy_retry_info,
|
||||
ProxyInfo* result) override {}
|
||||
void OnFallback(const ProxyChain& bad_proxy, int net_error) override {}
|
||||
void OnSuccessfulRequestAfterFailures(
|
||||
const ProxyRetryInfoMap& proxy_retry_info) override {}
|
||||
|
||||
// This only affects h2 proxy client socket.
|
||||
Error OnBeforeTunnelRequest(const ProxyChain& proxy_chain,
|
||||
size_t chain_index,
|
||||
HttpRequestHeaders* extra_headers) override;
|
||||
|
||||
Error OnTunnelHeadersReceived(
|
||||
const ProxyChain& proxy_chain,
|
||||
size_t chain_index,
|
||||
const HttpResponseHeaders& response_headers) override;
|
||||
|
||||
void SetProxyResolutionService(
|
||||
ProxyResolutionService* proxy_resolution_service) override {}
|
||||
|
||||
// Returns empty if the padding type has not been negotiated.
|
||||
std::optional<PaddingType> GetProxyChainPaddingType(
|
||||
const ProxyChain& proxy_chain);
|
||||
|
||||
private:
|
||||
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 SetClientPaddingType(PaddingType padding_type) = 0;
|
||||
};
|
||||
|
||||
class PaddingDetectorDelegate : public ClientPaddingDetectorDelegate {
|
||||
public:
|
||||
PaddingDetectorDelegate(NaiveProxyDelegate* naive_proxy_delegate,
|
||||
const ProxyChain& proxy_chain,
|
||||
ClientProtocol client_protocol);
|
||||
~PaddingDetectorDelegate() override;
|
||||
|
||||
std::optional<PaddingType> GetClientPaddingType();
|
||||
std::optional<PaddingType> GetServerPaddingType();
|
||||
void SetClientPaddingType(PaddingType padding_type) override;
|
||||
|
||||
private:
|
||||
NaiveProxyDelegate* naive_proxy_delegate_;
|
||||
const ProxyChain& proxy_chain_;
|
||||
ClientProtocol client_protocol_;
|
||||
|
||||
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.
|
||||
std::optional<PaddingType> cached_server_padding_type_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
#endif // NET_TOOLS_NAIVE_NAIVE_PROXY_DELEGATE_H_
|
243
src/net/tools/naive/redirect_resolver.cc
Normal file
243
src/net/tools/naive/redirect_resolver.cc
Normal file
@ -0,0 +1,243 @@
|
||||
// Copyright 2019 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/redirect_resolver.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/task/single_thread_task_runner.h"
|
||||
#include "net/base/io_buffer.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/base/url_util.h"
|
||||
#include "net/dns/dns_names_util.h"
|
||||
#include "net/dns/dns_query.h"
|
||||
#include "net/dns/dns_response.h"
|
||||
#include "net/dns/dns_util.h"
|
||||
#include "net/socket/datagram_server_socket.h"
|
||||
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||
|
||||
namespace {
|
||||
constexpr int kUdpReadBufferSize = 1024;
|
||||
constexpr int kResolutionTtl = 60;
|
||||
constexpr int kResolutionRecycleTime = 60 * 5;
|
||||
|
||||
std::string PackedIPv4ToString(uint32_t addr) {
|
||||
return net::IPAddress(addr >> 24, addr >> 16, addr >> 8, addr).ToString();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace net {
|
||||
|
||||
Resolution::Resolution() = default;
|
||||
|
||||
Resolution::~Resolution() = default;
|
||||
|
||||
RedirectResolver::RedirectResolver(std::unique_ptr<DatagramServerSocket> socket,
|
||||
const IPAddress& range,
|
||||
size_t prefix)
|
||||
: socket_(std::move(socket)),
|
||||
range_(range),
|
||||
prefix_(prefix),
|
||||
offset_(0),
|
||||
buffer_(base::MakeRefCounted<IOBufferWithSize>(kUdpReadBufferSize)) {
|
||||
DCHECK(socket_);
|
||||
// Start accepting connections in next run loop in case when delegate is not
|
||||
// ready to get callbacks.
|
||||
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||||
FROM_HERE, base::BindOnce(&RedirectResolver::DoRead,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
RedirectResolver::~RedirectResolver() = default;
|
||||
|
||||
void RedirectResolver::DoRead() {
|
||||
for (;;) {
|
||||
int rv = socket_->RecvFrom(
|
||||
buffer_.get(), kUdpReadBufferSize, &recv_address_,
|
||||
base::BindOnce(&RedirectResolver::OnRecv, base::Unretained(this)));
|
||||
if (rv == ERR_IO_PENDING)
|
||||
return;
|
||||
rv = HandleReadResult(rv);
|
||||
if (rv == ERR_IO_PENDING)
|
||||
return;
|
||||
if (rv < 0) {
|
||||
LOG(INFO) << "DoRead: ignoring error " << ErrorToShortString(rv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RedirectResolver::OnRecv(int result) {
|
||||
int rv;
|
||||
rv = HandleReadResult(result);
|
||||
if (rv == ERR_IO_PENDING)
|
||||
return;
|
||||
if (rv < 0) {
|
||||
LOG(INFO) << "OnRecv: ignoring error " << ErrorToShortString(rv);
|
||||
}
|
||||
|
||||
DoRead();
|
||||
}
|
||||
|
||||
void RedirectResolver::OnSend(int result) {
|
||||
if (result < 0) {
|
||||
LOG(INFO) << "OnSend: ignoring error " << ErrorToShortString(result);
|
||||
}
|
||||
|
||||
DoRead();
|
||||
}
|
||||
|
||||
int RedirectResolver::HandleReadResult(int result) {
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
DnsQuery query(buffer_.get());
|
||||
if (!query.Parse(result)) {
|
||||
LOG(INFO) << "Malformed DNS query from " << recv_address_.ToString();
|
||||
return ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
auto name_or = dns_names_util::NetworkToDottedName(query.qname());
|
||||
DnsResponse response;
|
||||
absl::optional<DnsQuery> query_opt;
|
||||
query_opt.emplace(query.id(), query.qname(), query.qtype());
|
||||
if (!name_or || !IsCanonicalizedHostCompliant(name_or.value())) {
|
||||
response =
|
||||
DnsResponse(query.id(), /*is_authoritative=*/false, /*answers=*/{},
|
||||
/*authority_records=*/{}, /*additional_records=*/{},
|
||||
query_opt, dns_protocol::kRcodeFORMERR);
|
||||
} else if (query.qtype() != dns_protocol::kTypeA) {
|
||||
response =
|
||||
DnsResponse(query.id(), /*is_authoritative=*/false, /*answers=*/{},
|
||||
/*authority_records=*/{}, /*additional_records=*/{},
|
||||
query_opt, dns_protocol::kRcodeNOTIMP);
|
||||
} else {
|
||||
Resolution res;
|
||||
|
||||
const auto& name = name_or.value();
|
||||
|
||||
auto by_name_lookup = resolution_by_name_.emplace(name, resolutions_.end());
|
||||
auto by_name = by_name_lookup.first;
|
||||
bool has_name = !by_name_lookup.second;
|
||||
if (has_name) {
|
||||
auto res_it = by_name->second;
|
||||
auto by_addr = res_it->by_addr;
|
||||
uint32_t addr = res_it->addr;
|
||||
|
||||
resolutions_.erase(res_it);
|
||||
resolutions_.emplace_back();
|
||||
res_it = std::prev(resolutions_.end());
|
||||
|
||||
by_name->second = res_it;
|
||||
by_addr->second = res_it;
|
||||
res_it->addr = addr;
|
||||
res_it->name = name;
|
||||
res_it->time = base::TimeTicks::Now();
|
||||
res_it->by_name = by_name;
|
||||
res_it->by_addr = by_addr;
|
||||
} else {
|
||||
uint32_t addr = (range_.bytes()[0] << 24) | (range_.bytes()[1] << 16) |
|
||||
(range_.bytes()[2] << 8) | range_.bytes()[3];
|
||||
uint32_t subnet = ~0U >> prefix_;
|
||||
addr &= ~subnet;
|
||||
addr += offset_;
|
||||
offset_ = (offset_ + 1) & subnet;
|
||||
|
||||
auto by_addr_lookup =
|
||||
resolution_by_addr_.emplace(addr, resolutions_.end());
|
||||
auto by_addr = by_addr_lookup.first;
|
||||
bool has_addr = !by_addr_lookup.second;
|
||||
if (has_addr) {
|
||||
// Too few available addresses. Overwrites old one.
|
||||
auto res_it = by_addr->second;
|
||||
|
||||
LOG(INFO) << "Overwrite " << res_it->name << " "
|
||||
<< PackedIPv4ToString(res_it->addr) << " with " << name << " "
|
||||
<< PackedIPv4ToString(addr);
|
||||
resolution_by_name_.erase(res_it->by_name);
|
||||
resolutions_.erase(res_it);
|
||||
resolutions_.emplace_back();
|
||||
res_it = std::prev(resolutions_.end());
|
||||
|
||||
by_name->second = res_it;
|
||||
by_addr->second = res_it;
|
||||
res_it->addr = addr;
|
||||
res_it->name = name;
|
||||
res_it->time = base::TimeTicks::Now();
|
||||
res_it->by_name = by_name;
|
||||
res_it->by_addr = by_addr;
|
||||
} else {
|
||||
LOG(INFO) << "Add " << name << " " << PackedIPv4ToString(addr);
|
||||
resolutions_.emplace_back();
|
||||
auto res_it = std::prev(resolutions_.end());
|
||||
|
||||
by_name->second = res_it;
|
||||
by_addr->second = res_it;
|
||||
res_it->addr = addr;
|
||||
res_it->name = name;
|
||||
res_it->time = base::TimeTicks::Now();
|
||||
res_it->by_name = by_name;
|
||||
res_it->by_addr = by_addr;
|
||||
|
||||
// Collects garbage.
|
||||
auto now = base::TimeTicks::Now();
|
||||
for (auto it = resolutions_.begin();
|
||||
it != resolutions_.end() &&
|
||||
(now - it->time).InSeconds() > kResolutionRecycleTime;) {
|
||||
auto next = std::next(it);
|
||||
LOG(INFO) << "Drop " << it->name << " "
|
||||
<< PackedIPv4ToString(it->addr);
|
||||
resolution_by_name_.erase(it->by_name);
|
||||
resolution_by_addr_.erase(it->by_addr);
|
||||
resolutions_.erase(it);
|
||||
it = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DnsResourceRecord record;
|
||||
record.name = name;
|
||||
record.type = dns_protocol::kTypeA;
|
||||
record.klass = dns_protocol::kClassIN;
|
||||
record.ttl = kResolutionTtl;
|
||||
uint32_t addr = by_name->second->addr;
|
||||
record.SetOwnedRdata(IPAddressToPackedString(
|
||||
IPAddress(addr >> 24, addr >> 16, addr >> 8, addr)));
|
||||
response = DnsResponse(query.id(), /*is_authoritative=*/false,
|
||||
/*answers=*/{std::move(record)},
|
||||
/*authority_records=*/{}, /*additional_records=*/{},
|
||||
query_opt);
|
||||
}
|
||||
int size = response.io_buffer_size();
|
||||
if (size > buffer_->size() || !response.io_buffer()) {
|
||||
return ERR_NO_BUFFER_SPACE;
|
||||
}
|
||||
std::memcpy(buffer_->data(), response.io_buffer()->data(), size);
|
||||
|
||||
return socket_->SendTo(
|
||||
buffer_.get(), size, recv_address_,
|
||||
base::BindOnce(&RedirectResolver::OnSend, base::Unretained(this)));
|
||||
}
|
||||
|
||||
bool RedirectResolver::IsInResolvedRange(const IPAddress& address) const {
|
||||
if (!address.IsIPv4())
|
||||
return false;
|
||||
return IPAddressMatchesPrefix(address, range_, prefix_);
|
||||
}
|
||||
|
||||
std::string RedirectResolver::FindNameByAddress(
|
||||
const IPAddress& address) const {
|
||||
if (!address.IsIPv4())
|
||||
return {};
|
||||
uint32_t addr = (address.bytes()[0] << 24) | (address.bytes()[1] << 16) |
|
||||
(address.bytes()[2] << 8) | address.bytes()[3];
|
||||
auto by_addr = resolution_by_addr_.find(addr);
|
||||
if (by_addr == resolution_by_addr_.end())
|
||||
return {};
|
||||
return by_addr->second->name;
|
||||
}
|
||||
|
||||
} // namespace net
|
69
src/net/tools/naive/redirect_resolver.h
Normal file
69
src/net/tools/naive/redirect_resolver.h
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2019 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_REDIRECT_RESOLVER_H_
|
||||
#define NET_TOOLS_NAIVE_REDIRECT_RESOLVER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/time/time.h"
|
||||
#include "net/base/ip_address.h"
|
||||
#include "net/base/ip_endpoint.h"
|
||||
|
||||
namespace net {
|
||||
|
||||
class DatagramServerSocket;
|
||||
class IOBufferWithSize;
|
||||
|
||||
struct Resolution {
|
||||
Resolution();
|
||||
~Resolution();
|
||||
|
||||
uint32_t addr;
|
||||
std::string name;
|
||||
base::TimeTicks time;
|
||||
std::map<std::string, std::list<Resolution>::iterator>::iterator by_name;
|
||||
std::map<uint32_t, std::list<Resolution>::iterator>::iterator by_addr;
|
||||
};
|
||||
|
||||
class RedirectResolver {
|
||||
public:
|
||||
RedirectResolver(std::unique_ptr<DatagramServerSocket> socket,
|
||||
const IPAddress& range,
|
||||
size_t prefix);
|
||||
~RedirectResolver();
|
||||
RedirectResolver(const RedirectResolver&) = delete;
|
||||
RedirectResolver& operator=(const RedirectResolver&) = delete;
|
||||
|
||||
bool IsInResolvedRange(const IPAddress& address) const;
|
||||
std::string FindNameByAddress(const IPAddress& address) const;
|
||||
|
||||
private:
|
||||
void DoRead();
|
||||
void OnRecv(int result);
|
||||
void OnSend(int result);
|
||||
int HandleReadResult(int result);
|
||||
|
||||
std::unique_ptr<DatagramServerSocket> socket_;
|
||||
IPAddress range_;
|
||||
size_t prefix_;
|
||||
uint32_t offset_;
|
||||
scoped_refptr<IOBufferWithSize> buffer_;
|
||||
IPEndPoint recv_address_;
|
||||
|
||||
std::map<std::string, std::list<Resolution>::iterator> resolution_by_name_;
|
||||
std::map<uint32_t, std::list<Resolution>::iterator> resolution_by_addr_;
|
||||
std::list<Resolution> resolutions_;
|
||||
|
||||
base::WeakPtrFactory<RedirectResolver> weak_ptr_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
#endif // NET_TOOLS_NAIVE_REDIRECT_RESOLVER_H_
|
664
src/net/tools/naive/socks5_server_socket.cc
Normal file
664
src/net/tools/naive/socks5_server_socket.cc
Normal file
@ -0,0 +1,664 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// 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.
|
||||
#ifdef UNSAFE_BUFFERS_BUILD
|
||||
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
|
||||
#pragma allow_unsafe_buffers
|
||||
#endif
|
||||
|
||||
#include "net/tools/naive/socks5_server_socket.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/functional/callback_helpers.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/sys_byteorder.h"
|
||||
#include "net/base/ip_address.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/base/sys_addrinfo.h"
|
||||
#include "net/log/net_log.h"
|
||||
#include "net/log/net_log_event_type.h"
|
||||
|
||||
namespace net {
|
||||
|
||||
enum SocksCommandType {
|
||||
kCommandConnect = 0x01,
|
||||
kCommandBind = 0x02,
|
||||
kCommandUDPAssociate = 0x03,
|
||||
};
|
||||
|
||||
static constexpr unsigned int kGreetReadHeaderSize = 2;
|
||||
static constexpr unsigned int kAuthReadHeaderSize = 2;
|
||||
static constexpr unsigned int kReadHeaderSize = 5;
|
||||
static constexpr char kSOCKS5Version = '\x05';
|
||||
static constexpr char kSOCKS5Reserved = '\x00';
|
||||
static constexpr char kAuthMethodNone = '\x00';
|
||||
static constexpr char kAuthMethodUserPass = '\x02';
|
||||
static constexpr char kAuthMethodNoAcceptable = '\xff';
|
||||
static constexpr char kSubnegotiationVersion = '\x01';
|
||||
static constexpr char kAuthStatusSuccess = '\x00';
|
||||
static constexpr char kAuthStatusFailure = '\xff';
|
||||
static constexpr char kReplySuccess = '\x00';
|
||||
static constexpr char kReplyCommandNotSupported = '\x07';
|
||||
|
||||
static_assert(sizeof(struct in_addr) == 4, "incorrect system size of IPv4");
|
||||
static_assert(sizeof(struct in6_addr) == 16, "incorrect system size of IPv6");
|
||||
|
||||
Socks5ServerSocket::Socks5ServerSocket(
|
||||
std::unique_ptr<StreamSocket> transport_socket,
|
||||
const std::string& user,
|
||||
const std::string& pass,
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation)
|
||||
: io_callback_(base::BindRepeating(&Socks5ServerSocket::OnIOComplete,
|
||||
base::Unretained(this))),
|
||||
transport_(std::move(transport_socket)),
|
||||
next_state_(STATE_NONE),
|
||||
completed_handshake_(false),
|
||||
bytes_sent_(0),
|
||||
was_ever_used_(false),
|
||||
user_(user),
|
||||
pass_(pass),
|
||||
net_log_(transport_->NetLog()),
|
||||
traffic_annotation_(traffic_annotation) {}
|
||||
|
||||
Socks5ServerSocket::~Socks5ServerSocket() {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
const HostPortPair& Socks5ServerSocket::request_endpoint() const {
|
||||
return request_endpoint_;
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::Connect(CompletionOnceCallback callback) {
|
||||
DCHECK(transport_);
|
||||
DCHECK_EQ(STATE_NONE, next_state_);
|
||||
DCHECK(!user_callback_);
|
||||
|
||||
// If already connected, then just return OK.
|
||||
if (completed_handshake_)
|
||||
return OK;
|
||||
|
||||
net_log_.BeginEvent(NetLogEventType::SOCKS5_CONNECT);
|
||||
|
||||
next_state_ = STATE_GREET_READ;
|
||||
buffer_.clear();
|
||||
|
||||
int rv = DoLoop(OK);
|
||||
if (rv == ERR_IO_PENDING) {
|
||||
user_callback_ = std::move(callback);
|
||||
} else {
|
||||
net_log_.EndEventWithNetErrorCode(NetLogEventType::SOCKS5_CONNECT, rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void Socks5ServerSocket::Disconnect() {
|
||||
completed_handshake_ = false;
|
||||
transport_->Disconnect();
|
||||
|
||||
// Reset other states to make sure they aren't mistakenly used later.
|
||||
// These are the states initialized by Connect().
|
||||
next_state_ = STATE_NONE;
|
||||
user_callback_.Reset();
|
||||
}
|
||||
|
||||
bool Socks5ServerSocket::IsConnected() const {
|
||||
return completed_handshake_ && transport_->IsConnected();
|
||||
}
|
||||
|
||||
bool Socks5ServerSocket::IsConnectedAndIdle() const {
|
||||
return completed_handshake_ && transport_->IsConnectedAndIdle();
|
||||
}
|
||||
|
||||
const NetLogWithSource& Socks5ServerSocket::NetLog() const {
|
||||
return net_log_;
|
||||
}
|
||||
|
||||
bool Socks5ServerSocket::WasEverUsed() const {
|
||||
return was_ever_used_;
|
||||
}
|
||||
|
||||
NextProto Socks5ServerSocket::GetNegotiatedProtocol() const {
|
||||
if (transport_) {
|
||||
return transport_->GetNegotiatedProtocol();
|
||||
}
|
||||
NOTREACHED();
|
||||
return kProtoUnknown;
|
||||
}
|
||||
|
||||
bool Socks5ServerSocket::GetSSLInfo(SSLInfo* ssl_info) {
|
||||
if (transport_) {
|
||||
return transport_->GetSSLInfo(ssl_info);
|
||||
}
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t Socks5ServerSocket::GetTotalReceivedBytes() const {
|
||||
return transport_->GetTotalReceivedBytes();
|
||||
}
|
||||
|
||||
void Socks5ServerSocket::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 SOCKS handshake is complete.
|
||||
int Socks5ServerSocket::Read(IOBuffer* buf,
|
||||
int buf_len,
|
||||
CompletionOnceCallback callback) {
|
||||
DCHECK(completed_handshake_);
|
||||
DCHECK_EQ(STATE_NONE, next_state_);
|
||||
DCHECK(!user_callback_);
|
||||
DCHECK(callback);
|
||||
|
||||
int rv = transport_->Read(
|
||||
buf, buf_len,
|
||||
base::BindOnce(&Socks5ServerSocket::OnReadWriteComplete,
|
||||
base::Unretained(this), std::move(callback)));
|
||||
if (rv > 0)
|
||||
was_ever_used_ = true;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Write is called by the transport layer. This can only be done if the
|
||||
// SOCKS handshake is complete.
|
||||
int Socks5ServerSocket::Write(
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
CompletionOnceCallback callback,
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation) {
|
||||
DCHECK(completed_handshake_);
|
||||
DCHECK_EQ(STATE_NONE, next_state_);
|
||||
DCHECK(!user_callback_);
|
||||
DCHECK(callback);
|
||||
|
||||
int rv = transport_->Write(
|
||||
buf, buf_len,
|
||||
base::BindOnce(&Socks5ServerSocket::OnReadWriteComplete,
|
||||
base::Unretained(this), std::move(callback)),
|
||||
traffic_annotation);
|
||||
if (rv > 0)
|
||||
was_ever_used_ = true;
|
||||
return rv;
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::SetReceiveBufferSize(int32_t size) {
|
||||
return transport_->SetReceiveBufferSize(size);
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::SetSendBufferSize(int32_t size) {
|
||||
return transport_->SetSendBufferSize(size);
|
||||
}
|
||||
|
||||
void Socks5ServerSocket::DoCallback(int result) {
|
||||
DCHECK_NE(ERR_IO_PENDING, result);
|
||||
DCHECK(user_callback_);
|
||||
|
||||
// Since Run() may result in Read being called,
|
||||
// clear user_callback_ up front.
|
||||
std::move(user_callback_).Run(result);
|
||||
}
|
||||
|
||||
void Socks5ServerSocket::OnIOComplete(int result) {
|
||||
DCHECK_NE(STATE_NONE, next_state_);
|
||||
int rv = DoLoop(result);
|
||||
if (rv != ERR_IO_PENDING) {
|
||||
net_log_.EndEvent(NetLogEventType::SOCKS5_CONNECT);
|
||||
DoCallback(rv);
|
||||
}
|
||||
}
|
||||
|
||||
void Socks5ServerSocket::OnReadWriteComplete(CompletionOnceCallback callback,
|
||||
int result) {
|
||||
DCHECK_NE(ERR_IO_PENDING, result);
|
||||
DCHECK(callback);
|
||||
|
||||
if (result > 0)
|
||||
was_ever_used_ = true;
|
||||
std::move(callback).Run(result);
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoLoop(int last_io_result) {
|
||||
DCHECK_NE(next_state_, STATE_NONE);
|
||||
int rv = last_io_result;
|
||||
do {
|
||||
State state = next_state_;
|
||||
next_state_ = STATE_NONE;
|
||||
switch (state) {
|
||||
case STATE_GREET_READ:
|
||||
DCHECK_EQ(OK, rv);
|
||||
net_log_.BeginEvent(NetLogEventType::SOCKS5_GREET_READ);
|
||||
rv = DoGreetRead();
|
||||
break;
|
||||
case STATE_GREET_READ_COMPLETE:
|
||||
rv = DoGreetReadComplete(rv);
|
||||
net_log_.EndEventWithNetErrorCode(NetLogEventType::SOCKS5_GREET_READ,
|
||||
rv);
|
||||
break;
|
||||
case STATE_GREET_WRITE:
|
||||
DCHECK_EQ(OK, rv);
|
||||
net_log_.BeginEvent(NetLogEventType::SOCKS5_GREET_WRITE);
|
||||
rv = DoGreetWrite();
|
||||
break;
|
||||
case STATE_GREET_WRITE_COMPLETE:
|
||||
rv = DoGreetWriteComplete(rv);
|
||||
net_log_.EndEventWithNetErrorCode(NetLogEventType::SOCKS5_GREET_WRITE,
|
||||
rv);
|
||||
break;
|
||||
case STATE_AUTH_READ:
|
||||
DCHECK_EQ(OK, rv);
|
||||
rv = DoAuthRead();
|
||||
break;
|
||||
case STATE_AUTH_READ_COMPLETE:
|
||||
rv = DoAuthReadComplete(rv);
|
||||
break;
|
||||
case STATE_AUTH_WRITE:
|
||||
DCHECK_EQ(OK, rv);
|
||||
rv = DoAuthWrite();
|
||||
break;
|
||||
case STATE_AUTH_WRITE_COMPLETE:
|
||||
rv = DoAuthWriteComplete(rv);
|
||||
break;
|
||||
case STATE_HANDSHAKE_READ:
|
||||
DCHECK_EQ(OK, rv);
|
||||
net_log_.BeginEvent(NetLogEventType::SOCKS5_HANDSHAKE_READ);
|
||||
rv = DoHandshakeRead();
|
||||
break;
|
||||
case STATE_HANDSHAKE_READ_COMPLETE:
|
||||
rv = DoHandshakeReadComplete(rv);
|
||||
net_log_.EndEventWithNetErrorCode(
|
||||
NetLogEventType::SOCKS5_HANDSHAKE_READ, rv);
|
||||
break;
|
||||
case STATE_HANDSHAKE_WRITE:
|
||||
DCHECK_EQ(OK, rv);
|
||||
net_log_.BeginEvent(NetLogEventType::SOCKS5_HANDSHAKE_WRITE);
|
||||
rv = DoHandshakeWrite();
|
||||
break;
|
||||
case STATE_HANDSHAKE_WRITE_COMPLETE:
|
||||
rv = DoHandshakeWriteComplete(rv);
|
||||
net_log_.EndEventWithNetErrorCode(
|
||||
NetLogEventType::SOCKS5_HANDSHAKE_WRITE, rv);
|
||||
break;
|
||||
default:
|
||||
NOTREACHED() << "bad state";
|
||||
rv = ERR_UNEXPECTED;
|
||||
break;
|
||||
}
|
||||
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoGreetRead() {
|
||||
next_state_ = STATE_GREET_READ_COMPLETE;
|
||||
|
||||
if (buffer_.empty()) {
|
||||
read_header_size_ = kGreetReadHeaderSize;
|
||||
}
|
||||
|
||||
int handshake_buf_len = read_header_size_ - buffer_.size();
|
||||
DCHECK_LT(0, handshake_buf_len);
|
||||
handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
|
||||
return transport_->Read(handshake_buf_.get(), handshake_buf_len,
|
||||
io_callback_);
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoGreetReadComplete(int result) {
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
if (result == 0) {
|
||||
net_log_.AddEvent(
|
||||
NetLogEventType::SOCKS_UNEXPECTEDLY_CLOSED_DURING_GREETING);
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
buffer_.append(handshake_buf_->data(), result);
|
||||
|
||||
// When the first few bytes are read, check how many more are required
|
||||
// and accordingly increase them
|
||||
if (buffer_.size() == kGreetReadHeaderSize) {
|
||||
if (buffer_[0] != kSOCKS5Version) {
|
||||
net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_VERSION,
|
||||
"version", buffer_[0]);
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
int nmethods = buffer_[1];
|
||||
if (nmethods == 0) {
|
||||
net_log_.AddEvent(NetLogEventType::SOCKS_NO_REQUESTED_AUTH);
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
read_header_size_ += nmethods;
|
||||
next_state_ = STATE_GREET_READ;
|
||||
return OK;
|
||||
}
|
||||
|
||||
if (buffer_.size() == read_header_size_) {
|
||||
int nmethods = buffer_[1];
|
||||
char expected_method = kAuthMethodNone;
|
||||
if (!user_.empty() || !pass_.empty()) {
|
||||
expected_method = kAuthMethodUserPass;
|
||||
}
|
||||
void* match =
|
||||
std::memchr(&buffer_[kGreetReadHeaderSize], expected_method, nmethods);
|
||||
if (match) {
|
||||
auth_method_ = expected_method;
|
||||
} else {
|
||||
auth_method_ = kAuthMethodNoAcceptable;
|
||||
}
|
||||
buffer_.clear();
|
||||
next_state_ = STATE_GREET_WRITE;
|
||||
return OK;
|
||||
}
|
||||
|
||||
next_state_ = STATE_GREET_READ;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoGreetWrite() {
|
||||
if (buffer_.empty()) {
|
||||
const char write_data[] = {kSOCKS5Version, auth_method_};
|
||||
buffer_ = std::string(write_data, std::size(write_data));
|
||||
bytes_sent_ = 0;
|
||||
}
|
||||
|
||||
next_state_ = STATE_GREET_WRITE_COMPLETE;
|
||||
int handshake_buf_len = buffer_.size() - bytes_sent_;
|
||||
DCHECK_LT(0, handshake_buf_len);
|
||||
handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
|
||||
std::memcpy(handshake_buf_->data(), &buffer_.data()[bytes_sent_],
|
||||
handshake_buf_len);
|
||||
return transport_->Write(handshake_buf_.get(), handshake_buf_len,
|
||||
io_callback_, traffic_annotation_);
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoGreetWriteComplete(int result) {
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
bytes_sent_ += result;
|
||||
if (bytes_sent_ == buffer_.size()) {
|
||||
buffer_.clear();
|
||||
if (auth_method_ == kAuthMethodNone) {
|
||||
next_state_ = STATE_HANDSHAKE_READ;
|
||||
} else if (auth_method_ == kAuthMethodUserPass) {
|
||||
next_state_ = STATE_AUTH_READ;
|
||||
} else {
|
||||
net_log_.AddEvent(NetLogEventType::SOCKS_NO_ACCEPTABLE_AUTH);
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
} else {
|
||||
next_state_ = STATE_GREET_WRITE;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoAuthRead() {
|
||||
next_state_ = STATE_AUTH_READ_COMPLETE;
|
||||
|
||||
if (buffer_.empty()) {
|
||||
read_header_size_ = kAuthReadHeaderSize;
|
||||
}
|
||||
|
||||
int handshake_buf_len = read_header_size_ - buffer_.size();
|
||||
DCHECK_LT(0, handshake_buf_len);
|
||||
handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
|
||||
return transport_->Read(handshake_buf_.get(), handshake_buf_len,
|
||||
io_callback_);
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoAuthReadComplete(int result) {
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
if (result == 0) {
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
buffer_.append(handshake_buf_->data(), result);
|
||||
|
||||
// When the first few bytes are read, check how many more are required
|
||||
// and accordingly increase them
|
||||
if (buffer_.size() == kAuthReadHeaderSize) {
|
||||
if (buffer_[0] != kSubnegotiationVersion) {
|
||||
net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_VERSION,
|
||||
"version", buffer_[0]);
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
int username_len = buffer_[1];
|
||||
read_header_size_ += username_len + 1;
|
||||
next_state_ = STATE_AUTH_READ;
|
||||
return OK;
|
||||
}
|
||||
|
||||
if (buffer_.size() == read_header_size_) {
|
||||
int username_len = buffer_[1];
|
||||
int password_len = buffer_[kAuthReadHeaderSize + username_len];
|
||||
size_t password_offset = kAuthReadHeaderSize + username_len + 1;
|
||||
if (buffer_.size() == password_offset && password_len != 0) {
|
||||
read_header_size_ += password_len;
|
||||
next_state_ = STATE_AUTH_READ;
|
||||
return OK;
|
||||
}
|
||||
|
||||
if (buffer_.compare(kAuthReadHeaderSize, username_len, user_) == 0 &&
|
||||
buffer_.compare(password_offset, password_len, pass_) == 0) {
|
||||
auth_status_ = kAuthStatusSuccess;
|
||||
} else {
|
||||
auth_status_ = kAuthStatusFailure;
|
||||
}
|
||||
buffer_.clear();
|
||||
next_state_ = STATE_AUTH_WRITE;
|
||||
return OK;
|
||||
}
|
||||
|
||||
next_state_ = STATE_AUTH_READ;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoAuthWrite() {
|
||||
if (buffer_.empty()) {
|
||||
const char write_data[] = {kSubnegotiationVersion, auth_status_};
|
||||
buffer_ = std::string(write_data, std::size(write_data));
|
||||
bytes_sent_ = 0;
|
||||
}
|
||||
|
||||
next_state_ = STATE_AUTH_WRITE_COMPLETE;
|
||||
int handshake_buf_len = buffer_.size() - bytes_sent_;
|
||||
DCHECK_LT(0, handshake_buf_len);
|
||||
handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
|
||||
std::memcpy(handshake_buf_->data(), &buffer_.data()[bytes_sent_],
|
||||
handshake_buf_len);
|
||||
return transport_->Write(handshake_buf_.get(), handshake_buf_len,
|
||||
io_callback_, traffic_annotation_);
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoAuthWriteComplete(int result) {
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
bytes_sent_ += result;
|
||||
if (bytes_sent_ == buffer_.size()) {
|
||||
buffer_.clear();
|
||||
if (auth_status_ == kAuthStatusSuccess) {
|
||||
next_state_ = STATE_HANDSHAKE_READ;
|
||||
} else {
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
} else {
|
||||
next_state_ = STATE_AUTH_WRITE;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoHandshakeRead() {
|
||||
next_state_ = STATE_HANDSHAKE_READ_COMPLETE;
|
||||
|
||||
if (buffer_.empty()) {
|
||||
read_header_size_ = kReadHeaderSize;
|
||||
}
|
||||
|
||||
int handshake_buf_len = read_header_size_ - buffer_.size();
|
||||
DCHECK_LT(0, handshake_buf_len);
|
||||
handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
|
||||
return transport_->Read(handshake_buf_.get(), handshake_buf_len,
|
||||
io_callback_);
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoHandshakeReadComplete(int result) {
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
// The underlying socket closed unexpectedly.
|
||||
if (result == 0) {
|
||||
net_log_.AddEvent(
|
||||
NetLogEventType::SOCKS_UNEXPECTEDLY_CLOSED_DURING_HANDSHAKE);
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
buffer_.append(handshake_buf_->data(), result);
|
||||
|
||||
// When the first few bytes are read, check how many more are required
|
||||
// and accordingly increase them
|
||||
if (buffer_.size() == kReadHeaderSize) {
|
||||
if (buffer_[0] != kSOCKS5Version || buffer_[2] != kSOCKS5Reserved) {
|
||||
net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_VERSION,
|
||||
"version", buffer_[0]);
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
SocksCommandType command = static_cast<SocksCommandType>(buffer_[1]);
|
||||
if (command == kCommandConnect) {
|
||||
// The proxy replies with success immediately without first connecting
|
||||
// to the requested endpoint.
|
||||
reply_ = kReplySuccess;
|
||||
} else if (command == kCommandBind || command == kCommandUDPAssociate) {
|
||||
reply_ = kReplyCommandNotSupported;
|
||||
} else {
|
||||
net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_COMMAND,
|
||||
"commmand", buffer_[1]);
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
// We check the type of IP/Domain the server returns and accordingly
|
||||
// increase the size of the request. For domains, we need to read the
|
||||
// size of the domain, so the initial request size is upto the domain
|
||||
// size. Since for IPv4/IPv6 the size is fixed and hence no 'size' is
|
||||
// read, we substract 1 byte from the additional request size.
|
||||
address_type_ = static_cast<SocksEndPointAddressType>(buffer_[3]);
|
||||
if (address_type_ == kEndPointDomain) {
|
||||
address_size_ = static_cast<uint8_t>(buffer_[4]);
|
||||
if (address_size_ == 0) {
|
||||
net_log_.AddEvent(NetLogEventType::SOCKS_ZERO_LENGTH_DOMAIN);
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
} else if (address_type_ == kEndPointResolvedIPv4) {
|
||||
address_size_ = sizeof(struct in_addr);
|
||||
--read_header_size_;
|
||||
} else if (address_type_ == kEndPointResolvedIPv6) {
|
||||
address_size_ = sizeof(struct in6_addr);
|
||||
--read_header_size_;
|
||||
} else {
|
||||
// Aborts connection on unspecified address type.
|
||||
net_log_.AddEventWithIntParams(
|
||||
NetLogEventType::SOCKS_UNKNOWN_ADDRESS_TYPE, "address_type",
|
||||
buffer_[3]);
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
read_header_size_ += address_size_ + sizeof(uint16_t);
|
||||
next_state_ = STATE_HANDSHAKE_READ;
|
||||
return OK;
|
||||
}
|
||||
|
||||
// When the final bytes are read, setup handshake.
|
||||
if (buffer_.size() == read_header_size_) {
|
||||
size_t port_start = read_header_size_ - sizeof(uint16_t);
|
||||
uint16_t port_net;
|
||||
std::memcpy(&port_net, &buffer_[port_start], sizeof(uint16_t));
|
||||
uint16_t port_host = base::NetToHost16(port_net);
|
||||
|
||||
size_t address_start = port_start - address_size_;
|
||||
if (address_type_ == kEndPointDomain) {
|
||||
std::string domain(&buffer_[address_start], address_size_);
|
||||
request_endpoint_ = HostPortPair(domain, port_host);
|
||||
} else {
|
||||
IPAddress ip_addr(base::span<const uint8_t>{
|
||||
reinterpret_cast<const uint8_t*>(&buffer_[address_start]),
|
||||
static_cast<size_t>(address_size_)});
|
||||
IPEndPoint endpoint(ip_addr, port_host);
|
||||
request_endpoint_ = HostPortPair::FromIPEndPoint(endpoint);
|
||||
}
|
||||
buffer_.clear();
|
||||
next_state_ = STATE_HANDSHAKE_WRITE;
|
||||
return OK;
|
||||
}
|
||||
|
||||
next_state_ = STATE_HANDSHAKE_READ;
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Writes the SOCKS handshake data to the underlying socket connection.
|
||||
int Socks5ServerSocket::DoHandshakeWrite() {
|
||||
next_state_ = STATE_HANDSHAKE_WRITE_COMPLETE;
|
||||
|
||||
if (buffer_.empty()) {
|
||||
const char write_data[] = {
|
||||
// clang-format off
|
||||
kSOCKS5Version,
|
||||
reply_,
|
||||
kSOCKS5Reserved,
|
||||
kEndPointResolvedIPv4,
|
||||
0x00, 0x00, 0x00, 0x00, // BND.ADDR
|
||||
0x00, 0x00, // BND.PORT
|
||||
// clang-format on
|
||||
};
|
||||
buffer_ = std::string(write_data, std::size(write_data));
|
||||
bytes_sent_ = 0;
|
||||
}
|
||||
|
||||
int handshake_buf_len = buffer_.size() - bytes_sent_;
|
||||
DCHECK_LT(0, handshake_buf_len);
|
||||
handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
|
||||
std::memcpy(handshake_buf_->data(), &buffer_[bytes_sent_], handshake_buf_len);
|
||||
return transport_->Write(handshake_buf_.get(), handshake_buf_len,
|
||||
io_callback_, traffic_annotation_);
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::DoHandshakeWriteComplete(int result) {
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
// We ignore the case when result is 0, since the underlying Write
|
||||
// may return spurious writes while waiting on the socket.
|
||||
|
||||
bytes_sent_ += result;
|
||||
if (bytes_sent_ == buffer_.size()) {
|
||||
buffer_.clear();
|
||||
if (reply_ == kReplySuccess) {
|
||||
completed_handshake_ = true;
|
||||
next_state_ = STATE_NONE;
|
||||
} else {
|
||||
net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_SERVER_ERROR,
|
||||
"error_code", reply_);
|
||||
return ERR_SOCKS_CONNECTION_FAILED;
|
||||
}
|
||||
} else {
|
||||
next_state_ = STATE_HANDSHAKE_WRITE;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::GetPeerAddress(IPEndPoint* address) const {
|
||||
return transport_->GetPeerAddress(address);
|
||||
}
|
||||
|
||||
int Socks5ServerSocket::GetLocalAddress(IPEndPoint* address) const {
|
||||
return transport_->GetLocalAddress(address);
|
||||
}
|
||||
|
||||
} // namespace net
|
165
src/net/tools/naive/socks5_server_socket.h
Normal file
165
src/net/tools/naive/socks5_server_socket.h
Normal file
@ -0,0 +1,165 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// 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_SOCKS5_SERVER_SOCKET_H_
|
||||
#define NET_TOOLS_NAIVE_SOCKS5_SERVER_SOCKET_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/scoped_refptr.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/ip_endpoint.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"
|
||||
|
||||
namespace net {
|
||||
struct NetworkTrafficAnnotationTag;
|
||||
|
||||
// This StreamSocket is used to setup a SOCKSv5 handshake with a socks client.
|
||||
// Currently no SOCKSv5 authentication is supported.
|
||||
class Socks5ServerSocket : public StreamSocket {
|
||||
public:
|
||||
Socks5ServerSocket(std::unique_ptr<StreamSocket> transport_socket,
|
||||
const std::string& user,
|
||||
const std::string& pass,
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation);
|
||||
|
||||
// On destruction Disconnect() is called.
|
||||
~Socks5ServerSocket() override;
|
||||
|
||||
Socks5ServerSocket(const Socks5ServerSocket&) = delete;
|
||||
Socks5ServerSocket& operator=(const Socks5ServerSocket&) = delete;
|
||||
|
||||
const HostPortPair& request_endpoint() const;
|
||||
|
||||
// StreamSocket implementation.
|
||||
|
||||
// Does the SOCKS handshake and completes the protocol.
|
||||
int Connect(CompletionOnceCallback callback) override;
|
||||
void Disconnect() override;
|
||||
bool IsConnected() const override;
|
||||
bool IsConnectedAndIdle() const override;
|
||||
const NetLogWithSource& NetLog() const override;
|
||||
bool WasEverUsed() const override;
|
||||
NextProto GetNegotiatedProtocol() const override;
|
||||
bool GetSSLInfo(SSLInfo* ssl_info) override;
|
||||
int64_t GetTotalReceivedBytes() const override;
|
||||
void ApplySocketTag(const SocketTag& tag) override;
|
||||
|
||||
// Socket implementation.
|
||||
int Read(IOBuffer* buf,
|
||||
int buf_len,
|
||||
CompletionOnceCallback callback) override;
|
||||
int Write(IOBuffer* buf,
|
||||
int buf_len,
|
||||
CompletionOnceCallback callback,
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation) override;
|
||||
|
||||
int SetReceiveBufferSize(int32_t size) override;
|
||||
int SetSendBufferSize(int32_t size) override;
|
||||
|
||||
int GetPeerAddress(IPEndPoint* address) const override;
|
||||
int GetLocalAddress(IPEndPoint* address) const override;
|
||||
|
||||
private:
|
||||
enum State {
|
||||
STATE_GREET_READ,
|
||||
STATE_GREET_READ_COMPLETE,
|
||||
STATE_GREET_WRITE,
|
||||
STATE_GREET_WRITE_COMPLETE,
|
||||
STATE_AUTH_READ,
|
||||
STATE_AUTH_READ_COMPLETE,
|
||||
STATE_AUTH_WRITE,
|
||||
STATE_AUTH_WRITE_COMPLETE,
|
||||
STATE_HANDSHAKE_WRITE,
|
||||
STATE_HANDSHAKE_WRITE_COMPLETE,
|
||||
STATE_HANDSHAKE_READ,
|
||||
STATE_HANDSHAKE_READ_COMPLETE,
|
||||
STATE_NONE,
|
||||
};
|
||||
|
||||
// Addressing type that can be specified in requests or responses.
|
||||
enum SocksEndPointAddressType {
|
||||
kEndPointDomain = 0x03,
|
||||
kEndPointResolvedIPv4 = 0x01,
|
||||
kEndPointResolvedIPv6 = 0x04,
|
||||
};
|
||||
|
||||
void DoCallback(int result);
|
||||
void OnIOComplete(int result);
|
||||
void OnReadWriteComplete(CompletionOnceCallback callback, int result);
|
||||
|
||||
int DoLoop(int last_io_result);
|
||||
int DoGreetRead();
|
||||
int DoGreetReadComplete(int result);
|
||||
int DoGreetWrite();
|
||||
int DoGreetWriteComplete(int result);
|
||||
int DoAuthRead();
|
||||
int DoAuthReadComplete(int result);
|
||||
int DoAuthWrite();
|
||||
int DoAuthWriteComplete(int result);
|
||||
int DoHandshakeRead();
|
||||
int DoHandshakeReadComplete(int result);
|
||||
int DoHandshakeWrite();
|
||||
int DoHandshakeWriteComplete(int result);
|
||||
|
||||
CompletionRepeatingCallback io_callback_;
|
||||
|
||||
// Stores the underlying socket.
|
||||
std::unique_ptr<StreamSocket> transport_;
|
||||
|
||||
State next_state_;
|
||||
|
||||
// Stores the callback to the layer above, called on completing Connect().
|
||||
CompletionOnceCallback user_callback_;
|
||||
|
||||
// This IOBuffer is used by the class to read and write
|
||||
// SOCKS handshake data. The length contains the expected size to
|
||||
// read or write.
|
||||
scoped_refptr<IOBuffer> handshake_buf_;
|
||||
|
||||
// While writing, this buffer stores the complete write handshake data.
|
||||
// While reading, it stores the handshake information received so far.
|
||||
std::string buffer_;
|
||||
|
||||
// This becomes true when the SOCKS handshake has completed and the
|
||||
// overlying connection is free to communicate.
|
||||
bool completed_handshake_;
|
||||
|
||||
// Contains the bytes sent by the SOCKS handshake.
|
||||
size_t bytes_sent_;
|
||||
|
||||
size_t read_header_size_;
|
||||
|
||||
bool was_ever_used_;
|
||||
|
||||
SocksEndPointAddressType address_type_;
|
||||
int address_size_;
|
||||
|
||||
std::string user_;
|
||||
std::string pass_;
|
||||
char auth_method_;
|
||||
char auth_status_;
|
||||
char reply_;
|
||||
|
||||
HostPortPair request_endpoint_;
|
||||
|
||||
NetLogWithSource net_log_;
|
||||
|
||||
// Traffic annotation for socket control.
|
||||
const NetworkTrafficAnnotationTag& traffic_annotation_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
#endif // NET_TOOLS_NAIVE_SOCKS5_SERVER_SOCKET_H_
|
Loading…
Reference in New Issue
Block a user