
# 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.

