From 52eb9dc9fc1e0472aea4fd5bd0bb7ea259431d41 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Tue, 11 Aug 2020 05:11:29 +0000 Subject: Do not do ddns update if ip is same --- .gitignore | 2 +- dns/cilent.go | 81 --------------------------- dns/client.go | 144 ++++++++++++++++++++++++++++++++++++++++++++++++ go.sum | 1 + web/controllers/ddns.go | 13 ++++- 5 files changed, 158 insertions(+), 83 deletions(-) delete mode 100644 dns/cilent.go create mode 100644 dns/client.go diff --git a/.gitignore b/.gitignore index 75c9781..0b56a16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -./dns-service +/dns-service # Generated files have a zzz_ prefix **/zzz_*.go diff --git a/dns/cilent.go b/dns/cilent.go deleted file mode 100644 index 6efe1b5..0000000 --- a/dns/cilent.go +++ /dev/null @@ -1,81 +0,0 @@ -package dns - -import ( - "fmt" - - "github.com/miekg/dns" - - "code.crute.me/mcrute/go_ddns_manager/bind" -) - -type DNSClient struct { - Server string -} - -type DNSTransaction struct { - zone *bind.Zone - key *bind.Key - msg *dns.Msg -} - -func (t *DNSTransaction) Upsert(rrs ...RR) *DNSTransaction { - t.RemoveAll(rrs...).Insert(rrs...) - return t -} - -func (t *DNSTransaction) Insert(rrs ...RR) *DNSTransaction { - t.msg.Insert(toRRSet(t.zone, rrs...)) - return t -} - -func (t *DNSTransaction) Remove(rrs ...RR) *DNSTransaction { - t.msg.Remove(toRRSet(t.zone, rrs...)) - return t -} - -func (t *DNSTransaction) RemoveAll(rrs ...RR) *DNSTransaction { - t.msg.RemoveRRset(toRRSet(t.zone, rrs...)) - return t -} - -func (c *DNSClient) AXFR(zone *bind.Zone) (chan *dns.Envelope, error) { - k := zone.Keys()[0] - t := &dns.Transfer{TsigSecret: k.AsMap()} // Always uses tcp - - m := &dns.Msg{} - m.SetAxfr(zone.Name) - k.Sign(m) - - return t.In(m, fmt.Sprintf("%s:53", c.Server)) -} - -func (c *DNSClient) StartUpdate(zone *bind.Zone) *DNSTransaction { - m := &dns.Msg{} - m.SetUpdate(zone.Name) - - return &DNSTransaction{ - zone: zone, - key: zone.Keys()[0], - msg: m, - } -} - -func (c *DNSClient) SendUpdate(t *DNSTransaction) error { - udp := &dns.Client{Net: "udp", TsigSecret: t.key.AsMap()} - tcp := &dns.Client{Net: "tcp", TsigSecret: t.key.AsMap()} - - t.msg.SetEdns0(4096, false) - t.key.Sign(t.msg) - - in, _, err := udp.Exchange(t.msg, c.Server) - if in != nil && in.Truncated { - // If the TCP request succeeds, the err will reset to nil - in, _, err = tcp.Exchange(t.msg, c.Server) - } - - if err != nil { - return err - } - - return nil -} diff --git a/dns/client.go b/dns/client.go new file mode 100644 index 0000000..f322b45 --- /dev/null +++ b/dns/client.go @@ -0,0 +1,144 @@ +package dns + +import ( + "fmt" + + "github.com/miekg/dns" + + "code.crute.me/mcrute/go_ddns_manager/bind" +) + +type DNSClient struct { + Server string +} + +type DNSTransaction struct { + zone *bind.Zone + key *bind.Key + msg *dns.Msg +} + +func (t *DNSTransaction) Upsert(rrs ...RR) *DNSTransaction { + t.RemoveAll(rrs...).Insert(rrs...) + return t +} + +func (t *DNSTransaction) Insert(rrs ...RR) *DNSTransaction { + t.msg.Insert(toRRSet(t.zone, rrs...)) + return t +} + +func (t *DNSTransaction) Remove(rrs ...RR) *DNSTransaction { + t.msg.Remove(toRRSet(t.zone, rrs...)) + return t +} + +func (t *DNSTransaction) RemoveAll(rrs ...RR) *DNSTransaction { + t.msg.RemoveRRset(toRRSet(t.zone, rrs...)) + return t +} + +func (c *DNSClient) AXFR(zone *bind.Zone) (chan *dns.Envelope, error) { + k := zone.Keys()[0] + t := &dns.Transfer{TsigSecret: k.AsMap()} // Always uses tcp + + m := &dns.Msg{} + m.SetAxfr(zone.Name) + k.Sign(m) + + return t.In(m, fmt.Sprintf("%s:53", c.Server)) +} + +func (c *DNSClient) ReadRemoteZone(zone *bind.Zone) ([]RR, error) { + rrs := []RR{} + seenSoa := false + + data, err := c.AXFR(zone) + if err != nil { + return nil, err + } + + for rd := range data { + for _, r := range rd.RR { + switch dr := FromDNS(r).(type) { + case *SOA: + // Transfers have 2 SOA records, exclude the last one + if !seenSoa { + rrs = append(rrs, dr) + seenSoa = true + } + case RR: + rrs = append(rrs, dr) + default: + // This should only be possible if we somehow are + // missing generated DNS data types. + return nil, fmt.Errorf("Invalid return type") + } + } + } + + return rrs, nil +} + +func (c *DNSClient) StartUpdate(zone *bind.Zone) *DNSTransaction { + m := &dns.Msg{} + m.SetUpdate(zone.Name) + + return &DNSTransaction{ + zone: zone, + key: zone.Keys()[0], + msg: m, + } +} + +func (c *DNSClient) QueryRecursive(zone *bind.Zone, fqdn string, rtype uint16) *DNSTransaction { + m := &dns.Msg{} + m.RecursionDesired = true + m.SetQuestion(fqdn, rtype) + + return &DNSTransaction{ + zone: zone, + key: zone.Keys()[0], + msg: m, + } +} + +func (c *DNSClient) SendUpdate(t *DNSTransaction) error { + udp := &dns.Client{Net: "udp", TsigSecret: t.key.AsMap()} + tcp := &dns.Client{Net: "tcp", TsigSecret: t.key.AsMap()} + + t.msg.SetEdns0(4096, false) + t.key.Sign(t.msg) + + in, _, err := udp.Exchange(t.msg, c.Server) + if in != nil && in.Truncated { + // If the TCP request succeeds, the err will reset to nil + in, _, err = tcp.Exchange(t.msg, c.Server) + } + + if err != nil { + return err + } + + return nil +} + +func (c *DNSClient) SendQuery(t *DNSTransaction) ([]dns.RR, error) { + udp := &dns.Client{Net: "udp", TsigSecret: t.key.AsMap()} + tcp := &dns.Client{Net: "tcp", TsigSecret: t.key.AsMap()} + + t.msg.SetEdns0(4096, false) + t.key.Sign(t.msg) + + in, _, err := udp.Exchange(t.msg, c.Server) + if in != nil && in.Truncated { + // If the TCP request succeeds, the err will reset to nil + in, _, err = tcp.Exchange(t.msg, c.Server) + } + + if err != nil { + return nil, err + } + + return in.Answer, nil +} diff --git a/go.sum b/go.sum index fbb01ae..89417d5 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/web/controllers/ddns.go b/web/controllers/ddns.go index 692b59f..d476255 100644 --- a/web/controllers/ddns.go +++ b/web/controllers/ddns.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/gin-gonic/gin" + godns "github.com/miekg/dns" "code.crute.me/mcrute/go_ddns_manager/dns" "code.crute.me/mcrute/go_ddns_manager/util" @@ -35,7 +36,17 @@ func UpdateDynamicDNS(c *gin.Context) { return } - txn := cfg.DNSClient.StartUpdate(zone).Upsert(&dns.A{ + txn := cfg.DNSClient.QueryRecursive(zone, res, godns.TypeA) + cur, err := cfg.DNSClient.SendQuery(txn) + if err == nil && len(cur) == 1 { + if cur[0].(*godns.A).A.Equal(inip) { + log.Printf("ddns: Update not required for '%s', IP %s", res, inip) + c.String(http.StatusNotModified, "") + return + } + } + + txn = cfg.DNSClient.StartUpdate(zone).Upsert(&dns.A{ Name: part, Ttl: 60, A: inip, -- cgit v1.2.3