diff --git a/README.md b/README.md index 31248c2..b24c227 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,11 @@ host. $ sudo vim /etc/powerdns/pdns.d/pipe.conf `pipe.conf` should have the following content. Please adjust the path of `ddns` -and the supplied domain name `--domain=sub.example.com`: +and the values supplied to `--domain` and `--soa_fqdn`: launch=pipe pipebackend-abi-version=1 - pipe-command=/home/user/gocode/bin/ddns --domain=sub.example.com backend + pipe-command=/home/user/gocode/bin/ddns --soa_fqdn=dns.example.com --domain=sub.example.com backend Then restart `pdns`: diff --git a/backend.go b/backend.go index 36a195d..13f9da9 100644 --- a/backend.go +++ b/backend.go @@ -5,60 +5,95 @@ import ( "fmt" "os" "strings" + "time" ) +type responder func() + +func respondWithFAIL() { + fmt.Printf("FAIL\n") +} + +func respondWithEND() { + fmt.Printf("END\n") +} + // This function implements the PowerDNS-Pipe-Backend protocol and generates // the response data it possible func RunBackend(conn *RedisConnection) { + bio := bufio.NewReader(os.Stdin) // handshake with PowerDNS + _, _, _ = bio.ReadLine() fmt.Printf("OK\tDDNS Go Backend\n") - bio := bufio.NewReader(os.Stdin) - for { line, _, err := bio.ReadLine() + if err != nil { + respondWithFAIL() + continue + } - HandleErr(err) - HandleRequest(string(line), conn) + HandleRequest(string(line), conn)() } } -func HandleRequest(line string, conn *RedisConnection) { - defer fmt.Printf("END\n") - +func HandleRequest(line string, conn *RedisConnection) responder { if Verbose { fmt.Printf("LOG\t'%s'\n", line) } parts := strings.Split(line, "\t") if len(parts) != 6 { - return + return respondWithFAIL } query_name := parts[1] query_class := parts[2] - // query_type := parts[3] // TODO Handle SOA Requests + query_type := parts[3] query_id := parts[4] - // get the host part of the fqdn - // pi.d.example.org -> pi - hostname := "" - if strings.HasSuffix(query_name, DdnsDomain) { - hostname = query_name[:len(query_name)-len(DdnsDomain)] - } + var response, record string + record = query_type - if hostname == "" || !conn.HostExist(hostname) { - return - } + switch query_type { + case "SOA": + response = fmt.Sprintf("%s. hostmaster.example.com. %d 1800 3600 7200 5", + DdnsSoaFqdn, getSoaSerial()) - host := conn.GetHost(hostname) + case "NS": + response = DdnsSoaFqdn - record := "A" - if !host.IsIPv4() { - record = "AAAA" + case "A": + case "ANY": + // get the host part of the fqdn: pi.d.example.org -> pi + hostname := "" + if strings.HasSuffix(query_name, DdnsDomain) { + hostname = query_name[:len(query_name)-len(DdnsDomain)] + } + + if hostname == "" || !conn.HostExist(hostname) { + return respondWithFAIL + } + + host := conn.GetHost(hostname) + response = host.Ip + + record = "A" + if !host.IsIPv4() { + record = "AAAA" + } + + default: + return respondWithFAIL } fmt.Printf("DATA\t%s\t%s\t%s\t10\t%s\t%s\n", - query_name, query_class, record, query_id, host.Ip) + query_name, query_class, record, query_id, response) + return respondWithEND +} + +func getSoaSerial() int64 { + // return current time in milliseconds + return time.Now().UnixNano() } diff --git a/ddns.go b/ddns.go index f4a88d4..515d64b 100644 --- a/ddns.go +++ b/ddns.go @@ -12,10 +12,16 @@ func HandleErr(err error) { } } +const ( + CmdBackend string = "backend" + CmdWeb string = "web" +) + var ( DdnsDomain string DdnsWebListenSocket string DdnsRedisHost string + DdnsSoaFqdn string Verbose bool ) @@ -29,28 +35,37 @@ func init() { flag.StringVar(&DdnsRedisHost, "redis", ":6379", "The Redis socket that should be used") + flag.StringVar(&DdnsSoaFqdn, "soa_fqdn", "", + "The FQDN of the DNS server which is returned as a SOA record") + flag.BoolVar(&Verbose, "verbose", false, "Be more verbose") } -func ValidateCommandArgs() { +func ValidateCommandArgs(cmd string) { if DdnsDomain == "" { log.Fatal("You have to supply the domain via --domain=DOMAIN") } else if !strings.HasPrefix(DdnsDomain, ".") { // get the domain in the right format DdnsDomain = "." + DdnsDomain } + + if cmd == CmdBackend { + if DdnsSoaFqdn == "" { + log.Fatal("You have to supply the server FQDN via --soa_fqdn=FQDN") + } + } } func PrepareForExecution() string { flag.Parse() - ValidateCommandArgs() if len(flag.Args()) != 1 { usage() } cmd := flag.Args()[0] + ValidateCommandArgs(cmd) return cmd } @@ -61,10 +76,10 @@ func main() { defer conn.Close() switch cmd { - case "backend": + case CmdBackend: log.Printf("Starting PDNS Backend\n") RunBackend(conn) - case "web": + case CmdWeb: log.Printf("Starting Web Service\n") RunWebService(conn) default: