diff --git a/doc/zmq_getsockopt.txt b/doc/zmq_getsockopt.txt index 5a1c56e7..357936b8 100644 --- a/doc/zmq_getsockopt.txt +++ b/doc/zmq_getsockopt.txt @@ -352,6 +352,17 @@ Default value:: 1 (true) Applicable socket types:: all, when using TCP transports. +ZMQ_TOS: Retrieve the Type-of-Service socket override status +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Retrieve the IP_TOS option for the socket. + +[horizontal] +Option value type:: int +Option value unit:: >0 +Default value:: 0 +Applicable socket types:: all, only for connection-oriented transports + + ZMQ_IMMEDIATE: Retrieve attach-on-connect value ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Retrieve the state of the attach on connect value. If set to `1`, will delay the diff --git a/doc/zmq_setsockopt.txt b/doc/zmq_setsockopt.txt index 0f5077e4..9d8ba4ea 100644 --- a/doc/zmq_setsockopt.txt +++ b/doc/zmq_setsockopt.txt @@ -13,8 +13,8 @@ SYNOPSIS *int zmq_setsockopt (void '*socket', int 'option_name', const void '*option_value', size_t 'option_len');* Caution: All options, with the exception of ZMQ_SUBSCRIBE, ZMQ_UNSUBSCRIBE, -ZMQ_LINGER, ZMQ_ROUTER_HANDOVER, ZMQ_ROUTER_MANDATORY, ZMQ_PROBE_ROUTER, -ZMQ_XPUB_VERBOSE, ZMQ_REQ_CORRELATE, and ZMQ_REQ_RELAXED, only take effect for +ZMQ_LINGER, ZMQ_ROUTER_HANDOVER, ZMQ_ROUTER_MANDATORY, ZMQ_PROBE_ROUTER, +ZMQ_XPUB_VERBOSE, ZMQ_REQ_CORRELATE, and ZMQ_REQ_RELAXED, only take effect for subsequent socket bind/connects. Specifically, security options take effect for subsequent bind/connect calls, @@ -377,6 +377,21 @@ Default value:: 1 (true) Applicable socket types:: all, when using TCP transports. +ZMQ_TOS: Set the Type-of-Service on socket +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Sets the ToS fields (Differentiated services (DS) and Explicit Congestion Notification +(ECN) field of the IP header. The ToS field is typically used to specify a packets +priority. The availability of this option is dependent on intermediate network +equipment that inspect the ToS field andprovide a path for low-delay, high-throughput, +highly-reliable service, etc. + +[horizontal] +Option value type:: int +Option value unit:: >0 +Default value:: 0 +Applicable socket types:: all, only for connection-oriented transports + + ZMQ_IMMEDIATE: Queue messages only to completed connections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -397,11 +412,11 @@ ZMQ_ROUTER_HANDOVER: handle peer identity name collisions on ROUTER sockets ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sets the ROUTER socket behavior when it encounters peers with the same identity. -By default, if two peers with the same identity connect to the same ROUTER -socket the results will be undefined. A value of `1` will cause the ROUTER +By default, if two peers with the same identity connect to the same ROUTER +socket the results will be undefined. A value of `1` will cause the ROUTER socket to reassign the identity upon encountering an identity name collision. Specifically, the first peer to connect with that identity will be terminated -and the second peer will receive any subsequent messages routed to that +and the second peer will receive any subsequent messages routed to that identity. Option value type:: int @@ -480,7 +495,7 @@ Applicable socket types:: ZMQ_XPUB ZMQ_REQ_CORRELATE: match replies with requests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The default behavior of REQ sockets is to rely on the ordering of messages to +The default behavior of REQ sockets is to rely on the ordering of messages to match requests and responses and that is usually sufficient. When this option is set to 1, the REQ socket will prefix outgoing messages with an extra frame containing a request id. That means the full message is (request id, 0, @@ -506,7 +521,7 @@ The request-reply state machine is reset and a new request is sent to the next available peer. If set to 1, also enable ZMQ_REQ_CORRELATE to ensure correct matching of -requests and replies. Otherwise a late reply to an aborted request can be +requests and replies. Otherwise a late reply to an aborted request can be reported as the reply to the superseding request. [horizontal] diff --git a/include/zmq.h b/include/zmq.h index c3992bad..2b7809ec 100644 --- a/include/zmq.h +++ b/include/zmq.h @@ -289,6 +289,7 @@ ZMQ_EXPORT int zmq_msg_set (zmq_msg_t *msg, int option, int optval); #define ZMQ_CONFLATE 54 #define ZMQ_ZAP_DOMAIN 55 #define ZMQ_ROUTER_HANDOVER 56 +#define ZMQ_TOS 57 /* Message options */ #define ZMQ_MORE 1 diff --git a/src/ip.cpp b/src/ip.cpp index f5c9129c..02781f11 100644 --- a/src/ip.cpp +++ b/src/ip.cpp @@ -148,3 +148,18 @@ bool zmq::get_peer_ip_address (fd_t sockfd_, std::string &ip_addr_) ip_addr_ = host; return true; } + + +void zmq::set_ip_type_of_service (fd_t s_, int iptos) +{ + (void) s_; + + int rc = setsockopt(s_, IPPROTO_IP, IP_TOS, &iptos, sizeof(iptos)); + +#ifdef ZMQ_HAVE_WINDOWS + wsa_assert (rc != SOCKET_ERROR); +#else + errno_assert (rc == 0); +#endif + +} diff --git a/src/ip.hpp b/src/ip.hpp index 7d0bb84e..7b8621c8 100644 --- a/src/ip.hpp +++ b/src/ip.hpp @@ -39,6 +39,9 @@ namespace zmq // Socket sockfd_ must be connected. Returns true iff successful. bool get_peer_ip_address (fd_t sockfd_, std::string &ip_addr_); + // Sets the IP Type-Of-Service for the underlying socket + void set_ip_type_of_service (fd_t s_, int iptos); + } #endif diff --git a/src/options.cpp b/src/options.cpp index ea932b03..f173a031 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -33,6 +33,7 @@ zmq::options_t::options_t () : multicast_hops (1), sndbuf (0), rcvbuf (0), + tos (0), type (-1), linger (-1), reconnect_ivl (100), @@ -125,6 +126,13 @@ int zmq::options_t::setsockopt (int option_, const void *optval_, } break; + case ZMQ_TOS: + if (is_int && value >= 0) { + tos = value; + return 0; + } + break; + case ZMQ_LINGER: if (is_int && value >= -1) { linger = value; @@ -424,6 +432,12 @@ int zmq::options_t::getsockopt (int option_, void *optval_, size_t *optvallen_) } break; + case ZMQ_TOS: + if (is_int) { + *value = tos; + return 0; + } + break; case ZMQ_TYPE: if (is_int) { *value = type; diff --git a/src/options.hpp b/src/options.hpp index 5154fab6..e2ac24ac 100644 --- a/src/options.hpp +++ b/src/options.hpp @@ -66,6 +66,9 @@ namespace zmq int sndbuf; int rcvbuf; + // Type of service (containing DSCP and ECN socket options) + int tos; + // Socket type. int type; diff --git a/src/tcp_connecter.cpp b/src/tcp_connecter.cpp index f1258926..4c7f3afe 100644 --- a/src/tcp_connecter.cpp +++ b/src/tcp_connecter.cpp @@ -190,14 +190,14 @@ int zmq::tcp_connecter_t::get_new_reconnect_ivl () // Only change the current reconnect interval if the maximum reconnect // interval was set and if it's larger than the reconnect interval. - if (options.reconnect_ivl_max > 0 && + if (options.reconnect_ivl_max > 0 && options.reconnect_ivl_max > options.reconnect_ivl) { // Calculate the next interval current_reconnect_ivl = current_reconnect_ivl * 2; if(current_reconnect_ivl >= options.reconnect_ivl_max) { current_reconnect_ivl = options.reconnect_ivl_max; - } + } } return this_interval; } @@ -223,6 +223,10 @@ int zmq::tcp_connecter_t::open () if (addr->resolved.tcp_addr->family () == AF_INET6) enable_ipv4_mapping (s); + // Set the IP Type-Of-Service priority for this socket + if (options.tos != 0) + set_ip_type_of_service (s, options.tos); + // Set the socket to non-blocking mode so that we get async connect(). unblock_socket (s); @@ -232,6 +236,10 @@ int zmq::tcp_connecter_t::open () if (options.rcvbuf != 0) set_tcp_receive_buffer (s, options.rcvbuf); + // Set the IP Type-Of-Service for the underlying socket + if (options.tos != 0) + set_ip_type_of_service (s, options.tos); + // Connect to the remote peer. int rc = ::connect ( s, addr->resolved.tcp_addr->addr (), diff --git a/src/tcp_listener.cpp b/src/tcp_listener.cpp index 89e146b6..3ad5c67e 100644 --- a/src/tcp_listener.cpp +++ b/src/tcp_listener.cpp @@ -100,7 +100,7 @@ void zmq::tcp_listener_t::in_event () io_thread_t *io_thread = choose_io_thread (options.affinity); zmq_assert (io_thread); - // Create and launch a session object. + // Create and launch a session object. session_base_t *session = session_base_t::create (io_thread, false, socket, options, NULL); errno_assert (session); @@ -188,6 +188,10 @@ int zmq::tcp_listener_t::set_address (const char *addr_) if (address.family () == AF_INET6) enable_ipv4_mapping (s); + // Set the IP Type-Of-Service for the underlying socket + if (options.tos != 0) + set_ip_type_of_service (s, options.tos); + // Set the socket buffer limits for the underlying socket. if (options.sndbuf != 0) set_tcp_send_buffer (s, options.sndbuf); @@ -300,5 +304,9 @@ zmq::fd_t zmq::tcp_listener_t::accept () } } + // Set the IP Type-Of-Service priority for this client socket + if (options.tos != 0) + set_ip_type_of_service (sock, options.tos); + return sock; } diff --git a/tests/Makefile.am b/tests/Makefile.am index c00ae508..bb87a646 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -42,7 +42,8 @@ noinst_PROGRAMS = test_system \ test_issue_566 \ test_proxy \ test_abstract_ipc \ - test_many_sockets + test_many_sockets \ + test_diffserv if !ON_MINGW noinst_PROGRAMS += test_shutdown_stress \ @@ -103,6 +104,7 @@ test_issue_566_SOURCES = test_issue_566.cpp test_proxy_SOURCES = test_proxy.cpp test_abstract_ipc_SOURCES = test_abstract_ipc.cpp test_many_sockets_SOURCES = test_many_sockets.cpp +test_diffserv_SOURCES = test_diffserv.cpp if !ON_MINGW test_shutdown_stress_SOURCES = test_shutdown_stress.cpp test_pair_ipc_SOURCES = test_pair_ipc.cpp testutil.hpp diff --git a/tests/test_diffserv b/tests/test_diffserv new file mode 100755 index 00000000..802e6fdb --- /dev/null +++ b/tests/test_diffserv @@ -0,0 +1,228 @@ +#! /bin/bash + +# test_diffserv - temporary wrapper script for .libs/test_diffserv +# Generated by libtool (GNU libtool) 2.4.2 Debian-2.4.2-1ubuntu1 +# +# The test_diffserv program cannot be directly executed until all the libtool +# libraries that it depends on are installed. +# +# This wrapper script should never be moved out of the build directory. +# If it is, it will not operate correctly. + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='s/\([`"$\\]\)/\\\1/g' + +# Be Bourne compatible +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac +fi +BIN_SH=xpg4; export BIN_SH # for Tru64 +DUALCASE=1; export DUALCASE # for MKS sh + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +relink_command="(cd /home/claws/Development/git-repos/claws_libzmq/tests; { test -z \"\${LIBRARY_PATH+set}\" || unset LIBRARY_PATH || { LIBRARY_PATH=; export LIBRARY_PATH; }; }; { test -z \"\${COMPILER_PATH+set}\" || unset COMPILER_PATH || { COMPILER_PATH=; export COMPILER_PATH; }; }; { test -z \"\${GCC_EXEC_PREFIX+set}\" || unset GCC_EXEC_PREFIX || { GCC_EXEC_PREFIX=; export GCC_EXEC_PREFIX; }; }; { test -z \"\${LD_RUN_PATH+set}\" || unset LD_RUN_PATH || { LD_RUN_PATH=; export LD_RUN_PATH; }; }; { test -z \"\${LD_LIBRARY_PATH+set}\" || unset LD_LIBRARY_PATH || { LD_LIBRARY_PATH=; export LD_LIBRARY_PATH; }; }; PATH=/home/claws/bin:/usr/lib/lightdm/lightdm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/claws/bin; export PATH; g++ -g -O2 -o \$progdir/\$file test_diffserv.o ../src/.libs/libzmq.so -lsodium -lrt -lpthread -Wl,-rpath -Wl,/home/claws/Development/git-repos/claws_libzmq/src/.libs)" + +# This environment variable determines our operation mode. +if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then + # install mode needs the following variables: + generated_by_libtool_version='2.4.2' + notinst_deplibs=' ../src/libzmq.la' +else + # When we are sourced in execute mode, $file and $ECHO are already set. + if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then + file="$0" + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' +} + ECHO="printf %s\\n" + fi + +# Very basic option parsing. These options are (a) specific to +# the libtool wrapper, (b) are identical between the wrapper +# /script/ and the wrapper /executable/ which is used only on +# windows platforms, and (c) all begin with the string --lt- +# (application programs are unlikely to have options which match +# this pattern). +# +# There are only two supported options: --lt-debug and +# --lt-dump-script. There is, deliberately, no --lt-help. +# +# The first argument to this parsing function should be the +# script's ../libtool value, followed by no. +lt_option_debug= +func_parse_lt_options () +{ + lt_script_arg0=$0 + shift + for lt_opt + do + case "$lt_opt" in + --lt-debug) lt_option_debug=1 ;; + --lt-dump-script) + lt_dump_D=`$ECHO "X$lt_script_arg0" | /bin/sed -e 's/^X//' -e 's%/[^/]*$%%'` + test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=. + lt_dump_F=`$ECHO "X$lt_script_arg0" | /bin/sed -e 's/^X//' -e 's%^.*/%%'` + cat "$lt_dump_D/$lt_dump_F" + exit 0 + ;; + --lt-*) + $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2 + exit 1 + ;; + esac + done + + # Print the debug banner immediately: + if test -n "$lt_option_debug"; then + echo "test_diffserv:test_diffserv:${LINENO}: libtool wrapper (GNU libtool) 2.4.2 Debian-2.4.2-1ubuntu1" 1>&2 + fi +} + +# Used when --lt-debug. Prints its arguments to stdout +# (redirection is the responsibility of the caller) +func_lt_dump_args () +{ + lt_dump_args_N=1; + for lt_arg + do + $ECHO "test_diffserv:test_diffserv:${LINENO}: newargv[$lt_dump_args_N]: $lt_arg" + lt_dump_args_N=`expr $lt_dump_args_N + 1` + done +} + +# Core function for launching the target application +func_exec_program_core () +{ + + if test -n "$lt_option_debug"; then + $ECHO "test_diffserv:test_diffserv:${LINENO}: newargv[0]: $progdir/$program" 1>&2 + func_lt_dump_args ${1+"$@"} 1>&2 + fi + exec "$progdir/$program" ${1+"$@"} + + $ECHO "$0: cannot exec $program $*" 1>&2 + exit 1 +} + +# A function to encapsulate launching the target application +# Strips options in the --lt-* namespace from $@ and +# launches target application with the remaining arguments. +func_exec_program () +{ + case " $* " in + *\ --lt-*) + for lt_wr_arg + do + case $lt_wr_arg in + --lt-*) ;; + *) set x "$@" "$lt_wr_arg"; shift;; + esac + shift + done ;; + esac + func_exec_program_core ${1+"$@"} +} + + # Parse options + func_parse_lt_options "$0" ${1+"$@"} + + # Find the directory that this script lives in. + thisdir=`$ECHO "$file" | /bin/sed 's%/[^/]*$%%'` + test "x$thisdir" = "x$file" && thisdir=. + + # Follow symbolic links until we get to the real thisdir. + file=`ls -ld "$file" | /bin/sed -n 's/.*-> //p'` + while test -n "$file"; do + destdir=`$ECHO "$file" | /bin/sed 's%/[^/]*$%%'` + + # If there was a directory component, then change thisdir. + if test "x$destdir" != "x$file"; then + case "$destdir" in + [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;; + *) thisdir="$thisdir/$destdir" ;; + esac + fi + + file=`$ECHO "$file" | /bin/sed 's%^.*/%%'` + file=`ls -ld "$thisdir/$file" | /bin/sed -n 's/.*-> //p'` + done + + # Usually 'no', except on cygwin/mingw when embedded into + # the cwrapper. + WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no + if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then + # special case for '.' + if test "$thisdir" = "."; then + thisdir=`pwd` + fi + # remove .libs from thisdir + case "$thisdir" in + *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /bin/sed 's%[\\/][^\\/]*$%%'` ;; + .libs ) thisdir=. ;; + esac + fi + + # Try to get the absolute directory name. + absdir=`cd "$thisdir" && pwd` + test -n "$absdir" && thisdir="$absdir" + + program=lt-'test_diffserv' + progdir="$thisdir/.libs" + + if test ! -f "$progdir/$program" || + { file=`ls -1dt "$progdir/$program" "$progdir/../$program" 2>/dev/null | /bin/sed 1q`; \ + test "X$file" != "X$progdir/$program"; }; then + + file="$$-$program" + + if test ! -d "$progdir"; then + mkdir "$progdir" + else + rm -f "$progdir/$file" + fi + + # relink executable if necessary + if test -n "$relink_command"; then + if relink_command_output=`eval $relink_command 2>&1`; then : + else + printf %s\n "$relink_command_output" >&2 + rm -f "$progdir/$file" + exit 1 + fi + fi + + mv -f "$progdir/$file" "$progdir/$program" 2>/dev/null || + { rm -f "$progdir/$program"; + mv -f "$progdir/$file" "$progdir/$program"; } + rm -f "$progdir/$file" + fi + + if test -f "$progdir/$program"; then + if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then + # Run the actual program with our arguments. + func_exec_program ${1+"$@"} + fi + else + # The program doesn't exist. + $ECHO "$0: error: \`$progdir/$program' does not exist" 1>&2 + $ECHO "This script is just a wrapper for $program." 1>&2 + $ECHO "See the libtool documentation for more information." 1>&2 + exit 1 + fi +fi diff --git a/tests/test_diffserv.cpp b/tests/test_diffserv.cpp new file mode 100644 index 00000000..e6ad725b --- /dev/null +++ b/tests/test_diffserv.cpp @@ -0,0 +1,70 @@ +/* + Copyright (c) 2007-2013 Contributors as noted in the AUTHORS file + + This file is part of 0MQ. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 0MQ 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 "testutil.hpp" + +int main (void) +{ + int rc; + int tos = 0x28; + int o_tos; + size_t tos_size = sizeof(tos); + + setup_test_environment(); + void *ctx = zmq_ctx_new (); + assert (ctx); + + void *sb = zmq_socket (ctx, ZMQ_PAIR); + assert (sb); + rc = zmq_setsockopt (sb, ZMQ_TOS, &tos, tos_size); + assert (rc == 0); + rc = zmq_bind (sb, "tcp://127.0.0.1:5560"); + assert (rc == 0); + rc = zmq_getsockopt (sb, ZMQ_TOS, &o_tos, &tos_size); + assert (rc == 0); + assert (o_tos == tos); + + void *sc = zmq_socket (ctx, ZMQ_PAIR); + assert (sc); + tos = 0x58; + rc = zmq_setsockopt (sc, ZMQ_TOS, &tos, tos_size); + assert (rc == 0); + rc = zmq_connect (sc, "tcp://127.0.0.1:5560"); + assert (rc == 0); + rc = zmq_getsockopt (sc, ZMQ_TOS, &o_tos, &tos_size); + assert (rc == 0); + assert (o_tos == tos); + + // Wireshark can be used to verify that the server socket is + // using DSCP 0x28 in packets to the client while the client + // is using 0x58 in packets to the server. + bounce (sb, sc); + + rc = zmq_close (sc); + assert (rc == 0); + + rc = zmq_close (sb); + assert (rc == 0); + + rc = zmq_ctx_term (ctx); + assert (rc == 0); + + return 0 ; + +}