struct buffer_t inbuf;
struct buffer_t outbuf;
io_t io; /* input/output event on this metadata connection */
- int tcplen; /* length of incoming TCPpacket */
- int sptpslen; /* length of incoming SPTPS packet */
+ uint32_t tcplen; /* length of incoming TCPpacket */
+ uint32_t sptpslen; /* length of incoming SPTPS packet */
int allow_request; /* defined if there's only one request possible */
time_t last_ping_time; /* last time we saw some activity from the other end or pinged them */
'protocol_key.c',
'protocol_misc.c',
'protocol_subnet.c',
+ 'proxy.c',
'raw_socket_device.c',
'route.c',
'subnet.c',
#include "net.h"
#include "protocol.h"
#include "utils.h"
+#include "proxy.h"
#ifndef MIN
static ssize_t MIN(ssize_t x, ssize_t y) {
}
if(!c->node) {
- if(c->outgoing && proxytype == PROXY_SOCKS4 && c->allow_request == ID) {
- if(tcpbuffer[0] == 0 && tcpbuffer[1] == 0x5a) {
- logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request granted");
- } else {
- logger(DEBUG_CONNECTIONS, LOG_ERR, "Proxy request rejected");
- return false;
- }
- } else if(c->outgoing && proxytype == PROXY_SOCKS5 && c->allow_request == ID) {
- if(tcpbuffer[0] != 5) {
- logger(DEBUG_CONNECTIONS, LOG_ERR, "Invalid response from proxy server");
- return false;
- }
-
- if(tcpbuffer[1] == (char)0xff) {
- logger(DEBUG_CONNECTIONS, LOG_ERR, "Proxy request rejected: unsuitable authentication method");
- return false;
- }
-
- if(tcpbuffer[2] != 5) {
- logger(DEBUG_CONNECTIONS, LOG_ERR, "Invalid response from proxy server");
- return false;
- }
-
- if(tcpbuffer[3] == 0) {
- logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request granted");
- } else {
- logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request rejected");
+ if(c->outgoing && c->allow_request == ID && (proxytype == PROXY_SOCKS4 || proxytype == PROXY_SOCKS5)) {
+ if(!check_socks_resp(proxytype, tcpbuffer, c->tcplen)) {
return false;
}
} else {
sockaddr2str(&c->address, &host, &port);
setenv("REMOTEADDRESS", host, true);
setenv("REMOTEPORT", port, true);
- setenv("NODE", c->name, true);
setenv("NAME", myself->name, true);
+ if(c->name) {
+ setenv("NODE", c->name, true);
+ }
+
if(netname) {
setenv("NETNAME", netname, true);
}
#include "xalloc.h"
#include "random.h"
#include "compression.h"
+#include "proxy.h"
#include "ed25519/sha512.h"
#include "keys.h"
return true;
}
- case PROXY_SOCKS4: {
- if(c->address.sa.sa_family != AF_INET) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Cannot connect to an IPv6 host through a SOCKS 4 proxy!");
- return false;
- }
-
- const size_t s4reqlen = 9 + (proxyuser ? strlen(proxyuser) : 0);
- uint8_t *s4req = alloca(s4reqlen);
- s4req[0] = 4;
- s4req[1] = 1;
- memcpy(s4req + 2, &c->address.in.sin_port, 2);
- memcpy(s4req + 4, &c->address.in.sin_addr, 4);
-
- if(proxyuser) {
- memcpy(s4req + 8, proxyuser, strlen(proxyuser));
- }
-
- s4req[s4reqlen - 1] = 0;
- c->tcplen = 8;
- return send_meta(c, s4req, s4reqlen);
- }
-
+ case PROXY_SOCKS4:
case PROXY_SOCKS5: {
- size_t len = 3 + 6 + (c->address.sa.sa_family == AF_INET ? 4 : 16);
- c->tcplen = 2;
-
- if(proxypass) {
- len += 3 + strlen(proxyuser) + strlen(proxypass);
- }
-
- uint8_t *s5req = alloca(len);
-
- size_t i = 0;
- s5req[i++] = 5;
- s5req[i++] = 1;
-
- if(proxypass) {
- s5req[i++] = 2;
- s5req[i++] = 1;
- s5req[i++] = strlen(proxyuser);
- memcpy(s5req + i, proxyuser, strlen(proxyuser));
- i += strlen(proxyuser);
- s5req[i++] = strlen(proxypass);
- memcpy(s5req + i, proxypass, strlen(proxypass));
- i += strlen(proxypass);
- c->tcplen += 2;
- } else {
- s5req[i++] = 0;
- }
-
- s5req[i++] = 5;
- s5req[i++] = 1;
- s5req[i++] = 0;
-
- if(c->address.sa.sa_family == AF_INET) {
- s5req[i++] = 1;
- memcpy(s5req + i, &c->address.in.sin_addr, 4);
- i += 4;
- memcpy(s5req + i, &c->address.in.sin_port, 2);
- i += 2;
- c->tcplen += 10;
- } else if(c->address.sa.sa_family == AF_INET6) {
- s5req[i++] = 3;
- memcpy(s5req + i, &c->address.in6.sin6_addr, 16);
- i += 16;
- memcpy(s5req + i, &c->address.in6.sin6_port, 2);
- i += 2;
- c->tcplen += 22;
- } else {
- logger(DEBUG_ALWAYS, LOG_ERR, "Address family %x not supported for SOCKS 5 proxies!", c->address.sa.sa_family);
- return false;
- }
-
- if(i > len) {
- abort();
- }
-
- return send_meta(c, s5req, len);
+ size_t reqlen = socks_req_len(proxytype, &c->address);
+ uint8_t *req = alloca(reqlen);
+ c->tcplen = create_socks_req(proxytype, req, &c->address);
+ return c->tcplen ? send_meta(c, req, reqlen) : false;
}
case PROXY_SOCKS4A:
bool tcppacket_h(connection_t *c, const char *request) {
short int len;
- if(sscanf(request, "%*d %hd", &len) != 1) {
+ if(sscanf(request, "%*d %hd", &len) != 1 || len < 0) {
logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "PACKET", c->name,
c->hostname);
return false;
bool sptps_tcppacket_h(connection_t *c, const char *request) {
short int len;
- if(sscanf(request, "%*d %hd", &len) != 1) {
+ if(sscanf(request, "%*d %hd", &len) != 1 || len < 0) {
logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "SPTPS_PACKET", c->name,
c->hostname);
return false;
--- /dev/null
+#include "system.h"
+
+#include "logger.h"
+#include "proxy.h"
+
+typedef enum socks5_auth_method_t {
+ AUTH_ANONYMOUS = 0,
+ AUTH_PASSWORD = 2,
+ AUTH_FAILED = 0xFF,
+} socks5_auth_method_t;
+
+// SOCKS 4 constants (https://en.wikipedia.org/wiki/SOCKS#SOCKS4)
+static const uint8_t SOCKS4_CMD_CONN = 1;
+static const uint8_t SOCKS4_REPLY_VERSION = 0;
+static const uint8_t SOCKS4_STATUS_OK = 0x5A;
+static const uint8_t SOCKS4_VERSION = 4;
+
+// SOCKS 5 constants (https://en.wikipedia.org/wiki/SOCKS#SOCKS5)
+typedef enum socks5_addr_type_t {
+ SOCKS5_IPV4 = 1,
+ SOCKS5_IPV6 = 4,
+} socks5_addr_type_t;
+
+static const uint8_t SOCKS5_AUTH_METHOD_NONE = 0;
+static const uint8_t SOCKS5_AUTH_METHOD_PASSWORD = 2;
+static const uint8_t SOCKS5_AUTH_OK = 0;
+static const uint8_t SOCKS5_AUTH_VERSION = 1;
+static const uint8_t SOCKS5_COMMAND_CONN = 1;
+static const uint8_t SOCKS5_STATUS_OK = 0;
+static const uint8_t SOCKS5_VERSION = 5;
+
+static void log_proxy_grant(bool granted) {
+ if(granted) {
+ logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request granted");
+ } else {
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Proxy request rejected");
+ }
+}
+
+static void log_short_response(void) {
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Received short response from proxy");
+}
+
+static bool check_socks4_resp(const socks4_response_t *resp, size_t len) {
+ if(len < sizeof(socks4_response_t)) {
+ log_short_response();
+ return false;
+ }
+
+ if(resp->version != SOCKS4_REPLY_VERSION) {
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Bad response from SOCKS4 proxy");
+ return false;
+ }
+
+ bool granted = resp->status == SOCKS4_STATUS_OK;
+ log_proxy_grant(granted);
+ return granted;
+}
+
+static bool socks5_check_result(const socks5_conn_resp_t *re, size_t len) {
+ size_t addrlen;
+
+ switch((socks5_addr_type_t)re->addr_type) {
+ case SOCKS5_IPV4:
+ addrlen = sizeof(socks5_ipv4_t);
+ break;
+
+ case SOCKS5_IPV6:
+ addrlen = sizeof(socks5_ipv6_t);
+ break;
+
+ default:
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Unsupported address type 0x%x from proxy server", re->addr_type);
+ return false;
+ }
+
+ if(len < addrlen) {
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Received short address from proxy server");
+ return false;
+ }
+
+ if(re->socks_version != SOCKS5_VERSION) {
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Invalid response from proxy server");
+ return false;
+ }
+
+ bool granted = re->conn_status == SOCKS5_STATUS_OK;
+ log_proxy_grant(granted);
+ return granted;
+}
+
+static bool check_socks5_resp(const socks5_resp_t *resp, size_t len) {
+ if(len < sizeof(socks5_server_choice_t)) {
+ log_short_response();
+ return false;
+ }
+
+ len -= sizeof(socks5_server_choice_t);
+
+ if(resp->choice.socks_version != SOCKS5_VERSION) {
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Invalid response from proxy server");
+ return false;
+ }
+
+ switch((socks5_auth_method_t) resp->choice.auth_method) {
+ case AUTH_ANONYMOUS:
+ if(len < sizeof(socks5_conn_resp_t)) {
+ log_short_response();
+ return false;
+ } else {
+ return socks5_check_result(&resp->anon, len - sizeof(socks5_conn_resp_t));
+ }
+
+ case AUTH_PASSWORD: {
+ size_t header_len = sizeof(socks5_auth_status_t) + sizeof(socks5_conn_resp_t);
+
+ if(len < header_len) {
+ log_short_response();
+ return false;
+ }
+
+ if(resp->pass.status.auth_version != SOCKS5_AUTH_VERSION) {
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Invalid proxy authentication protocol version");
+ return false;
+ }
+
+ if(resp->pass.status.auth_status != SOCKS5_AUTH_OK) {
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Proxy authentication failed");
+ return false;
+ }
+
+ return socks5_check_result(&resp->pass.resp, len - header_len);
+ }
+
+ case AUTH_FAILED:
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Proxy request rejected: unsuitable authentication method");
+ return false;
+
+ default:
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Unsupported authentication method");
+ return false;
+ }
+}
+
+bool check_socks_resp(proxytype_t type, const void *buf, size_t len) {
+ if(type == PROXY_SOCKS4) {
+ return check_socks4_resp(buf, len);
+ } else if(type == PROXY_SOCKS5) {
+ return check_socks5_resp(buf, len);
+ } else {
+ return false;
+ }
+}
+
+static size_t create_socks4_req(socks4_request_t *req, const sockaddr_t *sa) {
+ if(sa->sa.sa_family != AF_INET) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Cannot connect to an IPv6 host through a SOCKS 4 proxy!");
+ return 0;
+ }
+
+ req->version = SOCKS4_VERSION;
+ req->command = SOCKS4_CMD_CONN;
+ req->dstport = sa->in.sin_port;
+ req->dstip = sa->in.sin_addr;
+
+ if(proxyuser) {
+ strcpy(req->id, proxyuser);
+ } else {
+ req->id[0] = '\0';
+ }
+
+ return sizeof(socks4_response_t);
+}
+
+static size_t create_socks5_req(void *buf, const sockaddr_t *sa) {
+ uint16_t family = sa->sa.sa_family;
+
+ if(family != AF_INET && family != AF_INET6) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Address family %x not supported for SOCKS 5 proxies!", family);
+ return 0;
+ }
+
+ socks5_greet_t *req = buf;
+ req->version = SOCKS5_VERSION;
+ req->nmethods = 1; // only one auth method is supported
+
+ size_t resplen = sizeof(socks5_server_choice_t);
+ uint8_t *auth = (uint8_t *)buf + sizeof(socks5_greet_t);
+
+ if(proxyuser && proxypass) {
+ req->authmethod = SOCKS5_AUTH_METHOD_PASSWORD;
+
+ // field | VER | IDLEN | ID | PWLEN | PW |
+ // bytes | 1 | 1 | 1-255 | 1 | 1-255 |
+
+ // Assign the first field (auth protocol version)
+ *auth++ = SOCKS5_AUTH_VERSION;
+
+ size_t userlen = strlen(proxyuser);
+ size_t passlen = strlen(proxypass);
+
+ // Assign the username length, and copy the username
+ *auth++ = userlen;
+ memcpy(auth, proxyuser, userlen);
+ auth += userlen;
+
+ // Do the same for password
+ *auth++ = passlen;
+ memcpy(auth, proxypass, passlen);
+ auth += passlen;
+
+ resplen += sizeof(socks5_auth_status_t);
+ } else {
+ req->authmethod = SOCKS5_AUTH_METHOD_NONE;
+ }
+
+ socks5_conn_req_t *conn = (socks5_conn_req_t *) auth;
+ conn->header.version = SOCKS5_VERSION;
+ conn->header.command = SOCKS5_COMMAND_CONN;
+ conn->header.reserved = 0;
+
+ resplen += sizeof(socks5_conn_resp_t);
+
+ if(family == AF_INET) {
+ conn->header.addr_type = SOCKS5_IPV4;
+ conn->dst.ipv4.addr = sa->in.sin_addr;
+ conn->dst.ipv4.port = sa->in.sin_port;
+ resplen += sizeof(socks5_ipv4_t);
+ } else {
+ conn->header.addr_type = SOCKS5_IPV6;
+ conn->dst.ipv6.addr = sa->in6.sin6_addr;
+ conn->dst.ipv6.port = sa->in6.sin6_port;
+ resplen += sizeof(socks5_ipv6_t);
+ }
+
+ return resplen;
+}
+
+size_t socks_req_len(proxytype_t type, const sockaddr_t *sa) {
+ uint16_t family = sa->sa.sa_family;
+
+ if(type == PROXY_SOCKS4) {
+ if(family != AF_INET) {
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "SOCKS 4 only supports IPv4 addresses");
+ return 0;
+ }
+
+ size_t userlen_size = 1;
+ size_t userlen = proxyuser ? strlen(proxyuser) : 0;
+ return sizeof(socks4_request_t) + userlen_size + userlen;
+ }
+
+ if(type == PROXY_SOCKS5) {
+ if(family != AF_INET && family != AF_INET6) {
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "SOCKS 5 only supports IPv4 and IPv6");
+ return 0;
+ }
+
+ size_t len = sizeof(socks5_greet_t) +
+ sizeof(socks5_conn_hdr_t) +
+ (family == AF_INET
+ ? sizeof(socks5_ipv4_t)
+ : sizeof(socks5_ipv6_t));
+
+ if(proxyuser && proxypass) {
+ // version, userlen, user, passlen, pass
+ len += 1 + 1 + strlen(proxyuser) + 1 + strlen(proxypass);
+ }
+
+ return len;
+ }
+
+ logger(DEBUG_CONNECTIONS, LOG_ERR, "Bad proxy type 0x%x", type);
+ return 0;
+}
+
+size_t create_socks_req(proxytype_t type, void *buf, const sockaddr_t *sa) {
+ if(type == PROXY_SOCKS4) {
+ return create_socks4_req(buf, sa);
+ } else if(type == PROXY_SOCKS5) {
+ return create_socks5_req(buf, sa);
+ } else {
+ abort();
+ }
+}
--- /dev/null
+#ifndef TINC_PROXY_H
+#define TINC_PROXY_H
+
+#include "system.h"
+
+#include "net.h"
+
+PACKED(struct socks4_request_t {
+ uint8_t version;
+ uint8_t command;
+ uint16_t dstport;
+ struct in_addr dstip;
+ char id[];
+});
+
+PACKED(struct socks4_response_t {
+ uint8_t version;
+ uint8_t status;
+ uint16_t dstport;
+ struct in_addr dstip;
+});
+
+typedef struct socks4_request_t socks4_request_t;
+typedef struct socks4_response_t socks4_response_t;
+
+PACKED(struct socks5_greet_t {
+ uint8_t version;
+ uint8_t nmethods;
+ uint8_t authmethod;
+});
+
+typedef struct socks5_greet_t socks5_greet_t;
+
+PACKED(struct socks5_conn_hdr_t {
+ uint8_t version;
+ uint8_t command;
+ uint8_t reserved;
+ uint8_t addr_type;
+});
+
+PACKED(struct socks5_ipv4_t {
+ struct in_addr addr;
+ uint16_t port;
+});
+
+PACKED(struct socks5_ipv6_t {
+ struct in6_addr addr;
+ uint16_t port;
+});
+
+typedef struct socks5_conn_hdr_t socks5_conn_hdr_t;
+typedef struct socks5_ipv4_t socks5_ipv4_t;
+typedef struct socks5_ipv6_t socks5_ipv6_t;
+
+PACKED(struct socks5_conn_req_t {
+ socks5_conn_hdr_t header;
+ union {
+ socks5_ipv4_t ipv4;
+ socks5_ipv6_t ipv6;
+ } dst;
+});
+
+PACKED(struct socks5_server_choice_t {
+ uint8_t socks_version;
+ uint8_t auth_method;
+});
+
+PACKED(struct socks5_auth_status_t {
+ uint8_t auth_version;
+ uint8_t auth_status;
+});
+
+typedef struct socks5_auth_status_t socks5_auth_status_t;
+
+PACKED(struct socks5_conn_resp_t {
+ uint8_t socks_version;
+ uint8_t conn_status;
+ uint8_t reserved;
+ uint8_t addr_type;
+});
+
+typedef struct socks5_conn_req_t socks5_conn_req_t;
+typedef struct socks5_server_choice_t socks5_server_choice_t;
+typedef struct socks5_conn_resp_t socks5_conn_resp_t;
+
+PACKED(struct socks5_resp_t {
+ socks5_server_choice_t choice;
+
+ union {
+ // if choice == password
+ struct {
+ socks5_auth_status_t status;
+ socks5_conn_resp_t resp;
+ } pass;
+
+ // if choice == anonymous
+ socks5_conn_resp_t anon;
+ };
+});
+
+typedef struct socks5_resp_t socks5_resp_t;
+
+// Get the length of a connection request to a SOCKS 4 or 5 proxy
+extern size_t socks_req_len(proxytype_t type, const sockaddr_t *sa);
+
+// Create a request to connect to a SOCKS 4 or 5 proxy.
+// Returns the expected response length, or zero on error.
+extern size_t create_socks_req(proxytype_t type, void *req, const sockaddr_t *sa);
+
+// Check that SOCKS server provided a valid response and permitted further requests
+extern bool check_socks_resp(proxytype_t type, const void *buf, size_t len);
+
+#endif // TINC_PROXY_H
'commandline.py',
'executables.py',
'import_export.py',
- 'invite_tinc_up.py',
'invite.py',
+ 'invite_tinc_up.py',
+ 'proxy.py',
'scripts.py',
'security.py',
'splice.py',
--- /dev/null
+#!/usr/bin/env python3
+
+"""Test that tincd works through proxies."""
+
+import os
+import re
+import tempfile
+import typing as T
+import multiprocessing.connection as mp
+import logging
+import select
+import socket
+import struct
+
+from threading import Thread
+from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler
+from testlib import check, cmd, path
+from testlib.proc import Tinc, Script
+from testlib.test import Test
+from testlib.util import random_string
+from testlib.log import log
+
+USERNAME = random_string(8)
+PASSWORD = random_string(8)
+
+proxy_stats = {"tx": 0}
+
+# socks4
+SOCKS_VERSION_4 = 4
+CMD_STREAM = 1
+REQUEST_GRANTED = 0x5A
+
+# socks5
+SOCKS_VERSION_5 = 5
+METHOD_NONE = 0
+METHOD_USERNAME_PASSWORD = 2
+NO_METHODS = 0xFF
+ADDR_TYPE_IPV4 = 1
+ADDR_TYPE_DOMAIN = 3
+CMD_CONNECT = 1
+REP_SUCCESS = 0
+RESERVED = 0
+AUTH_OK = 0
+AUTH_FAILURE = 0xFF
+
+
+def send_all(sock: socket.socket, data: bytes) -> bool:
+ """Send all data to socket, retrying as necessary."""
+
+ total = 0
+
+ while total < len(data):
+ sent = sock.send(data[total:])
+ if sent <= 0:
+ break
+ total += sent
+
+ return total == len(data)
+
+
+def proxy_data(client: socket.socket, remote: socket.socket) -> None:
+ """Pipe data between the two sockets."""
+
+ while True:
+ read, _, _ = select.select([client, remote], [], [])
+
+ if client in read:
+ data = client.recv(4096)
+ proxy_stats["tx"] += len(data)
+ log.debug("received from client: '%s'", data)
+ if not data or not send_all(remote, data):
+ log.info("remote finished")
+ return
+
+ if remote in read:
+ data = remote.recv(4096)
+ proxy_stats["tx"] += len(data)
+ log.debug("sending to client: '%s'", data)
+ if not data or not send_all(client, data):
+ log.info("client finished")
+ return
+
+
+def error_response(address_type: int, error: int) -> bytes:
+ """Create error response for SOCKS client."""
+ return struct.pack("!BBBBIH", SOCKS_VERSION_5, error, 0, address_type, 0, 0)
+
+
+def read_ipv4(sock: socket.socket) -> str:
+ """Read IPv4 address from socket and convert it into a string."""
+ ip_addr = sock.recv(4)
+ return socket.inet_ntoa(ip_addr)
+
+
+def ip_to_int(addr: str) -> int:
+ """Convert address to integer."""
+ return struct.unpack("!I", socket.inet_aton(addr))[0]
+
+
+def addr_response(address, port: T.Tuple[str, int]) -> bytes:
+ """Create address response. Format:
+ version rep rsv atyp bind_addr bind_port
+ """
+ return struct.pack(
+ "!BBBBIH",
+ SOCKS_VERSION_5,
+ REP_SUCCESS,
+ RESERVED,
+ ADDR_TYPE_IPV4,
+ ip_to_int(address),
+ port,
+ )
+
+
+class ProxyServer(StreamRequestHandler):
+ """Parent class for proxy server implementations."""
+
+ name: T.ClassVar[str] = ""
+
+
+class ThreadingTCPServer(ThreadingMixIn, TCPServer):
+ """TCPServer which handles each request in a separate thread."""
+
+
+class HttpProxy(ProxyServer):
+ """HTTP proxy server that handles CONNECT requests."""
+
+ name = "http"
+ _re = re.compile(r"CONNECT ([^:]+):(\d+) HTTP/1\.[01]")
+
+ def handle(self) -> None:
+ try:
+ self._handle_connection()
+ finally:
+ self.server.close_request(self.request)
+
+ def _handle_connection(self) -> None:
+ """Handle a single proxy connection"""
+ data = b""
+ while not data.endswith(b"\r\n\r\n"):
+ data += self.connection.recv(1)
+ log.info("got request: '%s'", data)
+
+ match = self._re.match(data.decode("utf-8"))
+ assert match
+
+ address, port = match.groups()
+ log.info("matched target address %s:%s", address, port)
+
+ with socket.socket() as sock:
+ sock.connect((address, int(port)))
+ log.info("connected to target")
+
+ self.connection.sendall(b"HTTP/1.1 200 OK\r\n\r\n")
+ log.info("sent successful response")
+
+ proxy_data(self.connection, sock)
+
+
+class Socks4Proxy(ProxyServer):
+ """SOCKS 4 proxy server."""
+
+ name = "socks4"
+ username = USERNAME
+
+ def handle(self) -> None:
+ try:
+ self._handle_connection()
+ finally:
+ self.server.close_request(self.request)
+
+ def _handle_connection(self) -> None:
+ """Handle a single proxy connection."""
+
+ version, command, port = struct.unpack("!BBH", self.connection.recv(4))
+ check.equals(SOCKS_VERSION_4, version)
+ check.equals(command, CMD_STREAM)
+ check.port(port)
+
+ addr = read_ipv4(self.connection)
+ log.info("received address %s:%d", addr, port)
+
+ user = ""
+ while True:
+ byte = self.connection.recv(1)
+ if byte == b"\0":
+ break
+ user += byte.decode("utf-8")
+
+ log.info("received username %s", user)
+ self._check_username(user)
+
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as remote:
+ remote.connect((addr, port))
+ logging.info("connected to %s:%s", addr, port)
+ self._process_remote(remote)
+
+ def _check_username(self, user: str) -> bool:
+ """Authenticate by comparing socks4 username."""
+ return user == self.username
+
+ def _process_remote(self, sock: socket.socket) -> None:
+ """Process a single proxy connection."""
+
+ addr, port = sock.getsockname()
+ reply = struct.pack("!BBHI", 0, REQUEST_GRANTED, port, ip_to_int(addr))
+ log.info("sending reply %s", reply)
+ self.connection.sendall(reply)
+
+ proxy_data(self.connection, sock)
+
+
+class AnonymousSocks4Proxy(Socks4Proxy):
+ """socks4 server without any authentication."""
+
+ def _check_username(self, user: str) -> bool:
+ return True
+
+
+class Socks5Proxy(ProxyServer):
+ """SOCKS 5 proxy server."""
+
+ name = "socks5"
+
+ def handle(self) -> None:
+ """Handle a proxy connection."""
+ try:
+ self._process_connection()
+ finally:
+ self.server.close_request(self.request)
+
+ def _process_connection(self) -> None:
+ """Handle a proxy connection."""
+
+ methods = self._read_header()
+ if not self._authenticate(methods):
+ raise RuntimeError("authentication failed")
+
+ command, address_type = self._read_command()
+ address = self._read_address(address_type)
+ port = struct.unpack("!H", self.connection.recv(2))[0]
+ log.info("got address %s:%d", address, port)
+
+ if command != CMD_CONNECT:
+ raise RuntimeError(f"bad command {command}")
+
+ try:
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as remote:
+ remote.connect((address, port))
+ bind_address = remote.getsockname()
+ logging.info("connected to %s:%d", address, port)
+
+ reply = addr_response(*bind_address)
+ log.debug("sending address '%s'", reply)
+ self.connection.sendall(reply)
+
+ proxy_data(self.connection, remote)
+ except OSError as ex:
+ log.error("socks server failed", exc_info=ex)
+ reply = error_response(address_type, 5)
+ self.connection.sendall(reply)
+ raise ex
+
+ def _read_address(self, address_type: int) -> str:
+ """Read target address."""
+
+ if address_type == ADDR_TYPE_IPV4:
+ return read_ipv4(self.connection)
+
+ if address_type == ADDR_TYPE_DOMAIN:
+ domain_len = self.connection.recv(1)[0]
+ domain = self.connection.recv(domain_len)
+ return socket.gethostbyname(domain.decode())
+
+ raise RuntimeError(f"unknown address type {address_type}")
+
+ def _read_command(self) -> T.Tuple[int, int]:
+ """Check protocol version and get command code and address type."""
+
+ version, command, _, address_type = struct.unpack(
+ "!BBBB", self.connection.recv(4)
+ )
+ check.equals(SOCKS_VERSION_5, version)
+ return command, address_type
+
+ @property
+ def _method(self) -> int:
+ """Supported authentication method."""
+ return METHOD_USERNAME_PASSWORD
+
+ def _authenticate(self, methods: T.List[int]) -> bool:
+ """Perform client authentication."""
+
+ found = self._method in methods
+ choice = self._method if found else NO_METHODS
+ result = struct.pack("!BB", SOCKS_VERSION_5, choice)
+
+ log.debug("sending authentication result '%s'", result)
+ self.connection.sendall(result)
+
+ if not found:
+ log.error("auth method not found in %s", methods)
+ return False
+
+ if not self._read_creds():
+ log.error("could not verify credentials")
+ return False
+
+ return True
+
+ def _read_header(self) -> T.List[int]:
+ """Get the list of methods supported by the client."""
+
+ version, methods = struct.unpack("!BB", self.connection.recv(2))
+ check.equals(SOCKS_VERSION_5, version)
+ check.greater(methods, 0)
+ return [ord(self.connection.recv(1)) for _ in range(methods)]
+
+ def _read_creds(self) -> bool:
+ """Read and verify auth credentials."""
+
+ version = ord(self.connection.recv(1))
+ check.equals(1, version)
+
+ user_len = ord(self.connection.recv(1))
+ user = self.connection.recv(user_len).decode("utf-8")
+
+ passw_len = ord(self.connection.recv(1))
+ passw = self.connection.recv(passw_len).decode("utf-8")
+
+ log.info("got credentials '%s', '%s'", user, passw)
+ log.info("want credentials '%s', '%s'", USERNAME, PASSWORD)
+
+ passed = user == USERNAME and passw == PASSWORD
+ response = struct.pack("!BB", version, AUTH_OK if passed else AUTH_FAILURE)
+ self.connection.sendall(response)
+
+ return passed
+
+
+class AnonymousSocks5Proxy(Socks5Proxy):
+ """SOCKS 5 server without authentication support."""
+
+ @property
+ def _method(self) -> int:
+ return METHOD_NONE
+
+ def _read_creds(self) -> bool:
+ return True
+
+
+def init(ctx: Test) -> T.Tuple[Tinc, Tinc]:
+ """Create a new tinc node."""
+
+ foo, bar = ctx.node(), ctx.node()
+ stdin = f"""
+ init {foo}
+ set Address 127.0.0.1
+ set Port 0
+ set DeviceType dummy
+ """
+ foo.cmd(stdin=stdin)
+
+ stdin = f"""
+ init {bar}
+ set Address 127.0.0.1
+ set Port 0
+ set DeviceType dummy
+ """
+ bar.cmd(stdin=stdin)
+
+ return foo, bar
+
+
+def create_exec_proxy(port: int) -> str:
+ """Create a fake exec proxy program."""
+
+ code = f"""
+import os
+import multiprocessing.connection as mp
+
+with mp.Client(("127.0.0.1", {port}), family="AF_INET") as client:
+ client.send({{ **os.environ }})
+"""
+
+ file = tempfile.mktemp()
+ with open(file, "w", encoding="utf-8") as f:
+ f.write(code)
+
+ return file
+
+
+def test_proxy(ctx: Test, handler: T.Type[ProxyServer], user="", passw="") -> None:
+ """Test socks proxy support."""
+
+ foo, bar = init(ctx)
+
+ bar.add_script(foo.script_up)
+ bar.add_script(Script.TINC_UP)
+ bar.start()
+
+ cmd.exchange(foo, bar)
+ foo.cmd("set", f"{bar}.Port", str(bar.port))
+
+ with ThreadingTCPServer(("127.0.0.1", 0), handler) as server:
+ _, port = server.server_address
+
+ worker = Thread(target=server.serve_forever)
+ worker.start()
+
+ foo.cmd("set", "Proxy", handler.name, f"127.0.0.1 {port} {user} {passw}")
+ foo.cmd("start")
+ bar[foo.script_up].wait()
+
+ foo.cmd("stop")
+ bar.cmd("stop")
+
+ server.shutdown()
+ worker.join()
+
+
+def test_proxy_exec(ctx: Test) -> None:
+ """Test that exec proxies work as expected."""
+ foo, bar = init(ctx)
+
+ log.info("exec proxy without arguments fails")
+ foo.cmd("set", "Proxy", "exec")
+ _, stderr = foo.cmd("start", code=1)
+ check.is_in("Argument expected for proxy type", stderr)
+
+ log.info("exec proxy with correct arguments works")
+ bar.cmd("start")
+ cmd.exchange(foo, bar)
+
+ with mp.Listener(("127.0.0.1", 0), family="AF_INET") as listener:
+ port = int(listener.address[1])
+ proxy = create_exec_proxy(port)
+
+ foo.cmd("set", "Proxy", "exec", f"{path.PYTHON_PATH} {path.PYTHON_CMD} {proxy}")
+ foo.cmd("start")
+
+ with listener.accept() as conn:
+ env: T.Dict[str, str] = conn.recv()
+
+ for var in "NAME", "REMOTEADDRESS", "REMOTEPORT":
+ check.true(env.get(var))
+
+ for var in "NODE", "NETNAME":
+ if var in env:
+ check.true(env[var])
+
+ os.remove(proxy)
+
+
+if os.name != "nt":
+ with Test("exec proxy") as context:
+ test_proxy_exec(context)
+
+with Test("HTTP CONNECT proxy") as context:
+ proxy_stats["tx"] = 0
+ test_proxy(context, HttpProxy)
+ check.greater(proxy_stats["tx"], 0)
+
+with Test("socks4 proxy with username") as context:
+ proxy_stats["tx"] = 0
+ test_proxy(context, Socks4Proxy, USERNAME)
+ check.greater(proxy_stats["tx"], 0)
+
+with Test("anonymous socks4 proxy") as context:
+ proxy_stats["tx"] = 0
+ test_proxy(context, AnonymousSocks4Proxy)
+ check.greater(proxy_stats["tx"], 0)
+
+with Test("authenticated socks5 proxy") as context:
+ proxy_stats["tx"] = 0
+ test_proxy(context, Socks5Proxy, USERNAME, PASSWORD)
+ check.greater(proxy_stats["tx"], 0)
+
+with Test("anonymous socks5 proxy") as context:
+ proxy_stats["tx"] = 0
+ test_proxy(context, AnonymousSocks5Proxy)
+ check.greater(proxy_stats["tx"], 0)
raise ValueError(f'expected "{value}" to be truthy', value)
+def port(value: int) -> None:
+ """Check that value resembles a port."""
+ if not isinstance(value, int) or value < 1 or value > 65535:
+ raise ValueError(f'expected "{value}" to be be a port')
+
+
def equals(expected: Val, actual: Val) -> None:
"""Check that the two values are equal."""
if expected != actual:
raise ValueError(f"expected {text!r} to start with {prefix!r}")
+def greater(value: Num, than: Num) -> None:
+ """Check that value is greater than the other value."""
+ if value <= than:
+ raise ValueError(f"value {value} must be greater than {than}")
+
+
def in_range(value: Num, gte: Num, lte: Num) -> None:
"""Check that value lies in the range [min, max]."""
if not gte >= value >= lte:
SPTPS_TEST_PATH = str(env["SPTPS_TEST_PATH"])
SPTPS_KEYPAIR_PATH = str(env["SPTPS_KEYPAIR_PATH"])
+PYTHON_CMD = "runpython" if "meson.exe" in PYTHON_PATH.lower() else ""
+
def _check() -> bool:
"""Basic sanity checks on passed environment variables."""
_CMD_VARS = os.linesep.join([f"set {var}={val}" for var, val in path.env.items()])
-_CMD_PY = "runpython" if "meson.exe" in path.PYTHON_PATH.lower() else ""
def _read_template(tpl_name: str, maps: T.Dict[str, T.Any]) -> str:
def make_cmd_wrap(script: str) -> str:
"""Create a .cmd wrapper for tincd script. Only makes sense on Windows."""
maps = {
- "PYTHON_CMD": _CMD_PY,
+ "PYTHON_CMD": path.PYTHON_CMD,
"PYTHON_PATH": path.PYTHON_PATH,
"SCRIPT_PATH": script,
"VARIABLES": _CMD_VARS,
'protocol': {
'code': 'test_protocol.c',
},
+ 'proxy': {
+ 'code': 'test_proxy.c',
+ },
'utils': {
'code': 'test_utils.c',
},
--- /dev/null
+#include "unittest.h"
+#include "../../src/net.h"
+#include "../../src/netutl.h"
+#include "../../src/proxy.h"
+#include "../../src/xalloc.h"
+
+static const char *user = "foo";
+static const size_t userlen = sizeof("foo") - 1;
+
+static const char *pass = "bar";
+static const size_t passlen = sizeof("bar") - 1;
+
+static int teardown(void **state) {
+ (void)state;
+
+ free(proxyuser);
+ proxyuser = NULL;
+
+ free(proxypass);
+ proxypass = NULL;
+
+ return 0;
+}
+
+static void test_socks_req_len_socks4_ipv4(void **state) {
+ (void)state;
+
+ const sockaddr_t sa = str2sockaddr("127.0.0.1", "4242");
+
+ size_t len = socks_req_len(PROXY_SOCKS4, &sa);
+ assert_int_equal(9, len);
+
+ proxyuser = xstrdup(user);
+ len = socks_req_len(PROXY_SOCKS4, &sa);
+ assert_int_equal(9 + userlen, len);
+}
+
+static void test_socks_req_len_socks4_ipv6(void **state) {
+ (void)state;
+
+ sockaddr_t sa = str2sockaddr("::1", "4242");
+ size_t len = socks_req_len(PROXY_SOCKS4, &sa);
+ assert_int_equal(0, len);
+}
+
+static void test_socks_req_len_socks5_ipv4(void **state) {
+ (void)state;
+
+ sockaddr_t sa = str2sockaddr("127.0.0.1", "4242");
+ size_t baselen = 13;
+
+ // Base test
+ size_t len = socks_req_len(PROXY_SOCKS5, &sa);
+ assert_int_equal(baselen, len);
+
+ // Setting only password must not change result
+ proxypass = xstrdup(pass);
+ len = socks_req_len(PROXY_SOCKS5, &sa);
+ assert_int_equal(baselen, len);
+
+ // Setting both must
+ proxyuser = xstrdup(user);
+ len = socks_req_len(PROXY_SOCKS5, &sa);
+ assert_int_equal(baselen + 3 + userlen + passlen, len);
+}
+
+static void test_socks_req_len_socks5_ipv6(void **state) {
+ (void)state;
+
+ sockaddr_t sa = str2sockaddr("::1", "4242");
+ size_t baselen = 25;
+
+ // Base test
+ size_t len = socks_req_len(PROXY_SOCKS5, &sa);
+ assert_int_equal(baselen, len);
+
+ // Setting only user must not change result
+ proxyuser = xstrdup(user);
+ len = socks_req_len(PROXY_SOCKS5, &sa);
+ assert_int_equal(baselen, len);
+
+ // Setting both must
+ proxypass = xstrdup(pass);
+ len = socks_req_len(PROXY_SOCKS5, &sa);
+ assert_int_equal(baselen + 3 + userlen + passlen, len);
+}
+
+static void test_socks_req_len_wrong_types(void **state) {
+ (void)state;
+
+ sockaddr_t sa = str2sockaddr("::1", "4242");
+
+ assert_int_equal(0, socks_req_len(PROXY_NONE, &sa));
+ assert_int_equal(0, socks_req_len(PROXY_SOCKS4A, &sa));
+ assert_int_equal(0, socks_req_len(PROXY_HTTP, &sa));
+ assert_int_equal(0, socks_req_len(PROXY_EXEC, &sa));
+}
+
+static void test_socks_req_len_wrong_family(void **state) {
+ (void)state;
+
+ sockaddr_t sa = {.sa.sa_family = AF_UNKNOWN};
+ assert_int_equal(0, socks_req_len(PROXY_SOCKS4, &sa));
+ assert_int_equal(0, socks_req_len(PROXY_SOCKS5, &sa));
+}
+
+static void test_check_socks_resp_wrong_types(void **state) {
+ (void)state;
+
+ uint8_t buf[512] = {0};
+ assert_false(check_socks_resp(PROXY_NONE, buf, sizeof(buf)));
+ assert_false(check_socks_resp(PROXY_SOCKS4A, buf, sizeof(buf)));
+ assert_false(check_socks_resp(PROXY_HTTP, buf, sizeof(buf)));
+ assert_false(check_socks_resp(PROXY_EXEC, buf, sizeof(buf)));
+}
+
+PACKED(struct socks4_response {
+ uint8_t version;
+ uint8_t status;
+ uint16_t port;
+ uint32_t addr;
+});
+
+static const uint32_t localhost_ipv4 = 0x7F000001;
+
+static void test_check_socks_resp_socks4_ok(void **state) {
+ (void)state;
+
+ const struct socks4_response resp = {
+ .version = 0x00,
+ .status = 0x5A,
+ .port = htons(12345),
+ .addr = htonl(localhost_ipv4),
+ };
+ assert_true(check_socks_resp(PROXY_SOCKS4, &resp, sizeof(resp)));
+}
+
+static void test_check_socks_resp_socks4_bad(void **state) {
+ (void)state;
+
+ const uint8_t short_len[] = {0x00, 0x5A};
+ assert_false(check_socks_resp(PROXY_SOCKS4, short_len, sizeof(short_len)));
+
+ const struct socks4_response bad_version = {
+ .version = 0x01,
+ .status = 0x5A,
+ .port = htons(12345),
+ .addr = htonl(0x7F000001),
+ };
+ assert_false(check_socks_resp(PROXY_SOCKS4, &bad_version, sizeof(bad_version)));
+
+ const struct socks4_response status_denied = {
+ .version = 0x00,
+ .status = 0x5B,
+ .port = htons(12345),
+ .addr = htonl(0x7F000001),
+ };
+ assert_false(check_socks_resp(PROXY_SOCKS4, &status_denied, sizeof(status_denied)));
+}
+
+PACKED(struct socks5_response {
+ struct {
+ uint8_t socks_version;
+ uint8_t auth_method;
+ } greet;
+
+ struct {
+ uint8_t version;
+ uint8_t status;
+ } auth;
+
+ struct {
+ uint8_t socks_version;
+ uint8_t status;
+ uint8_t reserved;
+ uint8_t addr_type;
+
+ union {
+ struct {
+ uint32_t addr;
+ uint16_t port;
+ } ipv4;
+
+ struct {
+ uint8_t addr[16];
+ uint16_t port;
+ } ipv6;
+ };
+ } resp;
+});
+
+PACKED(struct socks5_test_resp_t {
+ socks5_resp_t resp;
+
+ union {
+ struct {
+ uint32_t addr;
+ uint16_t port;
+ } ipv4;
+
+ struct {
+ uint8_t addr[16];
+ uint16_t port;
+ } ipv6;
+ };
+});
+
+typedef struct socks5_test_resp_t socks5_test_resp_t;
+
+static socks5_test_resp_t *make_good_socks5_ipv4(void) {
+ static const socks5_test_resp_t reference = {
+ .resp = {
+ .choice = {.socks_version = 0x05, .auth_method = 0x02},
+ .pass = {
+ .status = {.auth_version = 0x01, .auth_status = 0x00},
+ .resp = {
+ .socks_version = 0x05,
+ .conn_status = 0x00,
+ .reserved = 0x00,
+ .addr_type = 0x01,
+ },
+ },
+ },
+ .ipv4 = {.addr = 0x01020304, .port = 0x123},
+ };
+
+ socks5_test_resp_t *result = xmalloc(sizeof(socks5_test_resp_t));
+ memcpy(result, &reference, sizeof(reference));
+ return result;
+}
+
+static socks5_test_resp_t *make_good_socks5_ipv6(void) {
+ static const socks5_test_resp_t reference = {
+ .resp = {
+ .choice = {.socks_version = 0x05, .auth_method = 0x02},
+ .pass = {
+ .status = {.auth_version = 0x01, .auth_status = 0x00},
+ .resp = {
+ .socks_version = 0x05,
+ .conn_status = 0x00,
+ .reserved = 0x00,
+ .addr_type = 0x04,
+ },
+ },
+ },
+ .ipv6 = {
+ .addr = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ },
+ .port = 0x123,
+ },
+ };
+
+ socks5_test_resp_t *result = xmalloc(sizeof(socks5_test_resp_t));
+ memcpy(result, &reference, sizeof(reference));
+ return result;
+}
+
+static void test_check_socks_resp_socks5_ok_ipv4(void **state) {
+ (void)state;
+
+ socks5_test_resp_t *resp = make_good_socks5_ipv4();
+ assert_true(check_socks_resp(PROXY_SOCKS5, resp, sizeof(*resp)));
+ free(resp);
+}
+
+static void test_check_socks_resp_socks5_ok_ipv6(void **state) {
+ (void)state;
+
+ socks5_test_resp_t *resp = make_good_socks5_ipv6();
+ assert_true(check_socks_resp(PROXY_SOCKS5, resp, sizeof(*resp)));
+ free(resp);
+}
+
+static void test_check_socks_resp_socks5_short(void **state) {
+ (void)state;
+
+ const uint8_t resp[] = {0x05, 0x02};
+ assert_false(check_socks_resp(PROXY_SOCKS5, resp, sizeof(resp)));
+}
+
+// Define a test that assigns a bad value to one of the fields and checks that it fails
+#define BREAK_SOCKS5_FIELD_TEST(proto, name, expr) \
+ static void test_check_socks_resp_socks5_bad_##name##_##proto(void **state) { \
+ (void)state; \
+ socks5_test_resp_t *resp = make_good_socks5_##proto(); \
+ assert_true(check_socks_resp(PROXY_SOCKS5, resp, sizeof(*resp))); \
+ expr; \
+ assert_false(check_socks_resp(PROXY_SOCKS5, resp, sizeof(*resp))); \
+ free(resp); \
+ }
+
+// Define a test group for IPv4 or IPv6
+#define BREAK_SOCKS5_TEST_GROUP(proto) \
+ BREAK_SOCKS5_FIELD_TEST(proto, resp_socks_version, resp->resp.pass.resp.socks_version = 0x4) \
+ BREAK_SOCKS5_FIELD_TEST(proto, resp_conn_status, resp->resp.pass.resp.conn_status = 0x1) \
+ BREAK_SOCKS5_FIELD_TEST(proto, resp_addr_type, resp->resp.pass.resp.addr_type = 0x42) \
+ BREAK_SOCKS5_FIELD_TEST(proto, choice_socks_version, resp->resp.choice.socks_version = 0x04) \
+ BREAK_SOCKS5_FIELD_TEST(proto, choice_auth_method, resp->resp.choice.auth_method = 0x12) \
+ BREAK_SOCKS5_FIELD_TEST(proto, status_auth_version, resp->resp.pass.status.auth_version = 0x2) \
+ BREAK_SOCKS5_FIELD_TEST(proto, status_auth_status, resp->resp.pass.status.auth_status = 0x1)
+
+BREAK_SOCKS5_TEST_GROUP(ipv4)
+BREAK_SOCKS5_TEST_GROUP(ipv6)
+
+static void test_create_socks_req_socks4(void **state) {
+ (void)state;
+
+ const uint8_t ref[8] = {0x04, 0x01, 0x00, 0x7b, 0x01, 0x01, 0x01, 0x01};
+ const sockaddr_t sa = str2sockaddr("1.1.1.1", "123");
+
+ uint8_t buf[512];
+ assert_int_equal(sizeof(ref), create_socks_req(PROXY_SOCKS4, buf, &sa));
+ assert_memory_equal(ref, buf, sizeof(ref));
+}
+
+static void test_create_socks_req_socks5_ipv4_anon(void **state) {
+ (void) state;
+
+ const sockaddr_t sa = str2sockaddr("2.2.2.2", "16962");
+
+ const uint8_t ref[13] = {
+ 0x05, 0x01, 0x00,
+ 0x05, 0x01, 0x00, 0x01, 0x02, 0x02, 0x02, 0x02, 0x42, 0x42,
+ };
+
+ uint8_t buf[sizeof(ref)];
+ assert_int_equal(12, create_socks_req(PROXY_SOCKS5, buf, &sa));
+ assert_memory_equal(ref, buf, sizeof(ref));
+}
+
+static void test_create_socks_req_socks5_ipv4_password(void **state) {
+ (void)state;
+
+ proxyuser = xstrdup(user);
+ proxypass = xstrdup(pass);
+
+ const sockaddr_t sa = str2sockaddr("2.2.2.2", "16962");
+
+ const uint8_t ref[22] = {
+ 0x05, 0x01, 0x02,
+ 0x01, (uint8_t)userlen, 'f', 'o', 'o', (uint8_t)passlen, 'b', 'a', 'r',
+ 0x05, 0x01, 0x00, 0x01, 0x02, 0x02, 0x02, 0x02, 0x42, 0x42,
+ };
+
+ uint8_t buf[sizeof(ref)];
+ assert_int_equal(14, create_socks_req(PROXY_SOCKS5, buf, &sa));
+ assert_memory_equal(ref, buf, sizeof(ref));
+}
+
+static void test_create_socks_req_socks5_ipv6_anon(void **state) {
+ (void)state;
+
+ const sockaddr_t sa = str2sockaddr("1111:2222::3333:4444:5555", "18504");
+
+ const uint8_t ref[25] = {
+ 0x05, 0x01, 0x00,
+ 0x05, 0x01, 0x00, 0x04,
+ 0x11, 0x11, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55,
+ 0x48, 0x48,
+ };
+
+ uint8_t anon_buf[sizeof(ref)];
+ assert_int_equal(24, create_socks_req(PROXY_SOCKS5, anon_buf, &sa));
+ assert_memory_equal(ref, anon_buf, sizeof(ref));
+}
+
+
+static void test_create_socks_req_socks5_ipv6_password(void **state) {
+ (void)state;
+
+ proxyuser = xstrdup(user);
+ proxypass = xstrdup(pass);
+
+ const sockaddr_t sa = str2sockaddr("4444:2222::6666:4444:1212", "12850");
+
+ const uint8_t ref[34] = {
+ 0x05, 0x01, 0x02,
+ 0x01, (uint8_t)userlen, 'f', 'o', 'o', (uint8_t)passlen, 'b', 'a', 'r',
+ 0x05, 0x01, 0x00, 0x04,
+ 0x44, 0x44, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x44, 0x44, 0x12, 0x12,
+ 0x32, 0x32,
+ };
+
+ uint8_t anon_buf[sizeof(ref)];
+ assert_int_equal(26, create_socks_req(PROXY_SOCKS5, anon_buf, &sa));
+ assert_memory_equal(ref, anon_buf, sizeof(ref));
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_teardown(test_socks_req_len_socks4_ipv4, teardown),
+ cmocka_unit_test_teardown(test_socks_req_len_socks4_ipv6, teardown),
+ cmocka_unit_test_teardown(test_socks_req_len_socks5_ipv4, teardown),
+ cmocka_unit_test_teardown(test_socks_req_len_socks5_ipv6, teardown),
+ cmocka_unit_test_teardown(test_socks_req_len_wrong_types, teardown),
+ cmocka_unit_test_teardown(test_socks_req_len_wrong_family, teardown),
+
+ cmocka_unit_test(test_check_socks_resp_wrong_types),
+ cmocka_unit_test(test_check_socks_resp_socks4_ok),
+ cmocka_unit_test(test_check_socks_resp_socks4_bad),
+ cmocka_unit_test(test_check_socks_resp_socks5_ok_ipv4),
+ cmocka_unit_test(test_check_socks_resp_socks5_ok_ipv6),
+ cmocka_unit_test(test_check_socks_resp_socks5_short),
+
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_resp_socks_version_ipv4),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_resp_conn_status_ipv4),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_resp_addr_type_ipv4),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_choice_socks_version_ipv4),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_choice_auth_method_ipv4),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_status_auth_version_ipv4),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_status_auth_status_ipv4),
+
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_resp_socks_version_ipv6),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_resp_conn_status_ipv6),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_resp_addr_type_ipv6),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_choice_socks_version_ipv6),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_choice_auth_method_ipv6),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_status_auth_version_ipv6),
+ cmocka_unit_test(test_check_socks_resp_socks5_bad_status_auth_status_ipv6),
+
+ cmocka_unit_test_teardown(test_create_socks_req_socks4, teardown),
+ cmocka_unit_test_teardown(test_create_socks_req_socks5_ipv4_anon, teardown),
+ cmocka_unit_test_teardown(test_create_socks_req_socks5_ipv4_password, teardown),
+ cmocka_unit_test_teardown(test_create_socks_req_socks5_ipv6_anon, teardown),
+ cmocka_unit_test_teardown(test_create_socks_req_socks5_ipv6_password, teardown),
+ };
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}