mirror of https://github.com/perkeep/perkeep.git
Merge "server/camnetdns: implement more of the DNS protocol"
This commit is contained in:
commit
46ea7699a8
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue