commit 50fff4de31168dd5214e446843955652dafaf779 Author: David Goulet dgoulet@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@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 */
tor-commits@lists.torproject.org