89 lines
2.4 KiB
Go
89 lines
2.4 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)
|
|
},
|
|
}
|
|
}
|
|
|
|
// FIXME: mockdns.Resolver from foxccp/go-mockdns, which is used for testing,
|
|
// does not implement the complete net.Resolver interface. Thus, we need to
|
|
// define a more minimalistic resolver interface compatible with both
|
|
// net.Resolver and mockdns.Resolver to avoid compiler errors.
|
|
// Maybe there is a better way to circumvent this though.
|
|
type localResolver interface {
|
|
LookupHost(context.Context, string) ([]string, error)
|
|
}
|
|
|
|
func requiresRRUpdate(record string, addr net.IP, resolver localResolver) bool {
|
|
// TODO: the context could be replaced in order to allow cancelling the dns
|
|
// query if the user disconnects prematurely
|
|
addrs, err := resolver.LookupHost(context.Background(), 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 addr.Equal(net.ParseIP(ip)) {
|
|
// 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
|
|
}
|