diff options
author | arindam@chromium.org <arindam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-23 02:59:02 +0000 |
---|---|---|
committer | arindam@chromium.org <arindam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-23 02:59:02 +0000 |
commit | 3cd172426ed848f689a3c1363ed7832179ef7919 (patch) | |
tree | 56932a311fc046c8d3551b6449066dd1bc3375e8 /net | |
parent | d9117760905f5b6b5979cba386a2b8c41fe7699c (diff) | |
download | chromium_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.cc | 67 | ||||
-rw-r--r-- | net/http/http_network_transaction.h | 5 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 109 | ||||
-rw-r--r-- | net/net.gyp | 2 | ||||
-rw-r--r-- | net/proxy/proxy_config.cc | 11 | ||||
-rw-r--r-- | net/proxy/proxy_config_unittest.cc | 11 | ||||
-rw-r--r-- | net/proxy/proxy_server.cc | 2 | ||||
-rw-r--r-- | net/proxy/proxy_server_unittest.cc | 21 | ||||
-rw-r--r-- | net/socket/socks_client_socket.cc | 390 | ||||
-rw-r--r-- | net/socket/socks_client_socket.h | 131 |
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_ + |