diff --git a/Makefile.am b/Makefile.am
index 5b63f64a..2ac8999a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -932,12 +932,17 @@ endif
if ON_LINUX
test_apps += tests/test_abstract_ipc \
- tests/test_many_sockets
+ tests/test_many_sockets \
+ tests/test_socks
tests_test_abstract_ipc_SOURCES = tests/test_abstract_ipc.cpp
tests_test_abstract_ipc_LDADD = src/libzmq.la ${TESTUTIL_LIBS}
tests_test_abstract_ipc_CPPFLAGS = ${TESTUTIL_CPPFLAGS}
+tests_test_socks_SOURCES = tests/test_socks.cpp
+tests_test_socks_LDADD = src/libzmq.la ${TESTUTIL_LIBS}
+tests_test_socks_CPPFLAGS = ${TESTUTIL_CPPFLAGS}
+
endif
if HAVE_VMCI
diff --git a/tests/test_socks.cpp b/tests/test_socks.cpp
new file mode 100644
index 00000000..e99d1d76
--- /dev/null
+++ b/tests/test_socks.cpp
@@ -0,0 +1,894 @@
+/*
+ Copyright (c) 2019 Contributors as noted in the AUTHORS file
+
+ This file is part of libzmq, the ZeroMQ core engine in C++.
+
+ libzmq is free software; you can redistribute it and/or modify it under
+ the terms of the GNU Lesser General Public License (LGPL) as published
+ by the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ As a special exception, the Contributors give you permission to link
+ this library with independent modules to produce an executable,
+ regardless of the license terms of these independent modules, and to
+ copy and distribute the resulting executable under terms of your choice,
+ provided that you also meet, for each linked independent module, the
+ terms and conditions of the license of that module. An independent
+ module is a module which is not derived from or based on this library.
+ If you modify this library, you must extend this exception to your
+ version of the library.
+
+ libzmq is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "testutil.hpp"
+#include "testutil_unity.hpp"
+
+
+SETUP_TEARDOWN_TESTCONTEXT
+
+void recvall (int sock_fd, char *buffer, int len)
+{
+ int res;
+ int total = 0;
+ while (len - total > 0) {
+ res = recv (sock_fd, buffer + total, len - total, 0);
+ if (res == -1)
+ fprintf (stderr, "socks_server: error receiving %d bytes: %d %d\n",
+ len, res, errno);
+ TEST_ASSERT_SUCCESS_RAW_ERRNO (res);
+ TEST_ASSERT (res != 0);
+ total += res;
+ }
+ TEST_ASSERT (total == len);
+}
+
+int recvonce (int sock_fd, char *buffer, int len)
+{
+ int res;
+ res = recv (sock_fd, buffer, len, 0);
+ if (res == -1)
+ fprintf (stderr, "socks_server: error receiving bytes: %d %d\n", res,
+ errno);
+ TEST_ASSERT_SUCCESS_RAW_ERRNO (res);
+ return res;
+}
+
+void sendall (int sock_fd, char *buffer, int len)
+{
+ int res;
+ int total = 0;
+ while (len - total > 0) {
+ res = send (sock_fd, buffer + total, len - total, 0);
+ if (res == -1)
+ fprintf (stderr, "socks_server: error sending %d bytes: %d %d\n",
+ len, res, errno);
+ TEST_ASSERT_SUCCESS_RAW_ERRNO (res);
+ TEST_ASSERT (res != 0);
+ total += res;
+ }
+}
+
+int remote_connect (int socket, uint32_t addr, uint16_t port)
+{
+ int res;
+ struct sockaddr_in ip4addr;
+ ip4addr.sin_family = AF_INET;
+ ip4addr.sin_addr.s_addr = htonl (addr);
+ ip4addr.sin_port = htons (port);
+ res = connect (socket, (struct sockaddr *) &ip4addr, sizeof ip4addr);
+ return res;
+}
+
+void *setup_socks_server (char *socks_server_address,
+ int socks_server_address_len)
+{
+ fprintf (stderr, "socks_server: setup socks server\n");
+ int server_fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ TEST_ASSERT_NOT_EQUAL (-1, server_fd);
+ int flag = 1;
+ int res;
+ res = setsockopt (server_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof (int));
+ TEST_ASSERT_SUCCESS_RAW_ERRNO (res);
+ struct sockaddr_in saddr = bind_bsd_socket (server_fd);
+ int nbytes = snprintf (socks_server_address, socks_server_address_len,
+ "127.0.0.1:%d", ntohs (saddr.sin_port));
+ TEST_ASSERT (nbytes >= 0 && nbytes < socks_server_address_len);
+ fprintf (stderr, "socks_server: bound to: tcp://%s\n",
+ socks_server_address);
+ return (void *) (intptr_t) server_fd;
+}
+
+void socks_server_task (void *socks_server,
+ const char *username,
+ const char *password,
+ int max_client_connect)
+{
+ int server_fd = (int) (intptr_t) socks_server;
+ fprintf (stderr, "socks_server: starting server thread\n");
+
+ int res;
+ res = listen (server_fd, max_client_connect);
+ TEST_ASSERT_SUCCESS_RAW_ERRNO (res);
+
+ int auth_method;
+ if (username == NULL || username[0] == '\0') {
+ auth_method = 0x0; /* No auth */
+ } else {
+ auth_method = 0x2; /* Basic auth */
+ if (password == NULL)
+ password = "";
+ }
+
+ int count = 0;
+ while (count < max_client_connect) {
+ int client = -1;
+ do {
+ char buffer[4096];
+ fprintf (stderr, "socks_server: waiting for connection\n");
+ client = accept (server_fd, NULL, NULL);
+ TEST_ASSERT_SUCCESS_RAW_ERRNO (client);
+ count++;
+ fprintf (stderr, "socks_server: accepted client connection %d/%d\n",
+ count, max_client_connect);
+
+ /* Greetings [version, nmethods, methods...]. */
+ recvall (client, buffer, 2);
+ TEST_ASSERT (buffer[0] == 0x5);
+ int nmethods = buffer[1];
+ int method = 0xff;
+ recvall (client, buffer, nmethods);
+ for (int i = 0; i < nmethods; i++) {
+ if (buffer[i] == auth_method)
+ method = auth_method;
+ }
+ fprintf (stderr, "socks_server: received greetings\n");
+
+ /* Greetings response [version, method]. */
+ buffer[0] = 0x5;
+ buffer[1] = method;
+ sendall (client, buffer, 2);
+ fprintf (stderr,
+ "socks_server: answered greetings (method: 0x%x)\n",
+ method);
+
+ if (method == 0xff)
+ break; /* Out of client connection */
+
+ if (method == 0x2) {
+ int len;
+ int err = 0;
+ recvall (client, buffer, 1);
+ if (buffer[0] != 0x1) {
+ err = 1;
+ } else {
+ recvall (client, buffer, 1);
+ len = (unsigned char) buffer[0];
+ recvall (client, buffer, len);
+ buffer[len] = '\0';
+ if (strcmp (username, buffer) != 0) {
+ fprintf (stderr,
+ "socks_server: error on username check: '%s', "
+ "expected: '%s'\n",
+ buffer, username);
+ err = 1;
+ }
+ recvall (client, buffer, 1);
+ len = (unsigned char) buffer[0];
+ recvall (client, buffer, len);
+ buffer[len] = '\0';
+ if (strcmp (password, buffer) != 0) {
+ fprintf (stderr,
+ "socks_server: error on password check: '%s', "
+ "expected: '%s'\n",
+ buffer, password);
+ err = 1;
+ }
+ }
+ fprintf (stderr, "socks_server: received credentials\n");
+ buffer[0] = 0x1;
+ buffer[1] = err;
+ sendall (client, buffer, 2);
+ fprintf (stderr,
+ "socks_server: answered credentials (err: 0x%x)\n",
+ err);
+ if (err != 0)
+ break; /* Out of client connection. */
+ }
+
+ /* Request [version, cmd, rsv, atype, dst.addr, dst.port */
+ /* Test only tcp connect on top of IPV4 */
+ recvall (client, buffer, 4);
+ TEST_ASSERT (buffer[0] == 0x5);
+ TEST_ASSERT (buffer[1] == 0x1); /* CONNECT cmd */
+ TEST_ASSERT (buffer[2] == 0x0); /* reserved, ensure we send 0 */
+ fprintf (stderr,
+ "socks_server: received command (cmd: %d, atype: %d)\n",
+ buffer[1], buffer[3]);
+ /* IPv4 ADDR & PORT */
+ uint32_t naddr = 0, bind_naddr = 0;
+ uint16_t nport = 0, bind_nport = 0;
+ int remote = -1;
+ int err = 0;
+ int request_atype = buffer[3];
+ if (request_atype == 0x1) /* ATYPE IPv4 */ {
+ recvall (client, (char *) &naddr, 4);
+ fprintf (stderr,
+ "socks_server: received address (addr: 0x%x)\n",
+ ntohl (naddr));
+ } else if (request_atype == 0x3) /* ATYPE DOMAINNAME */ {
+ int len;
+ recvall (client, buffer, 1);
+ len = (unsigned char) buffer[0];
+ recvall (client, buffer, len);
+ buffer[len] = '\0';
+ fprintf (stderr,
+ "socks_server: received domainname (hostname: %s)\n",
+ buffer);
+ /* For the test we support static resolution of
+ hostname "somedomainnmame.org" to localhost */
+ if (strcmp ("somedomainname.org", buffer) == 0) {
+ naddr = htonl (0x7f000001); /* 127.0.0.1 */
+ } else {
+ err = 0x4; /* Host unreachable */
+ }
+ } else if (request_atype == 0x4) {
+ /* For the test we simulate IPV6 connection request ::1, but connect to IPv4 localhost */
+ unsigned char nipv6local[16] = {0};
+ nipv6local[15] = 1;
+ recvall (client, buffer, 16);
+ fprintf (stderr,
+ "socks_server: received ipv6 address (buffer:");
+ for (int i = 0; i < 16; i++)
+ fprintf (stderr, " 0x%x", (unsigned char) buffer[i]);
+ fprintf (stderr, ")\n");
+ if (memcmp (buffer, nipv6local, 16) == 0) {
+ naddr = htonl (0x7f000001); /* 127.0.0.1 */
+ } else {
+ err = 0x4; /* Host unreachable */
+ }
+ } else {
+ err = 0x8; /* ATYPE not supported */
+ }
+ recvall (client, (char *) &nport, 2);
+ fprintf (stderr, "socks_server: received port (port: %d)\n",
+ ntohs (nport));
+ if (err == 0) {
+ fprintf (stderr, "socks_server: trying to connect to %x:%d\n",
+ ntohl (naddr), ntohs (nport));
+ remote = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ res = remote_connect (remote, ntohl (naddr), ntohs (nport));
+ if (res != 0) {
+ err = 0x5; /* Connection refused */
+ } else {
+ struct sockaddr_in ip4addr;
+ socklen_t len = sizeof (ip4addr);
+ res =
+ getsockname (remote, (struct sockaddr *) &ip4addr, &len);
+ TEST_ASSERT_SUCCESS_RAW_ERRNO (res);
+ fprintf (stderr,
+ "socks_server: connected and bound at: %x:%d\n",
+ ntohl (ip4addr.sin_addr.s_addr),
+ ntohs (ip4addr.sin_port));
+ bind_naddr = ip4addr.sin_addr.s_addr;
+ bind_nport = ip4addr.sin_port;
+ }
+ }
+
+ /* Reply request */
+ buffer[0] = 0x5;
+ buffer[1] = err;
+ buffer[2] = 0;
+ buffer[3] = request_atype;
+ sendall (client, buffer, 4);
+ if (request_atype == 0x1) /* ATYPE IPv4 */ {
+ sendall (client, (char *) &bind_naddr, 4);
+ } else if (request_atype == 0x3) {
+ /* This is just for testing reply with a hostname,
+ normally a resolved address is passed in return to connect. */
+ buffer[0] = strlen ("localhost");
+ sendall (client, buffer, 1);
+ strcpy (buffer, "localhost");
+ sendall (client, buffer, strlen ("localhost"));
+ } else if (request_atype == 0x4) {
+ /* We simulate a bind to ::1, though the actual connection is IPv4 */
+ char nipv6local[16] = {0};
+ nipv6local[15] = 1;
+ sendall (client, nipv6local, sizeof nipv6local);
+ }
+ sendall (client, (char *) &bind_nport, 2);
+ fprintf (stderr, "socks_server: replied to request (err: 0x%x)\n",
+ err);
+ if (err != 0)
+ break; /* Out of client connection. */
+
+ /* Communication loop */
+ zmq_pollitem_t items[] = {
+ {NULL, client, ZMQ_POLLIN, 0},
+ {NULL, remote, ZMQ_POLLIN, 0},
+ };
+ fprintf (stderr,
+ "socks_server: waiting for input (client fd: %d, remote "
+ "fd: %d)\n",
+ client, remote);
+ while (1) {
+ if (client == -1 || remote == -1)
+ break;
+ if (zmq_poll (items, 2, -1) < 0)
+ break;
+ int nbytes;
+ for (int i = 0; i < 2; i++) {
+ if ((items[i].revents & ZMQ_POLLIN) == 0)
+ continue;
+ fprintf (stderr, "socks_server: ready to read from fd %d\n",
+ items[i].fd);
+ int write_fd, read_fd = items[i].fd;
+ if (read_fd == client) {
+ write_fd = remote;
+ } else {
+ write_fd = client;
+ }
+ nbytes = recvonce (read_fd, buffer, sizeof buffer);
+ fprintf (stderr, "socks_server: read returned: %d\n",
+ nbytes);
+ if (nbytes == 0 || nbytes == -1) {
+ /* End of stream or error */
+ if (read_fd == client) {
+ close (client);
+ client = -1;
+ }
+ if (read_fd == remote) {
+ close (remote);
+ remote = -1;
+ }
+ break;
+ }
+ sendall (write_fd, buffer, nbytes);
+ }
+ }
+ if (remote != -1) {
+ close (remote);
+ remote = -1;
+ }
+ fprintf (stderr, "socks_server: closed remote connection %d/%d\n",
+ count, max_client_connect);
+ } while (0); /* Client socket scope. */
+ if (client != -1) {
+ close (client);
+ client = -1;
+ }
+ fprintf (stderr, "socks_server: closed client connection %d/%d\n",
+ count, max_client_connect);
+ }
+ close (server_fd);
+ fprintf (stderr, "socks_server: closed server\n");
+}
+
+void socks_server_no_auth (void *socks_server)
+{
+ socks_server_task (socks_server, NULL, NULL, 1);
+}
+
+void socks_server_no_auth_delay (void *socks_server)
+{
+ fprintf (stderr, "socks_server: delay no auth socks server start\n");
+ // Enough delay to have client connecting before proxy listens
+ msleep (SETTLE_TIME * 10);
+ socks_server_task (socks_server, NULL, NULL, 1);
+}
+
+void socks_server_basic_auth (void *socks_server)
+{
+ socks_server_task (socks_server, "someuser", "somepass", 1);
+}
+
+void socks_server_basic_auth_delay (void *socks_server)
+{
+ fprintf (stderr, "socks_server: delay basic auth socks server start\n");
+ // Enough delay to have client connecting before proxy listens
+ msleep (SETTLE_TIME * 10);
+ socks_server_task (socks_server, "someuser", "somepass", 1);
+}
+
+void socks_server_basic_auth_no_pass (void *socks_server)
+{
+ socks_server_task (socks_server, "someuser", NULL, 1);
+}
+
+void *setup_push_server (char *connect_address, int connect_address_size)
+{
+ int res;
+ const char *bind_address = "tcp://127.0.0.1:*";
+ void *push = test_context_socket (ZMQ_PUSH);
+ res = zmq_bind (push, bind_address);
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+ size_t len = connect_address_size;
+ res = zmq_getsockopt (push, ZMQ_LAST_ENDPOINT, connect_address, &len);
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+ fprintf (stderr, "push_server: bound to: %s\n", connect_address);
+ return push;
+}
+
+void *setup_pull_client (const char *connect_address, const char *socks_proxy)
+{
+ int res;
+ void *pull = test_context_socket (ZMQ_PULL);
+ if (socks_proxy != NULL) {
+ res = zmq_setsockopt (pull, ZMQ_SOCKS_PROXY, socks_proxy,
+ strlen (socks_proxy));
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+ fprintf (stderr, "pull_client: use socks proxy: tcp://%s\n",
+ socks_proxy);
+ }
+ res = zmq_connect (pull, connect_address);
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+ fprintf (stderr, "pull_client: connected to: %s\n", connect_address);
+ return pull;
+}
+
+#ifdef ZMQ_BUILD_DRAFT_API
+void *setup_pull_client_with_auth (const char *connect_address,
+ const char *socks_proxy,
+ const char *username,
+ const char *password)
+{
+ int res;
+ void *pull = test_context_socket (ZMQ_PULL);
+
+ if (socks_proxy != NULL) {
+ res = zmq_setsockopt (pull, ZMQ_SOCKS_PROXY, socks_proxy,
+ strlen (socks_proxy));
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+ fprintf (stderr, "pull_client: use socks proxy: tcp://%s\n",
+ socks_proxy);
+ }
+
+ res = zmq_setsockopt (pull, ZMQ_SOCKS_USERNAME, username,
+ username == NULL ? 0 : strlen (username));
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+
+ res = zmq_setsockopt (pull, ZMQ_SOCKS_PASSWORD, password,
+ password == NULL ? 0 : strlen (password));
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+
+ res = zmq_connect (pull, connect_address);
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+ fprintf (stderr, "pull_client: connected to: %s\n", connect_address);
+ return pull;
+}
+#endif
+
+void communicate (void *push, void *pull)
+{
+ fprintf (stderr, "push_server: sending 2 messages\n");
+ s_send_seq (push, "ABC", SEQ_END);
+ s_send_seq (push, "DEF", SEQ_END);
+
+ fprintf (stderr, "pull_client: receiving 2 messages\n");
+ s_recv_seq (pull, "ABC", SEQ_END);
+ s_recv_seq (pull, "DEF", SEQ_END);
+}
+
+void test_socks_no_socks (void)
+{
+ char connect_address[MAX_SOCKET_STRING];
+
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ void *pull = setup_pull_client (connect_address, NULL);
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+}
+
+void test_socks (void)
+{
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ void *pull = setup_pull_client (connect_address, socks_server_address);
+ void *thread = zmq_threadstart (&socks_server_no_auth, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+}
+
+void test_socks_delay (void)
+{
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ void *pull = setup_pull_client (connect_address, socks_server_address);
+ void *thread = zmq_threadstart (&socks_server_no_auth_delay, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+}
+
+void test_socks_domainname (void)
+{
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+ char connect_address_hostname[MAX_SOCKET_STRING];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ // Will be resolved by the proxy test server to 127.0.0.1
+ strcpy (connect_address_hostname, "tcp://somedomainname.org");
+ strcat (connect_address_hostname, strrchr (connect_address, ':'));
+ void *pull =
+ setup_pull_client (connect_address_hostname, socks_server_address);
+ void *thread = zmq_threadstart (&socks_server_no_auth, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+}
+
+void test_socks_ipv6 (void)
+{
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+ char connect_address_hostname[MAX_SOCKET_STRING];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ // Will be resolved by the proxy test server to 127.0.0.1
+ strcpy (connect_address_hostname, "tcp://::1");
+ strcat (connect_address_hostname, strrchr (connect_address, ':'));
+ void *pull =
+ setup_pull_client (connect_address_hostname, socks_server_address);
+ void *thread = zmq_threadstart (&socks_server_no_auth, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+}
+
+void test_socks_ipv6_sb (void)
+{
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+ char connect_address_hostname[MAX_SOCKET_STRING];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ // Will be resolved by the proxy test server to 127.0.0.1
+ strcpy (connect_address_hostname, "tcp://[::1]");
+ strcat (connect_address_hostname, strrchr (connect_address, ':'));
+ void *pull =
+ setup_pull_client (connect_address_hostname, socks_server_address);
+ void *thread = zmq_threadstart (&socks_server_no_auth, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+}
+
+void test_socks_bind_before_connect (void)
+{
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+ char socks_address_bind_before_connect[MAX_SOCKET_STRING * 2];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ // Will do a bind before connect when connecting to proxy
+ strcpy (socks_address_bind_before_connect, "127.0.0.1:0;");
+ strcat (socks_address_bind_before_connect, socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ void *pull =
+ setup_pull_client (connect_address, socks_address_bind_before_connect);
+ void *thread = zmq_threadstart (&socks_server_no_auth, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+}
+
+void test_socks_basic_auth (void)
+{
+#ifdef ZMQ_BUILD_DRAFT_API
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ void *pull = setup_pull_client_with_auth (
+ connect_address, socks_server_address, "someuser", "somepass");
+ void *thread = zmq_threadstart (&socks_server_basic_auth, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+#else
+ TEST_IGNORE_MESSAGE (
+ "libzmq without DRAFT support, ignoring socks basic auth test");
+#endif
+}
+
+void test_socks_basic_auth_delay (void)
+{
+#ifdef ZMQ_BUILD_DRAFT_API
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ void *pull = setup_pull_client_with_auth (
+ connect_address, socks_server_address, "someuser", "somepass");
+ void *thread = zmq_threadstart (&socks_server_basic_auth_delay, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+#else
+ TEST_IGNORE_MESSAGE (
+ "libzmq without DRAFT support, ignoring socks basic auth test");
+#endif
+}
+
+void test_socks_basic_auth_empty_user (void)
+{
+#ifdef ZMQ_BUILD_DRAFT_API
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ void *pull = setup_pull_client_with_auth (connect_address,
+ socks_server_address, "", NULL);
+ void *thread = zmq_threadstart (&socks_server_no_auth, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+#else
+ TEST_IGNORE_MESSAGE (
+ "libzmq without DRAFT support, ignoring socks basic auth test");
+#endif
+}
+
+void test_socks_basic_auth_null_user (void)
+{
+#ifdef ZMQ_BUILD_DRAFT_API
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ void *pull = setup_pull_client_with_auth (connect_address,
+ socks_server_address, NULL, NULL);
+ void *thread = zmq_threadstart (&socks_server_no_auth, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+#else
+ TEST_IGNORE_MESSAGE (
+ "libzmq without DRAFT support, ignoring socks basic auth test");
+#endif
+}
+
+void test_socks_basic_auth_empty_pass (void)
+{
+#ifdef ZMQ_BUILD_DRAFT_API
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ void *pull = setup_pull_client_with_auth (
+ connect_address, socks_server_address, "someuser", "");
+ void *thread = zmq_threadstart (&socks_server_basic_auth_no_pass, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+#else
+ TEST_IGNORE_MESSAGE (
+ "libzmq without DRAFT support, ignoring socks basic auth test");
+#endif
+}
+
+void test_socks_basic_auth_null_pass (void)
+{
+#ifdef ZMQ_BUILD_DRAFT_API
+ char socks_server_address[MAX_SOCKET_STRING];
+ char connect_address[MAX_SOCKET_STRING];
+
+ void *socks =
+ setup_socks_server (socks_server_address, sizeof socks_server_address);
+ void *push = setup_push_server (connect_address, sizeof connect_address);
+ void *pull = setup_pull_client_with_auth (
+ connect_address, socks_server_address, "someuser", NULL);
+ void *thread = zmq_threadstart (&socks_server_basic_auth_no_pass, socks);
+
+ communicate (push, pull);
+
+ test_context_socket_close_zero_linger (push);
+ test_context_socket_close_zero_linger (pull);
+
+ zmq_threadclose (thread);
+#else
+ TEST_IGNORE_MESSAGE (
+ "libzmq without DRAFT support, ignoring socks basic auth test");
+#endif
+}
+
+
+void test_string_opt_ok (const char *msg, int opt, const char *value)
+{
+ int res;
+ void *sub = test_context_socket (ZMQ_SUB);
+ fprintf (stderr, "test_string_opt_ok: testing %s\n", msg);
+ res = zmq_setsockopt (sub, opt, value, strlen (value));
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+ char buffer[1024];
+ size_t res_len = (size_t) sizeof buffer;
+ res = zmq_getsockopt (sub, opt, buffer, &res_len);
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+ TEST_ASSERT_EQUAL (strlen (value) + 1, res_len);
+ TEST_ASSERT (strcmp (buffer, value) == 0);
+ test_context_socket_close_zero_linger (sub);
+}
+
+void test_opt_ok (const char *msg,
+ int opt,
+ const char *value,
+ size_t len,
+ const char *expected_value,
+ size_t expected_len)
+{
+ int res;
+ void *sub = test_context_socket (ZMQ_SUB);
+ fprintf (stderr, "test_opt_ok: testing %s\n", msg);
+ res = zmq_setsockopt (sub, opt, value, len);
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+ char buffer[1024];
+ size_t res_len = (size_t) sizeof buffer;
+ res = zmq_getsockopt (sub, opt, buffer, &res_len);
+ TEST_ASSERT_SUCCESS_ERRNO (res);
+ TEST_ASSERT_EQUAL (expected_len + 1, res_len);
+ TEST_ASSERT_EQUAL (expected_len, strlen (buffer));
+ TEST_ASSERT (strncmp (buffer, expected_value, expected_len) == 0);
+ test_context_socket_close_zero_linger (sub);
+}
+
+void test_opt_invalid (const char *msg, int opt, const char *value, int len)
+{
+ int res;
+ void *sub = test_context_socket (ZMQ_SUB);
+ fprintf (stderr, "test_opt_invalid: testing %s\n", msg);
+ res = zmq_setsockopt (sub, opt, value, len);
+ TEST_ASSERT_FAILURE_ERRNO (EINVAL, res);
+ test_context_socket_close_zero_linger (sub);
+}
+
+void test_socks_proxy_options (void)
+{
+ // NULL is equivalent to not set and returns empty string
+ test_opt_ok ("NULL proxy", ZMQ_SOCKS_PROXY, NULL, 0, "", 0);
+ test_string_opt_ok ("valid proxy", ZMQ_SOCKS_PROXY, "somehost:1080");
+ // Empty value not allowed for proxy server
+ test_opt_invalid ("empty proxy", ZMQ_SOCKS_PROXY, "", 0);
+}
+
+void test_socks_userpass_options (void)
+{
+#ifdef ZMQ_BUILD_DRAFT_API
+ char buffer[1024];
+ for (int i = 0; i < (int) sizeof buffer; i++) {
+ buffer[i] = 'a' + i % 26;
+ }
+
+ // NULL is equivalent to not-set or ""
+ test_opt_ok ("NULL username", ZMQ_SOCKS_USERNAME, NULL, 0, "", 0);
+ // Empty value is allowed for username, means no authentication
+ test_string_opt_ok ("empty username", ZMQ_SOCKS_USERNAME, "");
+ test_string_opt_ok ("valid username", ZMQ_SOCKS_USERNAME, "someuser");
+ test_opt_ok ("255 bytes username", ZMQ_SOCKS_USERNAME, buffer, 255, buffer,
+ 255);
+ test_opt_invalid ("too long username", ZMQ_SOCKS_USERNAME, buffer, 256);
+
+ // NULL is equivalent to not-set or ""
+ test_opt_ok ("NULL password", ZMQ_SOCKS_PASSWORD, NULL, 0, "", 0);
+ // Empty value allowed for password
+ test_string_opt_ok ("empty password", ZMQ_SOCKS_PASSWORD, "");
+ test_string_opt_ok ("valid password", ZMQ_SOCKS_PASSWORD, "someuser");
+ test_opt_ok ("255 bytes password", ZMQ_SOCKS_PASSWORD, buffer, 255, buffer,
+ 255);
+ test_opt_invalid ("too long password", ZMQ_SOCKS_PASSWORD, buffer, 256);
+#else
+ TEST_IGNORE_MESSAGE ("libzmq without DRAFT support, ignoring socks setopt "
+ "username/password test");
+#endif
+}
+
+int main ()
+{
+ setup_test_environment ();
+
+ UNITY_BEGIN ();
+ RUN_TEST (test_socks_proxy_options);
+ RUN_TEST (test_socks_userpass_options);
+ RUN_TEST (test_socks_no_socks);
+ RUN_TEST (test_socks);
+ RUN_TEST (test_socks_delay);
+ RUN_TEST (test_socks_domainname);
+ RUN_TEST (test_socks_ipv6);
+ RUN_TEST (test_socks_ipv6_sb);
+ RUN_TEST (test_socks_bind_before_connect);
+ RUN_TEST (test_socks_basic_auth);
+ RUN_TEST (test_socks_basic_auth_delay);
+ RUN_TEST (test_socks_basic_auth_empty_user);
+ RUN_TEST (test_socks_basic_auth_null_user);
+ RUN_TEST (test_socks_basic_auth_empty_pass);
+ RUN_TEST (test_socks_basic_auth_null_pass);
+ return UNITY_END ();
+}