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) } }