summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorarindam@chromium.org <arindam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-23 02:59:02 +0000
committerarindam@chromium.org <arindam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-23 02:59:02 +0000
commit3cd172426ed848f689a3c1363ed7832179ef7919 (patch)
tree56932a311fc046c8d3551b6449066dd1bc3375e8 /net
parentd9117760905f5b6b5979cba386a2b8c41fe7699c (diff)
downloadchromium_src-3cd172426ed848f689a3c1363ed7832179ef7919.zip
chromium_src-3cd172426ed848f689a3c1363ed7832179ef7919.tar.gz
chromium_src-3cd172426ed848f689a3c1363ed7832179ef7919.tar.bz2
Adding socks4 support for chromium. tested for windows and linux.
includes socks4, socks4a TEST=change proxy settings to use proxy and set only the socks proxy fields (others must remain blank). Use CCProxy for windows or 'ssh -D' for linux / cygwin if trying it via localhost. Browser should successfully open both http and https URLs. tests are also at HttpNetworkTransactionTest.SOCKS* BUG=none Review URL: http://codereview.chromium.org/113811 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19005 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/http/http_network_transaction.cc67
-rw-r--r--net/http/http_network_transaction.h5
-rw-r--r--net/http/http_network_transaction_unittest.cc109
-rw-r--r--net/net.gyp2
-rw-r--r--net/proxy/proxy_config.cc11
-rw-r--r--net/proxy/proxy_config_unittest.cc11
-rw-r--r--net/proxy/proxy_server.cc2
-rw-r--r--net/proxy/proxy_server_unittest.cc21
-rw-r--r--net/socket/socks_client_socket.cc390
-rw-r--r--net/socket/socks_client_socket.h131
10 files changed, 739 insertions, 10 deletions
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 06bba2b..a6b5fe3 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -26,6 +26,7 @@
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/socket/client_socket_factory.h"
+#include "net/socket/socks_client_socket.h"
#include "net/socket/ssl_client_socket.h"
using base::Time;
@@ -141,6 +142,7 @@ HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session,
using_ssl_(false),
using_proxy_(false),
using_tunnel_(false),
+ using_socks_proxy_(false),
establishing_tunnel_(false),
reading_body_from_socket_(false),
request_headers_(new RequestHeaders()),
@@ -448,6 +450,15 @@ int HttpNetworkTransaction::DoLoop(int result) {
rv = DoInitConnectionComplete(rv);
TRACE_EVENT_END("http.init_conn", request_, request_->url.spec());
break;
+ case STATE_SOCKS_CONNECT:
+ DCHECK_EQ(OK, rv);
+ TRACE_EVENT_BEGIN("http.socks_connect", request_, request_->url.spec());
+ rv = DoSOCKSConnect();
+ break;
+ case STATE_SOCKS_CONNECT_COMPLETE:
+ rv = DoSOCKSConnectComplete(rv);
+ TRACE_EVENT_END("http.socks_connect", request_, request_->url.spec());
+ break;
case STATE_SSL_CONNECT:
DCHECK_EQ(OK, rv);
TRACE_EVENT_BEGIN("http.ssl_connect", request_, request_->url.spec());
@@ -531,11 +542,10 @@ int HttpNetworkTransaction::DoResolveProxy() {
int HttpNetworkTransaction::DoResolveProxyComplete(int result) {
next_state_ = STATE_INIT_CONNECTION;
- // Since we only support HTTP proxies or DIRECT connections, remove
- // any other type of proxy from the list (i.e. SOCKS).
- // Supporting SOCKS is issue http://crbug.com/469.
+ // Remove unsupported proxies (like SOCKS5) from the list.
proxy_info_.RemoveProxiesWithoutScheme(
- ProxyServer::SCHEME_DIRECT | ProxyServer::SCHEME_HTTP);
+ ProxyServer::SCHEME_DIRECT | ProxyServer::SCHEME_HTTP |
+ ProxyServer::SCHEME_SOCKS4);
pac_request_ = NULL;
@@ -552,15 +562,17 @@ int HttpNetworkTransaction::DoInitConnection() {
next_state_ = STATE_INIT_CONNECTION_COMPLETE;
using_ssl_ = request_->url.SchemeIs("https");
- using_proxy_ = !proxy_info_.is_direct() && !using_ssl_;
- using_tunnel_ = !proxy_info_.is_direct() && using_ssl_;
+ using_socks_proxy_ = !proxy_info_.is_direct() &&
+ proxy_info_.proxy_server().is_socks();
+ using_proxy_ = !proxy_info_.is_direct() && !using_ssl_ && !using_socks_proxy_;
+ using_tunnel_ = !proxy_info_.is_direct() && using_ssl_ && !using_socks_proxy_;
// Build the string used to uniquely identify connections of this type.
// Determine the host and port to connect to.
std::string connection_group;
std::string host;
int port;
- if (using_proxy_ || using_tunnel_) {
+ if (using_proxy_ || using_tunnel_ || using_socks_proxy_) {
ProxyServer proxy_server = proxy_info_.proxy_server();
connection_group = "proxy/" + proxy_server.ToURI() + "/";
host = proxy_server.HostNoBrackets();
@@ -569,7 +581,7 @@ int HttpNetworkTransaction::DoInitConnection() {
host = request_->url.HostNoBrackets();
port = request_->url.EffectiveIntPort();
}
- if (!using_proxy_)
+ if (!using_proxy_ && !using_socks_proxy_)
connection_group.append(request_->url.GetOrigin().spec());
DCHECK(!connection_group.empty());
@@ -608,7 +620,9 @@ int HttpNetworkTransaction::DoInitConnectionComplete(int result) {
// Now we have a TCP connected socket. Perform other connection setup as
// needed.
LogTCPConnectedMetrics();
- if (using_ssl_ && !using_tunnel_) {
+ if (using_socks_proxy_)
+ next_state_ = STATE_SOCKS_CONNECT;
+ else if (using_ssl_ && !using_tunnel_) {
next_state_ = STATE_SSL_CONNECT;
} else {
next_state_ = STATE_WRITE_HEADERS;
@@ -620,6 +634,41 @@ int HttpNetworkTransaction::DoInitConnectionComplete(int result) {
return OK;
}
+int HttpNetworkTransaction::DoSOCKSConnect() {
+ DCHECK(using_socks_proxy_);
+ DCHECK(!using_proxy_);
+ DCHECK(!using_tunnel_);
+
+ next_state_ = STATE_SOCKS_CONNECT_COMPLETE;
+
+ // Add a SOCKS connection on top of our existing transport socket.
+ ClientSocket* s = connection_.release_socket();
+ HostResolver::RequestInfo req_info(request_->url.HostNoBrackets(),
+ request_->url.EffectiveIntPort());
+ req_info.set_referrer(request_->referrer);
+
+ s = new SOCKSClientSocket(s, req_info, session_->host_resolver());
+ connection_.set_socket(s);
+ return connection_.socket()->Connect(&io_callback_);
+}
+
+int HttpNetworkTransaction::DoSOCKSConnectComplete(int result) {
+ DCHECK(using_socks_proxy_);
+ DCHECK(!using_proxy_);
+ DCHECK(!using_tunnel_);
+
+ if (result == OK) {
+ if (using_ssl_) {
+ next_state_ = STATE_SSL_CONNECT;
+ } else {
+ next_state_ = STATE_WRITE_HEADERS;
+ }
+ } else {
+ result = ReconsiderProxyAfterError(result);
+ }
+ return result;
+}
+
int HttpNetworkTransaction::DoSSLConnect() {
next_state_ = STATE_SSL_CONNECT_COMPLETE;
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index 45bef58..bdb8e1b 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -99,6 +99,8 @@ class HttpNetworkTransaction : public HttpTransaction {
STATE_RESOLVE_PROXY_COMPLETE,
STATE_INIT_CONNECTION,
STATE_INIT_CONNECTION_COMPLETE,
+ STATE_SOCKS_CONNECT,
+ STATE_SOCKS_CONNECT_COMPLETE,
STATE_SSL_CONNECT,
STATE_SSL_CONNECT_COMPLETE,
STATE_WRITE_HEADERS,
@@ -128,6 +130,8 @@ class HttpNetworkTransaction : public HttpTransaction {
int DoResolveProxyComplete(int result);
int DoInitConnection();
int DoInitConnectionComplete(int result);
+ int DoSOCKSConnect();
+ int DoSOCKSConnectComplete(int result);
int DoSSLConnect();
int DoSSLConnectComplete(int result);
int DoWriteHeaders();
@@ -305,6 +309,7 @@ class HttpNetworkTransaction : public HttpTransaction {
bool using_ssl_; // True if handling a HTTPS request
bool using_proxy_; // True if using a proxy for HTTP (not HTTPS)
bool using_tunnel_; // True if using a tunnel for HTTPS
+ bool using_socks_proxy_; // True if using a SOCKS proxy
// True while establishing a tunnel. This allows the HTTP CONNECT
// request/response to reuse the STATE_WRITE_HEADERS,
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index df889cd..57ea425 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -3006,6 +3006,115 @@ TEST_F(HttpNetworkTransactionTest, BuildRequest_ExtraHeaders) {
EXPECT_EQ(OK, rv);
}
+TEST_F(HttpNetworkTransactionTest, SOCKS4_HTTP_GET) {
+ SessionDependencies session_deps;
+ session_deps.proxy_service.reset(CreateFixedProxyService(
+ "socks4://myproxy:1080"));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(
+ CreateSession(&session_deps),
+ &session_deps.socket_factory));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ char write_buffer[] = { 0x04, 0x01, 0x00, 0x50, 127, 0, 0, 1, 0 };
+ char read_buffer[] = { 0x00, 0x5A, 0x00, 0x00, 0, 0, 0, 0 };
+
+ MockWrite data_writes[] = {
+ MockWrite(true, write_buffer, 9),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n")
+ };
+
+ MockRead data_reads[] = {
+ MockWrite(true, read_buffer, 8),
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"),
+ MockRead("Payload"),
+ MockRead(false, OK)
+ };
+
+ StaticMockSocket data(data_reads, data_writes);
+ session_deps.socket_factory.AddMockSocket(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+
+ std::string response_text;
+ rv = ReadTransaction(trans.get(), &response_text);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("Payload", response_text);
+}
+
+TEST_F(HttpNetworkTransactionTest, SOCKS4_SSL_GET) {
+ SessionDependencies session_deps;
+ session_deps.proxy_service.reset(CreateFixedProxyService(
+ "socks4://myproxy:1080"));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(
+ CreateSession(&session_deps),
+ &session_deps.socket_factory));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ unsigned char write_buffer[] = { 0x04, 0x01, 0x01, 0xBB, 127, 0, 0, 1, 0 };
+ unsigned char read_buffer[] = { 0x00, 0x5A, 0x00, 0x00, 0, 0, 0, 0 };
+
+ MockWrite data_writes[] = {
+ MockWrite(true, reinterpret_cast<char*>(write_buffer), 9),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n")
+ };
+
+ MockRead data_reads[] = {
+ MockWrite(true, reinterpret_cast<char*>(read_buffer), 8),
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"),
+ MockRead("Payload"),
+ MockRead(false, OK)
+ };
+
+ StaticMockSocket data(data_reads, data_writes);
+ session_deps.socket_factory.AddMockSocket(&data);
+
+ MockSSLSocket ssl(true, OK);
+ session_deps.socket_factory.AddMockSSLSocket(&ssl);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+
+ std::string response_text;
+ rv = ReadTransaction(trans.get(), &response_text);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("Payload", response_text);
+}
+
TEST_F(HttpNetworkTransactionTest, ReconsiderProxyAfterFailedConnection) {
scoped_refptr<RuleBasedHostMapper> host_mapper(new RuleBasedHostMapper());
ScopedHostMapper scoped_host_mapper(host_mapper.get());
diff --git a/net/net.gyp b/net/net.gyp
index 4b5f76c..161c39a 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -267,6 +267,8 @@
'socket/client_socket_handle.h',
'socket/client_socket_pool.h',
'socket/socket.h',
+ 'socket/socks_client_socket.cc',
+ 'socket/socks_client_socket.h',
'socket/ssl_client_socket.h',
'socket/ssl_client_socket_mac.cc',
'socket/ssl_client_socket_nss.cc',
diff --git a/net/proxy/proxy_config.cc b/net/proxy/proxy_config.cc
index 751049b..f2ff8ce 100644
--- a/net/proxy/proxy_config.cc
+++ b/net/proxy/proxy_config.cc
@@ -56,6 +56,17 @@ void ProxyConfig::ProxyRules::ParseFromString(const std::string& proxy_rules) {
return;
}
+ // If the proxy settings has only socks and others blank,
+ // make that the default for all the proxies
+ // This gets hit only on windows when using IE settings.
+ if (url_scheme == "socks") {
+ std::string proxy_server_string = "socks://";
+ proxy_server_string.append(proxy_server_for_scheme.token());
+ single_proxy = ProxyServer::FromURI(proxy_server_string);
+ type = TYPE_SINGLE_PROXY;
+ return;
+ }
+
// Trim whitespace off the url scheme.
TrimWhitespaceASCII(url_scheme, TRIM_ALL, &url_scheme);
diff --git a/net/proxy/proxy_config_unittest.cc b/net/proxy/proxy_config_unittest.cc
index a4192df..06456834 100644
--- a/net/proxy/proxy_config_unittest.cc
+++ b/net/proxy/proxy_config_unittest.cc
@@ -170,6 +170,17 @@ TEST(ProxyConfigTest, ParseProxyRules) {
"ftp3:80",
},
+ // Only socks proxy present, others being blank.
+ { // NOLINT
+ "socks=foopy",
+
+ net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY,
+ "socks4://foopy:1080",
+ NULL,
+ NULL,
+ NULL,
+ },
+
// Include unsupported schemes -- they are discarded.
{
"crazy=foopy ; foo=bar ; https=myhttpsproxy",
diff --git a/net/proxy/proxy_server.cc b/net/proxy/proxy_server.cc
index 7475bbe..50bf4d9 100644
--- a/net/proxy/proxy_server.cc
+++ b/net/proxy/proxy_server.cc
@@ -47,6 +47,8 @@ ProxyServer::Scheme GetSchemeFromURI(std::string::const_iterator begin,
return ProxyServer::SCHEME_HTTP;
if (LowerCaseEqualsASCII(begin, end, "socks4"))
return ProxyServer::SCHEME_SOCKS4;
+ if (LowerCaseEqualsASCII(begin, end, "socks"))
+ return ProxyServer::SCHEME_SOCKS4;
if (LowerCaseEqualsASCII(begin, end, "socks5"))
return ProxyServer::SCHEME_SOCKS5;
if (LowerCaseEqualsASCII(begin, end, "direct"))
diff --git a/net/proxy/proxy_server_unittest.cc b/net/proxy/proxy_server_unittest.cc
index ad3fad5..3257851 100644
--- a/net/proxy/proxy_server_unittest.cc
+++ b/net/proxy/proxy_server_unittest.cc
@@ -125,6 +125,26 @@ TEST(ProxyServerTest, FromURI) {
"foopy:10",
"SOCKS5 foopy:10"
},
+
+ // SOCKS proxy URIs (should default to SOCKS4)
+ {
+ "socks://foopy", // No port.
+ "socks4://foopy:1080",
+ net::ProxyServer::SCHEME_SOCKS4,
+ "foopy",
+ 1080,
+ "foopy:1080",
+ "SOCKS foopy:1080"
+ },
+ {
+ "socks://foopy:10",
+ "socks4://foopy:10",
+ net::ProxyServer::SCHEME_SOCKS4,
+ "foopy",
+ 10,
+ "foopy:10",
+ "SOCKS foopy:10"
+ },
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
@@ -162,7 +182,6 @@ TEST(ProxyServerTest, Invalid) {
" ",
"dddf:", // not a valid port
"dddd:d", // not a valid port
- "socks://foopy", // not a valid scheme (needs to be socks4 or sock5).
"http://", // not a valid host/port.
"direct://xyz", // direct is not allowed a host/port.
"http:/", // ambiguous, but will fail because of bad port.
diff --git a/net/socket/socks_client_socket.cc b/net/socket/socks_client_socket.cc
new file mode 100644
index 0000000..1bcf80c
--- /dev/null
+++ b/net/socket/socks_client_socket.cc
@@ -0,0 +1,390 @@
+// Copyright (c) 2009 The Chromium Authors. 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/socket/socks_client_socket.h"
+
+#include "base/basictypes.h"
+#include "build/build_config.h"
+#if defined(OS_WIN)
+#include <ws2tcpip.h>
+#else
+#include <netdb.h>
+#endif
+#include "base/compiler_specific.h"
+#include "base/trace_event.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+// Every SOCKS server requests a user-id from the client. It is optional
+// and we send an empty string.
+static const char kEmptyUserId[] = "";
+
+// The SOCKS4a implementation suggests to use an invalid IP in case the DNS
+// resolution at client fails.
+static const uint8 kInvalidIp[] = { 0, 0, 0, 127 };
+
+// For SOCKS4, the client sends 8 bytes plus the size of the user-id.
+// For SOCKS4A, this increases to accomodate the unresolved hostname.
+static const int kWriteHeaderSize = 8;
+
+// For SOCKS4 and SOCKS4a, the server sends 8 bytes for acknowledgement.
+static const int kReadHeaderSize = 8;
+
+// Server Response codes for SOCKS.
+static const uint8 kServerResponseOk = 0x5A;
+static const uint8 kServerResponseRejected = 0x5B;
+static const uint8 kServerResponseNotReachable = 0x5C;
+static const uint8 kServerResponseMismatchedUserId = 0x5D;
+
+static const uint8 kSOCKSVersion4 = 0x04;
+static const uint8 kSOCKSStreamRequest = 0x01;
+
+// A struct holding the essential details of the SOCKS4/4a Server Request.
+// The port in the header is stored in network byte order.
+struct SOCKS4ServerRequest {
+ uint8 version;
+ uint8 command;
+ uint16 nw_port;
+ uint8 ip[4];
+};
+COMPILE_ASSERT(sizeof(SOCKS4ServerRequest) == kWriteHeaderSize,
+ socks4_server_request_struct_wrong_size);
+
+// A struct holding details of the SOCKS4/4a Server Response.
+struct SOCKS4ServerResponse {
+ uint8 reserved_null;
+ uint8 code;
+ uint16 port;
+ uint8 ip[4];
+};
+COMPILE_ASSERT(sizeof(SOCKS4ServerResponse) == kReadHeaderSize,
+ socks4_server_response_struct_wrong_size);
+
+SOCKSClientSocket::SOCKSClientSocket(ClientSocket* transport_socket,
+ const HostResolver::RequestInfo& req_info,
+ HostResolver* host_resolver)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &SOCKSClientSocket::OnIOComplete)),
+ transport_(transport_socket),
+ next_state_(STATE_NONE),
+ socks_version_(kSOCKS4Unresolved),
+ user_callback_(NULL),
+ handshake_buf_len_(0),
+ buffer_(NULL),
+ buffer_len_(0),
+ completed_handshake_(false),
+ bytes_sent_(0),
+ bytes_received_(0),
+ resolver_(host_resolver),
+ host_request_info_(req_info) {
+}
+
+SOCKSClientSocket::~SOCKSClientSocket() {
+ Disconnect();
+}
+
+int SOCKSClientSocket::Connect(CompletionCallback* callback) {
+ DCHECK(transport_.get());
+ DCHECK(transport_->IsConnected());
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(!user_callback_);
+
+ // If already connected, then just return OK.
+ if (completed_handshake_)
+ return OK;
+
+ next_state_ = STATE_RESOLVE_HOST;
+
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+void SOCKSClientSocket::Disconnect() {
+ completed_handshake_ = false;
+ transport_->Disconnect();
+}
+
+bool SOCKSClientSocket::IsConnected() const {
+ return completed_handshake_ && transport_->IsConnected();
+}
+
+bool SOCKSClientSocket::IsConnectedAndIdle() const {
+ return completed_handshake_ && transport_->IsConnectedAndIdle();
+}
+
+// Read is called by the transport layer above to read. This can only be done
+// if the SOCKS handshake is complete.
+int SOCKSClientSocket::Read(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(completed_handshake_);
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(!user_callback_);
+
+ return transport_->Read(buf, buf_len, callback);
+}
+
+// Write is called by the transport layer. This can only be done if the
+// SOCKS handshake is complete.
+int SOCKSClientSocket::Write(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(completed_handshake_);
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(!user_callback_);
+
+ return transport_->Write(buf, buf_len, callback);
+}
+
+void SOCKSClientSocket::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.
+ CompletionCallback* c = user_callback_;
+ user_callback_ = NULL;
+ DLOG(INFO) << "Finished setting up SOCKS handshake";
+ c->Run(result);
+}
+
+void SOCKSClientSocket::OnIOComplete(int result) {
+ DCHECK_NE(STATE_NONE, next_state_);
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+int SOCKSClientSocket::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_RESOLVE_HOST:
+ DCHECK_EQ(OK, rv);
+ rv = DoResolveHost();
+ break;
+ case STATE_RESOLVE_HOST_COMPLETE:
+ rv = DoResolveHostComplete(rv);
+ break;
+ case STATE_HANDSHAKE_WRITE:
+ DCHECK_EQ(OK, rv);
+ rv = DoHandshakeWrite();
+ break;
+ case STATE_HANDSHAKE_WRITE_COMPLETE:
+ rv = DoHandshakeWriteComplete(rv);
+ break;
+ case STATE_HANDSHAKE_READ:
+ DCHECK_EQ(OK, rv);
+ rv = DoHandshakeRead();
+ break;
+ case STATE_HANDSHAKE_READ_COMPLETE:
+ rv = DoHandshakeReadComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+ return rv;
+}
+
+int SOCKSClientSocket::DoResolveHost() {
+ DCHECK_EQ(kSOCKS4Unresolved, socks_version_);
+
+ next_state_ = STATE_RESOLVE_HOST_COMPLETE;
+ return resolver_.Resolve(host_request_info_, &addresses_, &io_callback_);
+}
+
+int SOCKSClientSocket::DoResolveHostComplete(int result) {
+ DCHECK_EQ(kSOCKS4Unresolved, socks_version_);
+
+ bool ok = (result == OK);
+ next_state_ = STATE_HANDSHAKE_WRITE;
+ if (ok) {
+ DCHECK(addresses_.head());
+
+ // If the host is resolved to an IPv6 address, we revert to SOCKS4a
+ // since IPv6 is unsupported by SOCKS4/4a protocol.
+ struct sockaddr *host_info = addresses_.head()->ai_addr;
+ if (host_info->sa_family == AF_INET) {
+ DLOG(INFO) << "Resolved host. Using SOCKS4 to communicate";
+ socks_version_ = kSOCKS4;
+ } else {
+ DLOG(INFO) << "Resolved host but to IPv6. Using SOCKS4a to communicate";
+ socks_version_ = kSOCKS4a;
+ }
+ } else {
+ DLOG(INFO) << "Could not resolve host. Using SOCKS4a to communicate";
+ socks_version_ = kSOCKS4a;
+ }
+
+ // Even if DNS resolution fails, we send OK since the server
+ // resolves the domain.
+ return OK;
+}
+
+// Builds the buffer that is to be sent to the server.
+// We check whether the SOCKS proxy is 4 or 4A.
+// In case it is 4A, the record size increases by size of the hostname.
+void SOCKSClientSocket::BuildHandshakeWriteBuffer() {
+ DCHECK_NE(kSOCKS4Unresolved, socks_version_);
+
+ int record_size = kWriteHeaderSize + arraysize(kEmptyUserId);
+ if (socks_version_ == kSOCKS4a) {
+ record_size += host_request_info_.hostname().size() + 1;
+ }
+
+ buffer_len_ = record_size;
+ buffer_.reset(new char[buffer_len_]);
+
+ SOCKS4ServerRequest* request =
+ reinterpret_cast<SOCKS4ServerRequest*>(buffer_.get());
+
+ request->version = kSOCKSVersion4;
+ request->command = kSOCKSStreamRequest;
+ request->nw_port = htons(host_request_info_.port());
+
+ if (socks_version_ == kSOCKS4) {
+ const struct addrinfo* ai = addresses_.head();
+ DCHECK(ai);
+ // If the sockaddr is IPv6, we have already marked the version to socks4a
+ // and so this step does not get hit.
+ struct sockaddr_in *ipv4_host =
+ reinterpret_cast<struct sockaddr_in*>(ai->ai_addr);
+ memcpy(&request->ip, &(ipv4_host->sin_addr), sizeof(ipv4_host->sin_addr));
+
+ DLOG(INFO) << "Resolved Host is : " << NetAddressToString(ai);
+ } else if (socks_version_ == kSOCKS4a) {
+ // invalid IP of the form 0.0.0.127
+ memcpy(&request->ip, kInvalidIp, arraysize(kInvalidIp));
+ } else {
+ NOTREACHED();
+ }
+
+ memcpy(&buffer_[kWriteHeaderSize], kEmptyUserId, arraysize(kEmptyUserId));
+
+ if (socks_version_ == kSOCKS4a) {
+ memcpy(&buffer_[kWriteHeaderSize + arraysize(kEmptyUserId)],
+ host_request_info_.hostname().c_str(),
+ host_request_info_.hostname().size() + 1);
+ }
+}
+
+// Writes the SOCKS handshake data to the underlying socket connection.
+int SOCKSClientSocket::DoHandshakeWrite() {
+ next_state_ = STATE_HANDSHAKE_WRITE_COMPLETE;
+
+ if (!buffer_.get()) {
+ BuildHandshakeWriteBuffer();
+ bytes_sent_ = 0;
+ }
+
+ handshake_buf_len_ = buffer_len_ - bytes_sent_;
+ DCHECK_GT(handshake_buf_len_, 0);
+ handshake_buf_ = new IOBuffer(handshake_buf_len_);
+ memcpy(handshake_buf_.get()->data(), &buffer_[bytes_sent_],
+ handshake_buf_len_);
+ return transport_->Write(handshake_buf_, handshake_buf_len_, &io_callback_);
+}
+
+int SOCKSClientSocket::DoHandshakeWriteComplete(int result) {
+ DCHECK_NE(kSOCKS4Unresolved, socks_version_);
+
+ if (result < 0)
+ return result;
+
+ bytes_sent_ += result;
+ if (bytes_sent_ == buffer_len_) {
+ next_state_ = STATE_HANDSHAKE_READ;
+ buffer_.reset(NULL);
+ } else if (bytes_sent_ < buffer_len_) {
+ next_state_ = STATE_HANDSHAKE_WRITE;
+ } else {
+ return ERR_UNEXPECTED;
+ }
+
+ return OK;
+}
+
+int SOCKSClientSocket::DoHandshakeRead() {
+ DCHECK_NE(kSOCKS4Unresolved, socks_version_);
+
+ next_state_ = STATE_HANDSHAKE_READ_COMPLETE;
+
+ if (!buffer_.get()) {
+ buffer_.reset(new char[kReadHeaderSize]);
+ buffer_len_ = kReadHeaderSize;
+ bytes_received_ = 0;
+ }
+
+ handshake_buf_len_ = buffer_len_ - bytes_received_;
+ handshake_buf_ = new IOBuffer(handshake_buf_len_);
+ return transport_->Read(handshake_buf_, handshake_buf_len_, &io_callback_);
+}
+
+int SOCKSClientSocket::DoHandshakeReadComplete(int result) {
+ DCHECK_NE(kSOCKS4Unresolved, socks_version_);
+
+ if (result < 0)
+ return result;
+ if (bytes_received_ + result > buffer_len_)
+ return ERR_INVALID_RESPONSE;
+
+ memcpy(buffer_.get() + bytes_received_, handshake_buf_->data(), result);
+ bytes_received_ += result;
+ if (bytes_received_ < buffer_len_) {
+ next_state_ = STATE_HANDSHAKE_READ;
+ return OK;
+ }
+
+ SOCKS4ServerResponse* response =
+ reinterpret_cast<SOCKS4ServerResponse*>(buffer_.get());
+
+ if (response->reserved_null != 0x00) {
+ LOG(ERROR) << "Unknown response from SOCKS server.";
+ return ERR_INVALID_RESPONSE;
+ }
+
+ // TODO(arindam): Add SOCKS specific failure codes in net_error_list.h
+ switch (response->code) {
+ case kServerResponseOk:
+ completed_handshake_ = true;
+ return OK;
+ case kServerResponseRejected:
+ LOG(ERROR) << "SOCKS request rejected or failed";
+ return ERR_FAILED;
+ case kServerResponseNotReachable:
+ LOG(ERROR) << "SOCKS request failed because client is not running "
+ << "identd (or not reachable from the server)";
+ return ERR_NAME_NOT_RESOLVED;
+ case kServerResponseMismatchedUserId:
+ LOG(ERROR) << "SOCKS request failed because client's identd could "
+ << "not confirm the user ID string in the request";
+ return ERR_FAILED;
+ default:
+ LOG(ERROR) << "SOCKS server sent unknown response";
+ return ERR_INVALID_RESPONSE;
+ }
+
+ // Note: we ignore the last 6 bytes as specified by the SOCKS protocol
+}
+
+#if defined(OS_LINUX)
+// Identical to posix system call getpeername().
+// Needed by ssl_client_socket_nss.
+int SOCKSClientSocket::GetPeerName(struct sockaddr *name, socklen_t *namelen) {
+ // Default implementation just permits some unit tests to link.
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+}
+#endif
+
+} // namespace net
+
diff --git a/net/socket/socks_client_socket.h b/net/socket/socks_client_socket.h
new file mode 100644
index 0000000..03925ba
--- /dev/null
+++ b/net/socket/socks_client_socket.h
@@ -0,0 +1,131 @@
+// Copyright (c) 2009 The Chromium Authors. 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_BASE_SOCKS_CLIENT_SOCKET_H_
+#define NET_BASE_SOCKS_CLIENT_SOCKET_H_
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_resolver.h"
+#include "net/base/net_errors.h"
+#include "net/socket/client_socket.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+namespace net {
+
+// The SOCKS client socket implementation
+class SOCKSClientSocket : public ClientSocket {
+ public:
+ // Takes ownership of the |transport_socket|, which should already be
+ // connected by the time Connect() is called.
+ //
+ // |req_info| contains the hostname and port to which the socket above will
+ // communicate to via the socks layer. For testing the referrer is optional.
+ SOCKSClientSocket(ClientSocket* transport_socket,
+ const HostResolver::RequestInfo& req_info,
+ HostResolver* host_resolver);
+
+ // On destruction Disconnect() is called.
+ virtual ~SOCKSClientSocket();
+
+ // ClientSocket methods:
+
+ // Does the SOCKS handshake and completes the protocol.
+ virtual int Connect(CompletionCallback* callback);
+ virtual void Disconnect();
+ virtual bool IsConnected() const;
+ virtual bool IsConnectedAndIdle() const;
+
+ // Socket methods:
+ virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
+ virtual int Write(IOBuffer* buf, int buf_len, CompletionCallback* callback);
+
+#if defined(OS_LINUX)
+ // Identical to posix system call getpeername().
+ // Needed by ssl_client_socket_nss.
+ virtual int GetPeerName(struct sockaddr *name, socklen_t *namelen);
+#endif
+
+ private:
+ enum State {
+ STATE_RESOLVE_HOST,
+ STATE_RESOLVE_HOST_COMPLETE,
+ STATE_HANDSHAKE_WRITE,
+ STATE_HANDSHAKE_WRITE_COMPLETE,
+ STATE_HANDSHAKE_READ,
+ STATE_HANDSHAKE_READ_COMPLETE,
+ STATE_NONE,
+ };
+
+ // The SOCKS proxy connection either has the hostname resolved via the
+ // client or via the server. This enum stores the state of the SOCKS
+ // connection. If the client can resolve the hostname, the connection is
+ // SOCKS4, otherwise it is SOCKS4A.
+ enum SocksVersion {
+ kSOCKS4Unresolved,
+ kSOCKS4,
+ kSOCKS4a,
+ };
+
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ int DoLoop(int last_io_result);
+ int DoResolveHost();
+ int DoResolveHostComplete(int result);
+ int DoHandshakeRead();
+ int DoHandshakeReadComplete(int result);
+ int DoHandshakeWrite();
+ int DoHandshakeWriteComplete(int result);
+
+ void BuildHandshakeWriteBuffer();
+
+ CompletionCallbackImpl<SOCKSClientSocket> io_callback_;
+
+ // Stores the underlying socket.
+ scoped_ptr<ClientSocket> transport_;
+
+ State next_state_;
+ SocksVersion socks_version_;
+
+ // Stores the callback to the layer above, called on completing Connect().
+ CompletionCallback* 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_;
+ int handshake_buf_len_;
+
+ // While writing, this buffer stores the complete write handshake data.
+ // While reading, it stores the handshake information received so far.
+ scoped_array<char> buffer_;
+ int buffer_len_;
+
+ // This becomes true when the SOCKS handshake has completed and the
+ // overlying connection is free to communicate.
+ bool completed_handshake_;
+
+ // These contain the bytes sent / received by the SOCKS handshake.
+ int bytes_sent_;
+ int bytes_received_;
+
+ // Used to resolve the hostname to which the SOCKS proxy will connect.
+ SingleRequestHostResolver resolver_;
+ AddressList addresses_;
+ HostResolver::RequestInfo host_request_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(SOCKSClientSocket);
+};
+
+} // namespace net
+
+#endif // NET_BASE_SOCKS_CLIENT_SOCKET_H_
+