Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(417)

Unified Diff: net/socket/socks_client_socket.cc

Issue 113811: Adding socks4 support for chromium. tested for windows and linux.... (Closed) Base URL: http://src.chromium.org/svn/trunk/src/
Patch Set: final patch Created 11 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/socket/socks_client_socket.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: net/socket/socks_client_socket.cc
===================================================================
--- net/socket/socks_client_socket.cc (revision 0)
+++ net/socket/socks_client_socket.cc (revision 0)
@@ -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
+
Property changes on: net\socket\socks_client_socket.cc
___________________________________________________________________
Added: svn:eol-style
+ LF
« no previous file with comments | « net/socket/socks_client_socket.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698