Skip to content
Snippets Groups Projects
Commit 59d1f20b authored by ale's avatar ale
Browse files

serve authoritative NS records for the zone

parent 34b1341a
No related branches found
No related tags found
No related merge requests found
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
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment