From 0273e6c5f54c44bc76055748b70076c0d9ce68a6 Mon Sep 17 00:00:00 2001 From: mpl Date: Wed, 19 Oct 2016 00:51:01 +0200 Subject: [PATCH] server/camnetdns: implement more of the DNS protocol As Let's Encrypt DNS server (Unbound) is pretty strict, it wouldn't resolve names camlistore.net until we implemented more of the DNS protocol and fix various things. Since I had no way at first to know what exactly it didn't like, I started by fixing all errors and warnings reported at: http://dnsviz.net/d/camlistore.net/dnssec/ Therefore, this CL adds: -TCP support -NS response -SOA response -MX (empty) response -DNSKEY (empty) response -TXT (empty) response -explicit non-support of EDNS Then I found out we also needed this: -https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00 -CAA response. A proper response is optional here, as Let's Encrypt only enforces the response if it finds one. But we do have to reply. Fixes #867 Change-Id: Ib45f8a642cd83cf19c8ab36435644a2c645a70e7 --- server/camnetdns/camnetdns.go | 154 +++++++++++++++++++++++++++------- 1 file changed, 125 insertions(+), 29 deletions(-) diff --git a/server/camnetdns/camnetdns.go b/server/camnetdns/camnetdns.go index d92706cb9..487288a7d 100644 --- a/server/camnetdns/camnetdns.go +++ b/server/camnetdns/camnetdns.go @@ -23,6 +23,7 @@ import ( "flag" "log" "net" + "strings" "camlistore.org/pkg/sorted" @@ -78,14 +79,22 @@ func NewDNSServer(src sorted.KeyValue) *DNSServer { } func (ds *DNSServer) HandleLookup(name string) (string, error) { - return ds.dataSource.Get(name) + // Lowercase it all, to satisfy https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00 + return ds.dataSource.Get(strings.ToLower(name)) } +const ( + domain = "camlistore.net." + authorityNS = "camnetdns.camlistore.org." + // Increment after every change with format YYYYMMDDnn. + soaSerial = 2016102003 +) + var ( authoritySection = &dns.NS{ - Ns: "camnetdns.camlistore.org.", + Ns: authorityNS, Hdr: dns.RR_Header{ - Name: "camlistore.net.", + Name: domain, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: DefaultResponseTTL, @@ -94,61 +103,138 @@ var ( additionalSection = &dns.A{ A: net.ParseIP(*flagServerIP), Hdr: dns.RR_Header{ - Name: "camnetdns.camlistore.org.", + Name: authorityNS, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: DefaultResponseTTL, }, } + startOfAuthoritySection = &dns.SOA{ + Hdr: dns.RR_Header{ + Name: domain, + Rrtype: dns.TypeSOA, + Class: dns.ClassINET, + Ttl: DefaultResponseTTL, + }, + Ns: authorityNS, + Mbox: "admin.camlistore.org.", + Serial: soaSerial, + Refresh: 3600, // TODO(mpl): set them lower once we got everything right. + Retry: 3600, + Expire: 86500, + Minttl: DefaultResponseTTL, + } ) +func commonHeader(q dns.Question) dns.RR_Header { + return dns.RR_Header{ + Name: q.Name, + Rrtype: q.Qtype, + Class: dns.ClassINET, + Ttl: DefaultResponseTTL, + } +} + func (ds *DNSServer) ServeDNS(rw dns.ResponseWriter, mes *dns.Msg) { resp := new(dns.Msg) + if mes.IsEdns0() != nil { + // Because apparently, if we're not going to handle EDNS + // properly, i.e. by returning an OPT section as well, we should + // return an RcodeFormatError. Not seen in the RFC, but doing that + // addresses some of the warnings from + // http://dnsviz.net/d/granivo.re/dnssec/ + log.Print("unhandled EDNS message\n") + resp.SetRcode(mes, dns.RcodeFormatError) + if err := rw.WriteMsg(resp); err != nil { + log.Printf("error responding to DNS query: %s", err) + } + return + } for _, q := range mes.Question { log.Printf("DNS request from %s: %s", rw.RemoteAddr(), &q) + if q.Qclass != dns.ClassINET { + log.Printf("error: got invalid DNS question class %d\n", q.Qclass) + continue + } + + // TODO(mpl): according to + // https://community.letsencrypt.org/t/is-lets-encrypt-dns-not-liking-my-domain-name-server/21303/18 + // we should probably always start here by doing a lookup and reply + // with RcodeNameError if there's no record, regardless of the + // query type. + switch q.Qtype { + // As long as we send a reply (even an empty one), we apparently + // look compliant. Or at least more than if we replied with + // RcodeNotImplemented. + case dns.TypeDNSKEY, dns.TypeTXT, dns.TypeMX: + break + + case dns.TypeSOA: + resp.Answer = []dns.RR{startOfAuthoritySection} + resp.Extra = []dns.RR{additionalSection} + + case dns.TypeNS: + resp.Answer = []dns.RR{authoritySection} + resp.Extra = []dns.RR{additionalSection} + + case dns.TypeCAA: + _, err := ds.HandleLookup(q.Name) + if err != nil { + log.Println(err) + continue + } + header := commonHeader(q) + rr := &dns.CAA{ + Hdr: header, + Flag: 1, + Tag: "issue", + Value: "letsencrypt.org", + } + resp.Answer = []dns.RR{rr} + case dns.TypeA, dns.TypeAAAA: val, err := ds.HandleLookup(q.Name) if err != nil { log.Println(err) continue } - - if q.Qclass != dns.ClassINET { - log.Printf("error: got invalid DNS question class %d\n", q.Qclass) - continue - } - - header := dns.RR_Header{ - Name: q.Name, - Rrtype: q.Qtype, - Class: dns.ClassINET, - Ttl: DefaultResponseTTL, - } - + ip := net.ParseIP(val) + // TODO(mpl): maybe we should have a distinct memstore for each type? + isIP6 := strings.Contains(ip.String(), ":") + header := commonHeader(q) var rr dns.RR - // not really super sure why these have to be different types if q.Qtype == dns.TypeA { + if isIP6 { + break + } rr = &dns.A{ - A: net.ParseIP(val), + A: ip, Hdr: header, } } else if q.Qtype == dns.TypeAAAA { + if !isIP6 { + break + } rr = &dns.AAAA{ - AAAA: net.ParseIP(val), + AAAA: ip, Hdr: header, } } else { panic("unreachable") } - resp.Answer = []dns.RR{rr} + // Not necessary, but I think they help. + resp.Ns = []dns.RR{authoritySection} + resp.Extra = []dns.RR{additionalSection} default: log.Printf("unhandled qtype: %d\n", q.Qtype) resp.SetRcode(mes, dns.RcodeNotImplemented) - rw.WriteMsg(resp) + if err := rw.WriteMsg(resp); err != nil { + log.Printf("error responding to DNS query: %s", err) + } return } break @@ -156,10 +242,6 @@ func (ds *DNSServer) ServeDNS(rw dns.ResponseWriter, mes *dns.Msg) { resp.SetReply(mes) resp.Authoritative = true - // Not necessary, but I think they can help. - resp.Ns = []dns.RR{authoritySection} - resp.Extra = []dns.RR{additionalSection} - if err := rw.WriteMsg(resp); err != nil { log.Printf("error responding to DNS query: %s", err) } @@ -174,17 +256,31 @@ func main() { if err := memkv.Set("6401800c.camlistore.net.", "159.203.246.79"); err != nil { panic(err) } - if err := memkv.Set("camlistore.net.", *flagServerIP); err != nil { + if err := memkv.Set(domain, *flagServerIP); err != nil { panic(err) } if err := memkv.Set("www.camlistore.net.", *flagServerIP); err != nil { panic(err) } + if err := memkv.Set("wip.camlistore.net.", "104.199.42.193"); err != nil { + panic(err) + } ds := NewDNSServer(memkv) log.Printf("serving DNS on %s\n", *addr) - if err := dns.ListenAndServe(*addr, "udp", ds); err != nil { - log.Fatal(err) + tcperr := make(chan error, 1) + udperr := make(chan error, 1) + go func() { + tcperr <- dns.ListenAndServe(*addr, "tcp", ds) + }() + go func() { + udperr <- dns.ListenAndServe(*addr, "udp", ds) + }() + select { + case err := <-tcperr: + log.Fatalf("DNS over TCP error: %v", err) + case err := <-udperr: + log.Fatalf("DNS error: %v", err) } }