diff --git a/main.go b/main.go index e9112a9..b8242b7 100644 --- a/main.go +++ b/main.go @@ -9,18 +9,20 @@ import ( type Options struct { Verbose bool `long:"verbose" short:"v"` + Watch bool `long:"watch" description:"Watch for interface changes"` 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"` + Retry time.Duration `long:"retry" value-name:"DURATION" default:"30s"` 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"` - TTL int `long:"ttl" default:"60"` + TTL time.Duration `long:"ttl" default:"60s"` Args struct { Name string `description:"DNS Name to update"` @@ -36,8 +38,10 @@ func main() { } var update = Update{ - ttl: options.TTL, + ttl: int(options.TTL.Seconds()), timeout: options.Timeout, + retry: options.Retry, + verbose: options.Verbose, } if err := update.Init(options.Args.Name, options.Zone, options.Server); err != nil { @@ -57,18 +61,37 @@ func main() { } // addrs - var addrs = new(AddrSet) - - if options.Interface == "" { - - } else if err := addrs.ScanInterface(options.Interface, options.InterfaceFamily); err != nil { + addrs, err := InterfaceAddrs(options.Interface, options.InterfaceFamily) + if err != nil { log.Fatalf("addrs scan: %v", err) } // update - if err := update.Update(addrs, options.Verbose); err != nil { - log.Fatalf("update: %v", err) + update.Start() + + for { + log.Printf("update...") + + if err := update.Update(addrs); err != nil { + log.Fatalf("update: %v", err) + } + + if !options.Watch { + break + } + + if err := addrs.Read(); err != nil { + log.Fatalf("addrs read: %v", err) + } else { + log.Printf("addrs update...") + } + } + + log.Printf("wait...") + + if err := update.Done(); err != nil { + log.Printf("update done: %v", err) } else { - log.Printf("update: ok") + log.Printf("update done") } } diff --git a/update.go b/update.go index 778423f..e9750a7 100644 --- a/update.go +++ b/update.go @@ -8,15 +8,27 @@ import ( "log" ) +type updateState struct { + updateZone string + removeNames []dns.RR + inserts []dns.RR +} + type Update struct { + ttl int + timeout time.Duration + retry time.Duration + verbose bool + zone string name string - ttl int tsig map[string]string tsigAlgo TSIGAlgorithm server string - timeout time.Duration + + updateChan chan updateState + doneChan chan error } func (u *Update) Init(name string, zone string, server string) error { @@ -78,20 +90,24 @@ func (u *Update) buildAddr(ip net.IP) dns.RR { return nil } -func (u *Update) buildAddrs(addrs *AddrSet) (rs []dns.RR) { - for _, ip := range addrs.addrs { - rs = append(rs, u.buildAddr(ip)) +func (u *Update) buildState(addrs *AddrSet) (state updateState, err error) { + state.updateZone = u.zone + state.removeNames = []dns.RR{ + &dns.RR_Header{Name:u.name}, } - return rs -} + addrs.Each(func(ip net.IP){ + state.inserts = append(state.inserts, u.buildAddr(ip)) + }) -func (u *Update) buildMsg(addrs *AddrSet) *dns.Msg { + return +} +func (u *Update) buildQuery(state updateState) *dns.Msg { var msg = new(dns.Msg) - msg.SetUpdate(u.zone) - msg.RemoveName([]dns.RR{&dns.RR_Header{Name:u.name}}) - msg.Insert(u.buildAddrs(addrs)) + msg.SetUpdate(state.updateZone) + msg.RemoveName(state.removeNames) + msg.Insert(state.inserts) if u.tsig != nil { for keyName, _ := range u.tsig { @@ -126,11 +142,13 @@ func (u *Update) query(msg *dns.Msg) (*dns.Msg, error) { } } -func (u *Update) Update(addrs *AddrSet, verbose bool) error { - q := u.buildMsg(addrs) +func (u *Update) update(state updateState) error { + q := u.buildQuery(state) - if verbose { - log.Printf("query:\n%v", q) + if u.verbose { + log.Printf("update query:\n%v", q) + } else { + log.Printf("update query...") } r, err := u.query(q) @@ -139,9 +157,101 @@ func (u *Update) Update(addrs *AddrSet, verbose bool) error { return err } - if verbose { - log.Printf("answer:\n%v", r) + if u.verbose { + log.Printf("update answer:\n%v", r) + } else { + log.Printf("update answer") } return nil } + +func (u *Update) run() { + var state updateState + var retry = 0 + var retryTimer = time.NewTimer(time.Duration(0)) + var updateChan = u.updateChan + var updateError error + + defer func(){u.doneChan <-updateError}() + + for { + select { + case updateState, running := <-updateChan: + if running { + // Update() called + state = updateState + + } else if retry > 0 { + // Done() called, but still waiting for retry... + updateChan = nil + continue + + } else { + // Done() called, no retrys or updates remaining + return + } + + case <-retryTimer.C: + if retry == 0 { + // spurious timer event.. + continue + } + + // trigger retry + } + + if err := u.update(state); err != nil { + log.Printf("update (retry=%v) error: %v", retry, err) + + updateError = err + retry++ + } else { + // success + updateError = nil + retry = 0 + } + + if retry == 0 && updateChan == nil { + // done, no more updates + return + + } else if retry == 0 { + // wait for next update + retryTimer.Stop() + + } else { + retryTimeout := time.Duration(retry * int(u.retry)) + + // wait for next retry + // TODO: exponential backoff? + retryTimer.Reset(retryTimeout) + + log.Printf("update retry in %v...", retryTimeout) + } + } +} + +func (u *Update) Start() { + u.updateChan = make(chan updateState) + + go u.run() +} + +func (u *Update) Update(addrs *AddrSet) error { + if state, err := u.buildState(addrs); err != nil { + return err + } else { + u.updateChan <- state + } + + return nil +} + +func (u *Update) Done() error { + u.doneChan = make(chan error) + + close(u.updateChan) + + return <-u.doneChan +}