[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