Commit b155db37 authored by Stephane Bortzmeyer's avatar Stephane Bortzmeyer
Browse files

Complete documentation. Closes #5

parent 91f5044a
# Homer
Homer is a DoH (DNS-over-HTTPS) client. Its main purpose is to test
DoH resolvers.
Homer is a DoH (DNS-over-HTTPS) and DoT (DNS-over-TLS) client. Its
main purpose is to test DoH and DoT resolvers.
It is currently quite experimental.
## Usage
Two mandatory arguments, the URL of the DoH server, and a domain name
to query.
Two mandatory arguments, the URL of the DoH server (or name/address of
the DoT resolver), and a domain name to query. By default, Homer uses
DoH. Also by defaut, the type of data is AAAA (IP address). You can
add a third argument to use another type, as in the second example
below.
```
% ./homer.py https://doh.powerdns.org/ framagit.org
Test 0
% homer https://doh.powerdns.org/ framagit.org
id 0
opcode QUERY
rcode NOERROR
......@@ -24,34 +26,200 @@ framagit.org. 10800 IN AAAA 2a01:4f8:200:1302::42
;AUTHORITY
;ADDITIONAL
Total elapsed time: 0.40 seconds (402.28 ms/request)
% homer --dot 9.9.9.9 cocca.fr A
id 42545
opcode QUERY
rcode NOERROR
flags QR RD RA
;QUESTION
cocca.fr. IN A
;ANSWER
cocca.fr. 43200 IN A 185.17.236.69
;AUTHORITY
;ADDITIONAL
Total elapsed time: 0.07 seconds (66.72 ms/request )
```
TODO other examples with other options and other resolvers (see
https://www.digitale-gesellschaft.ch/dns/
https://ffmuc.net/wiki/doku.php?id=knb:dohdot_en and the resolvers
listed later
Possible options, besides `--dot`:
* --verbose or -v: Makes the program more talkative
* --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)
* --insecure or -k: Does not check the certificate
### Repetition of tests
You can repeat the tests several times, for instance for performance
measurements. This is done with option `--repeat N` where N is the
number of repetitions.
```
% homer --repeat 3 https://doh.bortzmeyer.fr ça.fr SOA
Test 0
...
Test 1
...
Test 2
Total elapsed time: 0.10 seconds (33.56 ms/request , 7.88 ms/request if we ignore the first one)
```
Homer reuses the same connection for all requests, both for DoH and
DoT, which explains why the first request is often longer.
Repetition is often combined with the use of an external file, where
Homer reads the domain names (and types) to query. Here is a sample
file:
```
truc.fr
chose.fr
machin.fr
trucmachin.fr NS
```
Assuming the file is named `list.txt`, this command will run four
tests, with the above names (and the query type `NS` for the last
one):
```
% homer --repeat 4 --file list.txt https://doh.42l.fr/dns-query
```
When repeating tests, you can add a delay between tests, with `--delay
N`, where N is the (possibly fractional) number of seconds to wait.
### Monitoring with Nagios, Icinga, or similar software
If the program is named `check_doh` or ` check_doh` (either from
copying or symbolic linking), it will behave as a [monitoring
plugin](https://www.monitoring-plugins.org/), suitable to be used from monitoring program like Nagios
or [Icinga](https://icinga.com/). The options are different in that case, and follow the
monitoring plugins conventions:
* -H: host name or address to monitor
* -V: virtual hostname (the certificate check will be based on that)
* -n: domain name to lookup
* -t: DNS type to query
* -p: (DoH) path in the URLx
* -P: uses the HTTP method POST
* -h: uses the HTTP method HEAD
* -i: insecure (do not check the certificate)
For Icinga, the following definition enables the plugin:
```
object CheckCommand "doh_monitor" {
command = [ PluginContribDir + "/check_doh" ]
arguments = {
"-H" = "$address6$",
"-n" = "$doh_lookup$",
"-p" = "$doh_path$",
"-V" = "$doh_vhost$",
"-t" = "$doh_type$",
"-p" = "$doh_post$",
"-i" = "$doh_insecure$",
"-h" = "$doh_head$"
}
}
object CheckCommand "dot_monitor" {
command = [ PluginContribDir + "/check_dot" ]
arguments = {
"-H" = "$address6$",
"-n" = "$dot_lookup$",
"-p" = "$dot_path$",
"-V" = "$dot_vhost$",
"-t" = "$dot_type$",
"-p" = "$dot_post$",
"-i" = "$dot_insecure$",
"-h" = "$dot_head$"
}
}
```
And a possible use is:
```
apply Service "doh" {
import "generic-service"
check_command = "doh_monitor"
assign where (host.address || host.address6) && host.vars.doh
vars.doh_lookup = "fr.wikipedia.org"
}
apply Service "dot" {
import "generic-service"
check_command = "dot_monitor"
assign where (host.address || host.address6) && host.vars.dot
vars.dot_lookup = "fr.wikipedia.org"
}
```
```
object Host "myserver" {
...
vars.dot = true
vars.dot_vhost = "dot.me.example"
vars.doh = true
vars.doh_vhost = "doh.me.example"
vars.doh_post = true
```
## Installation
You need [DNSpython](http://www.dnspython.org/) and
[pycurl](http://pycurl.io/docs/latest).
You need Python 3, [DNSpython](http://www.dnspython.org/), [PyOpenSSL](https://www.pyopenssl.org/) and
[pycurl](http://pycurl.io/docs/latest). You can install them with pip
`pip3 install dnspython pyOpenSSL pycurl`. Then, just run the script
`homer` (or `homer.py`).
On Debian, if you prefer regular operating system packages to pip,
`apt install python3 python3-dnspython python3-openssl python3-pycurl` will
install everything you need.
## DoH servers
## Public servers
(Managed by non-profit organisations.)
### DoH
* `https://doh.powerdns.org/`
* `https://doh.bortzmeyer.fr/` ([Documentation](https://doh.bortzmeyer.fr/about))
* `https://doh.42l.fr/dns-query`
* `https://odvr.nic.cz/doh` ([Documentation](https://www.nic.cz/odvr/))
* `http://dns.hostux.net/dns-query` ([Documentation](https://dns.hostux.net/))
* `http://dns.hostux.net/dns-query`
([Documentation](https://dns.hostux.net/))
* `https://ns0.ldn-fai.net/dns-query` ([Documentation in french](https://ldn-fai.net/serveur-dns-recursif-ouvert/))
* `https://dns.digitale-gesellschaft.ch/dns-query` ([Documentation in german](https://www.digitale-gesellschaft.ch/dns/))
* `https://doh.ffmuc.net` ([Documentation](https://ffmuc.net/wiki/doku.php?id=knb:dohdot_en))
### DoT
* `dot.bortzmeyer.fr` ([Documentation](https://doh.bortzmeyer.fr/about))
* `dns.digitale-gesellschaft.ch` ([Documentation in german](https://www.digitale-gesellschaft.ch/dns/))
* `dot.ffmuc.net` ([Documentation](https://ffmuc.net/wiki/doku.php?id=knb:dohdot_en))
* `ns0.ldn-fai.net` ([Documentation in french](https://ldn-fai.net/serveur-dns-recursif-ouvert/))
## License
See LICENSE.
GPL. See LICENSE.
## Author
Stéphane Bortzmeyer <stephane+framagit@bortzmeyer.org>
## Reference site
https://framagit.org/bortzmeyer/homer/ Use the Gitlab issue tracker to
report bugs or wishes.
#!/usr/bin/env python3
# Homer is a DoH (DNS-over-HTTPS) and DoT (DNS-over-TLS) client. Its
# main purpose is to test DoH and DoT resolvers. Reference site is
# <https://framagit.org/bortzmeyer/homer/> See author, documentation,
# etc, there, or in the README.md included with the distribution.
# http://pycurl.io/docs/latest
import pycurl
......@@ -61,7 +66,8 @@ def error(msg=None):
def usage(msg=None):
if msg:
print(msg,file=sys.stderr)
print("Usage: %s [-P] [-k] url domain-name [DNS type]" % sys.argv[0], file=sys.stderr)
print("Usage: %s [--dot] url-or-servername domain-name [DNS type]" % sys.argv[0], file=sys.stderr)
print("See the README.md for more details.", file=sys.stderr)
def is_valid_hostname(name):
name = str(name.encode('idna').lower())
......@@ -221,6 +227,9 @@ def do_test(connection, qname, qtype=rtype):
except dns.message.TrailingJunk: # Not DNS.
response = "ERROR Not proper DNS data \"%s\"" % body
ok = False
except dns.name.BadLabelType: # Not DNS.
response = "ERROR Not proper DNS data (wrong path in the URL?) \"%s\"" % body[:100]
ok = False
else:
response = "HEAD successful"
else:
......@@ -246,16 +255,16 @@ if not monitoring:
if option == "--help" or option == "-h":
usage()
sys.exit(0)
elif option == "--dot" or option == "-t":
dot = True
elif option == "--verbose" or option == "-v":
verbose = True
elif option == "--head" or option == "-e":
head = True
elif option == "--dot" or option == "-t":
dot = True
elif option == "--insecure" or option == "-k":
insecure = True
elif option == "--POST" or option == "-P":
post = True
elif option == "--insecure" or option == "-k":
insecure = True
elif option == "--repeat" or option == "-r":
tests = int(value)
if tests <= 1:
......@@ -308,10 +317,10 @@ else: # Monitoring plugin
path = value
elif option == "-P":
post = True
elif option == "-i":
insecure = True
elif option == "-h":
head = True
elif option == "-i":
insecure = True
else:
# Should never occur, it is trapped by getopt
print("Unknown option %s" % option)
......@@ -328,6 +337,12 @@ else: # Monitoring plugin
if post and head:
print("POST or HEAD but not both")
sys.exit(STATE_UNKNOWN)
if dot and (post or head):
print("POST or HEAD makes no sense for DoT")
sys.exit(STATE_UNKNOWN)
if dot and path:
print("URL path makes no sense for DoT")
sys.exit(STATE_UNKNOWN)
if dot:
url = host
else:
......@@ -349,6 +364,8 @@ try:
conn = Connection(url, dot=dot, servername=extracheck, verbose=verbose, insecure=insecure, post=post, head=head)
except TimeoutError:
error("timeout")
except ConnectionRefusedError:
error("Connection to server refused")
if ifile is not None:
input = open(ifile)
for i in range (0, tests):
......@@ -378,10 +395,14 @@ for i in range (0, tests):
if dot:
print("Error: %s" % msg, file=sys.stderr)
else:
print("HTTP error %i: %s" % (rcode, msg.decode()), file=sys.stderr)
try:
msg = msg.decode()
except UnicodeDecodeError:
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" % (url, rcode, msg.decode()))
print("%s HTTP error - %i: %s" % (url, rcode, msg))
else:
print("%s Error - %i: %s" % (url, rcode, msg))
sys.exit(STATE_CRITICAL)
......
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