Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
DNS testing tools
Remoh
Commits
e9dc4d65
Commit
e9dc4d65
authored
Mar 24, 2020
by
Stephane Bortzmeyer
Browse files
Several levels of complicance for the --check option
parent
86ef3293
Changes
3
Hide whitespace changes
Inline
Side-by-side
README.md
View file @
e9dc4d65
...
...
@@ -65,8 +65,14 @@ The program stops after the first failed test.
OK
```
A future work would be to define different set of tests to test the
compliance of a DoT or DoH server.
Each test is marked with a level of compliance. There are three
levels, "legal" (compliant with the strict requirments of the RFCs),
"necessary" (in a typical setup) and "nicetohave". The default level
is "necessary" but you can change it with option
`--mandatory-check`
. For instance, sending a reply when the request
uses the HEAD method is "nicetohave" for a DoH server (the RFC does
not mandate it). The tests are always performed but are not fatal if
the choosen level is lower than the level of the test.
### Repetition of tests
...
...
@@ -234,7 +240,7 @@ future, to remove servers that do not validate with DNSSEC.)
*
`https://doh.libredns.gr/dns-query`
(
[
Documentation
](
https://libredns.gr/
)
; Also,
`https://doh.libredns.gr/ads`
is a lying resolver, blocking ads and trackers)
*
`https://dns.switch.ch/`
(
[
Documentation
](
https://www.switch.ch/security/info/public-dns/
)
)
*
`https://dns.switch.ch/
dns-query
`
(
[
Documentation
](
https://www.switch.ch/security/info/public-dns/
)
)
*
`https://nat64.tuxis.nl`
(
[
Documentation
](
https://www.tuxis.nl/blog/public-doh-dot-dns64-nat64-service-20191021/
)
;
NAT64, and no IPv4 address)
...
...
homer.py
View file @
e9dc4d65
...
...
@@ -33,8 +33,6 @@ import hashlib
import
base64
import
signal
check_additional
=
False
# Values that can be changed from the command line
dot
=
False
# DoH by default
verbose
=
False
...
...
@@ -52,6 +50,8 @@ forceIPv4 = False
forceIPv6
=
False
connectTo
=
None
check
=
False
mandatory_level
=
None
check_additional
=
True
# Monitoring plugin only:
host
=
None
path
=
None
...
...
@@ -70,6 +70,8 @@ STATE_DEPENDENT = 4
DOH_GET
=
0
DOH_POST
=
1
DOH_HEAD
=
2
# Is the test mandatory?
mandatory_levels
=
{
"legal"
:
30
,
"necessary"
:
20
,
"nicetohave"
:
10
}
TIMEOUT_CONN
=
2
...
...
@@ -482,7 +484,10 @@ class ConnectionDoH(Connection):
body
=
self
.
buffer
.
getvalue
()
body_size
=
len
(
body
)
http_code
=
self
.
curl
.
getinfo
(
pycurl
.
RESPONSE_CODE
)
content_type
=
self
.
curl
.
getinfo
(
pycurl
.
CONTENT_TYPE
)
try
:
content_type
=
self
.
curl
.
getinfo
(
pycurl
.
CONTENT_TYPE
)
except
TypeError
:
# This is the exception we get if there is no Content-Type: (for intance in rsponse to HEAD requests)
content_type
=
None
request
.
response
=
body
request
.
response_size
=
body_size
request
.
rcode
=
http_code
...
...
@@ -516,7 +521,7 @@ def get_next_domain(input_file):
(
name
,
rtype
)
=
line
.
split
()
return
name
,
rtype
def
print_result
(
connection
,
request
,
prefix
=
None
):
def
print_result
(
connection
,
request
,
prefix
=
None
,
display_err
=
True
):
ok
=
request
.
ok
dot
=
connection
.
dot
server
=
connection
.
server
...
...
@@ -535,16 +540,17 @@ def print_result(connection, request, prefix=None):
sys
.
exit
(
STATE_OK
)
else
:
if
not
monitoring
:
if
prefix
:
print
(
prefix
,
end
=
': '
,
file
=
sys
.
stderr
)
if
dot
:
print
(
"Error: %s"
%
msg
,
file
=
sys
.
stderr
)
else
:
try
:
msg
=
msg
.
decode
()
except
(
UnicodeDecodeError
,
AttributeError
):
pass
# Sometimes, msg can be binary, or Latin-1
print
(
"HTTP error %i: %s"
%
(
rcode
,
msg
),
file
=
sys
.
stderr
)
if
display_err
:
if
prefix
:
print
(
prefix
,
end
=
': '
,
file
=
sys
.
stderr
)
if
dot
:
print
(
"Error: %s"
%
msg
,
file
=
sys
.
stderr
)
else
:
try
:
msg
=
msg
.
decode
()
except
(
UnicodeDecodeError
,
AttributeError
):
pass
# Sometimes, msg can be binary, or Latin-1
print
(
"HTTP error %i: %s"
%
(
rcode
,
msg
),
file
=
sys
.
stderr
)
else
:
if
not
dot
:
print
(
"%s HTTP error - %i: %s"
%
(
server
,
rcode
,
msg
))
...
...
@@ -568,12 +574,21 @@ def create_request(dot=dot, trunc=False, **req_args):
def
create_requests_list
(
dot
=
dot
,
**
req_args
):
requests
=
[]
if
dot
:
requests
.
append
((
'Test 1'
,
create_request
(
dot
=
dot
,
**
req_args
)))
requests
.
append
((
'Test 2'
,
create_request
(
dot
=
dot
,
**
req_args
)))
requests
.
append
((
'Test 1'
,
create_request
(
dot
=
dot
,
**
req_args
),
mandatory_levels
[
"legal"
]))
requests
.
append
((
'Test 2'
,
create_request
(
dot
=
dot
,
**
req_args
),
mandatory_levels
[
"necessary"
]))
# RFC 7858,
# section 3.3, SHOULD accept several requests on one connection.
# TODO we miss the tests of pipelining and out-of-order.
else
:
requests
.
append
((
'Test GET'
,
create_request
(
**
req_args
),
DOH_GET
))
requests
.
append
((
'Test POST'
,
create_request
(
**
req_args
),
DOH_POST
))
requests
.
append
((
'Test HEAD'
,
create_request
(
**
req_args
),
DOH_HEAD
))
requests
.
append
((
'Test GET'
,
create_request
(
**
req_args
),
DOH_GET
,
mandatory_levels
[
"legal"
]))
# RFC 8484, section 4.1
requests
.
append
((
'Test POST'
,
create_request
(
**
req_args
),
DOH_POST
,
mandatory_levels
[
"legal"
]))
# RFC 8484, section 4.1
requests
.
append
((
'Test HEAD'
,
create_request
(
**
req_args
),
DOH_HEAD
,
mandatory_levels
[
"nicetohave"
]))
# HEAD
# method is not mentioned in RFC 8484 (see section 4.1), so
# just "nice to have".
return
requests
def
run_check_default
(
connection
):
...
...
@@ -582,9 +597,9 @@ def run_check_default(connection):
requests
=
create_requests_list
(
dot
=
dot
,
**
req_args
)
for
request_pack
in
requests
:
if
dot
:
test_name
,
request
=
request_pack
test_name
,
request
,
mandatory
=
request_pack
else
:
test_name
,
request
,
method
=
request_pack
test_name
,
request
,
method
,
mandatory
=
request_pack
if
verbose
:
print
(
test_name
)
if
not
dot
:
...
...
@@ -599,8 +614,10 @@ def run_check_default(connection):
error
(
e
)
break
request
.
check_response
()
if
not
print_result
(
connection
,
request
,
prefix
=
test_name
):
ok
=
False
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
break
return
ok
...
...
@@ -637,23 +654,26 @@ def run_check_trunc(connection):
request
=
create_request
(
trunc
=
True
,
**
req_args
)
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
)
except
CustomException
as
e
:
ok
=
False
error
(
e
)
except
OpenSSL
.
SSL
.
ZeroReturnError
:
# This is acceptable
return
ok
request
.
check_response
()
if
not
print_result
(
connection
,
request
,
prefix
=
test_name
):
ok
=
False
if
print_result
(
connection
,
request
,
prefix
=
test_name
,
display_err
=
False
):
# The test must fail, or returns FORMERR.
ok
=
(
request
.
rcode
==
dns
.
rcode
.
FORMERR
)
return
ok
def
run_check_additionals
(
connection
):
if
not
run_check_trunc
(
connection
):
return
False
if
not
run_check_mime
(
connection
,
accept
=
"text/html"
):
return
False
if
not
run_check_mime
(
connection
,
content_type
=
"text/html"
):
return
False
# The DoH server is right to reject these (Example: 'HTTP
# error 415: only Content-Type: application/dns-message is
# supported')
run_check_mime
(
connection
,
accept
=
"text/html"
)
run_check_mime
(
connection
,
content_type
=
"text/html"
)
return
True
def
run_check
(
connection
):
...
...
@@ -673,7 +693,8 @@ if not monitoring:
optlist
,
args
=
getopt
.
getopt
(
sys
.
argv
[
1
:],
"hvPkeV:r:f:d:t46"
,
[
"help"
,
"verbose"
,
"dot"
,
"head"
,
"insecure"
,
"POST"
,
"vhost="
,
"dnssec"
,
"noedns"
,
"repeat="
,
"file="
,
"delay="
,
"v4only"
,
"v6only"
,
"check"
])
"dnssec"
,
"noedns"
,
"repeat="
,
"file="
,
"delay="
,
"v4only"
,
"v6only"
,
"check"
,
"mandatory-level="
])
for
option
,
value
in
optlist
:
if
option
==
"--help"
or
option
==
"-h"
:
usage
()
...
...
@@ -710,6 +731,8 @@ if not monitoring:
forceIPv6
=
True
elif
option
==
"--check"
:
check
=
True
elif
option
==
"--mandatory-level"
:
mandatory_level
=
value
else
:
error
(
"Unknown option %s"
%
option
)
except
getopt
.
error
as
reason
:
...
...
@@ -723,6 +746,16 @@ if not monitoring:
if
dot
and
(
post
or
head
):
usage
(
"POST or HEAD makes non sense for DoT"
)
sys
.
exit
(
1
)
if
mandatory_level
is
not
None
and
\
mandatory_level
not
in
mandatory_levels
.
keys
():
usage
(
"Unknown mandatory level
\"
%s
\"
"
%
mandatory_level
)
sys
.
exit
(
1
)
if
mandatory_level
is
not
None
and
not
check
:
usage
(
"--mandatory-level only makes sense with --check"
)
sys
.
exit
(
1
)
if
mandatory_level
is
None
:
mandatory_level
=
"necessary"
mandatory_level
=
mandatory_levels
[
mandatory_level
]
if
ifile
is
None
and
(
len
(
args
)
!=
2
and
len
(
args
)
!=
3
):
usage
(
"Wrong number of arguments"
)
sys
.
exit
(
1
)
...
...
tests.yaml
View file @
e9dc4d65
...
...
@@ -6,6 +6,7 @@ config:
-
"
doh:
test
specific
to
DoH"
-
"
monitoring:
test
using
monitoring"
-
"
exception:
test
raising
an
exception"
-
"
check:
test
related
to
the
compliance
option
--check"
tests
:
-
exe
:
'
./homer.py'
...
...
@@ -50,6 +51,64 @@ tests:
partstderr
:
'
not
a
name
or'
stdout
:
'
'
###############################################################################
-
exe
:
'
./homer.py'
name
:
'
--check
of
a
correct
DoH'
markers
:
-
'
doh'
-
'
check'
args
:
-
'
--check'
-
'
https://doh.bortzmeyer.fr/'
-
'
ressources-pedagogiques.org'
retcode
:
0
stderr
:
'
'
stdout
:
"
OK
\n
"
-
exe
:
'
./homer.py'
name
:
'
--check
of
a
broken
DoH'
markers
:
-
'
doh'
-
'
check'
args
:
-
'
--check'
-
'
https://www.bortzmeyer.org/'
-
'
ressources-pedagogiques.org'
retcode
:
1
stderr
:
'
'
stdout
:
"
KO
\n
"
-
exe
:
'
./homer.py'
name
:
'
--check
of
a
DoH
with
HEAD
unimplemented'
markers
:
-
'
doh'
-
'
check'
args
:
-
'
--check'
-
'
--mandatory-level'
-
'
nicetohave'
-
'
https://doh.42l.fr/dns-query'
-
'
ressources-pedagogiques.org'
retcode
:
1
stderr
:
"
Test
HEAD:
HTTP
error
405:
[No
details]
\n
"
stdout
:
"
KO
\n
"
-
exe
:
'
./homer.py'
name
:
'
--check
of
a
correct
DoT'
markers
:
-
'
dot'
-
'
check'
args
:
-
'
--check'
-
'
--dot'
-
'
dot.bortzmeyer.fr'
-
'
ressources-pedagogiques.org'
retcode
:
0
stderr
:
'
'
stdout
:
"
OK
\n
"
###############################################################################
-
exe
:
'
./homer.py'
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment