From 7b32c9cb51dc97e3c467801843fa9ed1736ba8bd Mon Sep 17 00:00:00 2001 From: Ian Barber Date: Wed, 8 Feb 2012 22:06:46 +0000 Subject: [PATCH 1/4] Adding ZMQ_LAST_ENDPOINT for wildcard support on TCP and IPC sockets --- include/zmq.h | 4 ++++ src/ipc_listener.cpp | 20 ++++++++++++++++++++ src/ipc_listener.hpp | 7 +++++++ src/options.cpp | 12 ++++++++++-- src/options.hpp | 4 ++++ src/socket_base.cpp | 4 ++++ src/tcp_address.cpp | 16 +++++++++++----- src/tcp_listener.cpp | 32 ++++++++++++++++++++++++++++++++ src/tcp_listener.hpp | 7 +++++++ 9 files changed, 99 insertions(+), 7 deletions(-) diff --git a/include/zmq.h b/include/zmq.h index 5dc11461..c7fc6400 100644 --- a/include/zmq.h +++ b/include/zmq.h @@ -193,6 +193,7 @@ ZMQ_EXPORT int zmq_term (void *context); #define ZMQ_RCVTIMEO 27 #define ZMQ_SNDTIMEO 28 #define ZMQ_IPV4ONLY 31 +#define ZMQ_LAST_ENDPOINT 32 /* Message options */ #define ZMQ_MORE 1 @@ -201,6 +202,9 @@ ZMQ_EXPORT int zmq_term (void *context); #define ZMQ_DONTWAIT 1 #define ZMQ_SNDMORE 2 +/* Wildcard endpoint support. */ +#define ZMQ_ENDPOINT_MAX 256 + ZMQ_EXPORT void *zmq_socket (void *context, int type); ZMQ_EXPORT int zmq_close (void *s); ZMQ_EXPORT int zmq_setsockopt (void *s, int option, const void *optval, diff --git a/src/ipc_listener.cpp b/src/ipc_listener.cpp index 07a7dff6..40ce5f0b 100644 --- a/src/ipc_listener.cpp +++ b/src/ipc_listener.cpp @@ -95,8 +95,25 @@ void zmq::ipc_listener_t::in_event () send_attach (session, engine, false); } +int zmq::ipc_listener_t::get_address (unsigned char *addr, size_t *len) +{ + if (bound_addr_len == 0) { + return -1; + } + + memcpy (addr, bound_addr, bound_addr_len + 1); + *len = bound_addr_len + 1; + return 0; +} + int zmq::ipc_listener_t::set_address (const char *addr_) { + + // Allow wildcard file + if(*addr_ == '*') { + addr_ = tempnam(NULL, NULL); + } + // Get rid of the file associated with the UNIX domain socket that // may have been left behind by the previous run of the application. ::unlink (addr_); @@ -125,6 +142,9 @@ int zmq::ipc_listener_t::set_address (const char *addr_) if (rc != 0) return -1; + // Return the bound address + bound_addr_len = sprintf(bound_addr, "ipc://%s", addr_); + return 0; } diff --git a/src/ipc_listener.hpp b/src/ipc_listener.hpp index e1f48171..836162ce 100644 --- a/src/ipc_listener.hpp +++ b/src/ipc_listener.hpp @@ -48,6 +48,9 @@ namespace zmq // Set address to listen on. int set_address (const char *addr_); + + // Get the bound address for use with wildcards + int get_address(unsigned char *addr, size_t *len); private: @@ -65,6 +68,10 @@ namespace zmq // newly created connection. The function may return retired_fd // if the connection was dropped while waiting in the listen backlog. fd_t accept (); + + // Store the connected endpoint for binds to port 0 + char bound_addr[256]; + size_t bound_addr_len; // True, if the undelying file for UNIX domain socket exists. bool has_file; diff --git a/src/options.cpp b/src/options.cpp index 4db1a6cb..c8790a85 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -30,6 +30,7 @@ zmq::options_t::options_t () : rcvhwm (1000), affinity (0), identity_size (0), + last_endpoint_size(0), rate (100), recovery_ivl (10000), multicast_hops (1), @@ -213,7 +214,6 @@ int zmq::options_t::setsockopt (int option_, const void *optval_, ipv4only = val; return 0; } - } errno = EINVAL; @@ -385,7 +385,15 @@ int zmq::options_t::getsockopt (int option_, void *optval_, size_t *optvallen_) *((int*) optval_) = ipv4only; *optvallen_ = sizeof (int); return 0; - + + case ZMQ_LAST_ENDPOINT: + if (*optvallen_ < last_endpoint_size) { + errno = EINVAL; + return -1; + } + memcpy (optval_, last_endpoint, last_endpoint_size); + *optvallen_ = last_endpoint_size; + return 0; } errno = EINVAL; diff --git a/src/options.hpp b/src/options.hpp index bfc9dc79..7feea95e 100644 --- a/src/options.hpp +++ b/src/options.hpp @@ -46,6 +46,10 @@ namespace zmq // Socket identity unsigned char identity_size; unsigned char identity [256]; + + // Last socket endpoint URI + unsigned char last_endpoint [256]; + size_t last_endpoint_size; // Maximum tranfer rate [kb/s]. Default 100kb/s. int rate; diff --git a/src/socket_base.cpp b/src/socket_base.cpp index 72934193..617cfa75 100644 --- a/src/socket_base.cpp +++ b/src/socket_base.cpp @@ -160,6 +160,7 @@ int zmq::socket_base_t::parse_uri (const char *uri_, } protocol_ = uri.substr (0, pos); address_ = uri.substr (pos + 3); + if (protocol_.empty () || address_.empty ()) { errno = EINVAL; return -1; @@ -339,6 +340,8 @@ int zmq::socket_base_t::bind (const char *addr_) delete listener; return -1; } + + rc = listener->get_address (options.last_endpoint, &(options.last_endpoint_size)); launch_child (listener); return 0; } @@ -353,6 +356,7 @@ int zmq::socket_base_t::bind (const char *addr_) delete listener; return -1; } + rc = listener->get_address (options.last_endpoint, &(options.last_endpoint_size)); launch_child (listener); return 0; } diff --git a/src/tcp_address.cpp b/src/tcp_address.cpp index de6e0ad6..52618b28 100644 --- a/src/tcp_address.cpp +++ b/src/tcp_address.cpp @@ -387,11 +387,17 @@ int zmq::tcp_address_t::resolve (const char *name_, bool local_, bool ipv4only_) addr_str [addr_str.size () - 1] == ']') addr_str = addr_str.substr (1, addr_str.size () - 2); - // Parse the port number (0 is not a valid port). - uint16_t port = (uint16_t) atoi (port_str.c_str()); - if (port == 0) { - errno = EINVAL; - return -1; + uint16_t port; + if (port_str[0] == '*') { + // Resolve wildcard to 0 to allow autoselection of port + port = 0; + } else { + // Parse the port number (0 is not a valid port). + port = (uint16_t) atoi (port_str.c_str()); + if (port == 0) { + errno = EINVAL; + return -1; + } } // Resolve the IP address. diff --git a/src/tcp_listener.cpp b/src/tcp_listener.cpp index 0b7a90d3..9bb7f88e 100644 --- a/src/tcp_listener.cpp +++ b/src/tcp_listener.cpp @@ -52,6 +52,7 @@ zmq::tcp_listener_t::tcp_listener_t (io_thread_t *io_thread_, socket_base_t *socket_, const options_t &options_) : own_t (io_thread_, options_), io_object_t (io_thread_), + bound_addr_len (0), has_file (false), s (retired_fd), socket (socket_) @@ -119,6 +120,17 @@ void zmq::tcp_listener_t::close () s = retired_fd; } +int zmq::tcp_listener_t::get_address (unsigned char *addr, size_t *len) +{ + if (bound_addr_len == 0) { + return -1; + } + + memcpy (addr, bound_addr, bound_addr_len + 1); + *len = bound_addr_len + 1; + return 0; +} + int zmq::tcp_listener_t::set_address (const char *addr_) { // Convert the textual address into address structure. @@ -168,6 +180,7 @@ int zmq::tcp_listener_t::set_address (const char *addr_) errno_assert (rc == 0); #endif + // Bind the socket to the network interface and port. rc = bind (s, address.addr (), address.addrlen ()); #ifdef ZMQ_HAVE_WINDOWS @@ -180,6 +193,25 @@ int zmq::tcp_listener_t::set_address (const char *addr_) return -1; #endif + struct sockaddr sa; + socklen_t sl = sizeof(sockaddr); + rc = getsockname (s, &sa, &sl); + if (rc == 0) { + char host[INET6_ADDRSTRLEN]; + int port; + + if ( sa.sa_family == AF_INET ) { + inet_ntop(AF_INET, &(((struct sockaddr_in *)&sa)->sin_addr), host, INET6_ADDRSTRLEN); + port = ntohs( ((struct sockaddr_in *)&sa)->sin_port); + } else { + inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)&sa)->sin6_addr), host, INET6_ADDRSTRLEN); + port = ntohs( ((struct sockaddr_in6 *)&sa)->sin6_port); + } + + // Store the address for retrieval by users using wildcards + bound_addr_len = sprintf(bound_addr, "tcp://%s:%d", host, port); + } + // Listen for incomming connections. rc = listen (s, options.backlog); #ifdef ZMQ_HAVE_WINDOWS diff --git a/src/tcp_listener.hpp b/src/tcp_listener.hpp index c2116b39..1637518d 100644 --- a/src/tcp_listener.hpp +++ b/src/tcp_listener.hpp @@ -44,6 +44,9 @@ namespace zmq // Set address to listen on. int set_address (const char *addr_); + + // Get the bound address for use with wildcards + int get_address(unsigned char *addr, size_t *len); private: @@ -64,6 +67,10 @@ namespace zmq // Address to listen on. tcp_address_t address; + + // Store the connected endpoint for binds to port 0 + char bound_addr[256]; + size_t bound_addr_len; // True, if the undelying file for UNIX domain socket exists. bool has_file; From 770f84331f7a1ce03e87786f976a526af07c965f Mon Sep 17 00:00:00 2001 From: Ian Barber Date: Sat, 11 Feb 2012 15:09:03 +0000 Subject: [PATCH 2/4] Allowing value 0, and moving code to get_address functions based on feedback --- AUTHORS | 1 + src/ipc_listener.cpp | 21 +++++++++++-------- src/ipc_listener.hpp | 4 ---- src/tcp_address.cpp | 3 ++- src/tcp_listener.cpp | 50 ++++++++++++++++++++++---------------------- src/tcp_listener.hpp | 6 +----- 6 files changed, 41 insertions(+), 44 deletions(-) diff --git a/AUTHORS b/AUTHORS index 4510f679..99084a80 100644 --- a/AUTHORS +++ b/AUTHORS @@ -33,6 +33,7 @@ Gerard Toonstra Ghislain Putois Gonzalo Diethelm Guido Goldstein +Ian Barber Ilja Golshtein Ivo Danihelka Jacob Rideout diff --git a/src/ipc_listener.cpp b/src/ipc_listener.cpp index 40ce5f0b..67973447 100644 --- a/src/ipc_listener.cpp +++ b/src/ipc_listener.cpp @@ -97,12 +97,18 @@ void zmq::ipc_listener_t::in_event () int zmq::ipc_listener_t::get_address (unsigned char *addr, size_t *len) { - if (bound_addr_len == 0) { - return -1; + struct sockaddr_un sun; + int rc; + + // Get the details of the IPC socket + socklen_t sl = sizeof(sockaddr_un); + rc = getsockname (s, (sockaddr *)&sun, &sl); + if (rc != 0) { + return rc; } - - memcpy (addr, bound_addr, bound_addr_len + 1); - *len = bound_addr_len + 1; + // Store the address for retrieval by users using wildcards + *len = sprintf((char *)addr, "ipc://%s", sun.sun_path); + return 0; } @@ -141,10 +147,7 @@ int zmq::ipc_listener_t::set_address (const char *addr_) rc = listen (s, options.backlog); if (rc != 0) return -1; - - // Return the bound address - bound_addr_len = sprintf(bound_addr, "ipc://%s", addr_); - + return 0; } diff --git a/src/ipc_listener.hpp b/src/ipc_listener.hpp index 836162ce..57e04ef8 100644 --- a/src/ipc_listener.hpp +++ b/src/ipc_listener.hpp @@ -68,10 +68,6 @@ namespace zmq // newly created connection. The function may return retired_fd // if the connection was dropped while waiting in the listen backlog. fd_t accept (); - - // Store the connected endpoint for binds to port 0 - char bound_addr[256]; - size_t bound_addr_len; // True, if the undelying file for UNIX domain socket exists. bool has_file; diff --git a/src/tcp_address.cpp b/src/tcp_address.cpp index 52618b28..9fe6083c 100644 --- a/src/tcp_address.cpp +++ b/src/tcp_address.cpp @@ -388,7 +388,8 @@ int zmq::tcp_address_t::resolve (const char *name_, bool local_, bool ipv4only_) addr_str = addr_str.substr (1, addr_str.size () - 2); uint16_t port; - if (port_str[0] == '*') { + // Allow 0 specifically, to detect invalid port error in atoi if not + if (port_str[0] == '*' || port_str[0] == '0') { // Resolve wildcard to 0 to allow autoselection of port port = 0; } else { diff --git a/src/tcp_listener.cpp b/src/tcp_listener.cpp index 9bb7f88e..191e05c2 100644 --- a/src/tcp_listener.cpp +++ b/src/tcp_listener.cpp @@ -52,7 +52,6 @@ zmq::tcp_listener_t::tcp_listener_t (io_thread_t *io_thread_, socket_base_t *socket_, const options_t &options_) : own_t (io_thread_, options_), io_object_t (io_thread_), - bound_addr_len (0), has_file (false), s (retired_fd), socket (socket_) @@ -121,13 +120,33 @@ void zmq::tcp_listener_t::close () } int zmq::tcp_listener_t::get_address (unsigned char *addr, size_t *len) -{ - if (bound_addr_len == 0) { - return -1; +{ + struct sockaddr sa; + char host[INET6_ADDRSTRLEN]; + int port, rc; + + // Get the details of the TCP socket + socklen_t sl = sizeof(sockaddr); + rc = getsockname (s, &sa, &sl); + if (rc != 0) { + return rc; + } + + // Split the retrieval between IPv4 and v6 addresses + if ( sa.sa_family == AF_INET ) { + inet_ntop(AF_INET, &(((struct sockaddr_in *)&sa)->sin_addr), host, INET6_ADDRSTRLEN); + port = ntohs( ((struct sockaddr_in *)&sa)->sin_port); + + // Store the address for retrieval by users using wildcards + *len = sprintf((char *)addr, "tcp://%s:%d", host, port); + } else { + inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)&sa)->sin6_addr), host, INET6_ADDRSTRLEN); + port = ntohs( ((struct sockaddr_in6 *)&sa)->sin6_port); + + // Store the address for retrieval by users using wildcards + *len = sprintf((char *)*addr, "tcp://[%s]:%d", host, port); } - memcpy (addr, bound_addr, bound_addr_len + 1); - *len = bound_addr_len + 1; return 0; } @@ -193,25 +212,6 @@ int zmq::tcp_listener_t::set_address (const char *addr_) return -1; #endif - struct sockaddr sa; - socklen_t sl = sizeof(sockaddr); - rc = getsockname (s, &sa, &sl); - if (rc == 0) { - char host[INET6_ADDRSTRLEN]; - int port; - - if ( sa.sa_family == AF_INET ) { - inet_ntop(AF_INET, &(((struct sockaddr_in *)&sa)->sin_addr), host, INET6_ADDRSTRLEN); - port = ntohs( ((struct sockaddr_in *)&sa)->sin_port); - } else { - inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)&sa)->sin6_addr), host, INET6_ADDRSTRLEN); - port = ntohs( ((struct sockaddr_in6 *)&sa)->sin6_port); - } - - // Store the address for retrieval by users using wildcards - bound_addr_len = sprintf(bound_addr, "tcp://%s:%d", host, port); - } - // Listen for incomming connections. rc = listen (s, options.backlog); #ifdef ZMQ_HAVE_WINDOWS diff --git a/src/tcp_listener.hpp b/src/tcp_listener.hpp index 1637518d..fc6ebd10 100644 --- a/src/tcp_listener.hpp +++ b/src/tcp_listener.hpp @@ -45,7 +45,7 @@ namespace zmq // Set address to listen on. int set_address (const char *addr_); - // Get the bound address for use with wildcards + // Get the bound address for use with wildcard int get_address(unsigned char *addr, size_t *len); private: @@ -67,10 +67,6 @@ namespace zmq // Address to listen on. tcp_address_t address; - - // Store the connected endpoint for binds to port 0 - char bound_addr[256]; - size_t bound_addr_len; // True, if the undelying file for UNIX domain socket exists. bool has_file; From b5d3373905587aecc53e032e5619c1550952030f Mon Sep 17 00:00:00 2001 From: Ian Barber Date: Tue, 14 Feb 2012 23:10:06 +0000 Subject: [PATCH 3/4] Moving to std::string in options --- include/zmq.h | 3 --- src/ipc_listener.cpp | 5 +++-- src/ipc_listener.hpp | 2 +- src/options.cpp | 8 ++++---- src/options.hpp | 5 +++-- src/socket_base.cpp | 5 +++-- src/tcp_listener.cpp | 10 +++++++--- src/tcp_listener.hpp | 2 +- 8 files changed, 22 insertions(+), 18 deletions(-) diff --git a/include/zmq.h b/include/zmq.h index 78b92bc3..325cda6a 100644 --- a/include/zmq.h +++ b/include/zmq.h @@ -203,9 +203,6 @@ ZMQ_EXPORT int zmq_term (void *context); #define ZMQ_DONTWAIT 1 #define ZMQ_SNDMORE 2 -/* Wildcard endpoint support. */ -#define ZMQ_ENDPOINT_MAX 256 - ZMQ_EXPORT void *zmq_socket (void *context, int type); ZMQ_EXPORT int zmq_close (void *s); ZMQ_EXPORT int zmq_setsockopt (void *s, int option, const void *optval, diff --git a/src/ipc_listener.cpp b/src/ipc_listener.cpp index 67973447..668464a3 100644 --- a/src/ipc_listener.cpp +++ b/src/ipc_listener.cpp @@ -95,7 +95,7 @@ void zmq::ipc_listener_t::in_event () send_attach (session, engine, false); } -int zmq::ipc_listener_t::get_address (unsigned char *addr, size_t *len) +int zmq::ipc_listener_t::get_address (std::string *addr_) { struct sockaddr_un sun; int rc; @@ -106,8 +106,9 @@ int zmq::ipc_listener_t::get_address (unsigned char *addr, size_t *len) if (rc != 0) { return rc; } + // Store the address for retrieval by users using wildcards - *len = sprintf((char *)addr, "ipc://%s", sun.sun_path); + *addr_ = std::string("ipc://") + std::string(sun.sun_path); return 0; } diff --git a/src/ipc_listener.hpp b/src/ipc_listener.hpp index 57e04ef8..228e9f24 100644 --- a/src/ipc_listener.hpp +++ b/src/ipc_listener.hpp @@ -50,7 +50,7 @@ namespace zmq int set_address (const char *addr_); // Get the bound address for use with wildcards - int get_address(unsigned char *addr, size_t *len); + int get_address(std::string *addr_); private: diff --git a/src/options.cpp b/src/options.cpp index c8790a85..5f1d4440 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -30,7 +30,6 @@ zmq::options_t::options_t () : rcvhwm (1000), affinity (0), identity_size (0), - last_endpoint_size(0), rate (100), recovery_ivl (10000), multicast_hops (1), @@ -387,12 +386,13 @@ int zmq::options_t::getsockopt (int option_, void *optval_, size_t *optvallen_) return 0; case ZMQ_LAST_ENDPOINT: - if (*optvallen_ < last_endpoint_size) { + // don't allow string which cannot contain the entire message + if (*optvallen_ < last_endpoint.size() + 1) { errno = EINVAL; return -1; } - memcpy (optval_, last_endpoint, last_endpoint_size); - *optvallen_ = last_endpoint_size; + memcpy (optval_, last_endpoint.c_str(), last_endpoint.size()+1); + *optvallen_ = last_endpoint.size()+1; return 0; } diff --git a/src/options.hpp b/src/options.hpp index 7feea95e..2bf793ee 100644 --- a/src/options.hpp +++ b/src/options.hpp @@ -23,6 +23,8 @@ #ifndef __ZMQ_OPTIONS_HPP_INCLUDED__ #define __ZMQ_OPTIONS_HPP_INCLUDED__ +#include + #include "stddef.h" #include "stdint.hpp" @@ -48,8 +50,7 @@ namespace zmq unsigned char identity [256]; // Last socket endpoint URI - unsigned char last_endpoint [256]; - size_t last_endpoint_size; + std::string last_endpoint; // Maximum tranfer rate [kb/s]. Default 100kb/s. int rate; diff --git a/src/socket_base.cpp b/src/socket_base.cpp index 74c807fb..fc73ce9c 100644 --- a/src/socket_base.cpp +++ b/src/socket_base.cpp @@ -342,7 +342,7 @@ int zmq::socket_base_t::bind (const char *addr_) return -1; } - rc = listener->get_address (options.last_endpoint, &(options.last_endpoint_size)); + rc = listener->get_address (&options.last_endpoint); launch_child (listener); return 0; } @@ -357,7 +357,8 @@ int zmq::socket_base_t::bind (const char *addr_) delete listener; return -1; } - rc = listener->get_address (options.last_endpoint, &(options.last_endpoint_size)); + + rc = listener->get_address (&options.last_endpoint); launch_child (listener); return 0; } diff --git a/src/tcp_listener.cpp b/src/tcp_listener.cpp index 191e05c2..8c65ff7f 100644 --- a/src/tcp_listener.cpp +++ b/src/tcp_listener.cpp @@ -22,6 +22,7 @@ #include #include +#include #include "platform.hpp" #include "tcp_listener.hpp" @@ -119,11 +120,12 @@ void zmq::tcp_listener_t::close () s = retired_fd; } -int zmq::tcp_listener_t::get_address (unsigned char *addr, size_t *len) +int zmq::tcp_listener_t::get_address (std::string *addr_) { struct sockaddr sa; char host[INET6_ADDRSTRLEN]; int port, rc; + std::stringstream portnum; // Get the details of the TCP socket socklen_t sl = sizeof(sockaddr); @@ -136,15 +138,17 @@ int zmq::tcp_listener_t::get_address (unsigned char *addr, size_t *len) if ( sa.sa_family == AF_INET ) { inet_ntop(AF_INET, &(((struct sockaddr_in *)&sa)->sin_addr), host, INET6_ADDRSTRLEN); port = ntohs( ((struct sockaddr_in *)&sa)->sin_port); + portnum << port; // Store the address for retrieval by users using wildcards - *len = sprintf((char *)addr, "tcp://%s:%d", host, port); + *addr_ = std::string("tcp://") + std::string(host) + std::string(":") + portnum.str(); } else { inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)&sa)->sin6_addr), host, INET6_ADDRSTRLEN); port = ntohs( ((struct sockaddr_in6 *)&sa)->sin6_port); + portnum << port; // Store the address for retrieval by users using wildcards - *len = sprintf((char *)*addr, "tcp://[%s]:%d", host, port); + *addr_ = std::string("tcp://[") + std::string(host) + std::string("]:") + portnum.str(); } return 0; diff --git a/src/tcp_listener.hpp b/src/tcp_listener.hpp index fc6ebd10..66c4fb2e 100644 --- a/src/tcp_listener.hpp +++ b/src/tcp_listener.hpp @@ -46,7 +46,7 @@ namespace zmq int set_address (const char *addr_); // Get the bound address for use with wildcard - int get_address(unsigned char *addr, size_t *len); + int get_address(std::string *addr_); private: From cc10c00193525cd1d253a50ac3055f1af1684e82 Mon Sep 17 00:00:00 2001 From: Ian Barber Date: Tue, 14 Feb 2012 23:14:33 +0000 Subject: [PATCH 4/4] Updating docs with new sockopt --- doc/zmq_getsockopt.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/zmq_getsockopt.txt b/doc/zmq_getsockopt.txt index 50b80fd9..0d5e8a23 100644 --- a/doc/zmq_getsockopt.txt +++ b/doc/zmq_getsockopt.txt @@ -391,6 +391,17 @@ Option value unit:: N/A (flags) Default value:: N/A Applicable socket types:: all +ZMQ_LAST_ENDPOINT: Retrieve the last endpoint set +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The 'ZMQ_LAST_ENDPOINT' option shall retrieve the last endpoint bound for +TCP and IPC transports. The returned value will be a string in the form of +a ZMQ DSN. + +[horizontal] +Option value type:: character string +Option value unit:: N/A +Default value:: NULL +Applicable socket types:: all, when binding TCP or IPC transports RETURN VALUE ------------