package zone import ( "fmt" "sync" "git.giaever.org/joachimmg/go-log.git/log" "git.giaever.org/joachimmg/m-dns/config" "git.giaever.org/joachimmg/m-dns/errors" "git.giaever.org/joachimmg/m-dns/host" "github.com/miekg/dns" ) type Zone interface { Records(q dns.Question) []dns.RR } type Resource struct { sync.Mutex host.Host } func (r *Resource) qType(q dns.Question) string { switch q.Qtype { case dns.TypeANY: return "TypeANY" case dns.TypePTR: return "TypePTR" case dns.TypeSRV: return "TypeSRV" case dns.TypeA: return "TypeA" case dns.TypeAAAA: return "TypeAAAA" case dns.TypeTXT: return "TypeTXT" default: return fmt.Sprintf("%d", q.Qtype) } } func New(h host.Host) (Zone, error) { r := new(Resource) if r == nil { log.Traceln(errors.Zone, errors.OutOfMemory) return nil, errors.OutOfMemory } if h == nil { log.Traceln(errors.Zone, errors.HostIsNil) return nil, errors.HostIsNil } r.Host = h return r, nil } func (r *Resource) hdr(n string, rt uint16) dns.RR_Header { return dns.RR_Header{ Name: n, Rrtype: rt, Class: dns.ClassINET, Ttl: config.DefaultTTL, } } func (r *Resource) ptrRecord(q dns.Question, p host.HostString) dns.RR { r.Lock() defer r.Unlock() return &dns.PTR{ Hdr: r.hdr(q.Name, dns.TypePTR), Ptr: p.String(), } } func (r *Resource) srvRecord(q dns.Question) dns.RR { r.Lock() defer r.Unlock() log.Traceln("srvRecord") return &dns.SRV{ Hdr: r.hdr(q.Name, dns.TypeSRV), Priority: 10, Weight: 1, Port: r.GetPort().Uint16(), Target: r.GetHostnameAddr().String(), } } func (r *Resource) txtRecord(q dns.Question) dns.RR { r.Lock() defer r.Unlock() return &dns.TXT{ Hdr: r.hdr(q.Name, dns.TypeTXT), Txt: r.GetTXTs(), } } func (r *Resource) records(rr ...dns.RR) []dns.RR { rrn := make([]dns.RR, 0) if rr == nil { return rrn } return append(rrn, rr...) } func (r *Resource) aRecord(q dns.Question, ip host.HostIP) dns.RR { r.Lock() defer r.Unlock() return &dns.A{ Hdr: r.hdr(r.GetHostnameAddr().String(), dns.TypeA), A: ip.AsIP(), } } func (r *Resource) aaaaRecord(q dns.Question, ip host.HostIP) dns.RR { r.Lock() defer r.Unlock() return &dns.AAAA{ Hdr: r.hdr(r.GetHostnameAddr().String(), dns.TypeAAAA), AAAA: ip.AsIP(), } } func (r *Resource) axRecords(q dns.Question, atype uint16) []dns.RR { a := r.records(nil)[:0] for _, ip := range r.GetIPs() { switch atype { case dns.TypeA: if _, ipType := ip.Type(); ipType == host.IPv4 { a = append(a, r.aRecord(q, ip)) } case dns.TypeAAAA: if _, ipType := ip.Type(); ipType == host.IPv6 { a = append(a, r.aaaaRecord(q, ip)) } } } if len(a) == 0 { return nil } return a } // Records: Return DNS records based on the Question func (r *Resource) Records(q dns.Question) []dns.RR { log.Traceln(errors.Zone, "Records", q.Name, r.qType(q)) switch q.Name { case r.GetInstanceAddr().String(): // RFC 6763, 13.3 "instance"._. return r.iRecords(q) case r.GetServiceAddr().String(): // RFC 6763, 13.1 <_service>. return r.sRecords(q) case r.GetDiscoveryAddr().String(): return r.dRecords(q) case r.GetHostnameAddr().String(): switch q.Qtype { case dns.TypeANY, dns.TypeA, dns.TypeAAAA: return r.iRecords(q) } fallthrough default: return nil } } // iRecords: Instance records func (r *Resource) iRecords(q dns.Question) []dns.RR { switch q.Qtype { case dns.TypeANY: return append(r.iRecords(dns.Question{ Name: r.GetInstanceAddr().String(), Qtype: dns.TypeSRV, }), r.iRecords(dns.Question{ Name: r.GetInstanceAddr().String(), Qtype: dns.TypeTXT, })...) case dns.TypeA: return r.axRecords(q, dns.TypeA) case dns.TypeAAAA: return r.axRecords(q, dns.TypeAAAA) case dns.TypeSRV: return append( r.records(r.srvRecord(q)), append( r.iRecords(dns.Question{ Name: r.GetInstanceAddr().String(), Qtype: dns.TypeA, }), r.iRecords(dns.Question{ Name: r.GetInstanceAddr().String(), Qtype: dns.TypeAAAA, })..., )..., ) case dns.TypeTXT: return r.records(r.txtRecord(q)) default: log.Traceln(errors.Zone, "None iRecord for", r.qType(q)) return nil } } // sRecords: Service records func (r *Resource) sRecords(q dns.Question) []dns.RR { switch q.Qtype { case dns.TypeANY, dns.TypePTR: return append( r.records(r.ptrRecord(q, r.GetInstanceAddr())), r.iRecords(dns.Question{ Name: r.GetInstanceAddr().String(), Qtype: dns.TypeANY, })..., ) default: log.Traceln(errors.Zone, "None sRecord for", r.qType(q)) return nil } } // dRecords: DNS-SD / Discovery aka enumerate func (r *Resource) dRecords(q dns.Question) []dns.RR { switch q.Qtype { case dns.TypeANY, dns.TypePTR: return r.records(r.ptrRecord(q, r.GetServiceAddr())) default: log.Traceln(errors.Zone, "None dRecord for", r.qType(q)) return nil } }