use --watch to continuously watch for addr changes, and update with retry
This commit is contained in:
parent
3a9d9d6f61
commit
48b529ef7b
43
main.go
43
main.go
|
@ -9,18 +9,20 @@ import (
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Verbose bool `long:"verbose" short:"v"`
|
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"`
|
Interface string `long:"interface" short:"i" value-name:"IFACE" description:"Use address from interface"`
|
||||||
InterfaceFamily Family `long:"interface-family"`
|
InterfaceFamily Family `long:"interface-family"`
|
||||||
|
|
||||||
Server string `long:"server" value-name:"HOST[:PORT]"`
|
Server string `long:"server" value-name:"HOST[:PORT]"`
|
||||||
Timeout time.Duration `long:"timeout" value-name:"DURATION" default:"10s"`
|
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"`
|
TSIGName string `long:"tsig-name"`
|
||||||
TSIGSecret string `long:"tsig-secret" env:"TSIG_SECRET"`
|
TSIGSecret string `long:"tsig-secret" env:"TSIG_SECRET"`
|
||||||
TSIGAlgorithm TSIGAlgorithm `long:"tsig-algorithm" default:"hmac-sha1."`
|
TSIGAlgorithm TSIGAlgorithm `long:"tsig-algorithm" default:"hmac-sha1."`
|
||||||
|
|
||||||
Zone string `long:"zone" description:"Zone to update"`
|
Zone string `long:"zone" description:"Zone to update"`
|
||||||
TTL int `long:"ttl" default:"60"`
|
TTL time.Duration `long:"ttl" default:"60s"`
|
||||||
|
|
||||||
Args struct {
|
Args struct {
|
||||||
Name string `description:"DNS Name to update"`
|
Name string `description:"DNS Name to update"`
|
||||||
|
@ -36,8 +38,10 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var update = Update{
|
var update = Update{
|
||||||
ttl: options.TTL,
|
ttl: int(options.TTL.Seconds()),
|
||||||
timeout: options.Timeout,
|
timeout: options.Timeout,
|
||||||
|
retry: options.Retry,
|
||||||
|
verbose: options.Verbose,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := update.Init(options.Args.Name, options.Zone, options.Server); err != nil {
|
if err := update.Init(options.Args.Name, options.Zone, options.Server); err != nil {
|
||||||
|
@ -57,18 +61,37 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addrs
|
// addrs
|
||||||
var addrs = new(AddrSet)
|
addrs, err := InterfaceAddrs(options.Interface, options.InterfaceFamily)
|
||||||
|
if err != nil {
|
||||||
if options.Interface == "" {
|
|
||||||
|
|
||||||
} else if err := addrs.ScanInterface(options.Interface, options.InterfaceFamily); err != nil {
|
|
||||||
log.Fatalf("addrs scan: %v", err)
|
log.Fatalf("addrs scan: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update
|
// update
|
||||||
if err := update.Update(addrs, options.Verbose); err != nil {
|
update.Start()
|
||||||
log.Fatalf("update: %v", err)
|
|
||||||
|
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 {
|
} else {
|
||||||
log.Printf("update: ok")
|
log.Printf("update done")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
144
update.go
144
update.go
|
@ -8,15 +8,27 @@ import (
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type updateState struct {
|
||||||
|
updateZone string
|
||||||
|
removeNames []dns.RR
|
||||||
|
inserts []dns.RR
|
||||||
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
|
ttl int
|
||||||
|
timeout time.Duration
|
||||||
|
retry time.Duration
|
||||||
|
verbose bool
|
||||||
|
|
||||||
zone string
|
zone string
|
||||||
name string
|
name string
|
||||||
ttl int
|
|
||||||
|
|
||||||
tsig map[string]string
|
tsig map[string]string
|
||||||
tsigAlgo TSIGAlgorithm
|
tsigAlgo TSIGAlgorithm
|
||||||
server string
|
server string
|
||||||
timeout time.Duration
|
|
||||||
|
updateChan chan updateState
|
||||||
|
doneChan chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Update) Init(name string, zone string, server string) 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
|
return nil
|
||||||
}
|
}
|
||||||
func (u *Update) buildAddrs(addrs *AddrSet) (rs []dns.RR) {
|
func (u *Update) buildState(addrs *AddrSet) (state updateState, err error) {
|
||||||
for _, ip := range addrs.addrs {
|
state.updateZone = u.zone
|
||||||
rs = append(rs, u.buildAddr(ip))
|
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)
|
var msg = new(dns.Msg)
|
||||||
|
|
||||||
msg.SetUpdate(u.zone)
|
msg.SetUpdate(state.updateZone)
|
||||||
msg.RemoveName([]dns.RR{&dns.RR_Header{Name:u.name}})
|
msg.RemoveName(state.removeNames)
|
||||||
msg.Insert(u.buildAddrs(addrs))
|
msg.Insert(state.inserts)
|
||||||
|
|
||||||
if u.tsig != nil {
|
if u.tsig != nil {
|
||||||
for keyName, _ := range u.tsig {
|
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 {
|
func (u *Update) update(state updateState) error {
|
||||||
q := u.buildMsg(addrs)
|
q := u.buildQuery(state)
|
||||||
|
|
||||||
if verbose {
|
if u.verbose {
|
||||||
log.Printf("query:\n%v", q)
|
log.Printf("update query:\n%v", q)
|
||||||
|
} else {
|
||||||
|
log.Printf("update query...")
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := u.query(q)
|
r, err := u.query(q)
|
||||||
|
@ -139,9 +157,101 @@ func (u *Update) Update(addrs *AddrSet, verbose bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if verbose {
|
if u.verbose {
|
||||||
log.Printf("answer:\n%v", r)
|
log.Printf("update answer:\n%v", r)
|
||||||
|
} else {
|
||||||
|
log.Printf("update answer")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue