dyndns-server/nameserver.go
Thomas Preisner 204d5eacf6
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
nameserver.go: use custom resolver to avoid update delays caused by dns caching
2021-09-12 02:08:53 +02:00

80 lines
2 KiB
Go

package main
import (
"context"
"fmt"
"log"
"net"
"os/exec"
"strings"
"time"
)
const (
resolverTimeout = time.Millisecond * time.Duration(8000)
)
func getResolver(nameserver string) *net.Resolver {
return &net.Resolver{
// use go's built-in dns resolver
PreferGo: true,
// specify alternate dialer for go's dns resolver
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: resolverTimeout,
}
return d.DialContext(ctx, network, nameserver)
},
}
}
func requiresRRUpdate(record string, addr net.IP, resolver *net.Resolver) bool {
// TODO: the context could be replaced in order to allow cancelling the dns
// query if the user disconnects prematurely
addrs, err := resolver.LookupIP(context.Background(), "ip", record)
if err != nil {
log.Printf("dns lookup failed: %s", err)
// enforce update, it's better than not trying at all
return true
}
// check if the current ip matches
for _, ip := range addrs {
if ip.Equal(addr) {
// the ip seems to be still up-to-date -> no update required
return false
}
}
return true
}
func updateRR(entry *RRConfig, addr net.IP) error {
query := generateQuery(entry, addr)
return executeQuery(query)
}
func generateQuery(entry *RRConfig, addr net.IP) string {
var q strings.Builder
fmt.Fprintf(&q, "key %s:%s %s\n", entry.Tsigalgo, entry.Tsigid, entry.Tsigkey)
fmt.Fprintf(&q, "server %s\n", entry.Nameserver)
fmt.Fprintf(&q, "zone %s\n", entry.Zonename)
// TODO: check if addr is ipv4 or ipv6 (-> update A or AAAA)
fmt.Fprintf(&q, "delete %s. A\n", entry.Recordname)
fmt.Fprintf(&q, "add %s. %d A %s\n", entry.Recordname, entry.Ttl, addr.String())
fmt.Fprintf(&q, "send\n")
return q.String()
}
func executeQuery(query string) error {
cmd := exec.Command("knsupdate", "-v")
cmd.Stdin = strings.NewReader(query)
output, err := cmd.CombinedOutput()
// TODO: is outputting stdout+stderr on failure even necessary?
if err != nil {
log.Printf("executeQuery failed: %s", output)
}
return err
}