Skip to content

Commit

Permalink
Merge pull request #2 from natesales/draft-ietf-dprive-dnsoquic-02
Browse files Browse the repository at this point in the history
add experimental TLS interop mode
  • Loading branch information
natesales committed Feb 23, 2021
2 parents fecbda2 + 234248e commit 6745f24
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 32 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,25 @@
[![License](https://img.shields.io/github/license/natesales/doq?style=for-the-badge)](https://raw.githubusercontent.com/natesales/doq/main/LICENSE)
[![Release](https://img.shields.io/github/v/release/natesales/doq?style=for-the-badge)](https://github.com/natesales/doq/releases)

DNS over QUIC implementation in Go ([draft-ietf-dprive-dnsoquic-02](https://datatracker.ietf.org/doc/draft-ietf-dprive-dnsoquic/?include_text=1))
DNS over QUIC implementation in Go
([draft-ietf-dprive-dnsoquic-02](https://datatracker.ietf.org/doc/draft-ietf-dprive-dnsoquic/?include_text=1))

### Setup

##### Create a self-signed TLS certificate

```bash
openssl req -x509 -newkey rsa:4096 -sha256 -days 356 -nodes -keyout key.pem -out cert.pem -subj "/CN=localhost"
```

##### Sysctl tuning
As per the [quic-go wiki](https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size), quic-go recommends increasing the maximum UDP receive buffer size and will show a warning if this value is too small. For DNS queries where the packet sizes are small to begin with, increasing the value won't yield a performance improvement.

As per the [quic-go wiki](https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size), quic-go recommends
increasing the maximum UDP receive buffer size and will show a warning if this value is too small. For DNS queries where
the packet sizes are small to begin with, increasing the value won't yield a performance improvement.

### Interoperability

This DoQ implementation is designed to be in conformance with `draft-ietf-dprive-dnsoquic-02`, and therefore only
announces the `doq-i02` TLS protocol. For experimental interop testing, `doq.Server` and `doq.Client` can be created
with the `compat` parameter set to true to enable `doq-i02`, `dq`, and `doq` TLS protocols.
3 changes: 2 additions & 1 deletion cmd/client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
var (
server = flag.String("server", "[::1]:784", "DoQ server")
insecureSkipVerify = flag.Bool("insecureSkipVerify", false, "skip TLS certificate validation")
tlsCompat = flag.Bool("tlsCompat", false, "enable TLS compatibility mode")
dnssec = flag.Bool("dnssec", true, "send DNSSEC flag")
rec = flag.Bool("recursion", true, "send RD flag")
queryName = flag.String("queryName", "", "DNS QNAME")
Expand All @@ -36,7 +37,7 @@ func main() {
}

// Connect to DoQ server
doqClient, err := client.New(*server, *insecureSkipVerify)
doqClient, err := client.New(*server, *insecureSkipVerify, *tlsCompat)
if err != nil {
log.Fatalf("client create: %v\n", err)
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/daemon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var (
backend = flag.String("backend", "[::1]:53", "address of backend (UDP) DNS server")
tlsCert = flag.String("tlsCert", "cert.pem", "TLS certificate file")
tlsKey = flag.String("tlsKey", "key.pem", "TLS key file")
tlsCompat = flag.Bool("tlsCompat", false, "enable TLS compatibility mode")
maxProcs = flag.Int("maxProcs", 1, "GOMAXPROCS")
showVersion = flag.Bool("version", false, "show version")
)
Expand All @@ -40,7 +41,7 @@ func main() {

// Create the QUIC listener
log.Infof("starting quic listener on %s\n", *listenAddr)
doqServer, err := server.New(*listenAddr, cert, *backend)
doqServer, err := server.New(*listenAddr, cert, *backend, *tlsCompat)
if err != nil {
log.Fatal(err)
}
Expand Down
14 changes: 12 additions & 2 deletions pkg/client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/lucas-clemente/quic-go"
"github.com/miekg/dns"
log "github.com/sirupsen/logrus"

"github.com/natesales/doq/internal/doqproto"
)

// Client stores a DoQ client
Expand All @@ -16,11 +18,19 @@ type Client struct {
}

// New constructs a new client
func New(server string, tlsInsecureSkipVerify bool) (Client, error) {
func New(server string, tlsInsecureSkipVerify bool, compat bool) (Client, error) {
// Select TLS protocols for DoQ
var tlsProtos []string
if compat {
tlsProtos = doqproto.QuicProtosCompat
} else {
tlsProtos = doqproto.QuicProtos
}

// Connect to DoQ server
session, err := quic.DialAddr(server, &tls.Config{
InsecureSkipVerify: tlsInsecureSkipVerify,
NextProtos: []string{"doq-i02"},
NextProtos: tlsProtos,
}, nil)
if err != nil {
log.Fatal("failed to connect to the server: %v\n", err)
Expand Down
54 changes: 28 additions & 26 deletions pkg/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,15 @@ import (
"github.com/lucas-clemente/quic-go"
"github.com/miekg/dns"
log "github.com/sirupsen/logrus"
)

// Only implementations of the final, published RFC can identify
// themselves as "doq". Until such an RFC exists, implementations MUST
// NOT identify themselves using this string. Implementations of draft
// versions of the protocol MUST add the string "-" and the corresponding
// draft number to the identifier. For example, draft-ietf-dprive-dnsoquic-00
// is identified using the string "doq-i00".
var QuicProtos = []string{"doq-i02"}
"github.com/natesales/doq/internal/doqproto"
)

const (
DoqNoError = 0x00 // No error. This is used when the connection or stream needs to be closed, but there is no error to signal.
DoqInternalError = 0x01 // The DoQ implementation encountered an internal error and is incapable of pursuing the transaction or the connection
QuicMaxIdleTimeout = 5 * time.Minute
DnsMinPacketSize = 12 + 5
doqNoError = 0x00 // No error. This is used when the connection or stream needs to be closed, but there is no error to signal.
doqInternalError = 0x01 // The DoQ implementation encountered an internal error and is incapable of pursuing the transaction or the connection
quicMaxIdleTimeout = 5 * time.Minute
dnsMinPacketSize = 12 + 5
)

// Server stores a DoQ server
Expand All @@ -35,12 +29,20 @@ type Server struct {
}

// New constructs a new Server
func New(addr string, cert tls.Certificate, backend string) (Server, error) {
func New(addr string, cert tls.Certificate, backend string, compat bool) (Server, error) {
// Select TLS protocols for DoQ
var tlsProtos []string
if compat {
tlsProtos = doqproto.QuicProtosCompat
} else {
tlsProtos = doqproto.QuicProtos
}

// Create the QUIC listener
listener, err := quic.ListenAddr(addr, &tls.Config{
Certificates: []tls.Certificate{cert},
NextProtos: QuicProtos,
}, &quic.Config{MaxIdleTimeout: QuicMaxIdleTimeout})
NextProtos: tlsProtos,
}, &quic.Config{MaxIdleTimeout: quicMaxIdleTimeout})
if err != nil {
return Server{}, errors.New("quic listen: " + err.Error())
}
Expand Down Expand Up @@ -83,7 +85,7 @@ func handleClient(session quic.Session, backend string) error {
for {
stream, err := session.AcceptStream(context.Background())
if err != nil { // Close the session if we aren't able to accept the incoming stream
_ = session.CloseWithError(DoqInternalError, "")
_ = session.CloseWithError(doqInternalError, "")
return nil
}

Expand All @@ -96,12 +98,12 @@ func handleClient(session quic.Session, backend string) error {
// Read the DNS message
data, err := ioutil.ReadAll(stream)
if err != nil {
streamCloseErr = DoqInternalError
streamCloseErr = doqInternalError
streamErrChannel <- errors.New("read query: " + err.Error())
return
}
if len(data) < DnsMinPacketSize {
streamCloseErr = DoqInternalError
if len(data) < dnsMinPacketSize {
streamCloseErr = doqInternalError
streamErrChannel <- errors.New("dns packet too small")
return
}
Expand All @@ -110,7 +112,7 @@ func handleClient(session quic.Session, backend string) error {
var dnsMsg dns.Msg
err = dnsMsg.Unpack(data)
if err != nil {
streamCloseErr = DoqInternalError
streamCloseErr = doqInternalError
streamErrChannel <- errors.New("dns message unpack: " + err.Error())
return
}
Expand All @@ -120,7 +122,7 @@ func handleClient(session quic.Session, backend string) error {
opt := dnsMsg.IsEdns0()
for _, option := range opt.Option {
if option.Option() == dns.EDNS0TCPKEEPALIVE {
streamCloseErr = DoqInternalError
streamCloseErr = doqInternalError
streamErrChannel <- errors.New("client sent EDNS0_TCP_KEEPALIVE")
return
}
Expand All @@ -129,15 +131,15 @@ func handleClient(session quic.Session, backend string) error {
// Connect to the DNS backend
conn, err := net.Dial("udp", backend)
if err != nil {
streamCloseErr = DoqInternalError
streamCloseErr = doqInternalError
streamErrChannel <- errors.New("backend connect: " + err.Error())
return
}

// Send query to DNS backend
_, err = conn.Write(data)
if err != nil {
streamCloseErr = DoqInternalError
streamCloseErr = doqInternalError
streamErrChannel <- errors.New("backend query write: " + err.Error())
return
}
Expand All @@ -146,7 +148,7 @@ func handleClient(session quic.Session, backend string) error {
buf := make([]byte, 4096)
size, err := conn.Read(buf)
if err != nil {
streamCloseErr = DoqInternalError
streamCloseErr = doqInternalError
streamErrChannel <- errors.New("backend query read: " + err.Error())
return
}
Expand All @@ -155,13 +157,13 @@ func handleClient(session quic.Session, backend string) error {
// Write the response to the QUIC stream
_, err = stream.Write(buf)
if err != nil {
streamCloseErr = DoqInternalError
streamCloseErr = doqInternalError
streamErrChannel <- errors.New("quic stream write: " + err.Error())
return
}

// No error (success)
streamCloseErr = DoqNoError
streamCloseErr = doqNoError
}()

// Retrieve the stream error
Expand Down

0 comments on commit 6745f24

Please sign in to comment.