/*---------------------------------------------------------------------------
  Aug 9, 2020
  -----------
  I added the ability to test that TCP ports are NOT listening.  These
  are configured as 'tcp4Denied' in the configuration.  This is useful for
  testing that insecure services are not running.  Such as when I enable
  ftpd on my local LAN to deal with some old device's inability to use secure
  file transfer, then forget to disable it when I'm done.  Or just making
  sure that my hosts aren't running telnetd, ftpd, shell (rsh), finger,
  etc. on registered ports.
  
  May 22, 2020
  ------------
  I can get a current 3400x1600 radar mosaic from:

    https://radar.weather.gov/ridge/Conus/RadarImg/latest.gif

  And crop it to a usable size:
  
    convert -crop '1600x960+1480+0!' ....

  I can get XML forecast data for the next 48 hours from:

    https://forecast.weather.gov/MapClick.php?lat=42.7775&lon=-83.4215&FcstType=digitalDWML

  May 16, 2020
  ------------
  To get current weather conditions closest to me:

      https://api.weather.gov/stations/KPTK/observations/latest

  To get the stations near me:

      https://api.weather.gov/points/42.6641,-83.4202/stations

  In terms of display, I'd like to show the following for current
  conditions:
  
    - temperature         ('65F')
    - relativeHumidity    ('Hum 75%')
    - barometric pressure ('kPa 101.6')
    - dewpoint            ('Dew 50F')
    - windSpeed           ('15 mph')
    - windChill           ('65F')  - often 'null' in data
    - heatIndex           ('65F')  - often 'null' in data

  Note: to convert Pa to inches of Hg, multiply by 0.0002952998751.
  
  May 15, 2020
  ------------
  I consolidated my HTTP and HTTPS alerts into just WEB alerts, and am
  using Poco for both.  This reduces te amount of code I need to maintain,
  and simplifies the configuration file.

  Now that I have both HTTP and HTTPS tests, I should rarely need to probe
  any web ports for just plain connect() success.

  Note that one downside of using Poco at the moment is that I don't
  have a good means of making it all non-blocking.  So I'm using a
  thread pool here (Dwm::LoadBalancer), with at most 16 threads at the
  moment.  I have hardcoded the timeouts for Poco to 5 seconds for
  connect, 5 seconds for send, 5 seconds for receive.  Yes, 16 threads
  _could_ get all stuck waiting for I/O for up to 15 seconds.  But I
  don't probe very many URIs at the moment, so it's mostly a
  non-issue.  I could easily run a lot more than 16 threads here on
  most machines, I'm just trying to keep my total stack consumption
  within reason so I can run on Raspberry Pi 3 or even a 2 without
  worrrying about it (esp. with Linux whose response to heavy memory
  pressure is... well... crappy).
  
  Speaking of configuration, I can now specify one or more HTTP status
  codes as acceptable (no alert) for a given URI GET.  This allows me
  to probe services where I expect a redirect (many cloud services),
  without following the redirect, just as an example.  If status codes
  are not configured, I will alert whenever the status code is not
  200.
  
  May 13, 2020
  ------------
  I happen to need EGLFS working for my day job, and have been working
  earnestly on getting it working on the Raspberry Pi 3.  I have it working
  now with the older Braodcom drivers, but they're the ones with the most
  miles on them and there are still some quirks with the new drivers.
  I'm still working on a Qt 5 build on the Raspberry Pi 4, using the
  new drivers.

  The new Qt client works using EGLFS on the Raspberry Pi 3, and I dare
  say it works quite well.  I have an alert page and a weather page,
  which I can toggle between by pressing the 'dwm' logo on the screen.
  The weather forecast is scrollable, and I still need to put the smarts
  into the graphic for cloud cover, precipitation, etc.

  If there are no alerts and no user activity for 60 seconds, the
  display will automatically switch to the weather display.  If there
  are alerts and no user activity for 60 seconds, it will automatically
  switch to the alerts screen.

  So now I've got the old-school curses client (handy when I want to
  log in remotely to see things), and a graphical client than can run
  on a lowly Raspberry Pi in a useful manner with NO USER INTERACTION.
  I start the client with systemd at boot, with no X11.  Just what I
  need for a display on my office wall.
  
  May 10, 2020
  ------------
  I've been working on a Qt client, since in the end I want a wall-mounted
  display that runs all the time from a Raspberry Pi and it should use
  EGLFS so I don't need X11 or Wayland.  I also want a weather display
  when there are no alerts (which should be most of the time), showing
  the NWS forecast for my area.  I have much of this working, but it
  needs some cleanup and I haven't gotten EGLFS working correctly with
  Qt yet.  The Raspbian team REALLY messed up in the transition from
  Broadcom binary blobs to the new drivers, by reusing the filenames for
  the shared libraries but putting them in different directories.
  
  May 3, 2020
  -----------
  Over the weekened I did some work on the ASCII art to show in the ncurses
  client when there are no alerts.  And now the client can automatically
  switch to a different team member from the config as its source of alerts,
  in case one or more team members are unresponsive.

  Speaking of team members... I now have a simple alert distribution scheme.
  It's full mesh, with each team member asking each other team member for
  only the alerts it sourced.  Sort of IBGP-like except I don't do
  incremental updates (fetches of alerts are distinct periodic connections,
  not persistent).  The user client can ask any team member for ALL alerts,
  and hence doesn't need to be part of the mesh (route-reflector like).
  
  I added filesystem alerts.

    I now have tests and alerts for:

      - filesystems.  We can specify which filesystems we want to be
        always mounted, and optionally the percent full that will trigger
	an alert.
      - guests.  If an IP address from one of any configured ranges shows
        up in my ARP cache, I get an alert.
      - bandits.  If an IP address from one of any configured ranges shows
        up in my ARP cache, I get an alert.
      - routes.  This is quite flexible, since I can specify a prefix of
        interest and a set of ranges of acceptable gateways (which of
	coulrse could specify a specific gateway).  For me it's mostly
	about knowing that my default route on my gateway points to an
	address in my provider's acceptable range and that all my
	internal hosts have a fixed default that points where I expect.
      - ZFS pool health and ZFS pool capacity.
      - TCP connect() test (IPv4)
      - DNS server test (check that it can resolve configured queries)
      - SMTP NOOP test (IPv4)
      - HTTP GET test (IPv4)
      - RPC tests (IPv4), mostly to make sure my NFS services are up.
      - UPS tests: check that my UPSes are on line power, not battery.
        I likely need to add to this (check for faults), but this
	is sufficient for the moment.

  Apr 29, 2020
  ------------
    I now have tests and alerts for:

      - guests.  If an IP address from one of any configured ranges shows
        up in my ARP cache, I get an alert.
      - bandits.  If an IP address from one of any configured ranges shows
        up in my ARP cache, I get an alert.
      - routes.  This is quite flexible, since I can specify a prefix of
        interest and a set of ranges of acceptable gateways (which of
	coulrse could specify a specific gateway).  For me it's mostly
	about knowing that my default route on my gateway points to an
	address in my provider's acceptable range and that all my
	internal hosts have a fixed default that points where I expect.
      - ZFS pool health and ZFS pool capacity.
      - TCP connect() test (IPv4)
      - DNS server test (check that it can resolve configured queries)
      - SMTP NOOP test (IPv4)
      - HTTP GET test (IPv4)
      - RPC tests (IPv4), mostly to make sure my NFS services are up.
      - UPS tests: check that my UPSes are on line power, not battery.
        I likely need to add to this (check for faults), but this
	is sufficient for the moment.

    I'd say I'm in pretty good shape.  I need to add filesystem alers
    and probably some process alerts.
    
  Apr 26, 2020
  ------------
  Some of the infrastructure for client/server communication is now in place,
  using libDwmAuth.

  Now working on local tests.  First up is ZFS, since it's important to me.
  Getting the health of a pool:
  
     zpool list -H -o health <poolname>

  Getting the capacity of a pool:

    zpool list -H -o capacity <poolname>

  Getting the available and used counts for a ZFS filesystem:

    zfs list -Hpo avail,used <zfs_filesystem_name>

  Routing checks... what I basically want:
    - for a given prefix (which could be /32), do I have the expected route?
      Note that the expected route could be from a range.  For example,
      Comcast might change my default route (I get it via DHCP), so I just
      want to check that it's in some /16 or the like.  When my Comcast
      connection goes down, my cable modem winds up announcing itself as
      default (again, DHCP), with a private address (in 192.168/16).
      Watching this lets me know when my Comcast is down, with zero
      intrusive probing.
    - on my private networks, I generally have a static default route for
      every host.  Hence I should be able to check that the default is
      an exact IP.

  Routing alerts... I need the destination prefix (which would be
  0.0.0.0/0 for default), and the incorrect gateway's address or
  INADDR_NONE if there is no route.  For example, if I have no default
  route:

    04/25 19:19 kiva.rfdm.com > default:NONE ROUTE

  Or when my Comcast goes down, my gateway might have:

    04/25 19:19 ria.rfdm.com > default:192.168.100.1 ROUTE
    
  Apr 25, 2020
  ------------
    I now have tests for:
    
      - TCP connect() test (IPv4)
      - DNS server test (check that it can resolve configured queries)
      - SMTP NOOP test (IPv4)
      - HTTP GET test (IPv4)
      - RPC tests (IPv4), mostly to make sure my NFS services are up.
      - UPS tests: check that my UPSes are on line power, not battery.
        I likely need to add to this (check for faults), but this
	is sufficient for the moment.

    I ditched the TOML configuration and created my own.  That's because
    the toml11 library isn't ready for prime time (bugs in output
    serialization, some of which I fixed but it's still a mess).  And to
    be honest, any generic human readable/writable configuration syntax comes
    with a pile of compromises (lots of glue code).  Note I'm not counting
    XML as 'human readable/writable'; XML would work well but it's painful
    to edit.

    I need some local tests:
      - ZFS pool health
      - filesystem utilization exceeded
      - default route is private or public or non-existent.  Internally,
        my default route is expected to be a private address.  On my
	gateway, it should be a publically routable address.  On a host
	which I don't want to communicate with the outside world,
	might want no defualt route.
      - resolver is working

  Apr 13, 2020
  ------------
    I now have tests for:
      - TCP connect() test (IPv4)
      - SMTP NOOP test (IPv4)
      - HTTP GET test (IPv4)
      - RPC tests (IPv4), mostly to make sure my NFS services are up.
      - UPS tests: check that my UPSes are on line power, not battery.
        I likely need to add to this (check for faults), but this
	is sufficient for the moment.
      
  Apr 12, 2020 (Easter Sunday in coronavirus shelter-in-place)

    At the moment I have tests for:

      - TCP connect() test (IPv4)
      - SMTP NOOP test (IPv4)
      - HTTP GET test (IPv4)
      - RPC tests (IPv4), mostly to make sure my NFS services are up

    This covers a lot of the 'critical' stuff in my home.

    I need to put some serious thought into alerts.  Part of me says
    that the alert contents could simply be a string.  The problem
    there is that machine interpretation of that string is rigid and
    fragile.  The upside is that matching and indexing is fairly cheap
    (unordered_[map|set]<string> likely works well).  I need
    sorting/matching functionality to avoid duplicate alerts, of
    course.  But the base question boils down to "How do I want to
    represent alerts in a machine-readable form?".  And there's also a
    question of scale; this work is intended for home use, so it's not
    expected to scale to 10's of thousands of alerts.  That doesn't
    mean I shouldn't keep efficiecy in mind.  Ideally the full suite
    should run on a Raspberry Pi 3 for any home user.

    I'd also like some heirarchy to some alerts, so that (for example)
    if a host is unreachable, I can collapse alerts for services on that
    host.  One thought is that al 'alert' is a chain of alerts, sorted
    by priority (or an acyclic directed graph?).  This is just to help
    manage user information overload when a significant event happens
    (say the loss of one of my 10G ethernet switches).

 *---------------------------------------------------------------------------
 Old stuff...
 
 First and foremost, I need something that can check ICMP reachability
 and TCP reachability (connect() success).  For each source host, I can
 ping a set of configured destinations (I need both IPv4 and IPv6 here).
 
 I already have a host pinger in libDwm, but it's using pcap.  I
 don't need that kind of detail, and I need IPv6 support.  Maybe 4 classes?
 ICMPv4Pinger, TCPv4ConnectTester, ICMPv6Pinger, TCPv6ConnectTester?
 
 I can always trickle up common stuff as a refactoring pass.  Let's start
 with the 2 easiers ones, ICMPv4Pinger and TCPv4ConnectTester.
 
 I need a poll interval.  This winds up controlling the packet rate.
 Do I need it on a per-destination basis?  I don't think so.  In the grand
 scheme of things, sending and receiving a few dozen packets once a minute
 is nothing on my LAN.
 
 I also need service testers for DNS, mcblockd, mcpigdo... make a list.
 
 ----------------------------------------------------------------------------
 Ports I want to monitor:
 
  TCP port 22
    kiva, ria, www, pi1, us16xg, us16xg2, us24, us48poe, uapacpro1
  TCP port 21 (yikes!!!)
    waffles
  TCP port 23 (yikes!!!)
    waffles
  TCP port 25 (SMTP)
    www
  TCP port 53
    ria
  TCP port 80 (http)
    www, waffles
  TCP port 110 (POP3)
    www
  TCP port 111 (rpcbind)
    kiva, ria, www
  TCP port 139 (smbd)
    kiva
  TCP port 143 (IMAP)
    www
  TCP port 280 (http-mgmt)
    waffles
  TCP port 443 (https)
    www
  TCP port 445 (smbd)
    kiva
  TCP port 515 (LPDSVC)
    waffles
  TCP port 548 (afpd)
    kiva, ria, www
  TCP port 587 (mail)
    www
  TCP port 631 (IPP)
    www, waffles
  TCP port 995 (IMAPS)
    www
  TCP port 995 (SPOP3)
    www
  TCP port 1001 (mcblockd)
    ria
  TCP port 2049 (nfsd)
    kiva, ria, www
  TCP port 2121 (mcpigdod)
    pi1
  TCP port 3493 (upsd)
    ria
  TCP port 6363 (dwprdapd)
    ria
  TCP port 8080 (unifi)
    www
  TCP port 9100
    waffles
  TCP port 32400 (plex)
    kiva


Or if I were to look at it host-wise:

kiva:
    22, 111, 139, 445, 548, 2049, 32400
  ria:
    22, 53, 111, 2049, 3493, 6363
  www:
    22, 25, 80, 110, 111, 143, 443, 548, 587, 631, 995, 2049, 8080
  pi1:
    22, 2121
  us16xg:
    22
  us16xg2:
    22
  us24:
    22
  us48poe:
    22
  uapacpro1:
    22
  waffles:
    21, 23, 80, 280, 515, 631, 9100

----------------------------------------------------------------------------*/

class ICMPv4Pinger
{
public:
  using Clock = std::chrono::system_clock;

  class Destination
  {};
  
  class DestinationData
  {};
  
  bool Start();
  bool Stop();
  
  bool AddDestination(const ICMPv4Destination & dst);
  bool RemoveDestination(const ICMPv4Destination & dst);
  bool SetPollInterval(uint64_t secs);

  //--------------------------------------------------------------------------
  //!  Return true as long as we don't have more than one outstanding
  //!  echo request?
  //--------------------------------------------------------------------------
  bool Reachable(const ICMPv4Destination & dst) const;

  Clock::time_point LastRequest(const ICMPv4Destination & dst) const;
  Clock::time_point LastResponse(const ICMPv4Destination & dst) const;

private:
  bool  _shouldRun;
  
  void Run();  // in own thread
};
