[tor-commits] [torsocks/master] Add SOCKS5 interface to connect

dgoulet at torproject.org dgoulet at torproject.org
Fri Apr 4 22:40:25 UTC 2014


commit 50fff4de31168dd5214e446843955652dafaf779
Author: David Goulet <dgoulet at ev0ke.net>
Date:   Tue Jun 11 21:50:56 2013 -0400

    Add SOCKS5 interface to connect
    
    This changes the connection ABI to support IPv4/IPV6.
    
    Signed-off-by: David Goulet <dgoulet at ev0ke.net>
---
 src/common/Makefile.am  |    2 +-
 src/common/connection.h |   22 ++-
 src/common/defaults.h   |    6 +
 src/common/socks5.c     |  345 +++++++++++++++++++++++++++++++++++++++++++++++
 src/common/socks5.h     |   14 +-
 5 files changed, 383 insertions(+), 6 deletions(-)

diff --git a/src/common/Makefile.am b/src/common/Makefile.am
index d2123de..ab4d166 100644
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -4,4 +4,4 @@ AM_CFLAGS = -fno-strict-aliasing
 
 noinst_LTLIBRARIES = libcommon.la
 libcommon_la_SOURCES = log.c log.h config-file.c config-file.h utils.c utils.h \
-                       compat.c compat.h
+                       compat.c compat.h socks5.c socks5.h
diff --git a/src/common/connection.h b/src/common/connection.h
index c8711ad..75c3945 100644
--- a/src/common/connection.h
+++ b/src/common/connection.h
@@ -18,18 +18,34 @@
 #ifndef TORSOCKS_CONNECTION_H
 #define TORSOCKS_CONNECTION_H
 
+#include <netinet/in.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 
+#include "defaults.h"
+
+enum connection_domain {
+	CONNECTION_DOMAIN_INET	= 1,
+	CONNECTION_DOMAIN_INET6	= 2,
+};
+
+struct connection_addr {
+	enum connection_domain domain;
+	union {
+		struct sockaddr_in sin;
+		struct sockaddr_in6 sin6;
+	} u;
+};
+
 struct connection {
 	/* Socket fd and also unique ID. */
 	int fd;
 
 	/* Location of the SOCKS5 server. */
-	struct sockaddr_in socks5_addr;
+	struct connection_addr socks5_addr;
 
-	/* Remove destination that passes through Tor. */
-	struct sockaddr_in dest_addr;
+	/* Remote destination that passes through Tor. */
+	struct connection_addr dest_addr;
 
 	/* Next connection of the linked list. */
 	struct connection *next, *prev;
diff --git a/src/common/defaults.h b/src/common/defaults.h
index 6e11e83..3f4c8d6 100644
--- a/src/common/defaults.h
+++ b/src/common/defaults.h
@@ -31,4 +31,10 @@
 #define DEFAULT_LOG_TIME_STATUS		LOG_TIME_ADD
 #define DEFAULT_LOG_LEVEL			MSGWARN
 
+/*
+ * RFC 1035 specifies a maxium of 255 possibe for domain name.
+ * (https://www.ietf.org/rfc/rfc1035.txt).
+ */
+#define DEFAULT_DOMAIN_NAME_SIZE	255
+
 #endif /* TORSOCKS_DEFAULTS_H */
diff --git a/src/common/socks5.c b/src/common/socks5.c
index 68a1d2f..817f120 100644
--- a/src/common/socks5.c
+++ b/src/common/socks5.c
@@ -15,4 +15,349 @@
  * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+#include <assert.h>
+#include <errno.h>
+
+#include <lib/torsocks.h>
+
+#include "log.h"
 #include "socks5.h"
+
+/*
+ * Receive data on a given file descriptor using recv(2). This handles partial
+ * send and EINTR.
+ *
+ * Return the number of bytes received or a negative errno error.
+ */
+static ssize_t recv_data(int fd, void *buf, size_t len)
+{
+	ssize_t ret, read_len, read_left, index;
+
+	assert(buf);
+	assert(fd >= 0);
+
+	read_left = len;
+	index = 0;
+	do {
+		read_len = recv(fd, buf + index, read_left, 0);
+		if (read_len < 0) {
+			ret = -errno;
+			if (errno == EINTR) {
+				/* Try again after interruption. */
+				continue;
+			} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
+				if (index) {
+					/* Return the number of bytes received up to this point. */
+					ret = index;
+				}
+				goto error;
+			} else {
+				PERROR("recv socks5 data");
+				goto error;
+			}
+		}
+		read_left -= read_len;
+		index += read_len;
+	} while (read_left > 0);
+
+	/* Everything was received. */
+	ret = index;
+
+error:
+	return ret;
+}
+
+/*
+ * Send data to a given file descriptor using send(2). This handles partial
+ * send and EINTR.
+ *
+ * Return the number of bytes sent or a negative errno error.
+ */
+static ssize_t send_data(int fd, const void *buf, size_t len)
+{
+	ssize_t ret, sent_len, sent_left, index;
+
+	assert(buf);
+	assert(fd >= 0);
+
+	sent_left = len;
+	index = 0;
+	do {
+		sent_len = send(fd, buf + index, sent_left, 0);
+		if (sent_len < 0) {
+			ret = -errno;
+			if (errno == EINTR) {
+				/* Send again after interruption. */
+				continue;
+			} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
+				if (index) {
+					/* Return the number of bytes sent up to this point. */
+					ret = index;
+				}
+				goto error;
+			} else {
+				PERROR("send socks5 data");
+				goto error;
+			}
+		}
+		sent_left -= sent_len;
+		index += sent_len;
+	} while (sent_left > 0);
+
+	/* Everything was sent. */
+	ret = index;
+
+error:
+	return ret;
+}
+
+/*
+ * Connect to socks5 server address in the connection object.
+ *
+ * Return 0 on success or else a negative value.
+ */
+int socks5_connect(struct connection *conn)
+{
+	int ret;
+	struct sockaddr *socks5_addr = NULL;
+
+	assert(conn);
+	assert(conn->fd >= 0);
+
+	switch (conn->socks5_addr.domain) {
+	case CONNECTION_DOMAIN_INET:
+		socks5_addr = (struct sockaddr *) &conn->socks5_addr.u.sin;
+		break;
+	case CONNECTION_DOMAIN_INET6:
+		socks5_addr = (struct sockaddr *) &conn->socks5_addr.u.sin6;
+		break;
+	default:
+		ERR("Socks5 connect domain unknown %d", conn->socks5_addr.domain);
+		assert(0);
+		ret = -EBADF;
+		goto error;
+	}
+
+	/* Use the original libc connect() to the Tor. */
+	ret = tsocks_libc_connect(conn->fd, socks5_addr, sizeof(*socks5_addr));
+	if (ret < 0) {
+		ret = -errno;
+	}
+
+error:
+	return ret;
+}
+
+/*
+ * Send socks5 method packet to server.
+ *
+ * Return 0 on success or else a negative errno value.
+ */
+int socks5_send_method(struct connection *conn)
+{
+	int ret = 0;
+	ssize_t ret_send;
+	struct socks5_method_req msg;
+
+	assert(conn);
+	assert(conn->fd >= 0);
+
+	msg.ver = SOCKS5_VERSION;
+	msg.nmethods = 0x01;
+	msg.methods = SOCKS5_NO_AUTH_METHOD;
+
+	DBG("Socks5 sending method ver: %d, nmethods 0x%02x, methods 0x%02x",
+			msg.ver, msg.nmethods, msg.methods);
+
+	ret_send = send_data(conn->fd, &msg, sizeof(msg));
+	if (ret_send < 0) {
+		ret = ret_send;
+		goto error;
+	}
+
+error:
+	return ret;
+}
+
+/*
+ * Receive socks5 method response packet from server.
+ *
+ * Return 0 on success or else a negative errno value.
+ */
+int socks5_recv_method(struct connection *conn)
+{
+	int ret;
+	ssize_t ret_recv;
+	struct socks5_method_res msg;
+
+	assert(conn);
+	assert(conn->fd >= 0);
+
+	ret_recv = recv_data(conn->fd, &msg, sizeof(msg));
+	if (ret_recv < 0) {
+		ret = ret_recv;
+		goto error;
+	}
+
+	DBG("Socks5 received method ver: %d, method 0x%02x", msg.ver, msg.method);
+
+	if (msg.ver != SOCKS5_VERSION ||
+			msg.method == SOCKS5_NO_ACCPT_METHOD) {
+		ret = -ECONNABORTED;
+		goto error;
+	}
+
+	/* Successfully received. */
+	ret = 0;
+
+error:
+	return ret;
+}
+
+/*
+ * Send a connect request to the SOCKS5 server using the given connection and
+ * the destination address in it pointing to the destination that needs to be
+ * reached through Tor.
+ *
+ * Return 0 on success or else a negative value.
+ */
+int socks5_send_connect_request(struct connection *conn)
+{
+	int ret;
+	/* Buffer to send won't go over a full TCP size. */
+	char buffer[1500];
+	ssize_t buf_len, ret_send;
+	struct socks5_request msg;
+
+	assert(conn);
+	assert(conn->fd >= 0);
+
+	memset(buffer, 0, sizeof(buffer));
+	buf_len = sizeof(msg);
+
+	msg.ver = SOCKS5_VERSION;
+	msg.cmd = SOCKS5_CMD_CONNECT;
+	/* Always zeroed. */
+	msg.rsv = 0;
+
+	/* Select connection socket domain. */
+	if (conn->dest_addr.domain == CONNECTION_DOMAIN_INET) {
+		struct socks5_request_ipv4 req_ipv4;
+
+		msg.atyp = SOCKS5_ATYP_IPV4;
+		/* Copy the first part of the request. */
+		memcpy(buffer, &msg, buf_len);
+
+		/* Prepare the ipv4 payload to be copied in the send buffer. */
+		memcpy(req_ipv4.addr, &conn->dest_addr.u.sin.sin_addr,
+				sizeof(req_ipv4.addr));
+		req_ipv4.port = conn->dest_addr.u.sin.sin_port;
+
+		/* Copy ipv4 request portion in the buffer. */
+		memcpy(buffer + buf_len, &req_ipv4, sizeof(req_ipv4));
+		buf_len += sizeof(req_ipv4);
+	} else if (conn->dest_addr.domain == CONNECTION_DOMAIN_INET6) {
+		struct socks5_request_ipv6 req_ipv6;
+
+		msg.atyp = SOCKS5_ATYP_IPV6;
+		/* Copy the first part of the request. */
+		memcpy(buffer, &msg, buf_len);
+
+		/* Prepare the ipv6 payload to be copied in the send buffer. */
+		memcpy(req_ipv6.addr, &conn->dest_addr.u.sin6.sin6_addr,
+				sizeof(req_ipv6.addr));
+		req_ipv6.port = conn->dest_addr.u.sin6.sin6_port;
+
+		/* Copy ipv6 request portion in the buffer. */
+		memcpy(buffer + buf_len, &req_ipv6, sizeof(req_ipv6));
+		buf_len += sizeof(req_ipv6);
+	} else {
+		ERR("Socks5 connection domain unknown %d", conn->dest_addr.domain);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	DBG("Socks5 sending connect request to fd %d", conn->fd);
+
+	ret_send = send_data(conn->fd, &buffer, buf_len);
+	if (ret_send < 0) {
+		ret = ret_send;
+		goto error;
+	}
+
+	/* Data was sent successfully. */
+	ret = 0;
+
+error:
+	return ret;
+}
+
+/*
+ * Receive on the given connection the SOCKS5 connect reply.
+ *
+ * Return 0 on success or else a negative value.
+ */
+int socks5_recv_connect_reply(struct connection *conn)
+{
+	int ret;
+	ssize_t ret_recv;
+	struct socks5_reply msg;
+
+	assert(conn);
+	assert(conn >= 0);
+
+	ret_recv = recv_data(conn->fd, &msg, sizeof(msg));
+	if (ret_recv < 0) {
+		ret = ret_recv;
+		goto error;
+	}
+
+	DBG("Socks5 received connect reply - ver: %d, rep: 0x%02x, atype: 0x%02x",
+			msg.ver, msg.rep, msg.atyp);
+
+	switch (msg.rep) {
+	case SOCKS5_REPLY_SUCCESS:
+		DBG("Socks5 connection is successful.");
+		ret = 0;
+		break;
+	case SOCKS5_REPLY_FAIL:
+		ERR("General SOCKS server failure");
+		ret = -ECONNREFUSED;
+		break;
+	case SOCKS5_REPLY_DENY_RULE:
+		ERR("Connection not allowed by ruleset");
+		ret = -ECONNABORTED;
+		break;
+	case SOCKS5_REPLY_NO_NET:
+		ERR("Network unreachable");
+		ret = -ENETUNREACH;
+		break;
+	case SOCKS5_REPLY_NO_HOST:
+		ERR("Host unreachable");
+		ret = -EHOSTUNREACH;
+		break;
+	case SOCKS5_REPLY_REFUSED:
+		ERR("Connection refused to Tor SOCKS");
+		ret = -ECONNREFUSED;
+		break;
+	case SOCKS5_REPLY_TTL_EXP:
+		ERR("Connection timed out");
+		ret = -ETIMEDOUT;
+		break;
+	case SOCKS5_REPLY_CMD_NOTSUP:
+		ERR("Command not supported");
+		ret = -ECONNREFUSED;
+		break;
+	case SOCKS5_REPLY_ADR_NOTSUP:
+		ERR("Address type not supported");
+		ret = -ECONNREFUSED;
+		break;
+	default:
+		ERR("Socks5 server replied an unknown code %d", msg.rep);
+		ret = -ECONNABORTED;
+		break;
+	}
+
+error:
+	return ret;
+}
diff --git a/src/common/socks5.h b/src/common/socks5.h
index b8e0e69..34935f4 100644
--- a/src/common/socks5.h
+++ b/src/common/socks5.h
@@ -32,6 +32,7 @@
  * METHOD" [00] is supported and should be used.
  */
 #define SOCKS5_NO_AUTH_METHOD	0x00
+#define SOCKS5_NO_ACCPT_METHOD	0xFF
 
 /* Request to connect. */
 #define SOCKS5_CMD_CONNECT		0x01
@@ -56,6 +57,7 @@
 struct socks5_method_req {
 	uint8_t ver;
 	uint8_t nmethods;
+	uint8_t methods;
 };
 
 /* Reply data structure for the method. */
@@ -74,14 +76,12 @@ struct socks5_request {
 
 /* IPv4 destination addr for a request. */
 struct socks5_request_ipv4 {
-	uint8_t len;
 	uint8_t addr[4];
 	uint16_t port;
 };
 
 /* IPv6 destination addr for a request. */
 struct socks5_request_ipv6 {
-	uint8_t len;
 	uint8_t addr[16];
 	uint16_t port;
 };
@@ -102,4 +102,14 @@ struct socks5_reply {
 	uint8_t atyp;
 };
 
+int socks5_connect(struct connection *conn);
+
+/* Method messaging. */
+int socks5_send_method(struct connection *conn);
+int socks5_recv_method(struct connection *conn);
+
+/* Connect request. */
+int socks5_send_connect_request(struct connection *conn);
+int socks5_recv_connect_reply(struct connection *conn);
+
 #endif /* TORSOCKS_SOCKS_H */





More information about the tor-commits mailing list