package main import ( "context" "errors" "github.com/miekg/dns" "net" "time" ) type DnsUpdate struct { Timeout int TSigKeyName string TSigKey string TSigAlgorithm string DnsHost string Zone string } func NewTestInstance() *DnsUpdate { return &DnsUpdate{ DnsHost: "45.129.181.183:53", Timeout: 300, TSigKeyName: "ddns-key", TSigAlgorithm: dns.HmacSHA512, TSigKey: "rcDW6VdBFV8+LKH4JHXKRan0ZzoJBbbfeK6sL8VOSM60nt99Z/0jQbDp9PyF3q9TsmEBb0rTI2txySIH998Vfw==", Zone: "hub.voglfrei.net", } } func (d *DnsUpdate) createIp4Addr(host string, addr net.IP) *dns.A { ip := addr.To4() if ip == nil { return nil } return &dns.A{ Hdr: dns.RR_Header{ Name: dns.Fqdn(host+"."+d.Zone), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: uint32(d.Timeout), }, A: ip, } } func (d *DnsUpdate) createIp6Addr(host string, addr net.IP) *dns.AAAA { ip := addr.To16() if ip == nil { return nil } return &dns.AAAA{ Hdr: dns.RR_Header{ Name: dns.Fqdn(host+"."+d.Zone), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: uint32(d.Timeout), }, AAAA: ip, } } func (d *DnsUpdate) createRemovals(host string) []dns.RR { return []dns.RR{ &dns.RR_Header{Name: dns.Fqdn(host+"."+d.Zone)}, } } func (d *DnsUpdate) createInserts(host string, addr net.IP) []dns.RR { var rr = make([]dns.RR,0) if r := d.createIp4Addr(host, addr); r != nil { rr = append(rr, r) } if r := d.createIp6Addr(host, addr); r != nil { rr = append(rr, r) } return rr } func (d *DnsUpdate) exchange(msg *dns.Msg) (*dns.Msg, error) { var client = new(dns.Client) timeout := time.Duration(d.Timeout * int(time.Second)) client.DialTimeout = timeout client.ReadTimeout = timeout client.WriteTimeout = timeout client.TsigSecret = map[string]string {dns.Fqdn(d.TSigKeyName): d.TSigKey } r, _, err := client.Exchange(msg, d.DnsHost) if err != nil { return nil, err } if r.Rcode == dns.RcodeSuccess { return r, nil } else { return nil, errors.New(dns.RcodeToString[r.Rcode]) } } func (d *DnsUpdate) Remove(host string) error { var msg = new(dns.Msg) msg.SetUpdate(dns.Fqdn(d.Zone)) msg.RemoveName(d.createRemovals(host)) msg.SetTsig(dns.Fqdn(d.TSigKeyName), d.TSigAlgorithm, uint16(d.Timeout), time.Now().Unix()) _, err := d.exchange(msg) return err } func (d *DnsUpdate) Resolve(host string) ([]string, error) { var msg = new(dns.Msg) msg.SetQuestion(dns.Fqdn(host + "." + d.Zone), dns.TypeA) r, err := d.exchange(msg) if err != nil {return nil, err} hosts := make([]string, 0) for _, v := range r.Answer { if v.Header().Rrtype == dns.TypeA { hosts = append(hosts, v.(*dns.A).A.To4().String()) } else if v.Header().Rrtype == dns.TypeAAAA { hosts = append(hosts, v.(*dns.AAAA).AAAA.To16().String()) } } return hosts, nil } func (d *DnsUpdate) ExternalResolve(host string) ([]string, error ) { r := &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { d := net.Dialer{ Timeout: time.Millisecond * time.Duration(10000), } return d.DialContext(ctx, "udp", "8.8.8.8:53") }, } ip, err := r.LookupHost(context.Background(), host + "." + d.Zone) if err != nil { return nil, err } return ip, nil } func (d *DnsUpdate) Update(host string, addrs ...string) error { var err error err = d.Remove(host) if err != nil {return err } return d.AddMany(host, addrs...) } func (d *DnsUpdate) AddMany(host string, addrs ...string) error { var err error for _, v := range addrs { err = d.Add(host, v) if err != nil {return err } } return nil } func (d *DnsUpdate) Add(host string, addr string) error { ip, err := net.ResolveIPAddr("ip", addr) if err != nil { return err } var msg = new(dns.Msg) msg.SetUpdate(dns.Fqdn(d.Zone)) msg.Insert(d.createInserts(host, ip.IP)) msg.SetTsig(dns.Fqdn(d.TSigKeyName), d.TSigAlgorithm, uint16(d.Timeout), time.Now().Unix()) _, err = d.exchange(msg) return err }