Commit 59d1f20b authored by ale's avatar ale

serve authoritative NS records for the zone

parent 34b1341a
package main
import (
"errors"
"flag"
"fmt"
"log"
"strings"
"net"
_ "net/http/pprof"
"git.autistici.org/ale/autoradio"
......@@ -21,11 +24,25 @@ var (
staticDir = flag.String("static-dir", "/usr/share/autoradio/htdocs/static", "Static content directory")
templateDir = flag.String("template-dir", "/usr/share/autoradio/htdocs/templates", "HTML templates directory")
lbPolicy = flag.String("lb-policy", "listeners_available,listeners_score,weighted", "Load balancing rules specification (see godoc documentation for details)")
nameservers = flag.String("nameservers", "", "Comma-separated list of name servers (not IPs) for the zone specified in --domain")
// Default DNS TTL (seconds).
dnsTtl = 5
)
func getFQDN(ips []net.IP) (string, error) {
for _, ip := range ips {
if names, err := net.LookupAddr(ip.String()); err == nil && len(names) > 0 {
// This is a pretty weak criteria for qualification.
name := strings.TrimSuffix(names[0], ".")
if strings.Contains(name, ".") {
return names[0], nil
}
}
}
return "", errors.New("reverse resolution failed")
}
func main() {
log.SetFlags(0)
flag.Parse()
......@@ -42,7 +59,23 @@ func main() {
client := autoradio.NewClient(autoradio.NewEtcdClient(false))
dnsRed := fe.NewDNSRedirector(client, *domain, *publicIPs, dnsTtl)
// If no nameservers are specified, use the fqdn of the local
// host. It is not going to provide a lot of reliability for
// clients that cache the authoritative NS records for long,
// but at least it will work.
var ns []string
if *nameservers != "" {
ns = strings.Split(*nameservers, ",")
} else {
fqdn, err := getFQDN(*publicIPs)
if err != nil {
log.Fatal("Could not determine fully-qualified name of local host, and --nameservers is not specified")
}
log.Printf("autodetected fqdn %s", fqdn)
ns = []string{fqdn}
}
dnsRed := fe.NewDNSRedirector(client, *domain, *publicIPs, dnsTtl, ns)
dnsRed.Start(fmt.Sprintf(":%d", *dnsPort))
red, err := fe.NewHTTPRedirector(client, *domain, *lbPolicy, *staticDir, *templateDir)
......
......@@ -81,7 +81,26 @@ func parseEtcdClusterState(urls []string) *etcdClusterState {
return &state
}
func addDotToList(l []string) []string {
var out []string
for _, s := range l {
if !strings.HasSuffix(s, ".") {
s += "."
}
out = append(out, s)
}
return out
}
// DNSRedirector sends clients to backends using DNS.
//
// The DNSRedirector needs some basic information in order to generate
// the required structural records for the zone (such as NS records).
// Right now the list of nameservers has to be specified manually, but
// it would be nice if we were able to autodetect them, perhaps by
// using the list of nodes (which would require a way to translate
// short name -> fqdn for each node, which is difficult without
// additional requirements on the setup).
type DNSRedirector struct {
client *autoradio.Client
origin string
......@@ -90,12 +109,13 @@ type DNSRedirector struct {
etcdCluster *etcdClusterState
ttl int
soa dns.RR
nameservers []string
queryTable map[string]ipFunc
}
// NewDNSRedirector returns a DNS server for the given origin and
// publicIp. The A records served will have the specified ttl.
func NewDNSRedirector(client *autoradio.Client, origin string, publicIps []net.IP, ttl int) *DNSRedirector {
func NewDNSRedirector(client *autoradio.Client, origin string, publicIps []net.IP, ttl int, nameservers []string) *DNSRedirector {
if !strings.HasSuffix(origin, ".") {
origin += "."
}
......@@ -123,6 +143,7 @@ func NewDNSRedirector(client *autoradio.Client, origin string, publicIps []net.I
originNumParts: len(dns.SplitDomainName(origin)),
publicIps: publicIps,
ttl: ttl,
nameservers: addDotToList(nameservers),
soa: soa,
queryTable: map[string]ipFunc{
"": getAutoradioIPs,
......@@ -259,6 +280,20 @@ func (d *DNSRedirector) handleQuestion(q dns.Question, m *dns.Msg) bool {
m.Answer = append(m.Answer, d.soa)
return true
case query == "" && q.Qtype == dns.TypeNS:
for _, ns := range d.nameservers {
m.Answer = append(m.Answer, &dns.NS{
Hdr: dns.RR_Header{
Name: d.withOrigin(query),
Rrtype: dns.TypeNS,
Class: dns.ClassINET,
Ttl: 3600,
},
Ns: ns,
})
}
return true
case q.Qtype == dns.TypeSRV:
if d.etcdCluster == nil {
return false
......
......@@ -32,7 +32,7 @@ func createTestDNSRedirector(t testing.TB, withNode bool) *DNSRedirector {
client := autoradio.NewClient(etcd)
client.WaitForNodes()
d := NewDNSRedirector(client, "example.com", []net.IP{net.ParseIP("2.3.4.5")}, 30)
d := NewDNSRedirector(client, "example.com", []net.IP{net.ParseIP("2.3.4.5")}, 30, []string{"ns1", "ns2"})
d.updateEtcdCluster()
return d
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment