Commit 53f34611 authored by Alexandre's avatar Alexandre
Browse files

Merge branch 'exceptions' into 'master'

Exceptions

* fix regression inserted with 8cba1a31
* [DoT] raise exceptions instead of printing errors
* improve test coverage

See merge request bortzmeyer/homer!25
parents 58252f6f bfcfd787
......@@ -219,7 +219,16 @@ def run_check_default(connection):
handle = connection.curl_handle
handle.prepare(handle, connection, request)
bundle = handle
connection.send_and_receive(bundle)
try:
connection.send_and_receive(bundle)
except (homer.ConnectionException, homer.DOHException) as e:
# GET and POST are mandatory and therefore if an error is caught
# here, print it and exit
if method == homer.DOH_GET or method == homer.DOH_POST:
error(e)
else:
print(e, file=sys.stderr)
return False
request.check_response(connection.debug)
if not print_result(connection, request, prefix=test_name, display_err=False):
if level >= opts.mandatory_level:
......@@ -248,7 +257,11 @@ def run_check_mime(connection, accept="application/dns-message", content_type="a
handle = connection.curl_handle
handle.setopt(pycurl.HTTPHEADER, header)
handle.prepare(handle, connection, request)
connection.send_and_receive(handle)
try:
connection.send_and_receive(handle)
except (homer.ConnectionException, homer.DOHException) as e:
print(e, file=sys.stderr)
return False
request.check_response(connection.debug)
if not print_result(connection, request, prefix=f"Test Header {', '.join(header)}"):
ok = False
......@@ -288,6 +301,9 @@ def run_check_trunc(connection):
# the RCODE set to FORMERR
# so response can not be parsed in this case
return ok
except homer.DOHException as e:
print(e, file=sys.stderr)
return False
if request.check_response(connection.debug): # FORMERR is expected
if connection.dot:
ok = request.rcode == dns.rcode.FORMERR
......@@ -565,7 +581,7 @@ def run_default(name, connection, opts):
if not opts.pipelining:
try:
connection.do_test(request, synchronous = not opts.multistreams)
except OpenSSL.SSL.Error as e:
except (OpenSSL.SSL.Error, homer.DOHException) as e:
ok = False
error(e)
break
......@@ -688,7 +704,16 @@ for ip in ip_set:
error("\"%s\" not a name or an IP address" % url)
except socket.gaierror:
error("Could not resolve \"%s\"" % url)
except homer.ConnectionException as e:
except homer.ConnectionDOTException as e:
if not monitoring:
print(e, file=sys.stderr)
err = "Could not connect to \"%s\"" % url
if opts.connectTo is not None:
err += " on %s" % opts.connectTo
error(err)
else:
error(e)
except (homer.ConnectionException, homer.DOHException) as e:
error(e)
if conn.dot and not conn.success:
ok = False
......
import sys
import io
import socket
import signal
......@@ -110,24 +111,35 @@ class ConnectionDOT(Connection):
signal.signal(signal.SIGALRM, homer.exceptions.timeout_connection)
# contains a set of tuples ('ip address', 'error message')
errors = set()
i = 0
for addrinfo in addrinfo_set:
if self.establish_session(addrinfo[0], addrinfo[1]):
# catch the raised exceptions and store them in the error set
# if that the last element of the loop raises an exception
# it will also be catched, but in this case we are sure we can not
# establish a connection therefore we raise an exception containing
# a string with all the errors
try:
self.establish_session(addrinfo[0], addrinfo[1])
except homer.ConnectionDOTException as e:
errors.add((addrinfo[0][0], str(e)))
if self.verbose and self.connect_to is None:
print(e, file=sys.stderr)
# we tried all the resolved IPs
if i == (len(addrinfo_set) - 1):
if self.verbose and self.connect_to is None:
print("No other IP address")
# join all the errors into a single string
err = ', '.join( "%s: %s" % (e[0], e[1]) for e in errors)
raise homer.ConnectionDOTException(err)
if self.verbose and self.connect_to is None:
print("Could not connect to %s" % addrinfo[0][0])
print("Trying another IP address")
else:
self.success = True
break
if self.verbose and self.connect_to is None:
print("Could not connect to %s" % addrinfo[0][0])
print("Trying another IP address")
# we could not establish a connection
if not self.success:
# we tried all the resolved IPs
if self.verbose and self.connect_to is None:
print("No other IP address")
error = "Could not connect to \"%s\"" % self.server
if self.connect_to is not None:
error += " on %s" % self.connect_to
raise homer.ConnectionDOTException(error)
i += 1
def establish_session(self, addr, sock_family):
"""Return True if a TLS session is established."""
......@@ -162,26 +174,16 @@ class ConnectionDOT(Connection):
self.session.connect((addr))
self.session.do_handshake()
except homer.exceptions.TimeoutConnectionError:
if self.verbose:
print("Timeout")
return False
raise homer.ConnectionDOTException("Timeout")
except OSError:
if self.verbose:
print("Cannot connect")
return False
raise homer.ConnectionDOTException("Cannot connect")
except OpenSSL.SSL.SysCallError as e:
if self.verbose:
print("OpenSSL error: %s" % e.args[1], file=sys.stderr)
return False
raise homer.ConnectionDOTException("OpenSSL error: %s" % e.args[1])
except OpenSSL.SSL.ZeroReturnError:
# see #18
if self.verbose:
print("Error: The SSL connection has been closed (try with --nosni to avoid sending SNI ?)", file=sys.stderr)
return False
raise homer.ConnectionDOTException("Error: The SSL connection has been closed (try with --nosni to avoid sending SNI ?)")
except OpenSSL.SSL.Error as e:
if self.verbose:
print("OpenSSL error: %s" % ', '.join(err[0][2] for err in e.args), file=sys.stderr)
return False
raise homer.ConnectionDOTException("OpenSSL error: %s" % ', '.join(err[0][2] for err in e.args))
# RFC 7858, section 4.2 and appendix A
self.cert = self.session.get_peer_certificate()
......@@ -201,12 +203,10 @@ class ConnectionDOT(Connection):
if self.key is None:
valid = homer.validate_hostname(self.check_name_cert, self.cert)
if not valid:
error("Certificate error: \"%s\" is not in the certificate" % (self.check_name_cert), exit=False)
return False
raise homer.ConnectionDOTException("Certificate error: \"%s\" is not in the certificate" % (self.check_name_cert))
else:
if key_string != self.key:
error("Key error: expected \"%s\", got \"%s\"" % (self.key, key_string), exit=False)
return False
raise homer.ConnectionDOTException("Key error: expected \"%s\", got \"%s\"" % (self.key, key_string))
# restore the timer
signal.alarm(0)
......
......@@ -868,6 +868,20 @@ tests:
stderr: ''
partstdout: 'Cannot find'
- exe: './check_dot'
name: '[check_dot] Certificate error'
markers:
- 'dot'
- 'monitoring'
- 'exception'
args:
- '-H'
- 'doh.bortzmeyer.fr'
- '-n'
- 'dnsforum.bj'
retcode: 2
partstdout: 'certificate'
################################################################################
......@@ -1054,6 +1068,20 @@ tests:
stderr: ''
partstdout: 'Cannot find'
- exe: './check_doh'
name: '[check_doh] Certificate error'
markers:
- 'doh'
- 'monitoring'
- 'exception'
args:
- '-H'
- 'dot.bortzmeyer.fr'
- '-n'
- 'dnsforum.bj'
retcode: 2
partstdout: 'certificate'
################################################################################
- exe: './homer.py'
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment