Description
In src/tcp_listener.cpp there is this code at the moment:
// Create a listening socket.
s = open_socket (address.family (), SOCK_STREAM, IPPROTO_TCP);
// IPv6 address family not supported, try automatic downgrade to IPv4.
if (address.family () == AF_INET6
&& errno == EAFNOSUPPORT
&& options.ipv6) {
rc = address.resolve (addr_, true, true);
if (rc != 0)
return rc;
s = ::socket (address.family (), SOCK_STREAM, IPPROTO_TCP);
}
IMHO this doesn't make much sense. EAFNOSUPPORT is returned when you try to create an AF_INET6 socket and there's no IPv6 support on the system. The fallback detects this, and tries to recover by... opening an IPv6 socket!
This is an issue because even if you specify an IPv4 address in the endpoint, if IPv6 is enabled in ZMQ (which is a global option in CZMQ) the bind is going to fail, even though it should work, since the user explicitly passed an IPv4 address.
Easily reproduced by compiling and running this code on a machine without IPv6:
#include <stdio.h>
#include <zmq.h>
#include <assert.h>
#include "testutil.hpp"
int main(int argc, char **argv)
{
void *ctx = zmq_ctx_new();
void *rep = NULL, *req = NULL;
int ipv6 = 1;
int rc = 0;
rep = zmq_socket (ctx, ZMQ_REP);
req = zmq_socket (ctx, ZMQ_REQ);
rc = zmq_setsockopt (rep, ZMQ_IPV6, &ipv6, sizeof (int));
assert(rc == 0);
rc = zmq_setsockopt (req, ZMQ_IPV6, &ipv6, sizeof (int));
assert(rc == 0);
rc = zmq_bind (rep, "tcp://127.0.0.1:12345");
assert(rc == 0);
rc = zmq_connect (req, "tcp://127.0.0.1:12345");
assert(rc == 0);
bounce(rep,req); // from tests/testutil.hpp
zmq_close(req);
zmq_close(rep);
rep = zmq_socket (ctx, ZMQ_REP);
req = zmq_socket (ctx, ZMQ_REQ);
rc = zmq_setsockopt (rep, ZMQ_IPV6, &ipv6, sizeof (int));
assert(rc == 0);
rc = zmq_setsockopt (req, ZMQ_IPV6, &ipv6, sizeof (int));
assert(rc == 0);
rc = zmq_bind (rep, "tcp://[::1]:12346");
assert(rc == 0);
rc = zmq_connect (req, "tcp://[::1]:12346");
assert(rc == 0);
bounce(rep,req); // from tests/testutil.hpp
zmq_close(req);
zmq_close(rep);
zmq_ctx_term(ctx);
return 0;
}
The first bind will fail, and with the fix for the bind send/receive will be stuck (tcp cannot connect) even though they should work.
That is because for the connect code there's no fallback at all.
I'll send a PR shortly to fix this. With that change, the first bind and send/receive now work, and the second ones (with an IPv6 endpoint) correctly fail when no IPv6 is available.
Unfortunately there's really no way to test this on a machine where IPv6 is available, without any mocking infrastructure in the test code to intercept the socket syscalls, so I cannot add a unit test.
This is an annoying bug when building an application that runs on multiple architectures and environment, eg: x86 VMs and ARM embedded systems. In the latter often IPv6 is not available., so you have to change the application code as a workaround, which is less than optimal.