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

Merge branch 'option-debug' into 'master'

Option debug

Closes #25

See merge request bortzmeyer/homer!14
parents 2244deb3 203247a6
......@@ -45,6 +45,7 @@ Total elapsed time: 0.07 seconds (66.72 ms/request )
Possible options, besides `--dot`:
* --verbose or -v: Makes the program more talkative
* --debug or -d: Makes the program very talkative (sets verbose to true)
* --head or -e: (DoH) Uses only the HEAD HTTP method. Since the RFC
does not mention it, result is probably indefinite.
* --POST or -P: (DoH) Uses the POST HTTP method (default is GET)
......
......@@ -36,6 +36,7 @@ import signal
# Values that can be changed from the command line
dot = False # DoH by default
verbose = False
debug = False
insecure = False
post = False
head = False
......@@ -189,6 +190,12 @@ def validate_hostname(hostname, cert):
return True
return False
def dump_data(data, text="data"):
pref = ' ' * (len(text) - 4)
print(f'{text}: ', data)
print(pref, 'hex:', " ".join(format(c, '02x') for c in data))
print(pref, 'bin:', " ".join(format(c, '08b') for c in data))
def timeout_connection(signum, frame):
raise TimeoutConnectionError('Connection timeout')
......@@ -222,13 +229,15 @@ class Request:
class RequestDoT(Request):
def check_response(self):
def check_response(self, debug=False):
ok = self.ok
if not self.rcode:
self.ok = False
return False
if self.response.id != self.message.id:
self.response = "The ID in the answer does not match the one in the query"
if debug:
self.response += f'"(query id: {self.message.id}) (response id: {self.response.id})'
self.ok = False
return False
return self.ok
......@@ -241,7 +250,7 @@ class RequestDoH(Request):
self.post = False
self.head = False
def check_response(self):
def check_response(self, debug=False):
ok = self.ok
if self.rcode == 200:
if self.ctype != "application/dns-message":
......@@ -250,20 +259,29 @@ class RequestDoH(Request):
else:
if not self.head:
try:
self.response = dns.message.from_wire(self.response)
response = dns.message.from_wire(self.response)
except dns.message.TrailingJunk: # Not DNS. Should
# not happen for a content type
# application/dns-message but who knows?
self.response = "ERROR Not proper DNS data, trailing junk \"%s\"" % self.response
self.response = "ERROR Not proper DNS data, trailing junk"
if debug:
self.response += " \"%s\"" % response
ok = False
except dns.name.BadLabelType: # Not DNS.
self.response = "ERROR Not proper DNS data (wrong path in the URL?) \"%s\"" % self.response[:100]
self.response = "ERROR Not proper DNS data (wrong path in the URL?)"
if debug:
self.response += " \"%s\"" % response[:100]
ok = False
else:
self.response = response
else:
if self.response_size == 0:
self.response = "HEAD successful"
else:
self.response = "ERROR Body length is not null \"%s\"" % self.response[:100]
data = self.response
self.response = "ERROR Body length is not null"
if debug:
self.response += "\"%s\"" % data[:100]
ok = False
else:
ok = False
......@@ -277,7 +295,7 @@ class RequestDoH(Request):
class Connection:
def __init__(self, server, servername=None, connect=None, forceIPv4=False, forceIPv6=False,
dot=dot, verbose=verbose, insecure=insecure):
dot=dot, verbose=verbose, debug=debug, 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(server):
......@@ -293,6 +311,7 @@ class Connection:
self.check = self.server
self.dot = dot
self.verbose = verbose
self.debug = debug
self.insecure = insecure
self.connect_to = connect
......@@ -323,10 +342,10 @@ class Connection:
class ConnectionDoT(Connection):
def __init__(self, server, servername=None, connect=None, forceIPv4=False, forceIPv6=False,
verbose=verbose, insecure=insecure):
verbose=verbose, debug=debug, insecure=insecure):
Connection.__init__(self, server, servername=servername, connect=connect,
forceIPv4=forceIPv4, forceIPv6=forceIPv6, dot=True,
verbose=verbose, insecure=insecure)
verbose=verbose, debug=debug, insecure=insecure)
if connect is not None:
addr = connect
else:
......@@ -392,12 +411,12 @@ class ConnectionDoT(Connection):
# RFC 7858, section 4.2 and appendix A
self.cert = self.session.get_peer_certificate()
self.publickey = self.cert.get_pubkey()
if verbose or key is not None:
if debug or key is not None:
self.hasher.update(OpenSSL.crypto.dump_publickey(OpenSSL.crypto.FILETYPE_ASN1,
self.publickey))
self.digest = self.hasher.digest()
key_string = base64.standard_b64encode(self.digest).decode()
if verbose:
if debug:
print("Certificate #%x for \"%s\", delivered by \"%s\"" % \
(self.cert.get_serial_number(),
self.cert.get_subject().commonName,
......@@ -419,35 +438,39 @@ class ConnectionDoT(Connection):
self.session.shutdown()
self.session.close()
def send_data(self, data):
def send_data(self, data, dump=False):
if dump:
dump_data(data, 'data sent')
length = len(data)
self.session.send(length.to_bytes(2, byteorder='big') + data)
def receive_data(self, request):
def receive_data(self, request, dump=False):
buf = self.session.recv(2)
request.response_size = int.from_bytes(buf, byteorder='big')
buf = self.session.recv(request.response_size)
if dump:
dump_data(buf, 'data recv')
request.response = dns.message.from_wire(buf)
request.rcode = True
def send_and_receive(self, request):
self.send_data(request.data)
self.receive_data(request)
def send_and_receive(self, request, dump=False):
self.send_data(request.data, dump=dump)
self.receive_data(request, dump=dump)
def do_test(self, qname, qtype=rtype):
request = RequestDoT(qname, qtype, want_dnssec=dnssec, use_edns=edns)
request.to_wire()
self.send_and_receive(request)
request.check_response()
request.check_response(self.debug)
return request
class ConnectionDoH(Connection):
def __init__(self, server, servername=None, connect=None, forceIPv4=False, forceIPv6=False,
verbose=verbose, insecure=insecure):
verbose=verbose, debug=debug, insecure=insecure):
Connection.__init__(self, server, servername=servername, connect=connect,
forceIPv4=forceIPv4, forceIPv6=forceIPv6, dot=False,
verbose=verbose, insecure=insecure)
verbose=verbose, debug=debug, insecure=insecure)
self.url = server
self.connect = connect
......@@ -456,7 +479,7 @@ class ConnectionDoH(Connection):
# Does not work if pycurl was not compiled with nghttp2 (recent Debian
# packages are OK) https://github.com/pycurl/pycurl/issues/477
self.curl.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2)
if self.verbose:
if self.debug:
self.curl.setopt(pycurl.VERBOSE, True)
if self.insecure:
self.curl.setopt(pycurl.SSL_VERIFYPEER, False)
......@@ -536,7 +559,7 @@ class ConnectionDoH(Connection):
request.ctype = content_type
self.buffer.close()
def send_and_receive(self, request):
def send_and_receive(self, request, dump=False):
self.prepare(request)
self.perform()
self.receive(request)
......@@ -547,7 +570,7 @@ class ConnectionDoH(Connection):
request.post = post
request.to_wire()
self.send_and_receive(request)
request.check_response()
request.check_response(self.debug)
return request
......@@ -661,12 +684,16 @@ def run_check_default(connection):
ok = False
error(e)
break
request.check_response()
request.check_response(debug)
if not print_result(connection, request, prefix=test_name, display_err=False):
if mandatory >= mandatory_level:
print_result(connection, request, prefix=test_name, display_err=True)
ok = False
if verbose:
print()
break
if verbose:
print()
return ok
def run_check_mime(connection, accept="application/dns-message", content_type="application/dns-message"):
......@@ -674,6 +701,9 @@ def run_check_mime(connection, accept="application/dns-message", content_type="a
return True
ok = True
header = [f"Accept: {accept}", f"Content-type: {content_type}"]
if verbose:
test_name = f'Test mime: {", ".join(h for h in header)}'
print(test_name)
req_args = { 'qname': name, 'qtype': rtype, 'use_edns': edns, 'want_dnssec': dnssec }
request = create_request(**req_args)
connection.curl.setopt(pycurl.HTTPHEADER, header)
......@@ -682,12 +712,14 @@ def run_check_mime(connection, accept="application/dns-message", content_type="a
except CustomException as e:
ok = False
error(e)
request.check_response()
request.check_response(debug)
if not print_result(connection, request, prefix=f"Test Header {', '.join(header)}"):
ok = False
default = "application/dns-message"
default_header = [f"Accept: {default}", f"Content-type: {default}"]
connection.curl.setopt(pycurl.HTTPHEADER, default_header)
if verbose:
print()
return ok
def run_check_trunc(connection):
......@@ -703,7 +735,7 @@ def run_check_trunc(connection):
request.post = True
try:
# 8.8.8.8 replies FORMERR but most DoT servers violently shut down the connection (which is legal)
connection.send_and_receive(request)
connection.send_and_receive(request, dump=debug)
except CustomException as e:
ok = False
error(e)
......@@ -714,7 +746,7 @@ def run_check_trunc(connection):
# the RCODE set to FORMERR
# so response can not be parsed in this case
return ok
if request.check_response(): # FORMERR is expected
if request.check_response(debug): # FORMERR is expected
if dot:
ok = request.rcode == dns.rcode.FORMERR
else:
......@@ -725,6 +757,8 @@ def run_check_trunc(connection):
else: # a 400 response's status is acceptable
ok = (request.rcode >= 400 and request.rcode < 500)
print_result(connection, request, prefix=test_name, display_err=not ok)
if verbose:
print()
return ok
def run_check_additionals(connection):
......@@ -759,8 +793,8 @@ if not monitoring:
name = None
message = None
try:
optlist, args = getopt.getopt (sys.argv[1:], "hvPkeV:r:f:d:t46",
["help", "verbose", "dot", "head",
optlist, args = getopt.getopt (sys.argv[1:], "hvdPkeV:r:f:d:t46",
["help", "verbose", "debug", "dot", "head",
"insecure", "POST", "vhost=",
"dnssec", "noedns", "ecs", "repeat=", "file=", "delay=",
"key=", "nosni",
......@@ -774,6 +808,9 @@ if not monitoring:
dot = True
elif option == "--verbose" or option == "-v":
verbose = True
elif option == "--debug" or option == "-d":
debug = True
verbose = True
elif option == "--HEAD" or option == "--head" or option == "-e":
head = True
elif option == "--POST" or option == "--post" or option == "-P":
......@@ -960,11 +997,11 @@ for connectTo in ip_set:
try:
if dot:
conn = ConnectionDoT(url, servername=extracheck, connect=connectTo, verbose=verbose,
forceIPv4=forceIPv4, forceIPv6=forceIPv6,
debug=debug, forceIPv4=forceIPv4, forceIPv6=forceIPv6,
insecure=insecure)
else:
conn = ConnectionDoH(url, servername=extracheck, connect=connectTo, verbose=verbose,
forceIPv4=forceIPv4, forceIPv6=forceIPv6,
debug=debug, forceIPv4=forceIPv4, forceIPv6=forceIPv6,
insecure=insecure)
except TimeoutError:
error("timeout")
......
......@@ -144,7 +144,7 @@ tests:
- 'doh'
- 'check'
args:
- '-v'
- '-d'
- '--check'
- 'https://doh.bortzmeyer.fr'
- 'www.afnic.fr'
......@@ -156,7 +156,7 @@ tests:
- 'doh'
- 'check'
args:
- '-v'
- '-d'
- '--check'
- 'https://doh.bortzmeyer.fr'
- 'curl.haxx.se'
......@@ -255,7 +255,7 @@ tests:
- 'check'
- 'forceIPv4'
args:
- '-v'
- '-d'
- '-4'
- '--check'
- 'https://dns.google/dns-query'
......@@ -269,7 +269,7 @@ tests:
- 'check'
- 'forceIPv4'
args:
- '-v'
- '-d'
- '-4'
- '--check'
- 'https://dns.google/dns-query'
......@@ -283,7 +283,7 @@ tests:
- 'check'
- 'forceIPv6'
args:
- '-v'
- '-d'
- '-6'
- '--check'
- 'https://dns.google/dns-query'
......@@ -297,7 +297,7 @@ tests:
- 'check'
- 'forceIPv6'
args:
- '-v'
- '-d'
- '-6'
- '--check'
- 'https://dns.google/dns-query'
......
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