Add initial implementation of Naive client

This commit is contained in:
klzgrad 2018-01-20 12:15:45 -05:00
parent 3d0e43fe4d
commit 45f088e27a
8 changed files with 1873 additions and 0 deletions

View File

@ -438,6 +438,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:
// {

View File

@ -0,0 +1,342 @@
// 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_connection.h"
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.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/http/http_network_session.h"
#include "net/proxy_resolution/proxy_config.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_list.h"
#include "net/proxy_resolution/proxy_resolution_service.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"
namespace net {
namespace {
static const int kBufferSize = 64 * 1024;
} // namespace
NaiveConnection::NaiveConnection(
unsigned int id,
std::unique_ptr<StreamSocket> accepted_socket,
Delegate* delegate,
const NetworkTrafficAnnotationTag& traffic_annotation)
: id_(id),
next_state_(STATE_NONE),
delegate_(delegate),
client_socket_(std::move(accepted_socket)),
server_socket_handle_(std::make_unique<ClientSocketHandle>()),
sockets_{client_socket_.get(), 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;
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() {
DCHECK(delegate_);
next_state_ = STATE_CONNECT_SERVER_COMPLETE;
return delegate_->OnConnectServer(id_, client_socket_.get(),
server_socket_handle_.get(), io_callback_);
}
int NaiveConnection::DoConnectServerComplete(int result) {
if (result < 0)
return result;
DCHECK(server_socket_handle_->socket());
sockets_[kServer] = server_socket_handle_->socket();
full_duplex_ = true;
next_state_ = STATE_NONE;
return OK;
}
int NaiveConnection::Run(CompletionOnceCallback callback) {
DCHECK(sockets_[kClient]);
DCHECK(sockets_[kServer]);
DCHECK_EQ(next_state_, STATE_NONE);
DCHECK(!connect_callback_);
if (errors_[kClient] != OK)
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::TimeDelta::FromMilliseconds(kYieldAfterDurationMilliseconds);
yield_after_time_[kServer] = yield_after_time_[kClient];
can_push_to_server_ = true;
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;
read_buffers_[from] = new IOBuffer(kBufferSize);
DCHECK(sockets_[from]);
int rv = sockets_[from]->Read(
read_buffers_[from].get(), kBufferSize,
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] = new DrainableIOBuffer(read_buffers_[from].get(), size);
write_pending_[to] = true;
DCHECK(sockets_[to]);
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);
}
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];
}
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;
}
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) {
bytes_passed_without_yielding_[from] += result;
write_buffers_[to]->DidConsume(result);
int size = write_buffers_[to]->BytesRemaining();
if (size > 0) {
Push(from, to, size);
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::TimeDelta::FromMilliseconds(kYieldAfterDurationMilliseconds);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindRepeating(&NaiveConnection::Pull,
weak_ptr_factory_.GetWeakPtr(), from, to));
} else {
Pull(from, to);
}
}
} // namespace net

View File

@ -0,0 +1,126 @@
// 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 "base/macros.h"
#include "base/memory/ref_counted.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"
namespace net {
class ClientSocketHandle;
class DrainableIOBuffer;
class IOBuffer;
class StreamSocket;
struct NetworkTrafficAnnotationTag;
class NaiveConnection {
public:
using TimeFunc = base::TimeTicks (*)();
class Delegate {
public:
Delegate() {}
virtual ~Delegate() {}
virtual int OnConnectServer(unsigned int connection_id,
const StreamSocket* accepted_socket,
ClientSocketHandle* server_socket,
CompletionRepeatingCallback callback) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(Delegate);
};
NaiveConnection(unsigned int id,
std::unique_ptr<StreamSocket> accepted_socket,
Delegate* delegate,
const NetworkTrafficAnnotationTag& traffic_annotation);
~NaiveConnection();
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,
};
// From this direction.
enum Direction {
kClient = 0,
kServer = 1,
kNumDirections = 2,
};
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_;
CompletionRepeatingCallback io_callback_;
CompletionOnceCallback connect_callback_;
CompletionOnceCallback run_callback_;
State next_state_;
Delegate* delegate_;
std::unique_ptr<StreamSocket> client_socket_;
std::unique_ptr<ClientSocketHandle> server_socket_handle_;
StreamSocket* 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};
DISALLOW_COPY_AND_ASSIGN(NaiveConnection);
};
} // namespace net
#endif // NET_TOOLS_NAIVE_NAIVE_CONNECTION_H_

View File

@ -0,0 +1,209 @@
// 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 <utility>
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "net/base/net_errors.h"
#include "net/http/http_network_session.h"
#include "net/proxy_resolution/proxy_config.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_list.h"
#include "net/proxy_resolution/proxy_resolution_service.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/socks5_server_socket.h"
namespace net {
NaiveProxy::NaiveProxy(std::unique_ptr<ServerSocket> listen_socket,
Protocol protocol,
bool use_proxy,
HttpNetworkSession* session,
const NetworkTrafficAnnotationTag& traffic_annotation)
: listen_socket_(std::move(listen_socket)),
protocol_(protocol),
use_proxy_(use_proxy),
session_(session),
net_log_(
NetLogWithSource::Make(session->net_log(), NetLogSourceType::NONE)),
last_id_(0),
traffic_annotation_(traffic_annotation) {
DCHECK(listen_socket_);
// Start accepting connections in next run loop in case when delegate is not
// ready to get callbacks.
base::ThreadTaskRunnerHandle::Get()->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: rv=" << result;
return;
}
DoConnect();
}
void NaiveProxy::DoConnect() {
std::unique_ptr<StreamSocket> socket;
if (protocol_ == kSocks5) {
socket = std::make_unique<Socks5ServerSocket>(std::move(accepted_socket_));
} else {
return;
}
auto connection_ptr = std::make_unique<NaiveConnection>(
++last_id_, std::move(socket), this, 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);
}
int NaiveProxy::OnConnectServer(unsigned int connection_id,
const StreamSocket* client_socket,
ClientSocketHandle* server_socket,
CompletionRepeatingCallback callback) {
// Ignores socket limit set by socket pool for this type of socket.
constexpr int request_load_flags = LOAD_IGNORE_LIMITS;
constexpr RequestPriority request_priority = MAXIMUM_PRIORITY;
ProxyInfo proxy_info;
SSLConfig server_ssl_config;
SSLConfig proxy_ssl_config;
if (use_proxy_) {
const auto& proxy_config = session_->proxy_resolution_service()->config();
DCHECK(proxy_config);
const ProxyList& proxy_list =
proxy_config.value().value().proxy_rules().single_proxies;
if (proxy_list.IsEmpty())
return ERR_MANDATORY_PROXY_CONFIGURATION_FAILED;
proxy_info.UseProxyList(proxy_list);
proxy_info.set_traffic_annotation(
net::MutableNetworkTrafficAnnotationTag(traffic_annotation_));
HttpRequestInfo req_info;
session_->GetSSLConfig(req_info, &server_ssl_config, &proxy_ssl_config);
proxy_ssl_config.disable_cert_verification_network_fetches = true;
}
HostPortPair request_endpoint;
if (protocol_ == kSocks5) {
const auto* socket = static_cast<const Socks5ServerSocket*>(client_socket);
request_endpoint = socket->request_endpoint();
}
if (request_endpoint.port() == 0) {
LOG(ERROR) << "Connection " << connection_id << " has invalid upstream";
return ERR_ADDRESS_INVALID;
}
LOG(INFO) << "Connection " << connection_id << " to "
<< request_endpoint.ToString();
return InitSocketHandleForRawConnect(
request_endpoint, session_, request_load_flags, request_priority,
proxy_info, server_ssl_config, proxy_ssl_config, PRIVACY_MODE_DISABLED,
net_log_, server_socket, callback);
}
void NaiveProxy::OnConnectComplete(int connection_id, int result) {
NaiveConnection* 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(int connection_id, int result) {
NaiveConnection* connection = FindConnection(connection_id);
if (!connection)
return;
HandleRunResult(connection, result);
}
void NaiveProxy::HandleRunResult(NaiveConnection* connection, int result) {
Close(connection->id(), result);
}
void NaiveProxy::Close(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::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE,
std::move(it->second));
connection_by_id_.erase(it);
}
NaiveConnection* NaiveProxy::FindConnection(int connection_id) {
auto it = connection_by_id_.find(connection_id);
if (it == connection_by_id_.end())
return nullptr;
return it->second.get();
}
// This is called after any delegate callbacks are called to check if Close()
// has been called during callback processing. Using the pointer of connection,
// |connection| is safe here because Close() deletes the connection in next run
// loop.
bool NaiveProxy::HasClosedConnection(NaiveConnection* connection) {
return FindConnection(connection->id()) != connection;
}
} // namespace net

View File

@ -0,0 +1,84 @@
// 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 "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "net/base/completion_repeating_callback.h"
#include "net/log/net_log_with_source.h"
#include "net/tools/naive/naive_connection.h"
namespace net {
class ClientSocketHandle;
class HttpNetworkSession;
class NaiveConnection;
class ServerSocket;
class StreamSocket;
struct NetworkTrafficAnnotationTag;
class NaiveProxy : public NaiveConnection::Delegate {
public:
enum Protocol {
kSocks5,
kHttp,
};
NaiveProxy(std::unique_ptr<ServerSocket> server_socket,
Protocol protocol,
bool use_proxy,
HttpNetworkSession* session,
const NetworkTrafficAnnotationTag& traffic_annotation);
~NaiveProxy() override;
int OnConnectServer(unsigned int connection_id,
const StreamSocket* accepted_socket,
ClientSocketHandle* server_socket,
CompletionRepeatingCallback callback) override;
private:
void DoAcceptLoop();
void OnAcceptComplete(int result);
void HandleAcceptResult(int result);
void DoConnect();
void OnConnectComplete(int connection_id, int result);
void HandleConnectResult(NaiveConnection* connection, int result);
void DoRun(NaiveConnection* connection);
void OnRunComplete(int connection_id, int result);
void HandleRunResult(NaiveConnection* connection, int result);
void Close(int connection_id, int reason);
NaiveConnection* FindConnection(int connection_id);
bool HasClosedConnection(NaiveConnection* connection);
std::unique_ptr<ServerSocket> listen_socket_;
Protocol protocol_;
bool use_proxy_;
HttpNetworkSession* session_;
NetLogWithSource net_log_;
unsigned int last_id_;
std::unique_ptr<StreamSocket> accepted_socket_;
std::map<unsigned int, std::unique_ptr<NaiveConnection>> connection_by_id_;
const NetworkTrafficAnnotationTag& traffic_annotation_;
base::WeakPtrFactory<NaiveProxy> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(NaiveProxy);
};
} // namespace net
#endif // NET_TOOLS_NAIVE_NAIVE_PROXY_H_

View File

@ -0,0 +1,381 @@
// 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 <limits>
#include <memory>
#include <string>
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/run_loop.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/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_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_source.h"
#include "net/log/net_log_util.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/proxy_resolution/proxy_resolution_service.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/ssl/ssl_key_logger_impl.h"
#include "net/tools/naive/naive_proxy.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"
#if defined(OS_MACOSX)
#include "base/mac/scoped_nsautorelease_pool.h"
#endif
namespace {
constexpr int kListenBackLog = 512;
constexpr int kDefaultMaxSocketsPerPool = 256;
constexpr int kDefaultMaxSocketsPerGroup = 255;
constexpr int kExpectedMaxUsers = 8;
constexpr char kDefaultHostName[] = "example";
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("naive", "");
struct Params {
std::string listen_addr;
int listen_port;
net::NaiveProxy::Protocol protocol;
bool use_proxy;
std::string proxy_url;
std::string proxy_user;
std::string proxy_pass;
std::string host_resolver_rules;
logging::LoggingSettings log_settings;
base::FilePath net_log_path;
base::FilePath ssl_key_path;
};
std::unique_ptr<base::Value> GetConstants(
const base::CommandLine::StringType& command_line_string) {
auto constants_dict = net::GetNetConstants();
DCHECK(constants_dict);
// Add a dictionary with the version of the client and its command line
// arguments.
auto dict = std::make_unique<base::DictionaryValue>();
// We have everything we need to send the right values.
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->SetString("os_type", os_type);
dict->SetString("command_line", command_line_string);
constants_dict->Set("clientInfo", std::move(dict));
return std::move(constants_dict);
}
// Builds a URLRequestContext assuming there's only a single loop.
std::unique_ptr<net::URLRequestContext> BuildURLRequestContext(
const Params& params,
net::NetLog* net_log) {
net::URLRequestContextBuilder builder;
net::ProxyConfig proxy_config;
proxy_config.proxy_rules().ParseFromString(params.proxy_url);
auto proxy_service = net::ProxyResolutionService::CreateWithoutProxyResolver(
std::make_unique<net::ProxyConfigServiceFixed>(
net::ProxyConfigWithAnnotation(proxy_config, kTrafficAnnotation)),
net_log);
proxy_service->ForceReloadProxyConfig();
builder.set_proxy_resolution_service(std::move(proxy_service));
builder.DisableHttpCache();
builder.set_net_log(net_log);
if (!params.host_resolver_rules.empty()) {
builder.set_host_mapping_rules(params.host_resolver_rules);
}
auto context = builder.Build();
net::HttpNetworkSession* session =
context->http_transaction_factory()->GetSession();
net::HttpAuthCache* auth_cache = session->http_auth_cache();
GURL auth_origin(params.proxy_url);
net::AuthCredentials credentials(base::ASCIIToUTF16(params.proxy_user),
base::ASCIIToUTF16(params.proxy_pass));
auth_cache->Add(auth_origin, net::HttpAuth::AUTH_PROXY,
/*realm=*/std::string(), net::HttpAuth::AUTH_SCHEME_BASIC,
net::NetworkIsolationKey(), /*challenge=*/"Basic",
credentials, /*path=*/"/");
return context;
}
bool ParseCommandLineFlags(Params* params) {
const base::CommandLine& line = *base::CommandLine::ForCurrentProcess();
if (line.HasSwitch("h") || line.HasSwitch("help")) {
LOG(INFO) << "Usage: naive [options]\n"
"\n"
"Options:\n"
"-h, --help Show this message\n"
"--version Print version\n"
"--addr=<address> Address to listen on (0.0.0.0)\n"
"--port=<port> Port to listen on (1080)\n"
"--proto=[socks|http] Protocol to accept (socks)\n"
"--proxy=https://<user>:<pass>@<hostname>[:<port>]\n"
" Proxy specification.\n"
"--log Log to stderr, otherwise no log\n"
"--log-net-log=<path> Save NetLog\n"
"--ssl-key-log-file=<path> Save SSL keys for Wireshark\n";
exit(EXIT_SUCCESS);
return false;
}
if (line.HasSwitch("version")) {
LOG(INFO) << "Version: " << version_info::GetVersionNumber();
exit(EXIT_SUCCESS);
return false;
}
params->listen_addr = "0.0.0.0";
if (line.HasSwitch("addr")) {
params->listen_addr = line.GetSwitchValueASCII("addr");
}
if (params->listen_addr.empty()) {
LOG(ERROR) << "Invalid --addr";
return false;
}
params->listen_port = 1080;
if (line.HasSwitch("port")) {
if (!base::StringToInt(line.GetSwitchValueASCII("port"),
&params->listen_port)) {
LOG(ERROR) << "Invalid --port";
return false;
}
if (params->listen_port <= 0 ||
params->listen_port > std::numeric_limits<uint16_t>::max()) {
LOG(ERROR) << "Invalid --port";
return false;
}
}
params->protocol = net::NaiveProxy::kSocks5;
if (line.HasSwitch("proto")) {
const auto& proto = line.GetSwitchValueASCII("proto");
if (proto == "socks") {
params->protocol = net::NaiveProxy::kSocks5;
} else if (proto == "http") {
params->protocol = net::NaiveProxy::kHttp;
} else {
LOG(ERROR) << "Invalid --proto";
return false;
}
}
GURL url(line.GetSwitchValueASCII("proxy"));
if (line.HasSwitch("proxy")) {
params->use_proxy = true;
if (!url.is_valid()) {
LOG(ERROR) << "Invalid proxy URL";
return false;
}
if (url.scheme() != "https") {
LOG(ERROR) << "Must be HTTPS proxy";
return false;
}
if (url.username().empty() || url.password().empty()) {
LOG(ERROR) << "Missing user or pass";
return false;
}
params->proxy_url = url::SchemeHostPort(url).Serialize();
params->proxy_user = url.username();
params->proxy_pass = url.password();
}
if (line.HasSwitch("host-resolver-rules")) {
params->host_resolver_rules =
line.GetSwitchValueASCII("host-resolver-rules");
} else {
// SNI should only contain DNS hostnames not IP addresses per RFC 6066.
if (url.HostIsIPAddress()) {
GURL::Replacements replacements;
replacements.SetHostStr(kDefaultHostName);
params->proxy_url =
url::SchemeHostPort(url.ReplaceComponents(replacements)).Serialize();
LOG(INFO) << "Using '" << kDefaultHostName << "' as the hostname for "
<< url.host();
params->host_resolver_rules =
std::string("MAP ") + kDefaultHostName + " " + url.host();
}
}
if (line.HasSwitch("log")) {
params->log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
} else {
params->log_settings.logging_dest = logging::LOG_NONE;
}
if (line.HasSwitch("log-net-log")) {
params->net_log_path = line.GetSwitchValuePath("log-net-log");
}
if (line.HasSwitch("ssl-key-log-file")) {
params->ssl_key_path = line.GetSwitchValuePath("ssl-key-log-file");
}
return true;
}
// NetLog::ThreadSafeObserver implementation that simply prints events
// to the logs.
class PrintingLogObserver : public net::NetLog::ThreadSafeObserver {
public:
PrintingLogObserver() = default;
~PrintingLogObserver() override {
// This is guaranteed to be safe as this program is single threaded.
net_log()->RemoveObserver(this);
}
// NetLog::ThreadSafeObserver implementation:
void OnAddEntry(const net::NetLogEntry& entry) override {
switch (entry.type) {
case net::NetLogEventType::SOCKET_POOL_STALLED_MAX_SOCKETS:
case net::NetLogEventType::SOCKET_POOL_STALLED_MAX_SOCKETS_PER_GROUP:
case net::NetLogEventType::
HTTP2_SESSION_STREAM_STALLED_BY_SESSION_SEND_WINDOW:
case net::NetLogEventType::
HTTP2_SESSION_STREAM_STALLED_BY_STREAM_SEND_WINDOW:
case net::NetLogEventType::HTTP2_SESSION_STALLED_MAX_STREAMS:
case net::NetLogEventType::HTTP2_STREAM_FLOW_CONTROL_UNSTALLED:
break;
default:
return;
}
const char* const source_type =
net::NetLog::SourceTypeToString(entry.source.type);
const char* const event_type = net::NetLog::EventTypeToString(entry.type);
const char* const event_phase =
net::NetLog::EventPhaseToString(entry.phase);
base::Value params(entry.ToValue());
std::string params_str;
base::JSONWriter::Write(params, &params_str);
params_str.insert(0, ": ");
LOG(INFO) << source_type << "(" << entry.source.id << "): " << event_type
<< ": " << event_phase << params_str;
}
private:
DISALLOW_COPY_AND_ASSIGN(PrintingLogObserver);
};
} // namespace
int main(int argc, char* argv[]) {
base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("naive");
base::AtExitManager exit_manager;
#if defined(OS_MACOSX)
base::mac::ScopedNSAutoreleasePool pool;
#endif
base::CommandLine::Init(argc, argv);
Params params;
if (!ParseCommandLineFlags(&params)) {
return EXIT_FAILURE;
}
net::ClientSocketPoolManager::set_max_sockets_per_pool(
net::HttpNetworkSession::NORMAL_SOCKET_POOL,
kDefaultMaxSocketsPerPool * kExpectedMaxUsers);
net::ClientSocketPoolManager::set_max_sockets_per_proxy_server(
net::HttpNetworkSession::NORMAL_SOCKET_POOL,
kDefaultMaxSocketsPerPool * kExpectedMaxUsers);
net::ClientSocketPoolManager::set_max_sockets_per_group(
net::HttpNetworkSession::NORMAL_SOCKET_POOL,
kDefaultMaxSocketsPerGroup * kExpectedMaxUsers);
CHECK(logging::InitLogging(params.log_settings));
if (!params.ssl_key_path.empty()) {
net::SSLClientSocket::SetSSLKeyLogger(
std::make_unique<net::SSLKeyLoggerImpl>(params.ssl_key_path));
}
// 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 (!params.net_log_path.empty()) {
const auto& cmdline =
base::CommandLine::ForCurrentProcess()->GetCommandLineString();
observer = net::FileNetLogObserver::CreateUnbounded(params.net_log_path,
GetConstants(cmdline));
observer->StartObserving(net_log, net::NetLogCaptureMode::kDefault);
}
// Avoids net log overhead if logging is disabled.
std::unique_ptr<PrintingLogObserver> printing_log_observer;
if (params.log_settings.logging_dest != logging::LOG_NONE) {
PrintingLogObserver printing_log_observer;
net_log->AddObserver(&printing_log_observer,
net::NetLogCaptureMode::kDefault);
}
auto context = BuildURLRequestContext(params, net_log);
auto listen_socket =
std::make_unique<net::TCPServerSocket>(net_log, net::NetLogSource());
int result = listen_socket->ListenWithAddressAndPort(
params.listen_addr, params.listen_port, kListenBackLog);
if (result != net::OK) {
LOG(ERROR) << "Failed to listen: " << result;
return EXIT_FAILURE;
}
net::NaiveProxy naive_proxy(
std::move(listen_socket), params.protocol, params.use_proxy,
context->http_transaction_factory()->GetSession(), kTrafficAnnotation);
base::RunLoop().Run();
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,554 @@
// 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/socks5_server_socket.h"
#include <cstring>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/sys_byteorder.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
#include "net/log/net_log.h"
#include "net/log/net_log_event_type.h"
namespace net {
const unsigned int Socks5ServerSocket::kGreetReadHeaderSize = 2;
const unsigned int Socks5ServerSocket::kReadHeaderSize = 5;
const char Socks5ServerSocket::kSOCKS5Version = '\x05';
const char Socks5ServerSocket::kSOCKS5Reserved = '\x00';
const char Socks5ServerSocket::kAuthMethodNone = '\x00';
const char Socks5ServerSocket::kAuthMethodNoAcceptable = '\xff';
const char Socks5ServerSocket::kReplySuccess = '\x00';
const char Socks5ServerSocket::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");
namespace {
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("naive", "");
} // namespace
Socks5ServerSocket::Socks5ServerSocket(
std::unique_ptr<StreamSocket> transport_socket)
: io_callback_(base::BindRepeating(&Socks5ServerSocket::OnIOComplete,
base::Unretained(this))),
transport_(std::move(transport_socket)),
next_state_(STATE_NONE),
completed_handshake_(false),
bytes_received_(0),
bytes_sent_(0),
greet_read_header_size_(kGreetReadHeaderSize),
read_header_size_(kReadHeaderSize),
was_ever_used_(false),
net_log_(transport_->NetLog()),
traffic_annotation_(kTrafficAnnotation) {}
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_;
}
bool Socks5ServerSocket::WasAlpnNegotiated() const {
if (transport_) {
return transport_->WasAlpnNegotiated();
}
NOTREACHED();
return false;
}
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;
}
void Socks5ServerSocket::GetConnectionAttempts(ConnectionAttempts* out) const {
out->clear();
}
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_WRITE,
rv);
break;
case STATE_GREET_WRITE:
DCHECK_EQ(OK, rv);
net_log_.BeginEvent(NetLogEventType::SOCKS5_GREET_READ);
rv = DoGreetWrite();
break;
case STATE_GREET_WRITE_COMPLETE:
rv = DoGreetWriteComplete(rv);
net_log_.EndEventWithNetErrorCode(NetLogEventType::SOCKS5_GREET_READ,
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()) {
DCHECK_EQ(0U, bytes_received_);
DCHECK_EQ(kGreetReadHeaderSize, greet_read_header_size_);
}
int handshake_buf_len = greet_read_header_size_ - bytes_received_;
DCHECK_LT(0, handshake_buf_len);
handshake_buf_ = new IOBuffer(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;
}
bytes_received_ += result;
buffer_.append(handshake_buf_->data(), result);
// When the first few bytes are read, check how many more are required
// and accordingly increase them
if (bytes_received_ == kGreetReadHeaderSize) {
if (buffer_[0] != kSOCKS5Version) {
net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_VERSION,
"version", buffer_[0]);
return ERR_SOCKS_CONNECTION_FAILED;
}
if (buffer_[1] == 0) {
net_log_.AddEvent(NetLogEventType::SOCKS_NO_REQUESTED_AUTH);
return ERR_SOCKS_CONNECTION_FAILED;
}
greet_read_header_size_ += buffer_[1];
next_state_ = STATE_GREET_READ;
return OK;
}
if (bytes_received_ == greet_read_header_size_) {
void* match = std::memchr(&buffer_[kGreetReadHeaderSize], kAuthMethodNone,
greet_read_header_size_ - kGreetReadHeaderSize);
if (match) {
auth_method_ = kAuthMethodNone;
} 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, base::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_ = new IOBuffer(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();
bytes_received_ = 0;
if (auth_method_ != kAuthMethodNoAcceptable) {
next_state_ = STATE_HANDSHAKE_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::DoHandshakeRead() {
next_state_ = STATE_HANDSHAKE_READ_COMPLETE;
if (buffer_.empty()) {
DCHECK_EQ(0U, bytes_received_);
DCHECK_EQ(kReadHeaderSize, read_header_size_);
}
int handshake_buf_len = read_header_size_ - bytes_received_;
DCHECK_LT(0, handshake_buf_len);
handshake_buf_ = new IOBuffer(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);
bytes_received_ += result;
// When the first few bytes are read, check how many more are required
// and accordingly increase them
if (bytes_received_ == 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 (bytes_received_ == 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(
reinterpret_cast<const uint8_t*>(&buffer_[address_start]),
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[] = {
kSOCKS5Version,
reply_,
kSOCKS5Reserved,
kEndPointResolvedIPv4,
0x00,
0x00,
0x00,
0x00, // BND.ADDR
0x00,
0x00, // BND.PORT
};
buffer_ = std::string(write_data, base::size(write_data));
bytes_sent_ = 0;
}
int handshake_buf_len = buffer_.size() - bytes_sent_;
DCHECK_LT(0, handshake_buf_len);
handshake_buf_ = new IOBuffer(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 if (bytes_sent_ < buffer_.size()) {
next_state_ = STATE_HANDSHAKE_WRITE;
} else {
NOTREACHED();
}
return OK;
}
int Socks5ServerSocket::GetPeerAddress(IPEndPoint* address) const {
return transport_->GetPeerAddress(address);
}
int Socks5ServerSocket::GetLocalAddress(IPEndPoint* address) const {
return transport_->GetLocalAddress(address);
}
} // namespace net

View File

@ -0,0 +1,172 @@
// 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/macros.h"
#include "base/memory/ref_counted.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:
explicit Socks5ServerSocket(std::unique_ptr<StreamSocket> transport_socket);
// On destruction Disconnect() is called.
~Socks5ServerSocket() override;
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;
bool WasAlpnNegotiated() const override;
NextProto GetNegotiatedProtocol() const override;
bool GetSSLInfo(SSLInfo* ssl_info) override;
void GetConnectionAttempts(ConnectionAttempts* out) const override;
void ClearConnectionAttempts() override {}
void AddConnectionAttempts(const ConnectionAttempts& attempts) 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_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,
};
enum SocksCommandType {
kCommandConnect = 0x01,
kCommandBind = 0x02,
kCommandUDPAssociate = 0x03,
};
static const unsigned int kGreetReadHeaderSize;
static const unsigned int kReadHeaderSize;
static const char kSOCKS5Version;
static const char kSOCKS5Reserved;
static const char kAuthMethodNone;
static const char kAuthMethodNoAcceptable;
static const char kReplySuccess;
static const char kReplyCommandNotSupported;
void DoCallback(int result);
void OnIOComplete(int result);
void OnReadWriteComplete(CompletionOnceCallback callback, int result);
int DoLoop(int last_io_result);
int DoGreetWrite();
int DoGreetWriteComplete(int result);
int DoGreetRead();
int DoGreetReadComplete(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_;
// These contain the bytes received / sent by the SOCKS handshake.
size_t bytes_received_;
size_t bytes_sent_;
size_t greet_read_header_size_;
size_t read_header_size_;
bool was_ever_used_;
SocksEndPointAddressType address_type_;
int address_size_;
char auth_method_;
char reply_;
HostPortPair request_endpoint_;
NetLogWithSource net_log_;
// Traffic annotation for socket control.
const NetworkTrafficAnnotationTag& traffic_annotation_;
DISALLOW_COPY_AND_ASSIGN(Socks5ServerSocket);
};
} // namespace net
#endif // NET_TOOLS_NAIVE_SOCKS5_SERVER_SOCKET_H_