From 322268a9c4bf80a65d9ef7abaaf3ee24b1e4783d Mon Sep 17 00:00:00 2001
From: Thomas Bellman <bellman@lysator.liu.se>
Date: Sat, 24 Nov 2018 22:36:11 +0100
Subject: [PATCH] Implement address lookup in check_ping_multiaddr.

This adds lookup of IP addresses from names of the hostnames/addresses
given on the command line, and errors out if the address cannot be
resolved.  By default, only the "first" address of each host will be
used; even if both --ipv4 and --ipv6 are specified, only one address,
family (the one the resolver library prefers) will be collected.
However, giving the -A/--all-addresses option will cause all addresses
(of the requested address families) for each hostname to be used.

We still don't actually do anything with the addresses yet, except
print them if the new -d/--debug option is used.
---
 check_ping_multiaddr.py | 62 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/check_ping_multiaddr.py b/check_ping_multiaddr.py
index e533c12..ca74603 100755
--- a/check_ping_multiaddr.py
+++ b/check_ping_multiaddr.py
@@ -56,6 +56,9 @@ class Options(optparse.OptionParser):
             help=("Check all addresses each hostname resolves to."
                   " By default, only the first address for each host is"
                   " checked."))
+        self.add_option(
+            '-d', '--debug', action='count', default=0,
+            help=("Increase debug level [default: %default]."))
 
     def get_version(self):
         progname = self.get_prog_name()
@@ -88,11 +91,70 @@ def fail(status, fmt, *args):
     raise ProgramFailure(status=status, msg=msg)
 
 
+def chatter(level, fmt, *args, **kwargs):
+    if level <= OPTIONS.debug:
+        msg = fmt % (kwargs or args)
+        sys.stderr.write("#" + "  " * level + msg + "\n")
+
+
+def eai_errno_to_symbol(errno):
+    for symbol in dir(socket):
+        if symbol.startswith('EAI_') and getattr(socket, symbol) == errno:
+            return symbol
+    return
+
+
+
+def collect_addresses(hosts, all_addresses, do_v4, do_v6):
+    if do_v4 and do_v6:
+        ipfamily = socket.AF_UNSPEC ;  ipversion = None
+    elif do_v4:
+        ipfamily = socket.AF_INET ;    ipversion = 4
+    elif do_v6:
+        ipfamily = socket.AF_INET6 ;   ipversion = 6
+    else:
+        raise ValueError("Neither IPv4 nor IPv6 selected")
+
+    lookupflags = 0
+    lookupflags |= getattr(socket, 'AI_IDN', 0)
+
+    addresses = { 4: set(), 6: set() }
+    for host in hosts:
+        # Try it as a numerical IP address first
+        try:
+            addr = ipaddr.IPAddress(host, ipversion)
+        except ValueError:
+            pass
+        else:
+            addresses[addr.version].add(addr)
+            continue
+
+        # And if that failed, try resolving the name
+        try:
+            ipres = socket.getaddrinfo(host, None, ipfamily, 0, 0, lookupflags)
+        except socket.gaierror as e:
+            fail(os.EX_NOHOST, "%s, %s", e.strerror, host)
+        for ai in ipres:
+            ipfam = ai[0] ;  ip = ai[4][0]
+            addr = ipaddr.IPAddress(ip)
+            addresses[addr.version].add(addr)
+            if not all_addresses:
+                break
+
+    return addresses
+
+
 
 def main(argv):
     global OPTIONS
     OPTIONS, arg_addresses = Options().parse_args(argv[1:])
 
+    addresses = collect_addresses(
+        arg_addresses, OPTIONS.all_addresses, OPTIONS.ipv4, OPTIONS.ipv6)
+
+    for ipver,addrs in addresses.items():
+        chatter(2, "IPv%d addresses:   %s", ipver, "  ".join(map(str, addrs)))
+
     ping_statuses = {
         'UNKNOWN':  [ 'check_ping_multiaddr not yet implemented' ],
     }
-- 
GitLab