summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTero Marttila <[email protected]>2016-06-19 19:33:25 +0300
committerTero Marttila <[email protected]>2016-06-19 19:33:25 +0300
commitadab1510c992ba09983f6cbeebe46fe07eedaa5d (patch)
treeca335a3b45639e03e044bdca76d003b2d1981d11
scan interface addresses, and send nsupdate
-rw-r--r--addr.go29
-rw-r--r--main.go107
-rw-r--r--update.go143
3 files changed, 279 insertions, 0 deletions
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
+}