Commit c527f2f4 authored by Alexandre's avatar Alexandre
Browse files

Merge branch 'master' into streams

parents 657a5eb5 40322ce6
......@@ -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: 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)
......@@ -124,7 +125,8 @@ one):
```
When repeating tests, you can add a delay between tests, with `--delay
N`, where N is the (possibly fractional) number of seconds to wait.
N` or `-d N`, where N is the (possibly fractional) number of seconds
to wait.
### Mulitstreams
......
......@@ -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
......@@ -212,6 +213,12 @@ def check_ip_address(addr, dot=dot):
family = 0
return (family, repraddress)
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')
......@@ -245,13 +252,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
......@@ -264,7 +273,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":
......@@ -273,20 +282,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
......@@ -300,7 +318,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):
......@@ -316,6 +334,7 @@ class Connection:
self.check = self.server
self.dot = dot
self.verbose = verbose
self.debug = debug
self.insecure = insecure
self.forceIPv4 = forceIPv4
self.forceIPv6 = forceIPv6
......@@ -331,10 +350,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:
......@@ -400,12 +419,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,
......@@ -427,24 +446,28 @@ 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, request, synchronous=True):
self.send_and_receive(request)
request.check_response()
request.check_response(self.debug)
def create_handle(connection):
......@@ -479,7 +502,7 @@ def create_handle(connection):
# Does not work if pycurl was not compiled with nghttp2 (recent Debian
# packages are OK) https://github.com/pycurl/pycurl/issues/477
handle.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2)
if connection.verbose:
if connection.debug:
handle.setopt(pycurl.VERBOSE, True)
if connection.insecure:
handle.setopt(pycurl.SSL_VERIFYPEER, False)
......@@ -500,10 +523,10 @@ def create_handle(connection):
class ConnectionDoH(Connection):
def __init__(self, server, servername=None, connect=None, forceIPv4=False, forceIPv6=False,
multistreams=False, verbose=verbose, insecure=insecure):
multistreams=False, 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
self.multistreams = multistreams
......@@ -579,7 +602,7 @@ class ConnectionDoH(Connection):
request.ctype = content_type
handle.buffer.close()
def send_and_receive(self, handle):
def send_and_receive(self, handle, dump=False):
self.send(handle)
self.receive(handle)
......@@ -611,7 +634,7 @@ class ConnectionDoH(Connection):
handle.prepare(handle, self, request)
if synchronous:
self.send_and_receive(handle)
request.check_response()
request.check_response(self.debug)
else:
self.multi.add_handle(handle)
......@@ -731,12 +754,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"):
......@@ -744,6 +771,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)
handle = connection.curl_handle
......@@ -754,12 +784,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}"]
handle.setopt(pycurl.HTTPHEADER, default_header)
if verbose:
print()
return ok
def run_check_trunc(connection):
......@@ -779,7 +811,7 @@ def run_check_trunc(connection):
bundle = handle
try:
# 8.8.8.8 replies FORMERR but most DoT servers violently shut down the connection (which is legal)
connection.send_and_receive(bundle)
connection.send_and_receive(bundle, dump=debug)
except CustomException as e:
ok = False
error(e)
......@@ -790,7 +822,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:
......@@ -801,6 +833,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):
......@@ -836,7 +870,7 @@ if not monitoring:
message = None
try:
optlist, args = getopt.getopt (sys.argv[1:], "hvPkeV:r:f:d:t46",
["help", "verbose", "dot", "head",
["help", "verbose", "debug", "dot", "head",
"insecure", "POST", "vhost=", "multistreams",
"sync", "no-display-results", "time",
"dnssec", "noedns", "ecs", "repeat=", "file=", "delay=",
......@@ -851,6 +885,9 @@ if not monitoring:
dot = True
elif option == "--verbose" or option == "-v":
verbose = True
elif option == "--debug":
debug = True
verbose = True
elif option == "--HEAD" or option == "--head" or option == "-e":
head = True
elif option == "--POST" or option == "--post" or option == "-P":
......@@ -1066,11 +1103,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,
multistreams=multistreams, insecure=insecure)
except TimeoutError:
error("timeout")
......
......@@ -145,7 +145,7 @@ tests:
- 'doh'
- 'check'
args:
- '-v'
- '--debug'
- '--check'
- 'https://doh.bortzmeyer.fr'
- 'www.afnic.fr'
......@@ -157,7 +157,7 @@ tests:
- 'doh'
- 'check'
args:
- '-v'
- '--debug'
- '--check'
- 'https://doh.bortzmeyer.fr'
- 'curl.haxx.se'
......@@ -256,7 +256,7 @@ tests:
- 'check'
- 'forceIPv4'
args:
- '-v'
- '--debug'
- '-4'
- '--check'
- 'https://dns.google/dns-query'
......@@ -270,7 +270,7 @@ tests:
- 'check'
- 'forceIPv4'
args:
- '-v'
- '--debug'
- '-4'
- '--check'
- 'https://dns.google/dns-query'
......@@ -284,7 +284,7 @@ tests:
- 'check'
- 'forceIPv6'
args:
- '-v'
- '--debug'
- '-6'
- '--check'
- 'https://dns.google/dns-query'
......@@ -298,7 +298,7 @@ tests:
- 'check'
- 'forceIPv6'
args:
- '-v'
- '--debug'
- '-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