]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
squidclient: --ping mode module support
authorAmos Jeffries <squid3@treenet.co.nz>
Thu, 20 Feb 2014 13:03:07 +0000 (06:03 -0700)
committerAmos Jeffries <squid3@treenet.co.nz>
Thu, 20 Feb 2014 13:03:07 +0000 (06:03 -0700)
  Module support:

Update squidclient support modules with different logics
and configuration option sets as a basis for multiple
protocol support.

A mechanism is added to allow each module to have its own
command line option set. Any option unknown to the current
module handler drops back to the main loop for processing.

  --ping mode module:

Break the existing code "ping mode" operations and command
line processing out from the main squidclient.cc into Ping.*

Ping-specific short command line options are now only parsed
after a mode flag (--ping) is presented. This frees up the
-g and -I options for use by other non-ping modules in future.

Also, shuffle squidclient code into its own directory
tools/squidclient/ to keep the tool code files clearly
identifiable now that they are multiplying.

configure.ac
tools/Makefile.am
tools/squidclient/Makefile.am [new file with mode: 0644]
tools/squidclient/Parameters.h [new file with mode: 0644]
tools/squidclient/Ping.cc [new file with mode: 0644]
tools/squidclient/Ping.h [new file with mode: 0644]
tools/squidclient/squidclient.1 [moved from tools/squidclient.1 with 92% similarity]
tools/squidclient/squidclient.cc [moved from tools/squidclient.cc with 77% similarity]

index 126b6450cfdd1675b825712fe1d0907620175900..15687a4eb55107c18261d20f4ed37ac2ec97a204 100644 (file)
@@ -3483,6 +3483,7 @@ AC_CONFIG_FILES([
        helpers/storeid_rewrite/Makefile
        helpers/storeid_rewrite/file/Makefile
        tools/Makefile
+       tools/squidclient/Makefile
        tools/purge/Makefile
 ])
 
index a22e37b397df3fed863ad535ada1e6c37b8e07f3..8bdd011065352641891e1b75bff7bbc5cc786909 100644 (file)
@@ -11,7 +11,7 @@ AUTOMAKE_OPTIONS = subdir-objects
 ## we need our local files too (but avoid -I. at all costs)
 INCLUDES += -I$(srcdir)
 
-SUBDIRS = purge
+SUBDIRS = purge squidclient
 EXTRA_DIST = 
 man_MANS = 
 DISTCLEANFILES = 
@@ -52,19 +52,6 @@ EXTRA_DIST += helper-mux.pl helper-mux.README
 ## Test Scripts
 EXTRA_DIST += helper-ok-dying.pl helper-ok.pl
 
-## ##### squidclient  #####
-
-bin_PROGRAMS = squidclient
-
-squidclient_SOURCES = squidclient.cc \
-       stub_debug.cc \
-       test_tools.cc \
-       time.cc
-
-EXTRA_DIST += squidclient.1
-man_MANS += squidclient.1
-
-
 
 ## ##### cachemgr.cgi  #####
 
diff --git a/tools/squidclient/Makefile.am b/tools/squidclient/Makefile.am
new file mode 100644 (file)
index 0000000..2bcfa8a
--- /dev/null
@@ -0,0 +1,49 @@
+include $(top_srcdir)/src/Common.am
+
+AUTOMAKE_OPTIONS = subdir-objects
+
+SUBDIRS = 
+EXTRA_DIST = squidclient.1
+man_MANS = squidclient.1
+DISTCLEANFILES = 
+
+LDADD = \
+       $(top_builddir)/src/ip/libip.la \
+       $(top_builddir)/lib/libmiscencoding.la \
+       $(top_builddir)/lib/libmiscutil.la \
+       $(COMPAT_LIB) \
+       $(KRB5LIBS) \
+       $(XTRA_LIBS)
+
+include $(top_srcdir)/doc/manuals/Substitute.am
+
+## Several files need to be shared but we cannot depend on the other
+## directories to be built.
+test_tools.cc: $(top_srcdir)/test-suite/test_tools.cc
+       cp $(top_srcdir)/test-suite/test_tools.cc .
+
+stub_debug.cc: $(top_srcdir)/src/tests/stub_debug.cc
+       cp $(top_srcdir)/src/tests/stub_debug.cc .
+
+time.cc: $(top_srcdir)/src/time.cc
+       cp $(top_srcdir)/src/time.cc .
+
+# stock tools for unit tests - library independent versions of dlink_list
+# etc.
+# globals.cc is needed by test_tools.cc.
+# Neither of these should be disted from here.
+TESTSOURCES= test_tools.cc
+CLEANFILES += test_tools.cc stub_debug.cc time.cc
+
+## ##### squidclient  #####
+
+bin_PROGRAMS = squidclient
+
+squidclient_SOURCES = \
+       Parameters.h \
+       Ping.cc \
+       Ping.h \
+       squidclient.cc \
+       stub_debug.cc \
+       test_tools.cc \
+       time.cc
diff --git a/tools/squidclient/Parameters.h b/tools/squidclient/Parameters.h
new file mode 100644 (file)
index 0000000..e6ecb0e
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef _SQUID_TOOLS_SQUIDCLIENT_PARAMETERS_H
+#define _SQUID_TOOLS_SQUIDCLIENT_PARAMETERS_H
+
+/**
+ * squidclient command line parameters.
+ */
+class Parameters
+{
+public:
+    Parameters() : verbosityLevel(0) {}
+
+    /**
+     * What verbosity level to display.
+     *
+     *  0  : display no debug traces
+     *  1  : display outgoing request message
+     *  2+ : display all actions taken
+     */
+    int verbosityLevel;
+};
+
+/// global squidcleint parameters
+extern Parameters scParams;
+
+#endif /* _SQUID_TOOLS_SQUIDCLIENT_PARAMETERS_H */
diff --git a/tools/squidclient/Ping.cc b/tools/squidclient/Ping.cc
new file mode 100644 (file)
index 0000000..286a8a5
--- /dev/null
@@ -0,0 +1,205 @@
+#include "squid.h"
+#include "SquidTime.h"
+#include "tools/squidclient/Parameters.h"
+#include "tools/squidclient/Ping.h"
+
+#include <iostream>
+
+#if HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#if HAVE_SIGNAL_H
+#include <signal.h>
+#endif
+
+namespace Ping
+{
+Ping::TheConfig Config;
+
+/// measurements collected by the squidclient ping mode logics
+class pingStats_
+{
+public:
+    pingStats_() {memset(this, 0, sizeof(pingStats_));}
+
+    long counted;  ///< number of transactions which have so far been measured
+    long pMin;     ///< shortest transaction time seen
+    long pMax;     ///< longest transaction time seen
+    long sum;      ///< total time so far spent waiting on transactions
+
+} stats;
+
+} // namespace Ping
+
+/**
+ * Signal interrupt handler for squidclient ping.
+ * Displays final statistics and disables further pings.
+ */
+static void
+catchSignal(int sig)
+{
+    Ping::DisplayStats();
+    Ping::Config.enable = false;
+    std::cerr << "SIGNAL " << sig << " Interrupted." << std::endl;
+}
+
+uint32_t
+Ping::Init()
+{
+    if (Ping::Config.enable) {
+#if HAVE_SIGACTION
+        struct sigaction sa, osa;
+        if (sigaction(SIGINT, NULL, &osa) == 0 && osa.sa_handler == SIG_DFL) {
+            sa.sa_handler = catchSignal;
+            sa.sa_flags = 0;
+            sigemptyset(&sa.sa_mask);
+            (void) sigaction(SIGINT, &sa, NULL);
+        }
+#else
+        void (*osig) (int);
+        if ((osig = signal(SIGINT, catchSignal)) != SIG_DFL)
+            (void) signal(SIGINT, osig);
+#endif
+        return Ping::Config.count;
+    }
+
+    return 1;
+}
+
+static struct timeval tv1, tv2;
+
+void
+Ping::TimerStart()
+{
+    if (!Ping::Config.enable)
+        return;
+
+#if GETTIMEOFDAY_NO_TZP
+    (void)gettimeofday(&tv1);
+#else
+    (void)gettimeofday(&tv1, NULL);
+#endif
+}
+
+void
+Ping::TimerStop(size_t fsize)
+{
+    if (!Ping::Config.enable)
+        return;
+
+    struct tm *tmp;
+    time_t t2s;
+    long elapsed_msec;
+
+#if GETTIMEOFDAY_NO_TZP
+    (void)gettimeofday(&tv2);
+#else
+    (void)gettimeofday(&tv2, NULL);
+#endif
+
+    elapsed_msec = tvSubMsec(tv1, tv2);
+    t2s = tv2.tv_sec;
+    tmp = localtime(&t2s);
+    char tbuf[4096];
+    snprintf(tbuf, sizeof(tbuf)-1, "%d-%02d-%02d %02d:%02d:%02d [%d]: %ld.%03ld secs, %f KB/s",
+             tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
+             tmp->tm_hour, tmp->tm_min, tmp->tm_sec, stats.counted + 1,
+             elapsed_msec / 1000, elapsed_msec % 1000,
+             elapsed_msec ? (double) fsize / elapsed_msec : -1.0);
+    std::cerr << tbuf << std::endl;
+
+    if (!stats.counted || elapsed_msec < stats.pMin)
+        stats.pMin = elapsed_msec;
+
+    if (!stats.counted || elapsed_msec > stats.pMax)
+        stats.pMax = elapsed_msec;
+
+    stats.sum += elapsed_msec;
+
+    ++stats.counted;
+
+    /* Delay until next "ping.interval" boundary */
+    if (!LoopDone(stats.counted) && elapsed_msec < Ping::Config.interval) {
+
+        struct timeval tvs;
+        long msec_left = Ping::Config.interval - elapsed_msec;
+
+        tvs.tv_sec = msec_left / 1000;
+        tvs.tv_usec = (msec_left % 1000) * 1000;
+        select(0, NULL, NULL, NULL, &tvs);
+    }
+}
+
+void
+Ping::DisplayStats()
+{
+    if (Ping::Config.enable && stats.counted) {
+        long mean = stats.sum / stats.counted;
+        std::cerr << stats.counted << " requests, round-trip (secs) min/avg/max = "
+                  << (stats.pMin/1000) << "." << (stats.pMin%1000)
+                  << "/" << (mean/1000) << "." << (mean%1000)
+                  << "/" << (stats.pMax/1000) << "." << (stats.pMax%1000)
+                  << std::endl;
+    }
+}
+
+void
+Ping::TheConfig::usage()
+{
+    std::cerr << "Ping Mode" << std::endl
+              << "  --ping [options]  Enable ping mode." << std::endl
+              << std::endl
+              << "  options:" << std::endl
+              << "    -g count        Ping iteration count (default, loop until interrupted)." << std::endl
+              << "    -I interval     Ping interval in seconds (default 1 second)." << std::endl
+              << std::endl;
+}
+
+bool
+Ping::TheConfig::parseCommandOpts(int argc, char *argv[], int c, int &optIndex)
+{
+    // to get here --ping was seen
+    enable = true;
+    count = 0;           // default is infinite loop
+    interval = 1 * 1000; // default is 1s intervals
+
+    const char *shortOpStr = "g:I:?";
+
+    // options for controlling squidclient ping mode
+    static struct option pingOptions[] = {
+        {"count",    no_argument, 0, 'g'},
+        {"interval", no_argument, 0, 'I'},
+        {0, 0, 0, 0}
+    };
+
+    int saved_opterr = opterr;
+    opterr = 0; // suppress errors from getopt
+    while ((c = getopt_long(argc, argv, shortOpStr, pingOptions, &optIndex)) != -1) {
+        switch (c) {
+        case 'g':
+            if (optarg)
+                count = atoi(optarg);
+            else {
+                std::cerr << "ERROR: -g ping count missing parameter." << std::endl;
+                usage();
+            }
+            break;
+
+        case 'I':
+            if ((interval = atoi(optarg) * 1000) <= 0) {
+                std::cerr << "ERROR: -I ping interval out of range (0-" << (INT_MAX/1000) << ")." << std::endl;
+                usage();
+            }
+            break;
+
+        default:
+            // rewind and let the caller handle unknown options
+            --optind;
+            opterr = saved_opterr;
+            return true;
+        }
+    }
+
+    opterr = saved_opterr;
+    return false;
+}
diff --git a/tools/squidclient/Ping.h b/tools/squidclient/Ping.h
new file mode 100644 (file)
index 0000000..5e03192
--- /dev/null
@@ -0,0 +1,54 @@
+#ifndef _SQUID_TOOLS_CLIENT_PING_H
+#define _SQUID_TOOLS_CLIENT_PING_H
+
+/**
+ * API for looping the squidclient request message
+ * repeatedly.
+ */
+namespace Ping
+{
+
+/// parameters controlling 'ping' mode message looping.
+class TheConfig
+{
+public:
+    TheConfig() : enable(false), count(0), interval(1*1000) {}
+
+    /// display Ping Options command line help to stderr
+    void usage();
+
+    /**
+     * parse --ping command line options
+     * \return true if there are other options still to parse
+     */
+    bool parseCommandOpts(int argc, char *argv[], int c, int &optIndex);
+
+    bool enable;
+    int count;
+    int interval;
+};
+
+extern TheConfig Config;
+
+/// initialize the squidclient ping mode
+uint32_t Init();
+
+/// whether ping loop is completed at the given iteration.
+inline bool LoopDone(int i)
+{
+    return !Ping::Config.enable || (Ping::Config.count && i >= Ping::Config.count);
+}
+
+/// start timing a new transaction
+void TimerStart();
+
+/// calculate and display the statictics for a complete transaction
+/// \param fsize number of bytes transferred during this transaction (for KB/s measure)
+void TimerStop(size_t fsize);
+
+/// display summary of ping data collected
+void DisplayStats();
+
+} // namespace Ping
+
+#endif /* _SQUID_TOOLS_CLIENT_PING_H */
similarity index 92%
rename from tools/squidclient.1
rename to tools/squidclient/squidclient.1
index f34fe0fb67658e324cdedb9c2109173e58c6b25f..b158298db16181dc312f431b035c6835a4b48614 100644 (file)
@@ -7,18 +7,15 @@ A simple HTTP web client tool
 .
 .SH SYNOPSIS
 .if !'po4a'hide' .B squidclient
+.if !'po4a'hide' .B "[ \-\-ping [ping-options] ] "
 .if !'po4a'hide' .B "[ \-aknNrsv ] [ \-A"
 string
-.if !'po4a'hide' .B "] [ \-g"
-count
 .if !'po4a'hide' .B "] [ \-h"
 remote host
 .if !'po4a'hide' .B "] [ \-H '"
 string
 .if !'po4a'hide' .B "' ] [ \-i"
 IMS
-.if !'po4a'hide' .B "] [ \-I"
-ping interval
 .if !'po4a'hide' .B "] [ \-j '"
 Host header
 .if !'po4a'hide' .B "' ] [ \-l"
@@ -46,6 +43,12 @@ password
 .if !'po4a'hide' .B "] "
 url
 .
+.if !'po4a'hide' .B "Ping options: [ \-g"
+count
+.if !'po4a'hide' .B "] [ \-I"
+interval
+.if !'po4a'hide' .B "] "
+.
 .SH DESCRIPTION
 .B squidclient
 is a tool providing a command line interface for retrieving URLs.
@@ -67,12 +70,6 @@ Send
 as User-Agent: header. To omit the header completely set string to empty ('').
 .
 .if !'po4a'hide' .TP
-.if !'po4a'hide' .B "\-g count"
-Ping mode, perform
-.I count
-iterations (0 to loop until interrupted).
-.
-.if !'po4a'hide' .TP
 .if !'po4a'hide' .B "\-h host"
 Retrieve URL from cache on hostname.  Default is
 .B localhost
@@ -88,10 +85,6 @@ for new lines.
 If\-Modified\-Since time (in Epoch seconds).
 .
 .if !'po4a'hide' .TP
-.if !'po4a'hide' .B "\-I interval"
-Ping interval in seconds (default 1 second).
-.
-.if !'po4a'hide' .TP
 .if !'po4a'hide' .B "\-j hosthdr"
 Host header content
 .
@@ -184,6 +177,21 @@ Proxy authentication password
 .if !'po4a'hide' .B "\-W password"
 WWW authentication password
 .
+.if !'po4a'hide' .TP 10
+.if !'po4a'hide' .B "\-\-ping [options]"
+Enable ping mode. Optional \-g and \-I parameters must follow immediately if used.
+Repeated use resets to default ping settings.
+.
+.if !'po4a'hide' .TP 12
+.if !'po4a'hide' .B "\-g count"
+Ping mode, perform
+.I count
+iterations (default is to loop until interrupted).
+.
+.if !'po4a'hide' .TP
+.if !'po4a'hide' .B "\-I interval"
+Ping interval in seconds (default 1 second).
+.
 .SH AUTHOR
 Derived from Harvest. Further developed by by numerous individuals from
 the internet community. Development is led by Duane Wessels of the
similarity index 77%
rename from tools/squidclient.cc
rename to tools/squidclient/squidclient.cc
index 5bd8742fededb4a734ba1cc0ffc3f072ede586e8..414f97dcfb86948772fc924a563b06ba3669fae6 100644 (file)
@@ -35,7 +35,8 @@
 #include "ip/Address.h"
 #include "ip/tools.h"
 #include "rfc1123.h"
-#include "SquidTime.h"
+#include "tools/squidclient/Parameters.h"
+#include "tools/squidclient/Ping.h"
 
 #if _SQUID_WINDOWS_
 /** \cond AUTODOCS-IGNORE */
@@ -118,29 +119,17 @@ gss_OID gss_mech_spnego = &_gss_mech_spnego;
 #define HEADERLEN      65536
 #endif
 
-typedef void SIGHDLR(int sig);
-
 /// display debug messages at varying verbosity levels
 #define debugVerbose(LEVEL, MESSAGE) \
-    while ((LEVEL) <= verbosityLevel) {std::cerr << MESSAGE << std::endl; break;}
-
-/**
- * What verbosity level to display.
- *
- *  0  : display no debug traces
- *  1  : display outgoing request message
- *  2+ : display all actions taken
- */
-int verbosityLevel = 0;
+    while ((LEVEL) <= scParams.verbosityLevel) {std::cerr << MESSAGE << std::endl; break;}
 
 /* Local functions */
 static int client_comm_bind(int, const Ip::Address &);
 
-static int client_comm_connect(int, const Ip::Address &, struct timeval *);
+static int client_comm_connect(int, const Ip::Address &);
 static void usage(const char *progname);
 
-static int Now(struct timeval *);
-SIGHDLR catchSignal;
+typedef void SIGHDLR(int sig);
 SIGHDLR pipe_handler;
 static void set_our_signal(void);
 static ssize_t myread(int fd, void *buf, size_t len);
@@ -151,6 +140,8 @@ static bool check_gss_err(OM_uint32 major_status, OM_uint32 minor_status, const
 static char *GSSAPI_token(const char *server);
 #endif
 
+Parameters scParams;
+
 static int put_fd;
 static char *put_file = NULL;
 
@@ -178,54 +169,52 @@ static void
 usage(const char *progname)
 {
     std::cerr << "Version: " << VERSION << std::endl
-            << "Usage: " << progname << " [Basic Options] [HTTP Options]" << std::endl
-            << std::endl
-            << "Basic Options:" << std::endl
-            << "    -g count        Ping mode, perform \"count\" iterations (0 to loop until interrupted)." << std::endl
-            << "    -h host         Send message to server on 'host'.  Default is localhost." << std::endl
-            << "    -I interval     Ping interval in seconds (default 1 second)." << std::endl
-            << "    -l host         Specify a local IP address to bind to.  Default is none." << std::endl
-            << "    -p port         Port number on server to contact. Default is " << CACHE_HTTP_PORT << "." << std::endl
-            << "    -s | --quiet    Silent.  Do not print response message to stdout." << std::endl
-            << "    -T timeout      Timeout value (seconds) for read/write operations" << std::endl
-            << "    -v | --verbose  Verbose debugging. Repeat (-vv) to increase output level." << std::endl
-            << "                    Levels:" << std::endl
-            << "                      1 - Print outgoing request message to stderr." << std::endl
-            << "                      2 - Print action trace to stderr." << std::endl
-            << "    --help          Display this help text." << std::endl
-            << std::endl
-            << "HTTP Options:" << std::endl
-            << "    -a           Do NOT include Accept: header." << std::endl
-            << "    -A           User-Agent: header. Use \"\" to omit." << std::endl
-            << "    -H 'string'  Extra headers to send. Use '\\n' for new lines." << std::endl
-            << "    -i IMS       If-Modified-Since time (in Epoch seconds)." << std::endl
-            << "    -j hosthdr   Host header content" << std::endl
-            << "    -k           Keep the connection active. Default is to do only one request then close." << std::endl
-            << "    -m method    Request method, default is GET." << std::endl
+              << "Usage: " << progname << " [Basic Options] [HTTP Options]" << std::endl
+              << std::endl
+              << "Basic Options:" << std::endl
+              << "    -h host         Send message to server on 'host'.  Default is localhost." << std::endl
+              << "    -l host         Specify a local IP address to bind to.  Default is none." << std::endl
+              << "    -p port         Port number on server to contact. Default is " << CACHE_HTTP_PORT << "." << std::endl
+              << "    -s | --quiet    Silent.  Do not print response message to stdout." << std::endl
+              << "    -T timeout      Timeout value (seconds) for read/write operations" << std::endl
+              << "    -v | --verbose  Verbose debugging. Repeat (-vv) to increase output level." << std::endl
+              << "                    Levels:" << std::endl
+              << "                      1 - Print outgoing request message to stderr." << std::endl
+              << "                      2 - Print action trace to stderr." << std::endl
+              << "    --help          Display this help text." << std::endl
+              << std::endl;
+    Ping::Config.usage();
+    std::cerr
+        << "HTTP Options:" << std::endl
+        << "    -a           Do NOT include Accept: header." << std::endl
+        << "    -A           User-Agent: header. Use \"\" to omit." << std::endl
+        << "    -H 'string'  Extra headers to send. Use '\\n' for new lines." << std::endl
+        << "    -i IMS       If-Modified-Since time (in Epoch seconds)." << std::endl
+        << "    -j hosthdr   Host header content" << std::endl
+        << "    -k           Keep the connection active. Default is to do only one request then close." << std::endl
+        << "    -m method    Request method, default is GET." << std::endl
 #if HAVE_GSSAPI
-            << "    -n           Proxy Negotiate(Kerberos) authentication" << std::endl
-            << "    -N           WWW Negotiate(Kerberos) authentication" << std::endl
-#endif
-            << "    -P file      Send content from the named file as request payload" << std::endl
-            << "    -r           Force cache to reload URL" << std::endl
-            << "    -t count     Trace count cache-hops" << std::endl
-            << "    -u user      Proxy authentication username" << std::endl
-            << "    -U user      WWW authentication username" << std::endl
-            << "    -V version   HTTP Version. Use '-' for HTTP/0.9 omitted case" << std::endl
-            << "    -w password  Proxy authentication password" << std::endl
-            << "    -W password  WWW authentication password" << std::endl
-    ;
+        << "    -n           Proxy Negotiate(Kerberos) authentication" << std::endl
+        << "    -N           WWW Negotiate(Kerberos) authentication" << std::endl
+#endif
+        << "    -P file      Send content from the named file as request payload" << std::endl
+        << "    -r           Force cache to reload URL" << std::endl
+        << "    -t count     Trace count cache-hops" << std::endl
+        << "    -u user      Proxy authentication username" << std::endl
+        << "    -U user      WWW authentication username" << std::endl
+        << "    -V version   HTTP Version. Use '-' for HTTP/0.9 omitted case" << std::endl
+        << "    -w password  Proxy authentication password" << std::endl
+        << "    -W password  WWW authentication password" << std::endl
+        ;
     exit(1);
 }
 
-static int interrupted = 0;
 int
 main(int argc, char *argv[])
 {
     int conn, len, bytesWritten;
     uint16_t port;
     bool to_stdout, reload;
-    int ping, pcount;
     int keep_alive = 0;
     int opt_noaccept = 0;
 #if HAVE_GSSAPI
@@ -240,10 +229,7 @@ main(int argc, char *argv[])
     time_t ims = 0;
     int max_forwards = -1;
 
-    struct timeval tv1, tv2;
-    int i = 0, loops;
-    long ping_int;
-    long ping_min = 0, ping_max = 0, ping_sum = 0, ping_mean = 0;
+    int i = 0;
     const char *proxy_user = NULL;
     const char *proxy_password = NULL;
     const char *www_user = NULL;
@@ -259,9 +245,6 @@ main(int argc, char *argv[])
     port = CACHE_HTTP_PORT;
     to_stdout = true;
     reload = false;
-    ping = 0;
-    pcount = 0;
-    ping_int = 1 * 1000;
 
     Ip::ProbeTransport(); // determine IPv4 or IPv6 capabilities before parsing.
     if (argc < 2 || argv[argc-1][0] == '-') {
@@ -271,21 +254,37 @@ main(int argc, char *argv[])
         url[BUFSIZ - 1] = '\0';
 
         int optIndex = 0;
-        const char *shortOpStr = "aA:h:j:V:l:P:i:kmnN:p:rsvt:g:p:I:H:T:u:U:w:W:?";
+        const char *shortOpStr = "aA:h:j:V:l:P:i:kmnN:p:rsvt:p:H:T:u:U:w:W:?";
 
         // options for controlling squidclient
-        static struct option basicOptions[] =
-        {
-          /* These are the generic options for squidclient itself */
-          {"help",    no_argument, 0, '?'},
-          {"verbose", no_argument, 0, 'v'},
-          {"quiet",   no_argument, 0, 's'},
-          {0, 0, 0, 0}
+        static struct option basicOptions[] = {
+            /* These are the generic options for squidclient itself */
+            {"help",    no_argument, 0, '?'},
+            {"verbose", no_argument, 0, 'v'},
+            {"quiet",   no_argument, 0, 's'},
+            {"ping",    no_argument, 0, '\1'},
+            {0, 0, 0, 0}
         };
 
         int c;
         while ((c = getopt_long(argc, argv, shortOpStr, basicOptions, &optIndex)) != -1) {
+
+            // modules parse their own specific options
             switch (c) {
+            case '\1':
+                to_stdout = 0;
+                if (Ping::Config.parseCommandOpts(argc, argv, c, optIndex))
+                    continue;
+                break;
+
+            default: // fall through to next switch
+                break;
+            }
+
+            switch (c) {
+
+            case '\0': // dummy value for end-of-options
+                break;
 
             case 'a':
                 opt_noaccept = 1;
@@ -346,17 +345,6 @@ main(int argc, char *argv[])
                 max_forwards = atoi(optarg);
                 break;
 
-            case 'g':
-                ping = 1;
-                pcount = atoi(optarg);
-                to_stdout = 0;
-                break;
-
-            case 'I':
-                if ((ping_int = atoi(optarg) * 1000) <= 0)
-                    usage(argv[0]);
-                break;
-
             case 'H':
                 if (strlen(optarg)) {
                     char *t;
@@ -406,8 +394,8 @@ main(int argc, char *argv[])
 
             case 'v':
                 /* undocumented: may increase verb-level by giving more -v's */
-                ++verbosityLevel;
-                debugVerbose(2, "verbosity level set to " << verbosityLevel);
+                ++scParams.verbosityLevel;
+                debugVerbose(2, "verbosity level set to " << scParams.verbosityLevel);
                 break;
 
             case '?':          /* usage */
@@ -575,30 +563,10 @@ main(int argc, char *argv[])
 
     debugVerbose(1, "Request:" << std::endl << msg << std::endl << ".");
 
-    if (ping) {
-#if HAVE_SIGACTION
-
-        struct sigaction sa, osa;
-
-        if (sigaction(SIGINT, NULL, &osa) == 0 && osa.sa_handler == SIG_DFL) {
-            sa.sa_handler = catchSignal;
-            sa.sa_flags = 0;
-            sigemptyset(&sa.sa_mask);
-            (void) sigaction(SIGINT, &sa, NULL);
-        }
-#else
-        void (*osig) (int);
-
-        if ((osig = signal(SIGINT, catchSignal)) != SIG_DFL)
-            (void) signal(SIGINT, osig);
-
-#endif
-
-    }
-    loops = ping ? pcount : 1;
+    uint32_t loops = Ping::Init();
 
     for (i = 0; loops == 0 || i < loops; ++i) {
-        int fsize = 0;
+        size_t fsize = 0;
         struct addrinfo *AI = NULL;
 
         debugVerbose(2, "Resolving... " << hostname);
@@ -642,7 +610,7 @@ main(int argc, char *argv[])
 
         debugVerbose(2, "Connecting... " << hostname << " (" << iaddr << ")");
 
-        if (client_comm_connect(conn, iaddr, ping ? &tv1 : NULL) < 0) {
+        if (client_comm_connect(conn, iaddr) < 0) {
             char hostnameBuf[MAX_IPSTRLEN];
             iaddr.toUrl(hostnameBuf, MAX_IPSTRLEN);
             std::cerr << "ERROR: Cannot connect to " << hostnameBuf
@@ -702,56 +670,13 @@ main(int argc, char *argv[])
 
         (void) close(conn);    /* done with socket */
 
-        if (interrupted)
+        if (Ping::LoopDone(i))
             break;
 
-        if (ping) {
-
-            struct tm *tmp;
-            time_t t2s;
-            long elapsed_msec;
-
-            (void) Now(&tv2);
-            elapsed_msec = tvSubMsec(tv1, tv2);
-            t2s = tv2.tv_sec;
-            tmp = localtime(&t2s);
-            char tbuf[4096];
-            snprintf(tbuf, sizeof(tbuf)-1, "%d-%02d-%02d %02d:%02d:%02d [%d]: %ld.%03ld secs, %f KB/s",
-                    tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
-                    tmp->tm_hour, tmp->tm_min, tmp->tm_sec, i + 1,
-                    elapsed_msec / 1000, elapsed_msec % 1000,
-                    elapsed_msec ? (double) fsize / elapsed_msec : -1.0);
-            std::cerr << tbuf << std::endl;
-
-            if (i == 0 || elapsed_msec < ping_min)
-                ping_min = elapsed_msec;
-
-            if (i == 0 || elapsed_msec > ping_max)
-                ping_max = elapsed_msec;
-
-            ping_sum += elapsed_msec;
-
-            /* Delay until next "ping_int" boundary */
-            if ((loops == 0 || i + 1 < loops) && elapsed_msec < ping_int) {
-
-                struct timeval tvs;
-                long msec_left = ping_int - elapsed_msec;
-
-                tvs.tv_sec = msec_left / 1000;
-                tvs.tv_usec = (msec_left % 1000) * 1000;
-                select(0, NULL, NULL, NULL, &tvs);
-            }
-        }
+        Ping::TimerStop(fsize);
     }
 
-    if (ping && i) {
-        ping_mean = ping_sum / i;
-        std::cerr << i << " requests, round-trip (secs) min/avg/max = "
-                  << (ping_min/1000) << "." << (ping_min%1000)
-                  << "/" << (ping_mean/1000) << "." << (ping_mean%1000)
-                  << "/" << (ping_max/1000) << "." << (ping_max%1000)
-                  << std::endl;
-    }
+    Ping::DisplayStats();
     return 0;
 }
 
@@ -768,34 +693,16 @@ client_comm_bind(int sock, const Ip::Address &addr)
 
 /// Set up the destination socket address for message to send to.
 static int
-client_comm_connect(int sock, const Ip::Address &addr, struct timeval *tvp)
+client_comm_connect(int sock, const Ip::Address &addr)
 {
     static struct addrinfo *AI = NULL;
     addr.getAddrInfo(AI);
     int res = connect(sock, AI->ai_addr, AI->ai_addrlen);
     Ip::Address::FreeAddrInfo(AI);
-    if (tvp)
-        (void) Now(tvp);
+    Ping::TimerStart();
     return res;
 }
 
-static int
-Now(struct timeval *tp)
-{
-#if GETTIMEOFDAY_NO_TZP
-    return gettimeofday(tp);
-#else
-    return gettimeofday(tp, NULL);
-#endif
-}
-
-void
-catchSignal(int sig)
-{
-    interrupted = 1;
-    std::cerr << "SIGNAL " << sig << " Interrupted." << std::endl;
-}
-
 void
 pipe_handler(int sig)
 {