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 }