diff options
| author | Tero Marttila <[email protected]> | 2016-06-19 19:33:25 +0300 | 
|---|---|---|
| committer | Tero Marttila <[email protected]> | 2016-06-19 19:33:25 +0300 | 
| commit | adab1510c992ba09983f6cbeebe46fe07eedaa5d (patch) | |
| tree | ca335a3b45639e03e044bdca76d003b2d1981d11 | |
scan interface addresses, and send nsupdate
| -rw-r--r-- | addr.go | 29 | ||||
| -rw-r--r-- | main.go | 107 | ||||
| -rw-r--r-- | update.go | 143 | 
3 files changed, 279 insertions, 0 deletions
@@ -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 +} + @@ -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 +}  | 
