doc(measurex): explain how to write experiments (#529)
Part of https://github.com/ooni/ooni.org/issues/361 Co-authored-by: Arturo Filastò <arturo@openobservatory.org>
This commit is contained in:
@@ -0,0 +1,567 @@
|
||||
|
||||
# Chapter III: using a custom DNS-over-UDP resolver
|
||||
|
||||
In this chapter we learn how to measure sending DNS queries to
|
||||
a DNS server speaking the DNS-over-UDP protocol.
|
||||
|
||||
Without further ado, let's describe our example `main.go` program
|
||||
and let's use it to better understand this flow.
|
||||
|
||||
(This file is auto-generated. Do not edit it directly! To apply
|
||||
changes you need to modify `./internal/tutorial/measurex/chapter03/main.go`.)
|
||||
|
||||
## main.go
|
||||
|
||||
The initial part of the program is pretty much the same as the one
|
||||
used in previous chapters, so I will not add further comments.
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/measurex"
|
||||
"github.com/ooni/probe-cli/v3/internal/runtimex"
|
||||
)
|
||||
|
||||
func main() {
|
||||
query := flag.String("query", "example.com", "domain to resolver")
|
||||
address := flag.String("address", "8.8.4.4:53", "DNS-over-UDP server address")
|
||||
timeout := flag.Duration("timeout", 60*time.Second, "timeout to use")
|
||||
flag.Parse()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
|
||||
defer cancel()
|
||||
mx := measurex.NewMeasurerWithDefaultSettings()
|
||||
```
|
||||
|
||||
### Using a custom UDP resolver
|
||||
|
||||
We now invoke `LookupHostUDP`. We specify:
|
||||
|
||||
- a context for timeout information;
|
||||
|
||||
- the domain to query for;
|
||||
|
||||
- the address of the DNS-over-UDP server endpoint.
|
||||
|
||||
```Go
|
||||
m := mx.LookupHostUDP(ctx, *query, *address)
|
||||
```
|
||||
|
||||
Also this operation returns a measurement, which
|
||||
we print using the usual three-liner.
|
||||
|
||||
```Go
|
||||
data, err := json.Marshal(m)
|
||||
runtimex.PanicOnError(err, "json.Marshal failed")
|
||||
fmt.Printf("%s\n", string(data))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Running the example program
|
||||
|
||||
As before, let us start off with a vanilla run:
|
||||
|
||||
```bash
|
||||
go run -race ./internal/tutorial/measurex/chapter03
|
||||
```
|
||||
|
||||
This time we get a much larger JSON, so I will pretend it is
|
||||
actually JavaScript and add comments to explain it inline.
|
||||
|
||||
(This is the first case in which we see how a single
|
||||
method call for measurer causes several events to
|
||||
be generated and inserted into a `Measurement`.)
|
||||
|
||||
```JavaScript
|
||||
{
|
||||
"domain": "example.com",
|
||||
|
||||
// This block tells us about the UDP connect events
|
||||
// where we bind to the server's endpoint
|
||||
"connect": [
|
||||
{
|
||||
"address": "8.8.4.4:53",
|
||||
"failure": null,
|
||||
"operation": "connect",
|
||||
"proto": "udp",
|
||||
"t": 0.00043175,
|
||||
"started": 0.000191958,
|
||||
"oddity": ""
|
||||
},
|
||||
{
|
||||
"address": "8.8.4.4:53",
|
||||
"failure": null,
|
||||
"operation": "connect",
|
||||
"proto": "udp",
|
||||
"t": 0.042198458,
|
||||
"started": 0.042113208,
|
||||
"oddity": ""
|
||||
}
|
||||
],
|
||||
|
||||
// This block shows the read and write events
|
||||
// occurred on the sockets (because we control
|
||||
// in full the implementation of this DNS
|
||||
// over UDP resolver, we can see these events)
|
||||
"read_write": [
|
||||
{
|
||||
"address": "8.8.4.4:53",
|
||||
"failure": null,
|
||||
"num_bytes": 29,
|
||||
"operation": "write",
|
||||
"proto": "udp",
|
||||
"t": 0.000459583,
|
||||
"started": 0.00043825,
|
||||
"oddity": ""
|
||||
},
|
||||
{
|
||||
"address": "8.8.4.4:53",
|
||||
"failure": null,
|
||||
"num_bytes": 45,
|
||||
"operation": "read",
|
||||
"proto": "udp",
|
||||
"t": 0.041955792,
|
||||
"started": 0.000471833,
|
||||
"oddity": ""
|
||||
},
|
||||
{
|
||||
"address": "8.8.4.4:53",
|
||||
"failure": null,
|
||||
"num_bytes": 29,
|
||||
"operation": "write",
|
||||
"proto": "udp",
|
||||
"t": 0.042218917,
|
||||
"started": 0.042203,
|
||||
"oddity": ""
|
||||
},
|
||||
{
|
||||
"address": "8.8.4.4:53",
|
||||
"failure": null,
|
||||
"num_bytes": 57,
|
||||
"operation": "read",
|
||||
"proto": "udp",
|
||||
"t": 0.196646583,
|
||||
"started": 0.042233167,
|
||||
"oddity": ""
|
||||
}
|
||||
],
|
||||
|
||||
// This is the same kind of result as before, we
|
||||
// show the emitted queries and the resolved addrs.
|
||||
//
|
||||
// Also note how here the resolver_address is the
|
||||
// correct endpoint address and the engine tells us
|
||||
// that we're using DNS over UDP.
|
||||
"lookup_host": [
|
||||
{
|
||||
"answers": [
|
||||
{
|
||||
"answer_type": "A",
|
||||
"ipv4": "93.184.216.34"
|
||||
}
|
||||
],
|
||||
"engine": "udp",
|
||||
"failure": null,
|
||||
"hostname": "example.com",
|
||||
"query_type": "A",
|
||||
"resolver_address": "8.8.4.4:53",
|
||||
"t": 0.196777042,
|
||||
"started": 0.000118542,
|
||||
"oddity": ""
|
||||
},
|
||||
{
|
||||
"answers": [
|
||||
{
|
||||
"answer_type": "AAAA",
|
||||
"ivp6": "2606:2800:220:1:248:1893:25c8:1946"
|
||||
}
|
||||
],
|
||||
"engine": "udp",
|
||||
"failure": null,
|
||||
"hostname": "example.com",
|
||||
"query_type": "AAAA",
|
||||
"resolver_address": "8.8.4.4:53",
|
||||
"t": 0.196777042,
|
||||
"started": 0.000118542,
|
||||
"oddity": ""
|
||||
}
|
||||
],
|
||||
|
||||
// This block shows the query we sent (encoded as base64)
|
||||
// and the response we received. Here we clearly see
|
||||
// that we perform two DNS "round trip" (i.e., send request
|
||||
// and receive response) to resolve a domain: one for
|
||||
// A and the other for AAAA.
|
||||
"dns_round_trip": [
|
||||
{
|
||||
"engine": "udp",
|
||||
"resolver_address": "8.8.4.4:53",
|
||||
"raw_query": {
|
||||
"data": "PrcBAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE=",
|
||||
"format": "base64"
|
||||
},
|
||||
"started": 0.000191625,
|
||||
"t": 0.041998667,
|
||||
"failure": null,
|
||||
"raw_reply": {
|
||||
"data": "PreBgAABAAEAAAAAB2V4YW1wbGUDY29tAAABAAHADAABAAEAAE8BAARduNgi",
|
||||
"format": "base64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"engine": "udp",
|
||||
"resolver_address": "8.8.4.4:53",
|
||||
"raw_query": {
|
||||
"data": "LAwBAAABAAAAAAAAB2V4YW1wbGUDY29tAAAcAAE=",
|
||||
"format": "base64"
|
||||
},
|
||||
"started": 0.04210775,
|
||||
"t": 0.196701333,
|
||||
"failure": null,
|
||||
"raw_reply": {
|
||||
"data": "LAyBgAABAAEAAAAAB2V4YW1wbGUDY29tAAAcAAHADAAcAAEAAE6nABAmBigAAiAAAQJIGJMlyBlG",
|
||||
"format": "base64"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This data format is really an extension of the `LookupHostSystem`
|
||||
one. It just adds more fields that clarify what happened at low
|
||||
level in terms of socket I/O and queries sent and received.
|
||||
|
||||
Let us now try to provoke some errors and see how the
|
||||
output JSON changes because of them.
|
||||
|
||||
### Measurement with NXDOMAIN
|
||||
|
||||
Let us try to get a NXDOMAIN error.
|
||||
|
||||
```bash
|
||||
go run -race ./internal/tutorial/measurex/chapter03 -query antani.ooni.org
|
||||
```
|
||||
|
||||
This produces the following JSON:
|
||||
|
||||
```JavaScript
|
||||
{
|
||||
"domain": "antani.ooni.org",
|
||||
"connect": [ /* snip */ ],
|
||||
"read_write": [ /* snip */ ],
|
||||
"lookup_host": [
|
||||
{
|
||||
"answers": null,
|
||||
"engine": "udp",
|
||||
"failure": "dns_nxdomain_error",
|
||||
"hostname": "antani.ooni.org",
|
||||
"query_type": "A",
|
||||
"resolver_address": "8.8.4.4:53",
|
||||
"t": 0.098208709,
|
||||
"started": 8.95e-05,
|
||||
"oddity": "dns.lookup.nxdomain"
|
||||
},
|
||||
{
|
||||
"answers": null,
|
||||
"engine": "udp",
|
||||
"failure": "dns_nxdomain_error",
|
||||
"hostname": "antani.ooni.org",
|
||||
"query_type": "AAAA",
|
||||
"resolver_address": "8.8.4.4:53",
|
||||
"t": 0.098208709,
|
||||
"started": 8.95e-05,
|
||||
"oddity": "dns.lookup.nxdomain"
|
||||
}
|
||||
],
|
||||
"dns_round_trip": [
|
||||
{
|
||||
"engine": "udp",
|
||||
"resolver_address": "8.8.4.4:53",
|
||||
"raw_query": {
|
||||
"data": "jLIBAAABAAAAAAAABmFudGFuaQRvb25pA29yZwAAAQAB",
|
||||
"format": "base64"
|
||||
},
|
||||
"started": 0.000141542,
|
||||
"t": 0.034689417,
|
||||
"failure": null,
|
||||
"raw_reply": {
|
||||
"data": "jLKBgwABAAAAAQAABmFudGFuaQRvb25pA29yZwAAAQABwBMABgABAAAHCAA9BGRuczERcmVnaXN0cmFyLXNlcnZlcnMDY29tAApob3N0bWFzdGVywDJhABz8AACowAAADhAACTqAAAAOEQ==",
|
||||
"format": "base64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"engine": "udp",
|
||||
"resolver_address": "8.8.4.4:53",
|
||||
"raw_query": {
|
||||
"data": "azEBAAABAAAAAAAABmFudGFuaQRvb25pA29yZwAAHAAB",
|
||||
"format": "base64"
|
||||
},
|
||||
"started": 0.034776709,
|
||||
"t": 0.098170542,
|
||||
"failure": null,
|
||||
"raw_reply": {
|
||||
"data": "azGBgwABAAAAAQAABmFudGFuaQRvb25pA29yZwAAHAABwBMABgABAAAHCAA9BGRuczERcmVnaXN0cmFyLXNlcnZlcnMDY29tAApob3N0bWFzdGVywDJhABz8AACowAAADhAACTqAAAAOEQ==",
|
||||
"format": "base64"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
We indeed get a NXDOMAIN error as the failure in `lookup_host`.
|
||||
|
||||
Let us now decode one of the replies by using this program:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func main() {
|
||||
const query = "azGBgwABAAAAAQAABmFudGFuaQRvb25pA29yZwAAHAABwBMABgABAAAHCAA9BGRuczERcmVnaXN0cmFyLXNlcnZlcnMDY29tAApob3N0bWFzdGVywDJhABz8AACowAAADhAACTqAAAAOEQ=="
|
||||
data, _ := base64.StdEncoding.DecodeString(query)
|
||||
msg := new(dns.Msg)
|
||||
_ = msg.Unpack(data)
|
||||
fmt.Printf("%s\n", msg)
|
||||
}
|
||||
```
|
||||
|
||||
where `query` is one of the replies. If we run this program
|
||||
we get as the output:
|
||||
|
||||
```
|
||||
;; opcode: QUERY, status: NXDOMAIN, id: 27441
|
||||
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;antani.ooni.org. IN AAAA
|
||||
|
||||
;; AUTHORITY SECTION:
|
||||
ooni.org. 1800 IN SOA dns1.registrar-servers.com. hostmaster.registrar-servers.com. 1627397372 43200 3600 604800 3601
|
||||
```
|
||||
|
||||
### Measurement with timeout
|
||||
|
||||
Let us now query an IP address known for not responding
|
||||
to DNS queries, to get a timeout.
|
||||
|
||||
```bash
|
||||
go run -race ./internal/tutorial/measurex/chapter03 -address 182.92.22.222:53
|
||||
```
|
||||
|
||||
Here's the corresponding JSON:
|
||||
|
||||
```JavaScript
|
||||
{
|
||||
"domain": "example.com",
|
||||
"connect": [ /* snip */ ],
|
||||
"read_write": [
|
||||
{
|
||||
"address": "182.92.22.222:53",
|
||||
"failure": null,
|
||||
"num_bytes": 29,
|
||||
"operation": "write",
|
||||
"proto": "udp",
|
||||
"t": 0.0005275,
|
||||
"started": 0.000500209,
|
||||
"oddity": ""
|
||||
},
|
||||
{
|
||||
"address": "182.92.22.222:53",
|
||||
"failure": "generic_timeout_error", /* <--- */
|
||||
"operation": "read",
|
||||
"proto": "udp",
|
||||
"t": 5.001140125,
|
||||
"started": 0.000544042,
|
||||
"oddity": ""
|
||||
}
|
||||
],
|
||||
"lookup_host": [
|
||||
{
|
||||
"answers": null,
|
||||
"engine": "udp",
|
||||
"failure": "generic_timeout_error", /* <--- */
|
||||
"hostname": "example.com",
|
||||
"query_type": "A",
|
||||
"resolver_address": "182.92.22.222:53",
|
||||
"t": 5.001462084,
|
||||
"started": 0.000127917,
|
||||
"oddity": "dns.lookup.timeout" /* <--- */
|
||||
},
|
||||
{
|
||||
"answers": null,
|
||||
"engine": "udp",
|
||||
"failure": "generic_timeout_error",
|
||||
"hostname": "example.com",
|
||||
"query_type": "AAAA",
|
||||
"resolver_address": "182.92.22.222:53",
|
||||
"t": 5.001462084,
|
||||
"started": 0.000127917,
|
||||
"oddity": "dns.lookup.timeout"
|
||||
}
|
||||
],
|
||||
"dns_round_trip": [
|
||||
{
|
||||
"engine": "udp",
|
||||
"resolver_address": "182.92.22.222:53",
|
||||
"raw_query": {
|
||||
"data": "ej8BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE=",
|
||||
"format": "base64"
|
||||
},
|
||||
"started": 0.000220584,
|
||||
"t": 5.001317417,
|
||||
"failure": "generic_timeout_error",
|
||||
"raw_reply": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
We see that we do fail with a timeout (I have marked some of them
|
||||
with comments inside the JSON). We see the timeout at three different
|
||||
level of abstractions (from lower to higher abstraction): at the socket layer,
|
||||
during the DNS round trip, during the DNS lookup.
|
||||
|
||||
What we also see is that `t`'s value is ~5s when the `read` event
|
||||
fails, which tells us about the socket's read timeout.
|
||||
|
||||
### Measurement with REFUSED error
|
||||
|
||||
Let us now try to get a REFUSED DNS Rcode, again from servers
|
||||
that are, let's say, kind enough to easily help.
|
||||
|
||||
```bash
|
||||
go run -race ./internal/tutorial/measurex/chapter03 -address 180.97.36.63:53
|
||||
```
|
||||
|
||||
Here's the answer I get:
|
||||
|
||||
```JavaScript
|
||||
{
|
||||
"domain": "example.com",
|
||||
"connect": [ /* snip */ ],
|
||||
|
||||
// The I/O events look normal this time
|
||||
"read_write": [
|
||||
{
|
||||
"address": "180.97.36.63:53",
|
||||
"failure": null,
|
||||
"num_bytes": 29,
|
||||
"operation": "write",
|
||||
"proto": "udp",
|
||||
"t": 0.000333583,
|
||||
"started": 0.000312125,
|
||||
"oddity": ""
|
||||
},
|
||||
{
|
||||
"address": "180.97.36.63:53",
|
||||
"failure": null,
|
||||
"num_bytes": 29,
|
||||
"operation": "read",
|
||||
"proto": "udp",
|
||||
"t": 0.334948125,
|
||||
"started": 0.000366625,
|
||||
"oddity": ""
|
||||
},
|
||||
{
|
||||
"address": "180.97.36.63:53",
|
||||
"failure": null,
|
||||
"num_bytes": 29,
|
||||
"operation": "write",
|
||||
"proto": "udp",
|
||||
"t": 0.3358025,
|
||||
"started": 0.335725958,
|
||||
"oddity": ""
|
||||
},
|
||||
{
|
||||
"address": "180.97.36.63:53",
|
||||
"failure": null,
|
||||
"num_bytes": 29,
|
||||
"operation": "read",
|
||||
"proto": "udp",
|
||||
"t": 0.739987666,
|
||||
"started": 0.335863875,
|
||||
"oddity": ""
|
||||
}
|
||||
],
|
||||
|
||||
// But we see both in the error and in the oddity
|
||||
// that the response was "REFUSED"
|
||||
"lookup_host": [
|
||||
{
|
||||
"answers": null,
|
||||
"engine": "udp",
|
||||
"failure": "dns_refused_error",
|
||||
"hostname": "example.com",
|
||||
"query_type": "A",
|
||||
"resolver_address": "180.97.36.63:53",
|
||||
"t": 0.7402975,
|
||||
"started": 7.2291e-05,
|
||||
"oddity": "dns.lookup.refused"
|
||||
},
|
||||
{
|
||||
"answers": null,
|
||||
"engine": "udp",
|
||||
"failure": "dns_refused_error",
|
||||
"hostname": "example.com",
|
||||
"query_type": "AAAA",
|
||||
"resolver_address": "180.97.36.63:53",
|
||||
"t": 0.7402975,
|
||||
"started": 7.2291e-05,
|
||||
"oddity": "dns.lookup.refused"
|
||||
}
|
||||
],
|
||||
|
||||
// Exercise: do like I did before and decode the messages
|
||||
"dns_round_trip": [
|
||||
{
|
||||
"engine": "udp",
|
||||
"resolver_address": "180.97.36.63:53",
|
||||
"raw_query": {
|
||||
"data": "crkBAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE=",
|
||||
"format": "base64"
|
||||
},
|
||||
"started": 0.000130666,
|
||||
"t": 0.33509925,
|
||||
"failure": null,
|
||||
"raw_reply": {
|
||||
"data": "crmBBQABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE=",
|
||||
"format": "base64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"engine": "udp",
|
||||
"resolver_address": "180.97.36.63:53",
|
||||
"raw_query": {
|
||||
"data": "ywcBAAABAAAAAAAAB2V4YW1wbGUDY29tAAAcAAE=",
|
||||
"format": "base64"
|
||||
},
|
||||
"started": 0.335321333,
|
||||
"t": 0.740152375,
|
||||
"failure": null,
|
||||
"raw_reply": {
|
||||
"data": "yweBBQABAAAAAAAAB2V4YW1wbGUDY29tAAAcAAE=",
|
||||
"format": "base64"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
We have seen how we sending DNS queries over UDP, measure the
|
||||
results, and what happens on common error conditions.
|
||||
|
||||
@@ -0,0 +1,569 @@
|
||||
// -=-=- StartHere -=-=-
|
||||
//
|
||||
// # Chapter III: using a custom DNS-over-UDP resolver
|
||||
//
|
||||
// In this chapter we learn how to measure sending DNS queries to
|
||||
// a DNS server speaking the DNS-over-UDP protocol.
|
||||
//
|
||||
// Without further ado, let's describe our example `main.go` program
|
||||
// and let's use it to better understand this flow.
|
||||
//
|
||||
// (This file is auto-generated. Do not edit it directly! To apply
|
||||
// changes you need to modify `./internal/tutorial/measurex/chapter03/main.go`.)
|
||||
//
|
||||
// ## main.go
|
||||
//
|
||||
// The initial part of the program is pretty much the same as the one
|
||||
// used in previous chapters, so I will not add further comments.
|
||||
//
|
||||
// ```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/measurex"
|
||||
"github.com/ooni/probe-cli/v3/internal/runtimex"
|
||||
)
|
||||
|
||||
func main() {
|
||||
query := flag.String("query", "example.com", "domain to resolver")
|
||||
address := flag.String("address", "8.8.4.4:53", "DNS-over-UDP server address")
|
||||
timeout := flag.Duration("timeout", 60*time.Second, "timeout to use")
|
||||
flag.Parse()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
|
||||
defer cancel()
|
||||
mx := measurex.NewMeasurerWithDefaultSettings()
|
||||
// ```
|
||||
//
|
||||
// ### Using a custom UDP resolver
|
||||
//
|
||||
// We now invoke `LookupHostUDP`. We specify:
|
||||
//
|
||||
// - a context for timeout information;
|
||||
//
|
||||
// - the domain to query for;
|
||||
//
|
||||
// - the address of the DNS-over-UDP server endpoint.
|
||||
//
|
||||
// ```Go
|
||||
m := mx.LookupHostUDP(ctx, *query, *address)
|
||||
// ```
|
||||
//
|
||||
// Also this operation returns a measurement, which
|
||||
// we print using the usual three-liner.
|
||||
//
|
||||
// ```Go
|
||||
data, err := json.Marshal(m)
|
||||
runtimex.PanicOnError(err, "json.Marshal failed")
|
||||
fmt.Printf("%s\n", string(data))
|
||||
}
|
||||
|
||||
// ```
|
||||
//
|
||||
// ## Running the example program
|
||||
//
|
||||
// As before, let us start off with a vanilla run:
|
||||
//
|
||||
// ```bash
|
||||
// go run -race ./internal/tutorial/measurex/chapter03
|
||||
// ```
|
||||
//
|
||||
// This time we get a much larger JSON, so I will pretend it is
|
||||
// actually JavaScript and add comments to explain it inline.
|
||||
//
|
||||
// (This is the first case in which we see how a single
|
||||
// method call for measurer causes several events to
|
||||
// be generated and inserted into a `Measurement`.)
|
||||
//
|
||||
// ```JavaScript
|
||||
// {
|
||||
// "domain": "example.com",
|
||||
//
|
||||
// // This block tells us about the UDP connect events
|
||||
// // where we bind to the server's endpoint
|
||||
// "connect": [
|
||||
// {
|
||||
// "address": "8.8.4.4:53",
|
||||
// "failure": null,
|
||||
// "operation": "connect",
|
||||
// "proto": "udp",
|
||||
// "t": 0.00043175,
|
||||
// "started": 0.000191958,
|
||||
// "oddity": ""
|
||||
// },
|
||||
// {
|
||||
// "address": "8.8.4.4:53",
|
||||
// "failure": null,
|
||||
// "operation": "connect",
|
||||
// "proto": "udp",
|
||||
// "t": 0.042198458,
|
||||
// "started": 0.042113208,
|
||||
// "oddity": ""
|
||||
// }
|
||||
// ],
|
||||
//
|
||||
// // This block shows the read and write events
|
||||
// // occurred on the sockets (because we control
|
||||
// // in full the implementation of this DNS
|
||||
// // over UDP resolver, we can see these events)
|
||||
// "read_write": [
|
||||
// {
|
||||
// "address": "8.8.4.4:53",
|
||||
// "failure": null,
|
||||
// "num_bytes": 29,
|
||||
// "operation": "write",
|
||||
// "proto": "udp",
|
||||
// "t": 0.000459583,
|
||||
// "started": 0.00043825,
|
||||
// "oddity": ""
|
||||
// },
|
||||
// {
|
||||
// "address": "8.8.4.4:53",
|
||||
// "failure": null,
|
||||
// "num_bytes": 45,
|
||||
// "operation": "read",
|
||||
// "proto": "udp",
|
||||
// "t": 0.041955792,
|
||||
// "started": 0.000471833,
|
||||
// "oddity": ""
|
||||
// },
|
||||
// {
|
||||
// "address": "8.8.4.4:53",
|
||||
// "failure": null,
|
||||
// "num_bytes": 29,
|
||||
// "operation": "write",
|
||||
// "proto": "udp",
|
||||
// "t": 0.042218917,
|
||||
// "started": 0.042203,
|
||||
// "oddity": ""
|
||||
// },
|
||||
// {
|
||||
// "address": "8.8.4.4:53",
|
||||
// "failure": null,
|
||||
// "num_bytes": 57,
|
||||
// "operation": "read",
|
||||
// "proto": "udp",
|
||||
// "t": 0.196646583,
|
||||
// "started": 0.042233167,
|
||||
// "oddity": ""
|
||||
// }
|
||||
// ],
|
||||
//
|
||||
// // This is the same kind of result as before, we
|
||||
// // show the emitted queries and the resolved addrs.
|
||||
// //
|
||||
// // Also note how here the resolver_address is the
|
||||
// // correct endpoint address and the engine tells us
|
||||
// // that we're using DNS over UDP.
|
||||
// "lookup_host": [
|
||||
// {
|
||||
// "answers": [
|
||||
// {
|
||||
// "answer_type": "A",
|
||||
// "ipv4": "93.184.216.34"
|
||||
// }
|
||||
// ],
|
||||
// "engine": "udp",
|
||||
// "failure": null,
|
||||
// "hostname": "example.com",
|
||||
// "query_type": "A",
|
||||
// "resolver_address": "8.8.4.4:53",
|
||||
// "t": 0.196777042,
|
||||
// "started": 0.000118542,
|
||||
// "oddity": ""
|
||||
// },
|
||||
// {
|
||||
// "answers": [
|
||||
// {
|
||||
// "answer_type": "AAAA",
|
||||
// "ivp6": "2606:2800:220:1:248:1893:25c8:1946"
|
||||
// }
|
||||
// ],
|
||||
// "engine": "udp",
|
||||
// "failure": null,
|
||||
// "hostname": "example.com",
|
||||
// "query_type": "AAAA",
|
||||
// "resolver_address": "8.8.4.4:53",
|
||||
// "t": 0.196777042,
|
||||
// "started": 0.000118542,
|
||||
// "oddity": ""
|
||||
// }
|
||||
// ],
|
||||
//
|
||||
// // This block shows the query we sent (encoded as base64)
|
||||
// // and the response we received. Here we clearly see
|
||||
// // that we perform two DNS "round trip" (i.e., send request
|
||||
// // and receive response) to resolve a domain: one for
|
||||
// // A and the other for AAAA.
|
||||
// "dns_round_trip": [
|
||||
// {
|
||||
// "engine": "udp",
|
||||
// "resolver_address": "8.8.4.4:53",
|
||||
// "raw_query": {
|
||||
// "data": "PrcBAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE=",
|
||||
// "format": "base64"
|
||||
// },
|
||||
// "started": 0.000191625,
|
||||
// "t": 0.041998667,
|
||||
// "failure": null,
|
||||
// "raw_reply": {
|
||||
// "data": "PreBgAABAAEAAAAAB2V4YW1wbGUDY29tAAABAAHADAABAAEAAE8BAARduNgi",
|
||||
// "format": "base64"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "engine": "udp",
|
||||
// "resolver_address": "8.8.4.4:53",
|
||||
// "raw_query": {
|
||||
// "data": "LAwBAAABAAAAAAAAB2V4YW1wbGUDY29tAAAcAAE=",
|
||||
// "format": "base64"
|
||||
// },
|
||||
// "started": 0.04210775,
|
||||
// "t": 0.196701333,
|
||||
// "failure": null,
|
||||
// "raw_reply": {
|
||||
// "data": "LAyBgAABAAEAAAAAB2V4YW1wbGUDY29tAAAcAAHADAAcAAEAAE6nABAmBigAAiAAAQJIGJMlyBlG",
|
||||
// "format": "base64"
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// This data format is really an extension of the `LookupHostSystem`
|
||||
// one. It just adds more fields that clarify what happened at low
|
||||
// level in terms of socket I/O and queries sent and received.
|
||||
//
|
||||
// Let us now try to provoke some errors and see how the
|
||||
// output JSON changes because of them.
|
||||
//
|
||||
// ### Measurement with NXDOMAIN
|
||||
//
|
||||
// Let us try to get a NXDOMAIN error.
|
||||
//
|
||||
// ```bash
|
||||
// go run -race ./internal/tutorial/measurex/chapter03 -query antani.ooni.org
|
||||
// ```
|
||||
//
|
||||
// This produces the following JSON:
|
||||
//
|
||||
// ```JavaScript
|
||||
// {
|
||||
// "domain": "antani.ooni.org",
|
||||
// "connect": [ /* snip */ ],
|
||||
// "read_write": [ /* snip */ ],
|
||||
// "lookup_host": [
|
||||
// {
|
||||
// "answers": null,
|
||||
// "engine": "udp",
|
||||
// "failure": "dns_nxdomain_error",
|
||||
// "hostname": "antani.ooni.org",
|
||||
// "query_type": "A",
|
||||
// "resolver_address": "8.8.4.4:53",
|
||||
// "t": 0.098208709,
|
||||
// "started": 8.95e-05,
|
||||
// "oddity": "dns.lookup.nxdomain"
|
||||
// },
|
||||
// {
|
||||
// "answers": null,
|
||||
// "engine": "udp",
|
||||
// "failure": "dns_nxdomain_error",
|
||||
// "hostname": "antani.ooni.org",
|
||||
// "query_type": "AAAA",
|
||||
// "resolver_address": "8.8.4.4:53",
|
||||
// "t": 0.098208709,
|
||||
// "started": 8.95e-05,
|
||||
// "oddity": "dns.lookup.nxdomain"
|
||||
// }
|
||||
// ],
|
||||
// "dns_round_trip": [
|
||||
// {
|
||||
// "engine": "udp",
|
||||
// "resolver_address": "8.8.4.4:53",
|
||||
// "raw_query": {
|
||||
// "data": "jLIBAAABAAAAAAAABmFudGFuaQRvb25pA29yZwAAAQAB",
|
||||
// "format": "base64"
|
||||
// },
|
||||
// "started": 0.000141542,
|
||||
// "t": 0.034689417,
|
||||
// "failure": null,
|
||||
// "raw_reply": {
|
||||
// "data": "jLKBgwABAAAAAQAABmFudGFuaQRvb25pA29yZwAAAQABwBMABgABAAAHCAA9BGRuczERcmVnaXN0cmFyLXNlcnZlcnMDY29tAApob3N0bWFzdGVywDJhABz8AACowAAADhAACTqAAAAOEQ==",
|
||||
// "format": "base64"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "engine": "udp",
|
||||
// "resolver_address": "8.8.4.4:53",
|
||||
// "raw_query": {
|
||||
// "data": "azEBAAABAAAAAAAABmFudGFuaQRvb25pA29yZwAAHAAB",
|
||||
// "format": "base64"
|
||||
// },
|
||||
// "started": 0.034776709,
|
||||
// "t": 0.098170542,
|
||||
// "failure": null,
|
||||
// "raw_reply": {
|
||||
// "data": "azGBgwABAAAAAQAABmFudGFuaQRvb25pA29yZwAAHAABwBMABgABAAAHCAA9BGRuczERcmVnaXN0cmFyLXNlcnZlcnMDY29tAApob3N0bWFzdGVywDJhABz8AACowAAADhAACTqAAAAOEQ==",
|
||||
// "format": "base64"
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// We indeed get a NXDOMAIN error as the failure in `lookup_host`.
|
||||
//
|
||||
// Let us now decode one of the replies by using this program:
|
||||
//
|
||||
// ```
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "encoding/base64"
|
||||
//
|
||||
// "github.com/miekg/dns"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// const query = "azGBgwABAAAAAQAABmFudGFuaQRvb25pA29yZwAAHAABwBMABgABAAAHCAA9BGRuczERcmVnaXN0cmFyLXNlcnZlcnMDY29tAApob3N0bWFzdGVywDJhABz8AACowAAADhAACTqAAAAOEQ=="
|
||||
// data, _ := base64.StdEncoding.DecodeString(query)
|
||||
// msg := new(dns.Msg)
|
||||
// _ = msg.Unpack(data)
|
||||
// fmt.Printf("%s\n", msg)
|
||||
//}
|
||||
// ```
|
||||
//
|
||||
// where `query` is one of the replies. If we run this program
|
||||
// we get as the output:
|
||||
//
|
||||
// ```
|
||||
// ;; opcode: QUERY, status: NXDOMAIN, id: 27441
|
||||
// ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
|
||||
//
|
||||
// ;; QUESTION SECTION:
|
||||
// ;antani.ooni.org. IN AAAA
|
||||
//
|
||||
// ;; AUTHORITY SECTION:
|
||||
// ooni.org. 1800 IN SOA dns1.registrar-servers.com. hostmaster.registrar-servers.com. 1627397372 43200 3600 604800 3601
|
||||
// ```
|
||||
//
|
||||
// ### Measurement with timeout
|
||||
//
|
||||
// Let us now query an IP address known for not responding
|
||||
// to DNS queries, to get a timeout.
|
||||
//
|
||||
// ```bash
|
||||
// go run -race ./internal/tutorial/measurex/chapter03 -address 182.92.22.222:53
|
||||
// ```
|
||||
//
|
||||
// Here's the corresponding JSON:
|
||||
//
|
||||
// ```JavaScript
|
||||
// {
|
||||
// "domain": "example.com",
|
||||
// "connect": [ /* snip */ ],
|
||||
// "read_write": [
|
||||
// {
|
||||
// "address": "182.92.22.222:53",
|
||||
// "failure": null,
|
||||
// "num_bytes": 29,
|
||||
// "operation": "write",
|
||||
// "proto": "udp",
|
||||
// "t": 0.0005275,
|
||||
// "started": 0.000500209,
|
||||
// "oddity": ""
|
||||
// },
|
||||
// {
|
||||
// "address": "182.92.22.222:53",
|
||||
// "failure": "generic_timeout_error", /* <--- */
|
||||
// "operation": "read",
|
||||
// "proto": "udp",
|
||||
// "t": 5.001140125,
|
||||
// "started": 0.000544042,
|
||||
// "oddity": ""
|
||||
// }
|
||||
// ],
|
||||
// "lookup_host": [
|
||||
// {
|
||||
// "answers": null,
|
||||
// "engine": "udp",
|
||||
// "failure": "generic_timeout_error", /* <--- */
|
||||
// "hostname": "example.com",
|
||||
// "query_type": "A",
|
||||
// "resolver_address": "182.92.22.222:53",
|
||||
// "t": 5.001462084,
|
||||
// "started": 0.000127917,
|
||||
// "oddity": "dns.lookup.timeout" /* <--- */
|
||||
// },
|
||||
// {
|
||||
// "answers": null,
|
||||
// "engine": "udp",
|
||||
// "failure": "generic_timeout_error",
|
||||
// "hostname": "example.com",
|
||||
// "query_type": "AAAA",
|
||||
// "resolver_address": "182.92.22.222:53",
|
||||
// "t": 5.001462084,
|
||||
// "started": 0.000127917,
|
||||
// "oddity": "dns.lookup.timeout"
|
||||
// }
|
||||
// ],
|
||||
// "dns_round_trip": [
|
||||
// {
|
||||
// "engine": "udp",
|
||||
// "resolver_address": "182.92.22.222:53",
|
||||
// "raw_query": {
|
||||
// "data": "ej8BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE=",
|
||||
// "format": "base64"
|
||||
// },
|
||||
// "started": 0.000220584,
|
||||
// "t": 5.001317417,
|
||||
// "failure": "generic_timeout_error",
|
||||
// "raw_reply": null
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// We see that we do fail with a timeout (I have marked some of them
|
||||
// with comments inside the JSON). We see the timeout at three different
|
||||
// level of abstractions (from lower to higher abstraction): at the socket layer,
|
||||
// during the DNS round trip, during the DNS lookup.
|
||||
//
|
||||
// What we also see is that `t`'s value is ~5s when the `read` event
|
||||
// fails, which tells us about the socket's read timeout.
|
||||
//
|
||||
// ### Measurement with REFUSED error
|
||||
//
|
||||
// Let us now try to get a REFUSED DNS Rcode, again from servers
|
||||
// that are, let's say, kind enough to easily help.
|
||||
//
|
||||
// ```bash
|
||||
// go run -race ./internal/tutorial/measurex/chapter03 -address 180.97.36.63:53
|
||||
// ```
|
||||
//
|
||||
// Here's the answer I get:
|
||||
//
|
||||
// ```JavaScript
|
||||
// {
|
||||
// "domain": "example.com",
|
||||
// "connect": [ /* snip */ ],
|
||||
//
|
||||
// // The I/O events look normal this time
|
||||
// "read_write": [
|
||||
// {
|
||||
// "address": "180.97.36.63:53",
|
||||
// "failure": null,
|
||||
// "num_bytes": 29,
|
||||
// "operation": "write",
|
||||
// "proto": "udp",
|
||||
// "t": 0.000333583,
|
||||
// "started": 0.000312125,
|
||||
// "oddity": ""
|
||||
// },
|
||||
// {
|
||||
// "address": "180.97.36.63:53",
|
||||
// "failure": null,
|
||||
// "num_bytes": 29,
|
||||
// "operation": "read",
|
||||
// "proto": "udp",
|
||||
// "t": 0.334948125,
|
||||
// "started": 0.000366625,
|
||||
// "oddity": ""
|
||||
// },
|
||||
// {
|
||||
// "address": "180.97.36.63:53",
|
||||
// "failure": null,
|
||||
// "num_bytes": 29,
|
||||
// "operation": "write",
|
||||
// "proto": "udp",
|
||||
// "t": 0.3358025,
|
||||
// "started": 0.335725958,
|
||||
// "oddity": ""
|
||||
// },
|
||||
// {
|
||||
// "address": "180.97.36.63:53",
|
||||
// "failure": null,
|
||||
// "num_bytes": 29,
|
||||
// "operation": "read",
|
||||
// "proto": "udp",
|
||||
// "t": 0.739987666,
|
||||
// "started": 0.335863875,
|
||||
// "oddity": ""
|
||||
// }
|
||||
// ],
|
||||
//
|
||||
// // But we see both in the error and in the oddity
|
||||
// // that the response was "REFUSED"
|
||||
// "lookup_host": [
|
||||
// {
|
||||
// "answers": null,
|
||||
// "engine": "udp",
|
||||
// "failure": "dns_refused_error",
|
||||
// "hostname": "example.com",
|
||||
// "query_type": "A",
|
||||
// "resolver_address": "180.97.36.63:53",
|
||||
// "t": 0.7402975,
|
||||
// "started": 7.2291e-05,
|
||||
// "oddity": "dns.lookup.refused"
|
||||
// },
|
||||
// {
|
||||
// "answers": null,
|
||||
// "engine": "udp",
|
||||
// "failure": "dns_refused_error",
|
||||
// "hostname": "example.com",
|
||||
// "query_type": "AAAA",
|
||||
// "resolver_address": "180.97.36.63:53",
|
||||
// "t": 0.7402975,
|
||||
// "started": 7.2291e-05,
|
||||
// "oddity": "dns.lookup.refused"
|
||||
// }
|
||||
// ],
|
||||
//
|
||||
// // Exercise: do like I did before and decode the messages
|
||||
// "dns_round_trip": [
|
||||
// {
|
||||
// "engine": "udp",
|
||||
// "resolver_address": "180.97.36.63:53",
|
||||
// "raw_query": {
|
||||
// "data": "crkBAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE=",
|
||||
// "format": "base64"
|
||||
// },
|
||||
// "started": 0.000130666,
|
||||
// "t": 0.33509925,
|
||||
// "failure": null,
|
||||
// "raw_reply": {
|
||||
// "data": "crmBBQABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE=",
|
||||
// "format": "base64"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "engine": "udp",
|
||||
// "resolver_address": "180.97.36.63:53",
|
||||
// "raw_query": {
|
||||
// "data": "ywcBAAABAAAAAAAAB2V4YW1wbGUDY29tAAAcAAE=",
|
||||
// "format": "base64"
|
||||
// },
|
||||
// "started": 0.335321333,
|
||||
// "t": 0.740152375,
|
||||
// "failure": null,
|
||||
// "raw_reply": {
|
||||
// "data": "yweBBQABAAAAAAAAB2V4YW1wbGUDY29tAAAcAAE=",
|
||||
// "format": "base64"
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// ## Conclusion
|
||||
//
|
||||
// We have seen how we sending DNS queries over UDP, measure the
|
||||
// results, and what happens on common error conditions.
|
||||
//
|
||||
// -=-=- StopHere -=-=-
|
||||
Reference in New Issue
Block a user