Merge "server/camnetdns: implement more of the DNS protocol"

This commit is contained in:
Mathieu Lonjaret 2016-10-20 22:26:47 +00:00 committed by Gerrit Code Review
commit 46ea7699a8
1 changed files with 125 additions and 29 deletions

View File

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