summaryrefslogtreecommitdiffstats
path: root/net/socket
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/socket
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/socket')
-rw-r--r--net/socket/socks_client_socket.cc390
-rw-r--r--net/socket/socks_client_socket.h131
2 files changed, 521 insertions, 0 deletions
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_
+