From 1586f3f25ebb4b479e4334eccb551634425bcb0f Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 18 Apr 2016 17:12:01 +0100 Subject: [PATCH 1/3] Problem: can't test if IPv6 is available in tests Solution: add helper function is_ipv6_available to testutil.hpp to test if IPv6 is available on the building platform. This function will try to open and bind a socket to ::1:*, as it's the ultimate way of knowing if, at least on the loopback, IPv6 is enabled. --- tests/testutil.hpp | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) Index: zeromq-4.1.4/tests/testutil.hpp =================================================================== --- zeromq-4.1.4.orig/tests/testutil.hpp +++ zeromq-4.1.4/tests/testutil.hpp @@ -47,6 +47,7 @@ #include #if defined _WIN32 +# include "windows.hpp" # if defined _MSC_VER # include # pragma warning(disable:4996) @@ -56,6 +57,9 @@ # include # include # include +# include +# include +# include #endif // Bounce a message from client to server and back @@ -299,5 +303,53 @@ void msleep (int milliseconds) #endif } +// check if IPv6 is available (0/false if not, 1/true if it is) +// only way to reliably check is to actually open a socket and try to bind it +int +is_ipv6_available(void) +{ + int rc, ipv6 = 1; + struct sockaddr_in6 test_addr; + + memset (&test_addr, 0, sizeof (test_addr)); + test_addr.sin6_family = AF_INET6; + inet_pton (AF_INET6, "::1", &(test_addr.sin6_addr)); + +#ifdef ZMQ_HAVE_WINDOWS + SOCKET fd = socket (AF_INET6, SOCK_STREAM, IPPROTO_IP); + if (fd == INVALID_SOCKET) + ipv6 = 0; + else { + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&ipv6, sizeof(int)); + rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&ipv6, sizeof(int)); + if (rc == SOCKET_ERROR) + ipv6 = 0; + else { + rc = bind (fd, (struct sockaddr *)&test_addr, sizeof (test_addr)); + if (rc == SOCKET_ERROR) + ipv6 = 0; + } + closesocket (fd); + } +#else + int fd = socket (AF_INET6, SOCK_STREAM, IPPROTO_IP); + if (fd == -1) + ipv6 = 0; + else { + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &ipv6, sizeof(int)); + rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6, sizeof(int)); + if (rc != 0) + ipv6 = 0; + else { + rc = bind (fd, (struct sockaddr *)&test_addr, sizeof (test_addr)); + if (rc != 0) + ipv6 = 0; + } + close (fd); + } +#endif + + return ipv6; +} #endif From 0af39a443f39c1a0bb5d7d60ca8667b4e33599f4 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 18 Apr 2016 00:02:36 +0100 Subject: [PATCH 2/3] Problem: can't unbind with bound addr with IPv6 Solution: try to resolve the TCP endpoint passed by the user in the zmq_unbind call before giving up, if it doesn't match. This fixes a breakage in the API, where after a call to zmq_bind(s, "tcp://127.0.0.1:9999") with IPv6 enabled on s would result in the call to zmq_unbind(s, "tcp://127.0.0.1:9999") failing. Add more test cases to increase coverage on all combinations of TCP endpoints. --- src/socket_base.cpp | 32 +++++- tests/test_unbind_wildcard.cpp | 228 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+), 1 deletion(-) diff --git a/src/socket_base.cpp b/src/socket_base.cpp index ec01836..9d34d7e 100644 Index: zeromq-4.1.4/src/socket_base.cpp =================================================================== --- zeromq-4.1.4.orig/src/socket_base.cpp +++ zeromq-4.1.4/src/socket_base.cpp @@ -765,8 +765,38 @@ int zmq::socket_base_t::term_endpoint (c return 0; } + std::string resolved_addr = std::string (addr_); + std::pair range; + + // The resolved last_endpoint is used as a key in the endpoints map. + // The address passed by the user might not match in the TCP case due to + // IPv4-in-IPv6 mapping (EG: tcp://[::ffff:127.0.0.1]:9999), so try to + // resolve before giving up. Given at this stage we don't know whether a + // socket is connected or bound, try with both. + if (protocol == "tcp") { + range = endpoints.equal_range (resolved_addr); + if (range.first == range.second) { + tcp_address_t *tcp_addr = new (std::nothrow) tcp_address_t (); + alloc_assert (tcp_addr); + rc = tcp_addr->resolve (address.c_str (), false, options.ipv6); + + if (rc == 0) { + tcp_addr->to_string (resolved_addr); + range = endpoints.equal_range (resolved_addr); + + if (range.first == range.second) { + rc = tcp_addr->resolve (address.c_str (), true, options.ipv6); + if (rc == 0) { + tcp_addr->to_string (resolved_addr); + } + } + } + delete tcp_addr; + } + } + // Find the endpoints range (if any) corresponding to the addr_ string. - std::pair range = endpoints.equal_range (std::string (addr_)); + range = endpoints.equal_range (resolved_addr); if (range.first == range.second) { errno = ENOENT; return -1; Index: zeromq-4.1.4/tests/test_unbind_wildcard.cpp =================================================================== --- zeromq-4.1.4.orig/tests/test_unbind_wildcard.cpp +++ zeromq-4.1.4/tests/test_unbind_wildcard.cpp @@ -22,11 +22,16 @@ int main (void) { setup_test_environment(); + int ipv6 = is_ipv6_available (); void *ctx = zmq_ctx_new (); assert (ctx); + /* Address wildcard, IPv6 disabled */ void *sb = zmq_socket (ctx, ZMQ_REP); assert (sb); + void *sc = zmq_socket (ctx, ZMQ_REQ); + assert (sc); + int rc = zmq_bind (sb, "tcp://*:5555"); assert (rc == 0); @@ -35,12 +40,235 @@ int main (void) rc = zmq_getsockopt (sb, ZMQ_LAST_ENDPOINT, endpoint, &endpoint_len); assert (rc == 0); + rc = zmq_connect (sc, endpoint); + assert (rc == 0); + + bounce (sb, sc); + + rc = zmq_disconnect (sc, endpoint); + assert (rc == 0); + rc = zmq_unbind (sb, endpoint); + assert (rc == 0); + + rc = zmq_close (sc); + assert (rc == 0); + rc = zmq_close (sb); + assert (rc == 0); + + /* Address wildcard, IPv6 enabled */ + sb = zmq_socket (ctx, ZMQ_REP); + assert (sb); + sc = zmq_socket (ctx, ZMQ_REQ); + assert (sc); + + rc = zmq_setsockopt (sb, ZMQ_IPV6, &ipv6, sizeof (int)); + assert (rc == 0); + rc = zmq_setsockopt (sc, ZMQ_IPV6, &ipv6, sizeof (int)); + assert (rc == 0); + + rc = zmq_bind (sb, "tcp://*:5556"); + assert (rc == 0); + + endpoint_len = sizeof (endpoint); + memset(endpoint, 0, endpoint_len); + rc = zmq_getsockopt (sb, ZMQ_LAST_ENDPOINT, endpoint, &endpoint_len); + assert (rc == 0); + + rc = zmq_connect (sc, endpoint); + assert (rc == 0); + + bounce (sb, sc); + + rc = zmq_disconnect (sc, endpoint); + assert (rc == 0); rc = zmq_unbind (sb, endpoint); assert (rc == 0); + rc = zmq_close (sc); + assert (rc == 0); rc = zmq_close (sb); assert (rc == 0); + /* Port wildcard, IPv4 address, IPv6 disabled */ + sb = zmq_socket (ctx, ZMQ_REP); + assert (sb); + sc = zmq_socket (ctx, ZMQ_REQ); + assert (sc); + + rc = zmq_bind (sb, "tcp://127.0.0.1:*"); + assert (rc == 0); + + endpoint_len = sizeof (endpoint); + memset(endpoint, 0, endpoint_len); + rc = zmq_getsockopt (sb, ZMQ_LAST_ENDPOINT, endpoint, &endpoint_len); + assert (rc == 0); + + rc = zmq_connect (sc, endpoint); + assert (rc == 0); + + bounce (sb, sc); + + rc = zmq_disconnect (sc, endpoint); + assert (rc == 0); + rc = zmq_unbind (sb, endpoint); + assert (rc == 0); + + rc = zmq_close (sc); + assert (rc == 0); + rc = zmq_close (sb); + assert (rc == 0); + + /* Port wildcard, IPv4 address, IPv6 enabled */ + sb = zmq_socket (ctx, ZMQ_REP); + assert (sb); + sc = zmq_socket (ctx, ZMQ_REQ); + assert (sc); + + rc = zmq_setsockopt (sb, ZMQ_IPV6, &ipv6, sizeof (int)); + assert (rc == 0); + rc = zmq_setsockopt (sc, ZMQ_IPV6, &ipv6, sizeof (int)); + assert (rc == 0); + + rc = zmq_bind (sb, "tcp://127.0.0.1:*"); + assert (rc == 0); + + endpoint_len = sizeof (endpoint); + memset(endpoint, 0, endpoint_len); + rc = zmq_getsockopt (sb, ZMQ_LAST_ENDPOINT, endpoint, &endpoint_len); + assert (rc == 0); + + rc = zmq_connect (sc, endpoint); + assert (rc == 0); + + bounce (sb, sc); + + rc = zmq_disconnect (sc, endpoint); + assert (rc == 0); + rc = zmq_unbind (sb, endpoint); + assert (rc == 0); + + rc = zmq_close (sc); + assert (rc == 0); + rc = zmq_close (sb); + assert (rc == 0); + + if (ipv6) { + /* Port wildcard, IPv6 address, IPv6 enabled */ + sb = zmq_socket (ctx, ZMQ_REP); + assert (sb); + sc = zmq_socket (ctx, ZMQ_REQ); + assert (sc); + + rc = zmq_setsockopt (sb, ZMQ_IPV6, &ipv6, sizeof (int)); + assert (rc == 0); + rc = zmq_setsockopt (sc, ZMQ_IPV6, &ipv6, sizeof (int)); + assert (rc == 0); + + rc = zmq_bind (sb, "tcp://[::1]:*"); + assert (rc == 0); + + endpoint_len = sizeof (endpoint); + memset(endpoint, 0, endpoint_len); + rc = zmq_getsockopt (sb, ZMQ_LAST_ENDPOINT, endpoint, &endpoint_len); + assert (rc == 0); + + rc = zmq_connect (sc, endpoint); + assert (rc == 0); + + bounce (sb, sc); + + rc = zmq_disconnect (sc, endpoint); + assert (rc == 0); + rc = zmq_unbind (sb, endpoint); + assert (rc == 0); + + rc = zmq_close (sc); + assert (rc == 0); + rc = zmq_close (sb); + assert (rc == 0); + } + + /* No wildcard, IPv4 address, IPv6 disabled */ + sb = zmq_socket (ctx, ZMQ_REP); + assert (sb); + sc = zmq_socket (ctx, ZMQ_REQ); + assert (sc); + + rc = zmq_bind (sb, "tcp://127.0.0.1:5557"); + assert (rc == 0); + rc = zmq_connect (sc, "tcp://127.0.0.1:5557"); + assert (rc == 0); + + bounce (sb, sc); + + rc = zmq_disconnect (sc, "tcp://127.0.0.1:5557"); + assert (rc == 0); + rc = zmq_unbind (sb, "tcp://127.0.0.1:5557"); + assert (rc == 0); + + rc = zmq_close (sc); + assert (rc == 0); + rc = zmq_close (sb); + assert (rc == 0); + + /* No wildcard, IPv4 address, IPv6 enabled */ + sb = zmq_socket (ctx, ZMQ_REP); + assert (sb); + sc = zmq_socket (ctx, ZMQ_REQ); + assert (sc); + + rc = zmq_setsockopt (sb, ZMQ_IPV6, &ipv6, sizeof (int)); + assert (rc == 0); + rc = zmq_setsockopt (sc, ZMQ_IPV6, &ipv6, sizeof (int)); + assert (rc == 0); + + rc = zmq_bind (sb, "tcp://127.0.0.1:5558"); + assert (rc == 0); + rc = zmq_connect (sc, "tcp://127.0.0.1:5558"); + assert (rc == 0); + + bounce (sb, sc); + + rc = zmq_disconnect (sc, "tcp://127.0.0.1:5558"); + assert (rc == 0); + rc = zmq_unbind (sb, "tcp://127.0.0.1:5558"); + assert (rc == 0); + + rc = zmq_close (sc); + assert (rc == 0); + rc = zmq_close (sb); + assert (rc == 0); + + if (ipv6) { + /* No wildcard, IPv6 address, IPv6 enabled */ + sb = zmq_socket (ctx, ZMQ_REP); + assert (sb); + sc = zmq_socket (ctx, ZMQ_REQ); + assert (sc); + + rc = zmq_setsockopt (sb, ZMQ_IPV6, &ipv6, sizeof (int)); + assert (rc == 0); + rc = zmq_setsockopt (sc, ZMQ_IPV6, &ipv6, sizeof (int)); + assert (rc == 0); + + rc = zmq_bind (sb, "tcp://[::1]:5559"); + assert (rc == 0); + rc = zmq_connect (sc, "tcp://[::1]:5559"); + assert (rc == 0); + + bounce (sb, sc); + + rc = zmq_disconnect (sc, "tcp://[::1]:5559"); + assert (rc == 0); + rc = zmq_unbind (sb, "tcp://[::1]:5559"); + assert (rc == 0); + + rc = zmq_close (sc); + assert (rc == 0); + rc = zmq_close (sb); + assert (rc == 0); + } + rc = zmq_ctx_term (ctx); assert (rc == 0);