summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTero Marttila <[email protected]>2016-06-19 21:30:44 +0300
committerTero Marttila <[email protected]>2016-06-19 21:30:44 +0300
commit48b529ef7b440d277d53bde8a2da7ba2ca44a4f5 (patch)
treef8ab00f194981ec9dadeba286e4ade3259522d85
parent3a9d9d6f612ef216cd5c34d303905208ecc18591 (diff)
use --watch to continuously watch for addr changes, and update with retry
-rw-r--r--main.go43
-rw-r--r--update.go144
2 files changed, 160 insertions, 27 deletions
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
+}