--- /dev/null
+socks5server
+.*.swp
--- /dev/null
+VERSION := 0.1
+CFLAGS += -Wall -Wextra -Werror -std=c99 -DVERSION='"$(VERSION)"'
+LDFLAGS += -lpthread -lbsd
+
+socks5server: socks5server.c
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
+
+test: socks5server
+ ./test_proxy.py
+
+clean:
+ rm -f socks5server
+.PHONY: clean
+
--- /dev/null
+
+
+
+
+
+
+Network Working Group M. Leech
+Request for Comments: 1928 Bell-Northern Research Ltd
+Category: Standards Track M. Ganis
+ International Business Machines
+ Y. Lee
+ NEC Systems Laboratory
+ R. Kuris
+ Unify Corporation
+ D. Koblas
+ Independent Consultant
+ L. Jones
+ Hewlett-Packard Company
+ March 1996
+
+
+ SOCKS Protocol Version 5
+
+Status of this Memo
+
+ This document specifies an Internet standards track protocol for the
+ Internet community, and requests discussion and suggestions for
+ improvements. Please refer to the current edition of the "Internet
+ Official Protocol Standards" (STD 1) for the standardization state
+ and status of this protocol. Distribution of this memo is unlimited.
+
+Acknowledgments
+
+ This memo describes a protocol that is an evolution of the previous
+ version of the protocol, version 4 [1]. This new protocol stems from
+ active discussions and prototype implementations. The key
+ contributors are: Marcus Leech: Bell-Northern Research, David Koblas:
+ Independent Consultant, Ying-Da Lee: NEC Systems Laboratory, LaMont
+ Jones: Hewlett-Packard Company, Ron Kuris: Unify Corporation, Matt
+ Ganis: International Business Machines.
+
+1. Introduction
+
+ The use of network firewalls, systems that effectively isolate an
+ organizations internal network structure from an exterior network,
+ such as the INTERNET is becoming increasingly popular. These
+ firewall systems typically act as application-layer gateways between
+ networks, usually offering controlled TELNET, FTP, and SMTP access.
+ With the emergence of more sophisticated application layer protocols
+ designed to facilitate global information discovery, there exists a
+ need to provide a general framework for these protocols to
+ transparently and securely traverse a firewall.
+
+
+
+
+
+Leech, et al Standards Track [Page 1]
+\f
+RFC 1928 SOCKS Protocol Version 5 March 1996
+
+
+ There exists, also, a need for strong authentication of such
+ traversal in as fine-grained a manner as is practical. This
+ requirement stems from the realization that client-server
+ relationships emerge between the networks of various organizations,
+ and that such relationships need to be controlled and often strongly
+ authenticated.
+
+ The protocol described here is designed to provide a framework for
+ client-server applications in both the TCP and UDP domains to
+ conveniently and securely use the services of a network firewall.
+ The protocol is conceptually a "shim-layer" between the application
+ layer and the transport layer, and as such does not provide network-
+ layer gateway services, such as forwarding of ICMP messages.
+
+2. Existing practice
+
+ There currently exists a protocol, SOCKS Version 4, that provides for
+ unsecured firewall traversal for TCP-based client-server
+ applications, including TELNET, FTP and the popular information-
+ discovery protocols such as HTTP, WAIS and GOPHER.
+
+ This new protocol extends the SOCKS Version 4 model to include UDP,
+ and extends the framework to include provisions for generalized
+ strong authentication schemes, and extends the addressing scheme to
+ encompass domain-name and V6 IP addresses.
+
+ The implementation of the SOCKS protocol typically involves the
+ recompilation or relinking of TCP-based client applications to use
+ the appropriate encapsulation routines in the SOCKS library.
+
+Note:
+
+ Unless otherwise noted, the decimal numbers appearing in packet-
+ format diagrams represent the length of the corresponding field, in
+ octets. Where a given octet must take on a specific value, the
+ syntax X'hh' is used to denote the value of the single octet in that
+ field. When the word 'Variable' is used, it indicates that the
+ corresponding field has a variable length defined either by an
+ associated (one or two octet) length field, or by a data type field.
+
+3. Procedure for TCP-based clients
+
+ When a TCP-based client wishes to establish a connection to an object
+ that is reachable only via a firewall (such determination is left up
+ to the implementation), it must open a TCP connection to the
+ appropriate SOCKS port on the SOCKS server system. The SOCKS service
+ is conventionally located on TCP port 1080. If the connection
+ request succeeds, the client enters a negotiation for the
+
+
+
+Leech, et al Standards Track [Page 2]
+\f
+RFC 1928 SOCKS Protocol Version 5 March 1996
+
+
+ authentication method to be used, authenticates with the chosen
+ method, then sends a relay request. The SOCKS server evaluates the
+ request, and either establishes the appropriate connection or denies
+ it.
+
+ Unless otherwise noted, the decimal numbers appearing in packet-
+ format diagrams represent the length of the corresponding field, in
+ octets. Where a given octet must take on a specific value, the
+ syntax X'hh' is used to denote the value of the single octet in that
+ field. When the word 'Variable' is used, it indicates that the
+ corresponding field has a variable length defined either by an
+ associated (one or two octet) length field, or by a data type field.
+
+ The client connects to the server, and sends a version
+ identifier/method selection message:
+
+ +----+----------+----------+
+ |VER | NMETHODS | METHODS |
+ +----+----------+----------+
+ | 1 | 1 | 1 to 255 |
+ +----+----------+----------+
+
+ The VER field is set to X'05' for this version of the protocol. The
+ NMETHODS field contains the number of method identifier octets that
+ appear in the METHODS field.
+
+ The server selects from one of the methods given in METHODS, and
+ sends a METHOD selection message:
+
+ +----+--------+
+ |VER | METHOD |
+ +----+--------+
+ | 1 | 1 |
+ +----+--------+
+
+ If the selected METHOD is X'FF', none of the methods listed by the
+ client are acceptable, and the client MUST close the connection.
+
+ The values currently defined for METHOD are:
+
+ o X'00' NO AUTHENTICATION REQUIRED
+ o X'01' GSSAPI
+ o X'02' USERNAME/PASSWORD
+ o X'03' to X'7F' IANA ASSIGNED
+ o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
+ o X'FF' NO ACCEPTABLE METHODS
+
+ The client and server then enter a method-specific sub-negotiation.
+
+
+
+Leech, et al Standards Track [Page 3]
+\f
+RFC 1928 SOCKS Protocol Version 5 March 1996
+
+
+ Descriptions of the method-dependent sub-negotiations appear in
+ separate memos.
+
+ Developers of new METHOD support for this protocol should contact
+ IANA for a METHOD number. The ASSIGNED NUMBERS document should be
+ referred to for a current list of METHOD numbers and their
+ corresponding protocols.
+
+ Compliant implementations MUST support GSSAPI and SHOULD support
+ USERNAME/PASSWORD authentication methods.
+
+4. Requests
+
+ Once the method-dependent subnegotiation has completed, the client
+ sends the request details. If the negotiated method includes
+ encapsulation for purposes of integrity checking and/or
+ confidentiality, these requests MUST be encapsulated in the method-
+ dependent encapsulation.
+
+ The SOCKS request is formed as follows:
+
+ +----+-----+-------+------+----------+----------+
+ |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+ +----+-----+-------+------+----------+----------+
+ | 1 | 1 | X'00' | 1 | Variable | 2 |
+ +----+-----+-------+------+----------+----------+
+
+ Where:
+
+ o VER protocol version: X'05'
+ o CMD
+ o CONNECT X'01'
+ o BIND X'02'
+ o UDP ASSOCIATE X'03'
+ o RSV RESERVED
+ o ATYP address type of following address
+ o IP V4 address: X'01'
+ o DOMAINNAME: X'03'
+ o IP V6 address: X'04'
+ o DST.ADDR desired destination address
+ o DST.PORT desired destination port in network octet
+ order
+
+ The SOCKS server will typically evaluate the request based on source
+ and destination addresses, and return one or more reply messages, as
+ appropriate for the request type.
+
+
+
+
+
+Leech, et al Standards Track [Page 4]
+\f
+RFC 1928 SOCKS Protocol Version 5 March 1996
+
+
+5. Addressing
+
+ In an address field (DST.ADDR, BND.ADDR), the ATYP field specifies
+ the type of address contained within the field:
+
+ o X'01'
+
+ the address is a version-4 IP address, with a length of 4 octets
+
+ o X'03'
+
+ the address field contains a fully-qualified domain name. The first
+ octet of the address field contains the number of octets of name that
+ follow, there is no terminating NUL octet.
+
+ o X'04'
+
+ the address is a version-6 IP address, with a length of 16 octets.
+
+6. Replies
+
+ The SOCKS request information is sent by the client as soon as it has
+ established a connection to the SOCKS server, and completed the
+ authentication negotiations. The server evaluates the request, and
+ returns a reply formed as follows:
+
+ +----+-----+-------+------+----------+----------+
+ |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+ +----+-----+-------+------+----------+----------+
+ | 1 | 1 | X'00' | 1 | Variable | 2 |
+ +----+-----+-------+------+----------+----------+
+
+ Where:
+
+ o VER protocol version: X'05'
+ o REP Reply field:
+ o X'00' succeeded
+ o X'01' general SOCKS server failure
+ o X'02' connection not allowed by ruleset
+ o X'03' Network unreachable
+ o X'04' Host unreachable
+ o X'05' Connection refused
+ o X'06' TTL expired
+ o X'07' Command not supported
+ o X'08' Address type not supported
+ o X'09' to X'FF' unassigned
+ o RSV RESERVED
+ o ATYP address type of following address
+
+
+
+Leech, et al Standards Track [Page 5]
+\f
+RFC 1928 SOCKS Protocol Version 5 March 1996
+
+
+ o IP V4 address: X'01'
+ o DOMAINNAME: X'03'
+ o IP V6 address: X'04'
+ o BND.ADDR server bound address
+ o BND.PORT server bound port in network octet order
+
+ Fields marked RESERVED (RSV) must be set to X'00'.
+
+ If the chosen method includes encapsulation for purposes of
+ authentication, integrity and/or confidentiality, the replies are
+ encapsulated in the method-dependent encapsulation.
+
+CONNECT
+
+ In the reply to a CONNECT, BND.PORT contains the port number that the
+ server assigned to connect to the target host, while BND.ADDR
+ contains the associated IP address. The supplied BND.ADDR is often
+ different from the IP address that the client uses to reach the SOCKS
+ server, since such servers are often multi-homed. It is expected
+ that the SOCKS server will use DST.ADDR and DST.PORT, and the
+ client-side source address and port in evaluating the CONNECT
+ request.
+
+BIND
+
+ The BIND request is used in protocols which require the client to
+ accept connections from the server. FTP is a well-known example,
+ which uses the primary client-to-server connection for commands and
+ status reports, but may use a server-to-client connection for
+ transferring data on demand (e.g. LS, GET, PUT).
+
+ It is expected that the client side of an application protocol will
+ use the BIND request only to establish secondary connections after a
+ primary connection is established using CONNECT. In is expected that
+ a SOCKS server will use DST.ADDR and DST.PORT in evaluating the BIND
+ request.
+
+ Two replies are sent from the SOCKS server to the client during a
+ BIND operation. The first is sent after the server creates and binds
+ a new socket. The BND.PORT field contains the port number that the
+ SOCKS server assigned to listen for an incoming connection. The
+ BND.ADDR field contains the associated IP address. The client will
+ typically use these pieces of information to notify (via the primary
+ or control connection) the application server of the rendezvous
+ address. The second reply occurs only after the anticipated incoming
+ connection succeeds or fails.
+
+
+
+
+
+Leech, et al Standards Track [Page 6]
+\f
+RFC 1928 SOCKS Protocol Version 5 March 1996
+
+
+ In the second reply, the BND.PORT and BND.ADDR fields contain the
+ address and port number of the connecting host.
+
+UDP ASSOCIATE
+
+ The UDP ASSOCIATE request is used to establish an association within
+ the UDP relay process to handle UDP datagrams. The DST.ADDR and
+ DST.PORT fields contain the address and port that the client expects
+ to use to send UDP datagrams on for the association. The server MAY
+ use this information to limit access to the association. If the
+ client is not in possesion of the information at the time of the UDP
+ ASSOCIATE, the client MUST use a port number and address of all
+ zeros.
+
+ A UDP association terminates when the TCP connection that the UDP
+ ASSOCIATE request arrived on terminates.
+
+ In the reply to a UDP ASSOCIATE request, the BND.PORT and BND.ADDR
+ fields indicate the port number/address where the client MUST send
+ UDP request messages to be relayed.
+
+Reply Processing
+
+ When a reply (REP value other than X'00') indicates a failure, the
+ SOCKS server MUST terminate the TCP connection shortly after sending
+ the reply. This must be no more than 10 seconds after detecting the
+ condition that caused a failure.
+
+ If the reply code (REP value of X'00') indicates a success, and the
+ request was either a BIND or a CONNECT, the client may now start
+ passing data. If the selected authentication method supports
+ encapsulation for the purposes of integrity, authentication and/or
+ confidentiality, the data are encapsulated using the method-dependent
+ encapsulation. Similarly, when data arrives at the SOCKS server for
+ the client, the server MUST encapsulate the data as appropriate for
+ the authentication method in use.
+
+7. Procedure for UDP-based clients
+
+ A UDP-based client MUST send its datagrams to the UDP relay server at
+ the UDP port indicated by BND.PORT in the reply to the UDP ASSOCIATE
+ request. If the selected authentication method provides
+ encapsulation for the purposes of authenticity, integrity, and/or
+ confidentiality, the datagram MUST be encapsulated using the
+ appropriate encapsulation. Each UDP datagram carries a UDP request
+ header with it:
+
+
+
+
+
+Leech, et al Standards Track [Page 7]
+\f
+RFC 1928 SOCKS Protocol Version 5 March 1996
+
+
+ +----+------+------+----------+----------+----------+
+ |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
+ +----+------+------+----------+----------+----------+
+ | 2 | 1 | 1 | Variable | 2 | Variable |
+ +----+------+------+----------+----------+----------+
+
+ The fields in the UDP request header are:
+
+ o RSV Reserved X'0000'
+ o FRAG Current fragment number
+ o ATYP address type of following addresses:
+ o IP V4 address: X'01'
+ o DOMAINNAME: X'03'
+ o IP V6 address: X'04'
+ o DST.ADDR desired destination address
+ o DST.PORT desired destination port
+ o DATA user data
+
+ When a UDP relay server decides to relay a UDP datagram, it does so
+ silently, without any notification to the requesting client.
+ Similarly, it will drop datagrams it cannot or will not relay. When
+ a UDP relay server receives a reply datagram from a remote host, it
+ MUST encapsulate that datagram using the above UDP request header,
+ and any authentication-method-dependent encapsulation.
+
+ The UDP relay server MUST acquire from the SOCKS server the expected
+ IP address of the client that will send datagrams to the BND.PORT
+ given in the reply to UDP ASSOCIATE. It MUST drop any datagrams
+ arriving from any source IP address other than the one recorded for
+ the particular association.
+
+ The FRAG field indicates whether or not this datagram is one of a
+ number of fragments. If implemented, the high-order bit indicates
+ end-of-fragment sequence, while a value of X'00' indicates that this
+ datagram is standalone. Values between 1 and 127 indicate the
+ fragment position within a fragment sequence. Each receiver will
+ have a REASSEMBLY QUEUE and a REASSEMBLY TIMER associated with these
+ fragments. The reassembly queue must be reinitialized and the
+ associated fragments abandoned whenever the REASSEMBLY TIMER expires,
+ or a new datagram arrives carrying a FRAG field whose value is less
+ than the highest FRAG value processed for this fragment sequence.
+ The reassembly timer MUST be no less than 5 seconds. It is
+ recommended that fragmentation be avoided by applications wherever
+ possible.
+
+ Implementation of fragmentation is optional; an implementation that
+ does not support fragmentation MUST drop any datagram whose FRAG
+ field is other than X'00'.
+
+
+
+Leech, et al Standards Track [Page 8]
+\f
+RFC 1928 SOCKS Protocol Version 5 March 1996
+
+
+ The programming interface for a SOCKS-aware UDP MUST report an
+ available buffer space for UDP datagrams that is smaller than the
+ actual space provided by the operating system:
+
+ o if ATYP is X'01' - 10+method_dependent octets smaller
+ o if ATYP is X'03' - 262+method_dependent octets smaller
+ o if ATYP is X'04' - 20+method_dependent octets smaller
+
+8. Security Considerations
+
+ This document describes a protocol for the application-layer
+ traversal of IP network firewalls. The security of such traversal is
+ highly dependent on the particular authentication and encapsulation
+ methods provided in a particular implementation, and selected during
+ negotiation between SOCKS client and SOCKS server.
+
+ Careful consideration should be given by the administrator to the
+ selection of authentication methods.
+
+9. References
+
+ [1] Koblas, D., "SOCKS", Proceedings: 1992 Usenix Security Symposium.
+
+Author's Address
+
+ Marcus Leech
+ Bell-Northern Research Ltd
+ P.O. Box 3511, Stn. C,
+ Ottawa, ON
+ CANADA K1Y 4H7
+
+ Phone: (613) 763-9145
+ EMail: mleech@bnr.ca
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Leech, et al Standards Track [Page 9]
+\f
--- /dev/null
+#define _POSIX_C_SOURCE 200809L
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <errno.h>
+
+#include <string.h>
+#include <bsd/string.h>
+#include <sys/types.h>
+
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+
+/***********
+ * Logging *
+ ***********/
+
+static __thread char* log_context = "";
+
+#define log_errno(fmt, ...) \
+ fprintf(stderr, "ERROR [%s] " fmt ": %s\n", log_context , ##__VA_ARGS__ , strerror(errno));
+
+#define log_error(fmt, ...) \
+ fprintf(stderr, "ERROR [%s] " fmt "\n", log_context , ##__VA_ARGS__);
+
+#define log_warn(fmt, ...) \
+ fprintf(stderr, "WARN [%s] " fmt "\n", log_context , ##__VA_ARGS__);
+
+#define log_info(fmt, ...) \
+ fprintf(stderr, "INFO [%s] " fmt "\n", log_context , ##__VA_ARGS__);
+
+#ifdef DEBUG
+#define log_debug(fmt, ...) \
+ fprintf(stderr, "DEBUG [%s] " fmt "\n", log_context , ##__VA_ARGS__);
+#else
+#define log_debug(fmt, ...)
+#endif
+
+void log_context_set(char* new_context) {
+ log_context = new_context;
+ log_debug("Thread started");
+}
+
+void log_context_clear() {
+ log_debug("Thread finished");
+ log_context = "";
+}
+
+
+
+/*************
+ * Utilities *
+ *************/
+
+bool itoa(char* buf, int buflen, int64_t val) {
+ int result = snprintf(buf, buflen, "%ld", val);
+ return result > 0 && result < buflen;
+}
+
+void sockaddr_stringify(char* dest, size_t dest_length, struct sockaddr_storage* addr) {
+ if (addr->ss_family == AF_INET) {
+ inet_ntop(AF_INET, &((struct sockaddr_in*)addr)->sin_addr, dest, dest_length);
+ } else if (addr->ss_family == AF_INET6) {
+ inet_ntop(AF_INET6, &((struct sockaddr_in6*)addr)->sin6_addr, dest, dest_length);
+ } else {
+ strlcpy(dest, "Unknown socket family", dest_length);
+ }
+}
+
+char* sockaddr_tostring(struct sockaddr_storage* addr) {
+ static char buf[INET6_ADDRSTRLEN];
+ sockaddr_stringify(buf, sizeof(buf), addr);
+ return buf;
+}
+
+void* smalloc(size_t sz) {
+ void* ptr = calloc(1, sz);
+ if (ptr == NULL) {
+ log_errno("Memory allocation failed");
+ exit(1);
+ }
+ return ptr;
+}
+
+
+
+/**********
+ * Config *
+ **********/
+
+extern char** environ;
+
+int getenv_int(char* key) {
+ char* value = getenv(key);
+ if (value == NULL) {
+ log_error("Missing environment variable: %s", key);
+ exit(1);
+ }
+
+ return atoi(value);
+}
+
+char* extract_env_value(char* str) {
+ char ch;
+ while ((ch = *str++)) {
+ if (ch == '=') {
+ return str;
+ }
+ }
+ return "";
+}
+
+bool getenv_list_matcher(char* key, bool (*matches)(char*, void*), void* data) {
+ size_t key_length = strlen(key);
+
+ char** ptr = environ;
+
+ while (*ptr) {
+ if (strncmp(*ptr, key, key_length) == 0) {
+ char* value = extract_env_value(*ptr);
+ if (matches(value, data)) {
+ return true;
+ }
+ }
+ ++ptr;
+ }
+
+ return false;
+}
+
+
+
+
+/**************
+ * Network IO *
+ **************/
+
+bool ssend(int fd, void* buf, size_t len) {
+ while (len > 0) {
+ ssize_t sz = send(fd, buf, len, MSG_NOSIGNAL);
+ if (sz < 0) {
+ log_errno("Failed to write, closing connection");
+ return false;
+ }
+ buf += sz;
+ len -= sz;
+ }
+ return true;
+}
+
+ssize_t srecv(int fd, void* buf, size_t len) {
+ return recv(fd, buf, len, 0);
+}
+
+bool read_bytes(int fd, uint8_t* value, ssize_t sz) {
+ if (srecv(fd, value, sz) != sz) {
+ log_errno("Failed to read from client socket");
+ return false;
+ }
+ for (ssize_t i = 0; i < sz; ++i) {
+ log_debug("got byte: %x", value[i]);
+ }
+ return true;
+}
+
+bool read_byte_check(int fd, uint8_t expected, char* msg) {
+ uint8_t actual;
+ if (!read_bytes(fd, &actual, 1)) {
+ return false;
+ }
+ if (actual != expected) {
+ log_warn("%s", msg);
+ return false;
+ }
+ return true;
+}
+
+
+
+/**********
+ * Server *
+ **********/
+
+void proxy_copy_fd(int wfd, int rfd) {
+ log_debug("proxy_copy_fd %d %d", wfd, rfd);
+
+ char buf[32768];
+
+ for (;;) {
+ log_debug("proxy_copy_fd blocking on read %d", rfd);
+ int bufsz = srecv(rfd, buf, sizeof(buf));
+ log_debug("proxy_copy_fd unblocked from read %d", rfd);
+ if (bufsz < 0) {
+ log_errno("Failed to read, closing connection");
+ break;
+ }
+ if (bufsz == 0) {
+ log_debug("proxy_copy_fd end of file %d %d", wfd, rfd);
+ break;
+ }
+
+ log_debug("proxy_copy_fd blocking on write %d", wfd);
+ if (!ssend(wfd, buf, bufsz)) {
+ log_errno("Failed to write, closing connection");
+ break;
+ }
+ log_debug("proxy_copy_fd unblocked from write %d", wfd);
+ }
+
+ shutdown(wfd, SHUT_WR);
+ shutdown(rfd, SHUT_RD);
+}
+
+
+struct proxy_copy_from_dest_data {
+ pthread_t tid;
+ int src_fd;
+ int dst_fd;
+ char log_context[INET6_ADDRSTRLEN];
+};
+
+void* proxy_copy_from_dest(void* _data) {
+ struct proxy_copy_from_dest_data* data = (struct proxy_copy_from_dest_data*)_data;
+ log_context_set(data->log_context);
+
+ proxy_copy_fd(data->dst_fd, data->src_fd);
+
+ log_context_clear();
+ free(data);
+
+ return NULL;
+}
+
+bool proxy_create_copy_from_dest_thread(
+ int dst_fd,
+ int src_fd
+) {
+ struct proxy_copy_from_dest_data* data = smalloc(sizeof(struct proxy_copy_from_dest_data));
+ data->dst_fd = dst_fd;
+ data->src_fd = src_fd;
+ strlcpy(data->log_context, log_context, sizeof(data->log_context));
+ if (pthread_create(&data->tid, NULL, &proxy_copy_from_dest, data) != 0) {
+ log_errno("Failed to spawn thread");
+ exit(1);
+ }
+ return true;
+}
+
+bool proxy_negotiate_auth(int fd) {
+ if (!read_byte_check(fd, 5, "Client sent invalid version!")) {
+ return false;
+ }
+
+ uint8_t nmethods;
+ if (!read_bytes(fd, &nmethods, 1)) {
+ return false;
+ }
+ if (nmethods < 1) {
+ log_warn("Client sent invalid authentication method count!");
+ return false;
+ }
+
+ uint8_t methods[nmethods];
+ if (!read_bytes(fd, methods, nmethods)) {
+ return false;
+ }
+ bool found_noauth = false;
+ for (int i = 0; i < nmethods; ++i) {
+ if (methods[i] == 0) {
+ found_noauth = true;
+ break;
+ }
+ }
+ if (!found_noauth) {
+ ssend(fd, "\x05\xff", 2);
+ log_warn("Client does not support no-authentication SOCKS5!");
+ return false;
+ }
+
+ if (!ssend(fd, "\x05\x00", 2)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool proxy_write_error_response(int fd, uint8_t errcode) {
+ char response[] = "\x05\xff\x00\x01\x00\x00\x00\x00\x00\x00";
+ int length = sizeof(response) - 1; // remove trailing null
+ response[1] = errcode;
+ if (!ssend(fd, response, length)) {
+ return false;
+ }
+ return true;
+}
+
+bool proxy_dns_handle_request(int* dst_fd, int fd) {
+ uint8_t name_length;
+ if (!read_bytes(fd, (uint8_t*)&name_length, 1)) {
+ return false;
+ }
+ if (name_length < 1) {
+ log_warn("Client sent invalid name length!");
+ return false;
+ }
+
+ char name[name_length+1];
+ if (!read_bytes(fd, (uint8_t*)name, name_length)) {
+ return false;
+ }
+ name[name_length] = '\0';
+
+ uint16_t port_int;
+ if (!read_bytes(fd, (uint8_t*)&port_int, 2)) {
+ return false;
+ }
+ port_int = ntohs(port_int);
+ char port[8];
+ if (!itoa(port, sizeof(port), port_int)) {
+ return false;
+ }
+
+
+ struct addrinfo hints;
+ struct addrinfo* result;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_NUMERICSERV;
+ hints.ai_protocol = 0;
+
+ log_debug("proxy_dns_handle_request getaddrinfo before '%s' '%s'", name, port);
+ if (getaddrinfo(name, port, &hints, &result) != 0) {
+ log_warn("proxy_dns_handle_request getaddrinfo failed");
+ proxy_write_error_response(fd, 4);
+ return false;
+ }
+ log_debug("proxy_dns_handle_request getaddrinfo success");
+
+ for (struct addrinfo* rp = result; rp != NULL; rp = rp->ai_next) {
+ *dst_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (*dst_fd < 0) {
+ continue;
+ }
+
+ if (connect(*dst_fd, rp->ai_addr, rp->ai_addrlen) == 0) {
+ proxy_write_error_response(fd, 0);
+ freeaddrinfo(result);
+ return true;
+ }
+
+ close(*dst_fd);
+ }
+
+ proxy_write_error_response(fd, 4);
+ freeaddrinfo(result);
+ return false;
+}
+
+bool proxy_ipv6_handle_request(int* dst_fd, int fd) {
+ struct sockaddr_in6 dst_addr;
+ memset(&dst_addr, 0, sizeof(dst_addr));
+ dst_addr.sin6_family = AF_INET6;
+
+ if (!read_bytes(fd, (uint8_t*)&dst_addr.sin6_addr, 16)) {
+ return false;
+ }
+
+ if (!read_bytes(fd, (uint8_t*)&dst_addr.sin6_port, 2)) {
+ return false;
+ }
+
+ log_debug("got addr: %s", sockaddr_tostring((struct sockaddr_storage*)&dst_addr));
+
+ *dst_fd = socket(AF_INET6, SOCK_STREAM, 0);
+ if (*dst_fd < 0) {
+ log_errno("Failed to create socket");
+ exit(1);
+ }
+
+ if (connect(*dst_fd, (struct sockaddr*)&dst_addr, sizeof(dst_addr)) != 0) {
+ proxy_write_error_response(fd, 4);
+ return false;
+ }
+
+ proxy_write_error_response(fd, 0);
+ return true;
+}
+
+bool proxy_ipv4_handle_request(int* dst_fd, int fd) {
+ struct sockaddr_in dst_addr;
+ memset(&dst_addr, 0, sizeof(dst_addr));
+ dst_addr.sin_family = AF_INET;
+
+ if (!read_bytes(fd, (uint8_t*)&dst_addr.sin_addr, 4)) {
+ return false;
+ }
+
+ if (!read_bytes(fd, (uint8_t*)&dst_addr.sin_port, 2)) {
+ return false;
+ }
+
+ log_debug("got addr: %s", sockaddr_tostring((struct sockaddr_storage*)&dst_addr));
+
+ *dst_fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (*dst_fd < 0) {
+ log_errno("Failed to create socket");
+ exit(1);
+ }
+
+ if (connect(*dst_fd, (struct sockaddr*)&dst_addr, sizeof(dst_addr)) != 0) {
+ proxy_write_error_response(fd, 4);
+ return false;
+ }
+
+ proxy_write_error_response(fd, 0);
+ return true;
+}
+
+bool proxy_handle_request(int* dst_fd, int fd) {
+ if (!read_byte_check(fd, 5, "Client sent invalid version!")) {
+ proxy_write_error_response(fd, 1);
+ return false;
+ }
+
+ if (!read_byte_check(fd, 1, "Client sent invalid command!")) {
+ proxy_write_error_response(fd, 7);
+ return false;
+ }
+
+ if (!read_byte_check(fd, 0, "Client sent invalid reserved section!")) {
+ proxy_write_error_response(fd, 1);
+ return false;
+ }
+
+ uint8_t addr_type;
+ if (!read_bytes(fd, &addr_type, 1)) {
+ return false;
+ }
+ switch (addr_type) {
+ case 1:
+ return proxy_ipv4_handle_request(dst_fd, fd);
+ case 3:
+ return proxy_dns_handle_request(dst_fd, fd);
+ case 4:
+ return proxy_ipv6_handle_request(dst_fd, fd);
+ }
+
+ proxy_write_error_response(fd, 8);
+ return false;
+}
+
+struct proxy_handle_accept_data {
+ pthread_t tid;
+ int fd;
+ char log_context[INET6_ADDRSTRLEN];
+};
+
+void* proxy_handle_accept(void* _data) {
+ struct proxy_handle_accept_data* data = (struct proxy_handle_accept_data*)_data;
+ log_context_set(data->log_context);
+
+ int dst_fd = -1;
+
+ log_debug("proxy_negotiate_auth before %d", data->fd);
+ if (!proxy_negotiate_auth(data->fd)) {
+ log_debug("negotiate_auth fail");
+ goto finish;
+ }
+
+ log_debug("proxy_handle_request before");
+ if (!proxy_handle_request(&dst_fd, data->fd)) {
+ log_debug("proxy_handle_request fail");
+ goto finish;
+ }
+
+ log_debug("proxy_create_copy_from_dest_thread before %d %d", dst_fd, data->fd);
+ if (!proxy_create_copy_from_dest_thread(dst_fd, data->fd)) {
+ log_debug("proxy_create_copy_from_dest_thread fail");
+ goto finish;
+ }
+
+ // opposite direction to the thread we just created
+ log_debug("proxy_copy_fd before %d %d", data->fd, dst_fd);
+ proxy_copy_fd(data->fd, dst_fd);
+ log_debug("proxy_copy_fd after");
+
+finish:
+ if (dst_fd >= 0) {
+ close(dst_fd);
+ }
+ close(data->fd);
+
+ log_context_clear();
+
+ free(data);
+
+ return NULL;
+}
+
+bool sockaddr_equals_addrinfo(struct sockaddr_storage* addr1, struct addrinfo* addr2) {
+ void* addr1_ptr = NULL;
+ void* addr2_ptr = NULL;
+ socklen_t length = 0;
+
+ if (addr1->ss_family == AF_INET && addr2->ai_family == AF_INET) {
+ addr1_ptr = &(((struct sockaddr_in*)addr1)->sin_addr);
+ addr2_ptr = &((struct sockaddr_in*)addr2->ai_addr)->sin_addr;
+ length = sizeof(struct in_addr);
+ } else if (addr1->ss_family == AF_INET6 && addr2->ai_family == AF_INET6) {
+ addr1_ptr = &(((struct sockaddr_in6*)addr1)->sin6_addr);
+ addr2_ptr = &((struct sockaddr_in6*)addr2->ai_addr)->sin6_addr;
+ length = sizeof(struct in6_addr);
+ } else {
+ return false;
+ }
+
+ return memcmp(addr1_ptr, addr2_ptr, length) == 0;
+}
+
+bool sockaddr_matches_string(char* str, void* _addr) {
+ struct sockaddr_storage* addr = (struct sockaddr_storage*)_addr;
+
+ struct addrinfo hints;
+ struct addrinfo* result;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_NUMERICSERV;
+ hints.ai_protocol = 0;
+
+ if (getaddrinfo(str, NULL, &hints, &result) != 0) {
+ log_warn("sockaddr_matches_string getaddrinfo failed: %s", str);
+ return false;
+ }
+
+ bool found = false;
+ for (struct addrinfo* rp = result; rp != NULL; rp = rp->ai_next) {
+ if (sockaddr_equals_addrinfo(addr, rp)) {
+ found = true;
+ break;
+ }
+ }
+
+ freeaddrinfo(result);
+ return found;
+}
+
+bool server_has_permission(struct sockaddr_storage* addr) {
+ if (getenv("ALLOW_ALL") != NULL) {
+ log_debug("Accepting connection because ALLOW_ALL is set");
+ return true;
+ }
+
+ if (getenv_list_matcher("ALLOW_HOST", sockaddr_matches_string, addr)) {
+ log_debug("Accepting connection because it matches ALLOW_HOST entry");
+ return true;
+ }
+
+ log_info("Rejecting connection: %s", sockaddr_tostring(addr));
+
+ return false;
+}
+
+void rewrite_ipv4_mapped_address(struct sockaddr_storage* addr) {
+ if (addr->ss_family != AF_INET6) {
+ return;
+ }
+
+ struct in6_addr* addr_data = &((struct sockaddr_in6*)addr)->sin6_addr;
+ if (!(
+ ((uint32_t*)(addr_data))[0] == 0 &&
+ ((uint32_t*)(addr_data))[1] == 0 &&
+ ((uint32_t*)(addr_data))[2] == htonl(0xffff)
+ )) {
+ return;
+ }
+
+ struct sockaddr_in new_addr;
+ memset(&new_addr, 0, sizeof(struct sockaddr));
+ new_addr.sin_family = AF_INET;
+ memcpy(&new_addr.sin_addr.s_addr, &((struct sockaddr_in6*)addr)->sin6_addr.s6_addr[12], 4);
+ memcpy(addr, &new_addr, sizeof(struct sockaddr_in));
+}
+
+void server_accept_connection(int listen_fd) {
+ struct sockaddr_storage client_addr;
+ socklen_t client_addr_length = sizeof(client_addr);
+
+ int fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_length);
+ if (fd < 0) {
+ log_errno("Failed to accept connection");
+ exit(1);
+ }
+
+ rewrite_ipv4_mapped_address(&client_addr);
+
+ if (!server_has_permission(&client_addr)) {
+ close(fd);
+ return;
+ }
+
+ log_info("Accepting connection: %s", sockaddr_tostring(&client_addr));
+
+ struct proxy_handle_accept_data* data = smalloc(sizeof(struct proxy_handle_accept_data));
+ data->fd = fd;
+ sockaddr_stringify(data->log_context, sizeof(data->log_context), &client_addr);
+ if (pthread_create(&data->tid, NULL, &proxy_handle_accept, data) != 0) {
+ log_errno("Failed to spawn thread");
+ exit(1);
+ }
+}
+
+int server_create_socket(int listen_port) {
+ int listen_fd = socket(AF_INET6, SOCK_STREAM, 0);
+ if (listen_fd < 0) {
+ log_errno("Failed to create socket");
+ exit(1);
+ }
+
+ int yes = 1;
+ if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) != 0) {
+ log_errno("Failed to set SO_REUSEADDR");
+ exit(1);
+ }
+
+
+ struct sockaddr_in6 server_addr;
+ memset(&server_addr, 0, sizeof(server_addr));
+ server_addr.sin6_family = AF_INET6;
+ server_addr.sin6_addr = in6addr_any;
+ server_addr.sin6_port = htons(listen_port);
+
+ if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
+ log_errno("Failed to bind to port");
+ exit(1);
+ }
+
+ if (listen(listen_fd, 5) < 0) {
+ log_errno("Failed to listen on socket");
+ exit(1);
+ }
+
+ return listen_fd;
+}
+
+
+
+int main() {
+ printf("socks5server " VERSION "\n");
+ log_context = "acceptor";
+
+ int listen_port = getenv_int("LISTEN_PORT");
+ int listen_fd = server_create_socket(listen_port);
+ log_info("Listening on port %d", listen_port);
+
+ for (;;) {
+ server_accept_connection(listen_fd);
+ }
+
+ return 0;
+}
--- /dev/null
+#!/usr/bin/env python3
+
+import socket
+import struct
+import subprocess
+import time
+import threading
+import unittest
+
+
+def get_free_port():
+ s = socket.socket()
+ s.bind(("", 0))
+ return s.getsockname()[1]
+
+SOCKS_PORT = get_free_port()
+ECHO_PORT = get_free_port()
+ECHO_PORT_B = struct.pack(">H", ECHO_PORT)
+
+
+class SocketHelper(object):
+ def init_socket(self):
+ self.sock = socket.socket(socket.AF_INET)
+ self.sock.connect(("localhost", SOCKS_PORT))
+
+ def init_ipv6_socket(self):
+ self.sock = socket.socket(socket.AF_INET6)
+ self.sock.connect(("localhost", SOCKS_PORT))
+
+ def destroy_socket(self):
+ self.sock.close()
+
+ def send(self, msg):
+ l = self.sock.send(msg)
+ self.assertEqual(len(msg), l)
+
+ def send_proxy_length(self, x):
+ self.send(("%s" % x).zfill(10).encode("ascii") + b"\n")
+
+ def recv(self, expected_length):
+ result = self.sock.recv(16384)
+ self.assertEqual(expected_length, len(result), str(result))
+ return result
+
+ def assertEnd(self):
+ try:
+ result = self.sock.recv(1)
+ self.assertEqual(0, len(result), str(result))
+ except ConnectionResetError:
+ return
+
+ def assertAuthSuccess(self):
+ result = self.recv(2)
+ self.assertEqual(b"\x05\x00", result)
+
+ def assertAuthFail(self):
+ result = self.recv(2)
+ self.assertEqual(b"\x05\xff", result)
+
+ def assertRequestSuccess(self):
+ self.assertRequestResponse(0)
+
+ def assertRequestFail(self, reply):
+ self.assertRequestResponse(reply)
+ self.assertEnd()
+
+ def assertRequestResponse(self, reply):
+ reply = struct.pack(">B", reply)
+ expected = b"\x05" + reply + b"\x00\x01\x00\x00\x00\x00\x00\x00"
+ result = self.recv(10)
+ self.assertEqual(expected, result)
+
+class TestAuthNegotiation(SocketHelper, unittest.TestCase):
+ def run(self, result=None):
+ with SocksServer():
+ unittest.TestCase.run(self, result)
+
+ def setUp(self):
+ self.init_socket()
+
+ def tearDown(self):
+ self.destroy_socket()
+
+ def test_one_method_success(self):
+ self.send(b"\x05\x01\x00")
+ self.assertAuthSuccess()
+
+ def test_two_methods_success_first(self):
+ self.send(b"\x05\x02\x00\x80")
+ self.assertAuthSuccess()
+
+ def test_two_methods_success_second(self):
+ self.send(b"\x05\x02\x80\x00")
+ self.assertAuthSuccess()
+
+ def test_no_methods_fail(self):
+ self.send(b"\x05\x00")
+ self.assertEnd()
+
+ def test_no_matching_methods_fail(self):
+ self.send(b"\x05\x01\x80")
+ self.assertAuthFail()
+
+ def test_invalid_version_fail(self):
+ self.send(b"\x04\x01\x00")
+ self.assertEnd()
+
+
+class TestRequestNegotiation(SocketHelper, unittest.TestCase):
+ def run(self, result=None):
+ with SocksServer():
+ unittest.TestCase.run(self, result)
+
+ def setUp(self):
+ self.init_socket()
+ self.send(b"\x05\x01\x00")
+ self.assertAuthSuccess()
+
+ def tearDown(self):
+ self.destroy_socket()
+
+ def test_invalid_version(self):
+ self.send(b"\x04\x01\x00\x01\x7f\x00\x00\x01\x00\01")
+ self.assertRequestFail(1)
+
+ def test_invalid_command(self):
+ self.send(b"\x05\x02\x00\x01\x7f\x00\x00\x01\x00\x01")
+ self.assertRequestFail(7)
+
+ def test_invalid_reserved_section(self):
+ self.send(b"\x05\x01\x01\x01\x7f\x00\x00\x01\x00\x01")
+ self.assertRequestFail(1)
+
+ def test_invalid_address_type(self):
+ self.send(b"\x05\x01\x00\x09\x7f\x00\x00\x01\x00\x01")
+ self.assertRequestFail(8)
+
+ def test_ipv4_success(self):
+ self.send(b"\x05\x01\x00\x01\x7f\x00\x00\x01" + ECHO_PORT_B)
+ self.assertRequestSuccess()
+
+ def test_ipv4_bad_port(self):
+ self.send(b"\x05\x01\x00\x01\x7f\x00\x00\x01\xff\xff")
+ self.assertRequestFail(4)
+
+ def test_ipv4_bad_host(self):
+ self.send(b"\x05\x01\x00\x01\x7f\x00\x00\x00\xff\xff")
+ self.assertRequestFail(4)
+
+ def test_dns_success(self):
+ self.send(b"\x05\x01\x00\x03\x09localhost" + ECHO_PORT_B)
+ self.assertRequestSuccess()
+
+ def test_dns_bad_port(self):
+ self.send(b"\x05\x01\x00\x03\x09localhost\xff\xff")
+ self.assertRequestFail(4)
+
+ def test_dns_bad_host(self):
+ self.send(b"\x05\x01\x00\x03\x09f.invalid" + ECHO_PORT_B)
+ self.assertRequestFail(4)
+
+ def test_dns_invalid_host(self):
+ self.send(b"\x05\x01\x00\x03\x00" + ECHO_PORT_B)
+ self.assertEnd()
+
+ def test_ipv6_success(self):
+ self.send(b"\x05\x01\x00\x04" + (b"\x00"*15) + b"\x01" + ECHO_PORT_B)
+ self.assertRequestSuccess()
+
+ def test_ipv6_bad_port(self):
+ self.send(b"\x05\x01\x00\x04" + (b"\x00"*15) + b"\x01" + b"\xff\xff")
+ self.assertRequestFail(4)
+
+ def test_ipv6_bad_host(self):
+ self.send(b"\x05\x01\x00\x04" + b"\xfe\x80" + (b"\x00"*13) + b"\x01" + ECHO_PORT_B)
+ self.assertRequestFail(4)
+
+
+class ProxyPacketHelper(object):
+ def test_one_packet(self):
+ self.send_proxy_length(3)
+ self.send(b"foo")
+ result = self.recv(3)
+ self.assertEqual(b"foo", result)
+ self.assertEnd()
+
+ def test_no_received_data(self):
+ self.send_proxy_length(0)
+ self.send(b"foo")
+ self.assertEnd()
+
+ def test_two_packets(self):
+ self.send_proxy_length(6)
+
+ self.send(b"foo")
+ result = self.recv(3)
+ self.assertEqual(b"foo", result)
+
+ self.send(b"bar")
+ result = self.recv(3)
+ self.assertEqual(b"bar", result)
+
+ self.assertEnd()
+
+ def test_large_packet(self):
+ msg = b"1234" * 1024
+ self.send_proxy_length(len(msg))
+ self.send(msg)
+ count = len(msg)
+ while count > 0:
+ part = self.sock.recv(4)
+ self.assertEqual(b"1234", part)
+ count -= 4
+ self.assertEnd()
+
+
+class TestIPv4Proxy(SocketHelper, ProxyPacketHelper, unittest.TestCase):
+ def run(self, result=None):
+ with SocksServer():
+ unittest.TestCase.run(self, result)
+
+ def setUp(self):
+ self.init_socket()
+
+ self.send(b"\x05\x01\x00")
+ self.assertAuthSuccess()
+
+ self.send(b"\x05\x01\x00\x01\x7f\x00\x00\x01" + ECHO_PORT_B)
+ self.assertRequestSuccess()
+
+ def tearDown(self):
+ self.destroy_socket()
+
+class TestDNSProxy(SocketHelper, ProxyPacketHelper, unittest.TestCase):
+ def run(self, result=None):
+ with SocksServer():
+ unittest.TestCase.run(self, result)
+
+ def setUp(self):
+ self.init_socket()
+
+ self.send(b"\x05\x01\x00")
+ self.assertAuthSuccess()
+
+ self.send(b"\x05\x01\x00\x03\x09localhost" + ECHO_PORT_B)
+ self.assertRequestSuccess()
+
+ def tearDown(self):
+ self.destroy_socket()
+
+class TestIPv6Proxy(SocketHelper, ProxyPacketHelper, unittest.TestCase):
+ def run(self, result=None):
+ with SocksServer():
+ unittest.TestCase.run(self, result)
+
+ def setUp(self):
+ self.init_socket()
+
+ self.send(b"\x05\x01\x00")
+ self.assertAuthSuccess()
+
+ self.send(b"\x05\x01\x00\x04" + (b"\x00"*15) + b"\x01" + ECHO_PORT_B)
+ self.assertRequestSuccess()
+
+ def tearDown(self):
+ self.destroy_socket()
+
+class TestPermissions(SocketHelper, unittest.TestCase):
+ def assert_connection_allowed(self):
+ try:
+ self.init_socket()
+ self.send(b"\x05\x01\x00")
+ self.assertAuthSuccess()
+ finally:
+ self.destroy_socket()
+
+ def assert_ipv6_connection_allowed(self):
+ try:
+ self.init_ipv6_socket()
+ self.send(b"\x05\x01\x00")
+ self.assertAuthSuccess()
+ finally:
+ self.destroy_socket()
+
+ def assert_connection_blocked(self):
+ try:
+ self.init_socket()
+ self.send(b"\x05\x01\x00")
+ self.assertAuthSuccess()
+ self.fail("Expected ConnectionResetError")
+ except ConnectionResetError:
+ pass
+ finally:
+ self.destroy_socket()
+
+ def test_allow_all_connections(self):
+ with SocksServer({"ALLOW_ALL": "1"}):
+ self.assert_connection_allowed()
+
+ def test_block_all_connections(self):
+ with SocksServer({}):
+ self.assert_connection_blocked()
+
+ def test_allow_ipv4_host(self):
+ with SocksServer({"ALLOW_HOST1": "127.0.0.1"}):
+ self.assert_connection_allowed()
+
+ def test_allow_ipv6_host(self):
+ with SocksServer({"ALLOW_HOST1": "::1"}):
+ self.assert_ipv6_connection_allowed()
+
+ def test_allow_multiple_hosts(self):
+ with SocksServer({"ALLOW_HOST1": "foo.invalid", "ALLOW_HOST2": "localhost"}):
+ self.assert_connection_allowed()
+
+
+class EchoServer(object):
+ def __enter__(self):
+ self.run = True
+
+ self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.sock.bind(("", ECHO_PORT))
+ self.sock.listen(5)
+
+ self.thread = threading.Thread(target=self.run_echo_server)
+ self.thread.start()
+
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.run = False
+ self.sock.shutdown(socket.SHUT_RDWR)
+ self.thread.join()
+
+ def run_echo_server(self):
+ try:
+ while self.run:
+ client, addr = self.sock.accept()
+ try:
+ self.handle_echo_client(client)
+ finally:
+ client.close()
+ except:
+ if not self.run:
+ return
+
+ def handle_echo_client(self, client):
+ line = client.recv(10+1)
+ if not line:
+ return
+ num_bytes = int(line)
+
+ while num_bytes > 0:
+ # force the test app to handle many packets by using small ones
+ data = client.recv(16)
+ if not data:
+ break
+ num_bytes -= len(data)
+ while data:
+ l = client.send(data)
+ data = data[l:]
+
+class SocksServer(object):
+ def __init__(self, extra_env={"ALLOW_ALL": "1"}):
+ self.env = {}
+ self.env["LISTEN_PORT"] = str(SOCKS_PORT)
+ self.env.update(extra_env)
+ self.devnull = open("/dev/null", "w")
+
+ def __enter__(self):
+ self.process = subprocess.Popen(
+ args=["./socks5server"],
+ stdout=self.devnull,
+ stderr=self.devnull,
+ env=self.env,
+ )
+
+ self.wait_for_port()
+
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.devnull.close()
+ self.process.terminate()
+ self.process.wait()
+
+ def wait_for_port(self):
+ start_time = time.time()
+ with socket.socket(socket.AF_INET) as s:
+ while start_time + 10 > time.time():
+ try:
+ s.connect(("localhost", SOCKS_PORT))
+ return
+ except ConnectionRefusedError:
+ time.sleep(0.01)
+
+
+if __name__ == "__main__":
+ with EchoServer():
+ unittest.main()