scan interface addresses, and send nsupdate

This commit is contained in:
Tero Marttila 2016-06-19 19:33:25 +03:00
commit adab1510c9
3 changed files with 279 additions and 0 deletions

29
addr.go Normal file
View File

@ -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
}

107
main.go Normal file
View File

@ -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)
}
}

143
update.go Normal file
View File

@ -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
}