Commit 651d9f7b authored by Stéphane Bortzmeyer's avatar Stéphane Bortzmeyer
Browse files

Merge branch 'check-all_ips' into 'master'

Try all ips in turn with --check

See merge request bortzmeyer/homer!9
parents 22dea724 75fce135
......@@ -79,17 +79,19 @@ mandatory_levels = {"legal": 30, "necessary": 20, "nicetohave": 10}
TIMEOUT_CONN = 2
def error(msg=None):
def error(msg=None, exit=True):
if msg is None:
msg = "Unknown error"
if monitoring:
print("%s: %s" % (url, msg))
sys.exit(STATE_CRITICAL)
if exit:
sys.exit(STATE_CRITICAL)
else:
print(msg, file=sys.stderr)
if check:
print('KO')
sys.exit(1)
if exit:
sys.exit(1)
def usage(msg=None):
if msg:
......@@ -271,7 +273,7 @@ class Connection:
dot=dot, verbose=verbose, insecure=insecure):
if dot and not is_valid_hostname(server):
error("DoT requires a host name or IP address, not \"%s\"" % server)
if not dot and not is_valid_url(url):
if not dot and not is_valid_url(server):
error("DoH requires a valid HTTPS URL, not \"%s\"" % server)
if forceIPv4 and forceIPv6:
raise CustomException("Force IPv4 *or* IPv6 but not both")
......@@ -317,22 +319,30 @@ class ConnectionDoT(Connection):
Connection.__init__(self, server, servername=servername, connect=connect,
forceIPv4=forceIPv4, forceIPv6=forceIPv6, dot=True,
verbose=verbose, insecure=insecure)
self.check_ip_address(self.server)
addrinfo_list = socket.getaddrinfo(self.server, 853, self.family)
if connect is not None:
addr = connect
else:
addr = self.server
self.check_ip_address(addr)
addrinfo_list = socket.getaddrinfo(addr, 853, self.family)
addrinfo_set = { (addrinfo[4], addrinfo[0]) for addrinfo in addrinfo_list }
signal.signal(signal.SIGALRM, timeout_connection)
connection_success = False
self.success = False
for addrinfo in addrinfo_set:
self.hasher = hashlib.sha256()
if self.connect(addrinfo[0], addrinfo[1]):
connection_success = True
self.success = True
break
if self.verbose:
if self.verbose and connect is None:
print("Trying another IP address")
if not connection_success:
if self.verbose:
if not self.success:
if self.verbose and connect is None:
print("No other IP address")
error(f'Could not connect to "{server}"')
if connect is None:
error(f'Could not connect to "{server}"')
else:
print(f'Could not connect to "{server}" on {connect}')
def connect(self, addr, sock_family):
signal.alarm(TIMEOUT_CONN)
......@@ -363,6 +373,10 @@ class ConnectionDoT(Connection):
if self.verbose:
print("Timeout")
return False
except OSError:
if self.verbose:
print("Cannot connect")
return False
# RFC 7858, section 4.2 and appendix A
self.cert = self.session.get_peer_certificate()
self.publickey = self.cert.get_pubkey()
......@@ -703,6 +717,14 @@ def run_check(connection):
return False
return True
def resolved_ips(host, port, family, dot=dot):
try:
addr_list = socket.getaddrinfo(host, port, family)
except socket.gaierror:
error(f'Could not resolve "{url}"')
ip_set = { addr[4][0] for addr in addr_list }
return ip_set
# Main program
me = os.path.basename(sys.argv[0])
monitoring = (me == "check_doh" or me == "check_dot")
......@@ -869,63 +891,99 @@ else: # Monitoring plugin
path = path[1:]
url += path
# retrieve all ips when using --check
# not necessary if connectTo is already defined
# as it is the case with --monitoring
if not check or connectTo is not None:
ip_set = {connectTo, }
else:
if dot:
port = 853
if not is_valid_hostname(url):
error("DoT requires a host name or IP address, not \"%s\"" % url)
netloc = url
else:
port = 443
if not is_valid_url(url):
error("DoH requires a valid HTTPS URL, not \"%s\"" % url)
try:
url_parts = urllib.parse.urlparse(url) # A very poor validation, many
# errors (for instance whitespaces, IPv6 address litterals without
# brackets...) are ignored.
except ValueError:
error(f'The provided url "{url}" could not be parsed')
netloc = url_parts.netloc
if forceIPv4:
family = socket.AF_INET
elif forceIPv6:
family = socket.AF_INET6
else:
family = 0
ip_set = resolved_ips(netloc, port, family, dot)
ok = True
start = time.time()
try:
for connectTo in ip_set:
start = time.time()
if dot and vhostname is not None:
extracheck = vhostname
else:
extracheck = None
if dot:
conn = ConnectionDoT(url, servername=extracheck, connect=connectTo, verbose=verbose,
forceIPv4=forceIPv4, forceIPv6=forceIPv6,
insecure=insecure)
if verbose and check and connectTo:
print(f'Checking "{url}" on {connectTo} ...')
try:
if dot:
conn = ConnectionDoT(url, servername=extracheck, connect=connectTo, verbose=verbose,
forceIPv4=forceIPv4, forceIPv6=forceIPv6,
insecure=insecure)
else:
conn = ConnectionDoH(url, servername=extracheck, connect=connectTo, verbose=verbose,
forceIPv4=forceIPv4, forceIPv6=forceIPv6,
insecure=insecure)
except TimeoutError:
error("timeout")
except ConnectionRefusedError:
error("Connection to server refused")
except ValueError:
error(f'"{url}" not a name or an IP address')
except socket.gaierror:
error(f'Could not resolve "{url}"')
except CustomException as e:
error(e)
if conn.dot and not conn.success:
ok = False
continue
if ifile is not None:
input = open(ifile)
if not check:
for i in range (0, tests):
if tests > 1:
print("\nTest %i" % i)
if ifile is not None:
name, rtype = get_next_domain(input)
try:
request = conn.do_test(name, rtype)
except (OpenSSL.SSL.Error, CustomException) as e:
ok = False
error(e)
break
if not print_result(conn, request):
ok = False
if tests > 1 and i == 0:
start2 = time.time()
if delay is not None:
time.sleep(delay)
else:
conn = ConnectionDoH(url, servername=extracheck, connect=connectTo, verbose=verbose,
forceIPv4=forceIPv4, forceIPv6=forceIPv6,
insecure=insecure)
except TimeoutError:
error("timeout")
except ConnectionRefusedError:
error("Connection to server refused")
except ValueError:
error(f'"{url}" not a name or an IP address')
except socket.gaierror:
error(f'Could not resolve "{url}"')
except CustomException as e:
error(e)
if ifile is not None:
input = open(ifile)
if not check:
for i in range (0, tests):
if tests > 1:
print("\nTest %i" % i)
if ifile is not None:
name, rtype = get_next_domain(input)
try:
request = conn.do_test(name, rtype)
except (OpenSSL.SSL.Error, CustomException) as e:
ok = False
error(e)
break
if not print_result(conn, request):
ok = False
if tests > 1 and i == 0:
start2 = time.time()
if delay is not None:
time.sleep(delay)
else:
ok = run_check(conn)
stop = time.time()
if tests > 1:
extra = ", %.2f ms/request if we ignore the first one" % ((stop-start2)*1000/(tests-1))
else:
extra = ""
if not monitoring and (not check or verbose):
print("\nTotal elapsed time: %.2f seconds (%.2f ms/request %s)" % (stop-start, (stop-start)*1000/tests, extra))
if ifile is not None:
input.close()
conn.end()
ok = run_check(conn) and ok # need to run run_check first
stop = time.time()
if tests > 1:
extra = ", %.2f ms/request if we ignore the first one" % ((stop-start2)*1000/(tests-1))
else:
extra = ""
if not monitoring and (not check or verbose):
print("\nTotal elapsed time: %.2f seconds (%.2f ms/request %s)" % (stop-start, (stop-start)*1000/tests, extra))
if ifile is not None:
input.close()
conn.end()
if ok:
print('OK')
if not monitoring:
......
---
config:
timeout: 2
timeout: 4
markers:
- "dot: test specific to DoT"
- "doh: test specific to DoH"
- "monitoring: test using monitoring"
- "exception: test raising an exception"
- "check: test related to the compliance option --check"
- "forceIPv4: test using the option -4"
- "forceIPv6: test using the option -6"
tests:
- exe: './homer.py'
......@@ -56,8 +58,8 @@ tests:
- exe: './homer.py'
name: '--check of a correct DoH'
markers:
- 'doh'
- 'check'
- 'doh'
- 'check'
args:
- '--check'
- 'https://doh.bortzmeyer.fr/'
......@@ -69,8 +71,8 @@ tests:
- exe: './homer.py'
name: '--check of a broken DoH'
markers:
- 'doh'
- 'check'
- 'doh'
- 'check'
args:
- '--check'
- 'https://www.bortzmeyer.org/'
......@@ -82,8 +84,8 @@ tests:
- exe: './homer.py'
name: '--check of a DoH with HEAD unimplemented'
markers:
- 'doh'
- 'check'
- 'doh'
- 'check'
args:
- '--check'
- '--mandatory-level'
......@@ -97,8 +99,8 @@ tests:
- exe: './homer.py'
name: '--check of a correct DoT'
markers:
- 'dot'
- 'check'
- 'dot'
- 'check'
args:
- '--check'
- '--dot'
......@@ -108,6 +110,200 @@ tests:
stderr: ''
stdout: "OK\n"
- exe: './homer.py'
name: '[doh][check] Test that all the resolved IPs are tried, try a first IP'
markers:
- 'doh'
- 'check'
args:
- '-v'
- '--check'
- 'https://doh.bortzmeyer.fr'
- 'www.afnic.fr'
partstderr: "Connecting to hostname: 2001:41d0:302:2200::180"
- exe: './homer.py'
name: '[doh][check] Test that all the resolved IPs are tried, try another IP'
markers:
- 'doh'
- 'check'
args:
- '-v'
- '--check'
- 'https://doh.bortzmeyer.fr'
- 'curl.haxx.se'
partstderr: "Connecting to hostname: 193.70.85.11"
- exe: './homer.py'
name: '[dot][check] Test that all the resolved IPs are tried, try a first IP'
markers:
- 'dot'
- 'check'
args:
- '-v'
- '--check'
- '--dot'
- 'dot.bortzmeyer.fr'
- 'www.afnic.fr'
partstdout: "on 193.70.85.11 ...\nConnecting to ('193.70.85.11', 853) ..."
- exe: './homer.py'
name: '[doh][check] Test that all the resolved IPs are tried, try another IP'
markers:
- 'dot'
- 'check'
args:
- '-v'
- '--check'
- '--dot'
- 'dot.bortzmeyer.fr'
- 'www.afnic.fr'
partstdout: "on 2001:41d0:302:2200::180 ...\nConnecting to ('2001:41d0:302:2200::180', 853, 0, 0) ..."
- exe: './homer.py'
name: '[dot][check] Test all the IPs, force IPv4'
markers:
- 'dot'
- 'check'
- 'forceIPv4'
args:
- '-v'
- '-4'
- '--check'
- '--dot'
- 'dns.google'
- 'framagit.org'
partstdout: "on 8.8.8.8 ...\nConnecting to ('8.8.8.8', 853) ..."
- exe: './homer.py'
name: '[dot][check] Test all the IPs, force IPv4, check another IP'
markers:
- 'dot'
- 'check'
- 'forceIPv4'
args:
- '-v'
- '-4'
- '--check'
- '--dot'
- 'dns.google'
- 'framagit.org'
partstdout: "on 8.8.4.4 ...\nConnecting to ('8.8.4.4', 853) ..."
- exe: './homer.py'
name: '[dot][check] Test all the IPs, force IPv6'
markers:
- 'dot'
- 'check'
- 'forceIPv6'
args:
- '-v'
- '-6'
- '--check'
- '--dot'
- 'dns.google'
- 'framagit.org'
partstdout: "on 2001:4860:4860::8844 ...\nConnecting to ('2001:4860:4860::8844', 853, 0, 0) ..."
- exe: './homer.py'
name: '[dot][check] Test all the IPs, force IPv6, check another IP'
markers:
- 'dot'
- 'check'
- 'forceIPv6'
args:
- '-v'
- '-6'
- '--check'
- '--dot'
- 'dns.google'
- 'framagit.org'
partstdout: "on 2001:4860:4860::8888 ...\nConnecting to ('2001:4860:4860::8888', 853, 0, 0) ..."
- exe: './homer.py'
name: '[doh][check] Test all the IPs, force IPv4'
markers:
- 'doh'
- 'check'
- 'forceIPv4'
args:
- '-v'
- '-4'
- '--check'
- 'https://dns.google/dns-query'
- 'www.afnic.fr'
partstderr: "Connecting to hostname: 8.8.8.8"
- exe: './homer.py'
name: '[doh][check] Test all the IPs, force IPv4, check another IP'
markers:
- 'doh'
- 'check'
- 'forceIPv4'
args:
- '-v'
- '-4'
- '--check'
- 'https://dns.google/dns-query'
- 'curl.haxx.se'
partstderr: "Connecting to hostname: 8.8.4.4"
- exe: './homer.py'
name: '[doh][check] Test all the IPs, force IPv6'
markers:
- 'doh'
- 'check'
- 'forceIPv6'
args:
- '-v'
- '-6'
- '--check'
- 'https://dns.google/dns-query'
- 'www.afnic.fr'
partstderr: "Connecting to hostname: 2001:4860:4860::8888"
- exe: './homer.py'
name: '[doh][check] Test all the IPs, force IPv6, check another IP'
markers:
- 'doh'
- 'check'
- 'forceIPv6'
args:
- '-v'
- '-6'
- '--check'
- 'https://dns.google/dns-query'
- 'curl.haxx.se'
partstderr: "Connecting to hostname: 2001:4860:4860::8844"
- exe: './homer.py'
name: '[dot][check] Test all IPs on brok.sources.org'
timeout: 6
markers:
- 'dot'
- 'check'
args:
- '-k'
- '--check'
- '--dot'
- 'brok.sources.org'
- 'in'
partstdout: 'Could not connect to'
- exe: './homer.py'
name: '[dot][check] Test all IPs on brok.sources.org, get a KO'
timeout: 6
markers:
- 'dot'
- 'check'
args:
- '-k'
- '--check'
- '--dot'
- 'brok.sources.org'
- 'in'
partstdout: "KO\n"
###############################################################################
......@@ -116,6 +312,8 @@ tests:
markers:
- 'doh'
- 'exception'
- 'forceIPv4'
- 'forceIPv6'
args:
- '-4'
- '-6'
......@@ -130,6 +328,8 @@ tests:
markers:
- 'dot'
- 'exception'
- 'forceIPv4'
- 'forceIPv6'
args:
- '-4'
- '-6'
......@@ -171,6 +371,7 @@ tests:
markers:
- 'dot'
- 'exception'
- 'forceIPv6'
args:
- '-6'
- '--insecure'
......@@ -212,6 +413,7 @@ tests:
markers:
- 'dot'
- 'exception'
- 'forceIPv4'
args:
- '-4'
- '--insecure'
......@@ -240,6 +442,7 @@ tests:
name: '[dot] Force IPv6 on brok.sources.org'
markers:
- 'dot'
- 'forceIPv6'
timeout: 6
args:
- '-6'
......@@ -316,6 +519,7 @@ tests:
markers:
- 'dot'
- 'monitoring'
- 'forceIPv6'
args:
- '-6'
- '-H'
......@@ -349,6 +553,7 @@ tests:
markers:
- 'dot'
- 'monitoring'
- 'forceIPv4'
args:
- '-4'
- '-H'
......@@ -367,6 +572,8 @@ tests:
- 'dot'
- 'monitoring'
- 'exception'
- 'forceIPv4'
- 'forceIPv6'
args:
- '-4'
- '-6'
......@@ -386,6 +593,8 @@ tests:
- 'dot'
- 'monitoring'
- 'exception'
- 'forceIPv4'
- 'forceIPv6'
args:
- '-4'
- '-6'
......@@ -405,6 +614,7 @@ tests:
- 'dot'
- 'monitoring'
- 'exception'
- 'forceIPv6'
args:
- '-6'
- '-H'
......@@ -423,6 +633,7 @@ tests:
- 'dot'
- 'monitoring'
- 'exception'
- 'forceIPv4'
args:
- '-4'
- '-H'
......@@ -494,6 +705,7 @@ tests:
markers:
- 'doh'
- 'monitoring'
- 'forceIPv6'
args:
- '-6'
- '-H'
......@@ -527,6 +739,7 @@ tests:
markers:
- 'doh'
- 'monitoring'
- 'forceIPv4'
args:
- '-4'
- '-H'
......@@ -545,6 +758,8 @@ tests:
- 'doh'
- 'monitoring'
- 'exception'
- 'forceIPv4'
- 'forceIPv6'
args:
- '-4'
- '-6'
......@@ -564,6 +779,8 @@ tests:
- 'doh'
- 'monitoring'
- 'exception'
- 'forceIPv4'
- 'forceIPv6'
args:
- '-4'
- '-6'
......@@ -583,6 +800,7 @@ tests:
- 'doh'
- 'monitoring'
- 'exception'
- 'forceIPv6'
args:
- '-6'
- '-H'
......@@ -601,6 +819,7 @@ tests:
- 'doh'
- 'monitoring'
- 'exception'
- 'forceIPv4'
</