commit adab1510c992ba09983f6cbeebe46fe07eedaa5d Author: Tero Marttila Date: Sun Jun 19 19:33:25 2016 +0300 scan interface addresses, and send nsupdate diff --git a/addr.go b/addr.go new file mode 100644 index 0000000..080f11b --- /dev/null +++ b/addr.go @@ -0,0 +1,29 @@ +package main + +import ( + "net" + "github.com/miekg/dns" +) + +type Addr struct { + IP net.IP +} + +func (addr Addr) buildRR(name string, ttl int) dns.RR { + if ip4 := addr.IP.To4(); ip4 != nil { + return &dns.A{ + Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: uint32(ttl)}, + A: ip4, + } + } + + if ip6 := addr.IP.To16(); ip6 != nil { + return &dns.AAAA{ + Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: uint32(ttl)}, + AAAA: ip6, + } + } + + return nil +} + diff --git a/main.go b/main.go new file mode 100644 index 0000000..fe7cd64 --- /dev/null +++ b/main.go @@ -0,0 +1,107 @@ +package main + +import ( + "github.com/jessevdk/go-flags" + "github.com/vishvananda/netlink" + "github.com/miekg/dns" + "net" + "fmt" + "log" + "os" + "time" +) + +// zero value is unspec=all +type Family int + +func (f *Family) UnmarshalFlag(value string) error { + switch (value) { + case "inet", "ipv4": + *f = netlink.FAMILY_V4 + case "inet6", "ipv6": + *f = netlink.FAMILY_V6 + default: + return fmt.Errorf("Invalid --family=%v", value) + } + + return nil +} + +const TSIG_FUDGE_SECONDS = 300 +type TSIGAlgorithm string + +func (t *TSIGAlgorithm) UnmarshalFlag(value string) error { + switch (value) { + case "hmac-md5", "md5": + *t = dns.HmacMD5 + case "hmac-sha1", "sha1": + *t = dns.HmacSHA1 + case "hmac-sha256", "sha256": + *t = dns.HmacSHA256 + case "hmac-sha512", "sha512": + *t = dns.HmacSHA512 + default: + return fmt.Errorf("Invalid --tsig-algorithm=%v", value) + } + + return nil +} + +type Options struct { + Verbose bool `long:"verbose" short:"v"` + + Interface string `long:"interface" short:"i" value-name:"IFACE" description:"Use address from interface"` + InterfaceFamily Family `long:"interface-family"` + + Server string `long:"server" value-name:"HOST[:PORT]"` + Timeout time.Duration `long:"timeout" value-name:"DURATION" default:"10s"` + TSIGName string `long:"tsig-name"` + TSIGSecret string `long:"tsig-secret" env:"TSIG_SECRET"` + TSIGAlgorithm TSIGAlgorithm `long:"tsig-algorithm" default:"hmac-sha1."` + + Zone string `long:"zone" description:"Zone to update"` + Name string `long:"name" description:"Name to update"` + TTL int `long:"ttl" default:"60"` +} + +func main() { + var options Options + + if args, err := flags.Parse(&options); err != nil { + log.Fatalf("flags.Parse: %v", err) + os.Exit(1) + } else if len(args) > 0 { + log.Fatalf("Usage: no args") + os.Exit(1) + } + + var update = &Update{ + zone: dns.Fqdn(options.Zone), + name: dns.Fqdn(options.Name), + ttl: options.TTL, + timeout: options.Timeout, + } + + if _, _, err := net.SplitHostPort(options.Server); err == nil { + update.server = options.Server + } else { + update.server = net.JoinHostPort(options.Server, "53") + } + + if options.TSIGName != "" { + log.Printf("using TSIG: %v (algo=%v)", options.TSIGName, options.TSIGAlgorithm) + + update.initTSIG(dns.Fqdn(options.TSIGName), options.TSIGSecret, string(options.TSIGAlgorithm)) + } + + // run + if options.Interface == "" { + + } else if err := update.scan(options.Interface, int(options.InterfaceFamily)); err != nil { + log.Fatalf("scan: %v", err) + } + + if err := update.update(options.Verbose); err != nil { + log.Fatalf("update: %v", err) + } +} diff --git a/update.go b/update.go new file mode 100644 index 0000000..c0a0c00 --- /dev/null +++ b/update.go @@ -0,0 +1,143 @@ +package main + +import ( + "github.com/vishvananda/netlink" + "github.com/miekg/dns" + "time" + "fmt" + "net" + "log" +) + +type Update struct { + zone string + name string + ttl int + + tsig map[string]string + tsigAlgo string + server string + timeout time.Duration + + link netlink.Link + addrs map[string]Addr +} + +func (u *Update) initTSIG(name string, secret string, algo string) { + u.tsig = map[string]string{name: secret} + u.tsigAlgo = algo +} + +// Update state for link +func (u *Update) scan(iface string, family int) error { + link, err := netlink.LinkByName(iface) + if err != nil { + return fmt.Errorf("netlink.LinkByName %v: %v", iface, err) + } + + addrs, err := netlink.AddrList(link, family) + if err != nil { + return fmt.Errorf("netlink.AddrList %v: %v", link, err) + } + + // set + u.addrs = make(map[string]Addr) + + for _, addr := range addrs { + u.applyLinkAddr(link, addr) + } + + return nil +} + +func (u *Update) applyLinkAddr(link netlink.Link, addr netlink.Addr) { + linkUp := link.Attrs().Flags & net.FlagUp != 0 + + if addr.Scope >= int(netlink.SCOPE_LINK) { + return + } + + u.apply(addr.IP, linkUp) +} + +// Update state for address +func (u *Update) apply(ip net.IP, up bool) { + if up { + log.Printf("update: up %v", ip) + + u.addrs[ip.String()] = Addr{IP: ip} + + } else { + log.Printf("update: down %v", ip) + + delete(u.addrs, ip.String()) + } +} + +func (u *Update) buildRR() (rs []dns.RR) { + for _, addr := range u.addrs { + rs = append(rs, addr.buildRR(u.name, u.ttl)) + } + + return rs +} + +func (u *Update) buildMsg() *dns.Msg { + var msg = new(dns.Msg) + + msg.SetUpdate(u.zone) + msg.RemoveName([]dns.RR{&dns.RR_Header{Name:u.name}}) + msg.Insert(u.buildRR()) + + if u.tsig != nil { + for keyName, _ := range u.tsig { + msg.SetTsig(keyName, u.tsigAlgo, TSIG_FUDGE_SECONDS, time.Now().Unix()) + } + } + + return msg +} + +func (u *Update) query(msg *dns.Msg) (*dns.Msg, error) { + var client = new(dns.Client) + + client.DialTimeout = u.timeout + client.ReadTimeout = u.timeout + client.WriteTimeout = u.timeout + + if u.tsig != nil { + client.TsigSecret = u.tsig + } + + msg, _, err := client.Exchange(msg, u.server) + + if err != nil { + return msg, fmt.Errorf("dns:Client.Exchange ... %v: %v", u.server, err) + } + + if msg.Rcode == dns.RcodeSuccess { + return msg, nil + } else { + return msg, fmt.Errorf("rcode=%v", dns.RcodeToString[msg.Rcode]) + } +} + +func (u *Update) update(verbose bool) error { + q := u.buildMsg() + + if verbose { + log.Printf("query:\n%v", q) + } + + r, err := u.query(q) + + if err != nil { + return err + } + + if verbose { + log.Printf("answer:\n%v", r) + } + + return nil +}