Commit 7859e494 authored by Alexandre's avatar Alexandre
Browse files

Merge branch 'master' into pipelining

parents 2f4ade58 63d193fa
...@@ -164,14 +164,13 @@ and [CURLINFO_PRETRANSFER_TIME](https://curl.haxx.se/libcurl/c/curl_easy_getinfo ...@@ -164,14 +164,13 @@ and [CURLINFO_PRETRANSFER_TIME](https://curl.haxx.se/libcurl/c/curl_easy_getinfo
``` ```
% ./homer.py --multistreams --file input_file --repeat 5 --no-display-results --time https://doh.powerdns.org % ./homer.py --multistreams --file input_file --repeat 5 --no-display-results --time https://doh.powerdns.org
0 36.165 ms 44.773 ms 8.608 ms 0 (200) 41.995 ms 51.409 ms 9.414 ms
1 0.142 ms 8.580 ms 8.438 ms 1 (200) 0.156 ms 8.648 ms 8.492 ms
3 0.095 ms 9.223 ms 9.128 ms 2 (200) 0.121 ms 8.494 ms 8.373 ms
2 0.103 ms 10.282 ms 10.179 ms 3 (200) 0.120 ms 11.185 ms 11.065 ms
4 0.104 ms 10.068 ms 9.964 ms 4 (200) 0.103 ms 11.922 ms 11.819 ms
Total elapsed time: 0.07 seconds (9.26 ms/request) Total elapsed time: 0.07 seconds (9.83 ms/request)
OK
``` ```
......
...@@ -106,7 +106,51 @@ def error(msg=None, exit=True): ...@@ -106,7 +106,51 @@ def error(msg=None, exit=True):
def usage(msg=None): def usage(msg=None):
if msg: if msg:
print(msg,file=sys.stderr) print(msg,file=sys.stderr)
print("Usage: %s [--dot] url-or-servername domain-name [DNS type]" % sys.argv[0], file=sys.stderr) print("Usage: %s [options] url-or-servername [domain-name [DNS type]]" % sys.argv[0], file=sys.stderr)
print("""Options
-t --dot Use DoT (by default use DoH)
-P --post --POST Use HTTP POST method for all transfers (DoH only)
-e --head --HEAD Use HTTP HEAD method for all transfers (DoH only)
-r --repeat <N> Perform N times the query. If used with -f, read up to
<N> rows of the <file>.
-d --delay <T> Time to wait in seconds between each synchronous
request (only with --repeat)
-f --file <file> Read domain names from <file>, one per row with an
optional DNS type
--check Perform a set of predefined tests.
--mandatory-level <level>
Define the <level> of test to perform (only with
--check)
Available <level> : legal, necessary, nicetohave
--multistreams Use HTTP/2 streams, needs an input file with -f
(DoH only)
--sync Process received queries synchronously (only with
--multistreams)
--no-display-results
Disable output of DNS response (only with
--multistreams)
--time Display the time elapsed for the query (only with
--multistreams)
--dnssec Request DNSSEC data (signatures)
--noedns Disable EDNS, default is to indicate EDNS support
--ecs Send ECS to authoritative servers, default is to
refuse it
--key <key> Authenticate a DoT resolver with its public <key> in
base64 (DoT only)
--nosni Do not perform SNI (DoT only)
-V --vhost <vhost> Use a specific virtual host
-k --insecure Do not check the certificate
-4 --v4only Force IPv4 resolution of url-or-servername
-6 --v6only Force IPv6 resolution of url-or-servername
-v --verbose Make the program more talkative
--debug Make the program even more talkative than -v
-h --help Print this message
url-or-servername The URL or domain name of the DoT/DoH server
domain-name The domain name to resolve, not required if -f is
provided
DNS type The DNS record type to resolve, default AAAA
""", file=sys.stderr)
print("See the README.md for more details.", file=sys.stderr) print("See the README.md for more details.", file=sys.stderr)
def is_valid_hostname(name): def is_valid_hostname(name):
...@@ -244,6 +288,7 @@ class Request: ...@@ -244,6 +288,7 @@ class Request:
use_edns=use_edns, want_dnssec=want_dnssec, options=options) use_edns=use_edns, want_dnssec=want_dnssec, options=options)
self.message.flags |= dns.flags.AD # Ask for validation self.message.flags |= dns.flags.AD # Ask for validation
self.ok = True self.ok = True
self.i = 0 # request's number on the connection (default to the first)
def trunc_data(self): def trunc_data(self):
self.data = self.message.to_wire() self.data = self.message.to_wire()
...@@ -677,10 +722,11 @@ class ConnectionDoH(Connection): ...@@ -677,10 +722,11 @@ class ConnectionDoH(Connection):
self.receive(handle) self.receive(handle)
handle.request.check_response() handle.request.check_response()
if show_time: if show_time:
print(f'{handle.request.i:3d}', end=' ') self.print_time(handle)
print(f'{handle.pretime * 1000:8.3f} ms', end=' ') try:
print(f'{handle.time * 1000:8.3f} ms', end=' ') self.finished['http'][handle.request.rcode] += 1
print(f'{(handle.time - handle.pretime) * 1000:8.3f} ms') except KeyError:
self.finished['http'][handle.request.rcode] = 1
if display_results: if display_results:
print("Return code %s (%.2f ms):" % (handle.request.rcode, print("Return code %s (%.2f ms):" % (handle.request.rcode,
(handle.time - handle.pretime) * 1000)) (handle.time - handle.pretime) * 1000))
...@@ -692,6 +738,13 @@ class ConnectionDoH(Connection): ...@@ -692,6 +738,13 @@ class ConnectionDoH(Connection):
for handle in self.all_handles: for handle in self.all_handles:
self.read_result_handle(handle) self.read_result_handle(handle)
def print_time(self, handle):
print(f'{handle.request.i:3d}', end=' ')
print(f'({handle.request.rcode})', end=' ')
print(f'{handle.pretime * 1000:8.3f} ms', end=' ')
print(f'{handle.time * 1000:8.3f} ms', end=' ')
print(f'{(handle.time - handle.pretime) * 1000:8.3f} ms')
def do_test(self, request, synchronous=True): def do_test(self, request, synchronous=True):
if synchronous: if synchronous:
handle = self.curl_handle handle = self.curl_handle
...@@ -727,6 +780,8 @@ def print_result(connection, request, prefix=None, display_err=True): ...@@ -727,6 +780,8 @@ def print_result(connection, request, prefix=None, display_err=True):
size = request.response_size size = request.response_size
if (dot and rcode) or (not dot and rcode == 200): if (dot and rcode) or (not dot and rcode == 200):
if not monitoring: if not monitoring:
if not dot and show_time:
connection.print_time(connection.curl_handle)
if display_results and (not check or verbose): if display_results and (not check or verbose):
print(msg) print(msg)
else: else:
...@@ -938,9 +993,9 @@ if not monitoring: ...@@ -938,9 +993,9 @@ if not monitoring:
try: try:
optlist, args = getopt.getopt (sys.argv[1:], "hvPkeV:r:f:d:t46", optlist, args = getopt.getopt (sys.argv[1:], "hvPkeV:r:f:d:t46",
["help", "verbose", "debug", "dot", ["help", "verbose", "debug", "dot",
"head", "POST", "insecure", "key=", "head", "HEAD", "post", "POST",
"vhost=", "multistreams", "insecure", "vhost=", "multistreams",
"pipelining", "max-in-flight=", "pipelining", "max-in-flight=", "key=",
"dnssec", "noedns", "ecs", "nosni", "dnssec", "noedns", "ecs", "nosni",
"sync", "no-display-results", "time", "sync", "no-display-results", "time",
"file=", "repeat=", "delay=", "file=", "repeat=", "delay=",
...@@ -1017,9 +1072,11 @@ if not monitoring: ...@@ -1017,9 +1072,11 @@ if not monitoring:
mandatory_level = value mandatory_level = value
else: else:
error("Unknown option %s" % option) error("Unknown option %s" % option)
except getopt.error as reason: except (getopt.error, ValueError) as reason:
usage(reason) usage(reason)
sys.exit(1) sys.exit(1)
if delay is not None and multistreams:
error("--delay makes no sense with multistreams")
if tests <= 1 and delay is not None: if tests <= 1 and delay is not None:
error("--delay makes no sense if there is no repetition") error("--delay makes no sense if there is no repetition")
if not dot and pipelining: if not dot and pipelining:
...@@ -1046,8 +1103,8 @@ if not monitoring: ...@@ -1046,8 +1103,8 @@ if not monitoring:
if sync and not multistreams: if sync and not multistreams:
usage("--sync cannot be used without --multistreams") usage("--sync cannot be used without --multistreams")
sys.exit(1) sys.exit(1)
if show_time and not multistreams: if show_time and dot:
usage("--time cannot be used without --multistreams") usage("--time cannot be used with --dot")
sys.exit(1) sys.exit(1)
if not edns and not no_ecs: if not edns and not no_ecs:
usage("ECS requires EDNS") usage("ECS requires EDNS")
...@@ -1124,9 +1181,6 @@ else: # Monitoring plugin ...@@ -1124,9 +1181,6 @@ else: # Monitoring plugin
if dot and (post or head): if dot and (post or head):
print("POST or HEAD makes no sense for DoT") print("POST or HEAD makes no sense for DoT")
sys.exit(STATE_UNKNOWN) sys.exit(STATE_UNKNOWN)
if dot and multistreams:
print("Multi-streams makes no sense for DoT")
sys.exit(STATE_UNKNOWN)
if dot and path: if dot and path:
print("URL path makes no sense for DoT") print("URL path makes no sense for DoT")
sys.exit(STATE_UNKNOWN) sys.exit(STATE_UNKNOWN)
...@@ -1208,6 +1262,8 @@ for connectTo in ip_set: ...@@ -1208,6 +1262,8 @@ for connectTo in ip_set:
if ifile is not None: if ifile is not None:
input = open(ifile) input = open(ifile)
if not check: if not check:
if multistreams:
conn.finished = { 'http': {} }
for i in range (0, tests): for i in range (0, tests):
if tests > 1 and (verbose or display_results): if tests > 1 and (verbose or display_results):
print("\nTest %i" % i) print("\nTest %i" % i)
...@@ -1281,18 +1337,17 @@ for connectTo in ip_set: ...@@ -1281,18 +1337,17 @@ for connectTo in ip_set:
else: else:
time_per_request = time_tot / tests * 1000 time_per_request = time_tot / tests * 1000
print("\nTotal elapsed time: %.2f seconds (%.2f ms/request%s)" % (time_tot, time_per_request, extra)) print("\nTotal elapsed time: %.2f seconds (%.2f ms/request%s)" % (time_tot, time_per_request, extra))
if multistreams and verbose:
for rcode, n in conn.finished['http'].items():
print('HTTP', rcode, ':', n, f'({n / tests * 100:0.2f}%)')
if ifile is not None: if ifile is not None:
input.close() input.close()
conn.end() conn.end()
if ok: if ok:
print('OK') if check:
if not monitoring: print('OK')
sys.exit(0) sys.exit(0)
else:
sys.exit(STATE_OK)
else: else:
print('KO') print('KO')
if not monitoring: sys.exit(1)
sys.exit(1)
else:
sys.exit(STATE_CRITICAL)
...@@ -19,14 +19,14 @@ tests: ...@@ -19,14 +19,14 @@ tests:
args: args:
- '-h' - '-h'
retcode: 0 retcode: 0
partstderr: 'url-or-servername domain-name [DNS type]' partstderr: 'url-or-servername [domain-name [DNS type]]'
stdout: '' stdout: ''
- exe: './homer.py' - exe: './homer.py'
args: args:
- '--zzz' - '--zzz'
retcode: 1 retcode: 1
partstderr: 'option --zzz not recognized' partstderr: 'url-or-servername [domain-name [DNS type]]'
stdout: '' stdout: ''
- exe: './homer.py' - exe: './homer.py'
...@@ -132,7 +132,7 @@ tests: ...@@ -132,7 +132,7 @@ tests:
- 'check' - 'check'
- 'fail' - 'fail'
- 'slow' - 'slow'
timeout: 10 timeout: 12
args: args:
- '--check' - '--check'
- 'https://doh.42l.fr/dns-query' - 'https://doh.42l.fr/dns-query'
...@@ -991,7 +991,19 @@ tests: ...@@ -991,7 +991,19 @@ tests:
stdout: '' stdout: ''
- exe: './homer.py' - exe: './homer.py'
name: '[doh] POST' name: '[doh] POST --post'
markers:
- 'doh'
args:
- '--post'
- 'https://doh.bortzmeyer.fr'
- 'framagit.org'
retcode: 0
stderr: ''
partstdout: '2a01:4f8:'
- exe: './homer.py'
name: '[doh] POST --POST'
markers: markers:
- 'doh' - 'doh'
args: args:
...@@ -1003,7 +1015,19 @@ tests: ...@@ -1003,7 +1015,19 @@ tests:
partstdout: '2a01:4f8:' partstdout: '2a01:4f8:'
- exe: './homer.py' - exe: './homer.py'
name: '[doh] HEAD' name: '[doh] POST -P'
markers:
- 'doh'
args:
- '-P'
- 'https://doh.bortzmeyer.fr'
- 'framagit.org'
retcode: 0
stderr: ''
partstdout: '2a01:4f8:'
- exe: './homer.py'
name: '[doh] HEAD --head'
markers: markers:
- 'doh' - 'doh'
args: args:
...@@ -1014,6 +1038,30 @@ tests: ...@@ -1014,6 +1038,30 @@ tests:
stderr: '' stderr: ''
partstdout: 'HEAD' partstdout: 'HEAD'
- exe: './homer.py'
name: '[doh] HEAD --HEAD'
markers:
- 'doh'
args:
- '--HEAD'
- 'https://doh.bortzmeyer.fr'
- 'framagit.org'
retcode: 0
stderr: ''
partstdout: 'HEAD'
- exe: './homer.py'
name: '[doh] HEAD -e'
markers:
- 'doh'
args:
- '-e'
- 'https://doh.bortzmeyer.fr'
- 'framagit.org'
retcode: 0
stderr: ''
partstdout: 'HEAD'
- exe : './homer.py' - exe : './homer.py'
name: '[doh] Multistreams with DoT (error)' name: '[doh] Multistreams with DoT (error)'
markers: markers:
......
Supports Markdown
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