plugin-dns/plugins/mdns/mdns.go

130 lines
3.9 KiB
Go

package mdns
import (
"context"
"net"
"strings"
"syscall"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/mdns/resolve1"
clog "github.com/coredns/coredns/plugin/pkg/log"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
var log = clog.NewWithPlugin("mdns")
type MDNS struct {
Next plugin.Handler
Resolver *resolve1.Manager
Ifc int32
}
func insertAnAnswer(answers []dns.RR, answer dns.RR, index int) []dns.RR {
return append(answers[:index], append([]dns.RR{answer}, answers[index:]...)...)
}
func (m MDNS) AddARecord(msg *dns.Msg, state *request.Request, name string, addresses []struct {
V0 int32
V1 int32
V2 []byte
}) bool {
// Add A and AAAA record for name (if it exists) to msg.
// A records need to be returned in A queries, this function
// provides common code for doing so.
// Success is always returned if any answers found, even if they don't match question type
// A noerror on A and nxdomain on AAAA (or vice versa) breaks some clients (musl)
if len(addresses) == 0 {
return false
}
ifc_index := 0
for i := 0; i < len(addresses); i++ {
addr := addresses[i]
var ip net.IP = addr.V2
if addr.V1 == syscall.AF_INET && state.QType() == dns.TypeA {
aheader := dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60}
if ip.Mask(net.CIDRMask(23, 32)).Equal(net.IPv4(172, 30, 32, 0)) {
// Prefer an address within hassio network if one is returned by inserting at front
msg.Answer = insertAnAnswer(msg.Answer, &dns.A{Hdr: aheader, A: ip}, 0)
ifc_index = 1
} else if addr.V0 == m.Ifc {
// Primary interface is next most preferred if we found it
msg.Answer = insertAnAnswer(msg.Answer, &dns.A{Hdr: aheader, A: ip}, ifc_index)
} else {
msg.Answer = append(msg.Answer, &dns.A{Hdr: aheader, A: ip})
}
} else if addr.V1 == syscall.AF_INET6 && state.QType() == dns.TypeAAAA {
aaaaheader := dns.RR_Header{Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 60}
msg.Answer = append(msg.Answer, &dns.AAAA{Hdr: aaaaheader, AAAA: ip})
}
}
return true
}
func (m MDNS) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
// If systemd-resolved isn't available, plugin can't do anything. Pass to the next one.
if m.Resolver == nil {
return plugin.NextOrFailure(m.Name(), m.Next, ctx, w, r)
}
msg := new(dns.Msg)
state := request.Request{W: w, Req: r}
hostName := strings.ToLower(state.QName())
// Prepare message
msg.SetReply(r)
msg.Authoritative = true
msg.RecursionAvailable = true
// Check requirements
if !(strings.HasSuffix(state.QName(), ".local.") || len(strings.Split(state.QName(), ".")) == 2) {
return plugin.NextOrFailure(m.Name(), m.Next, ctx, w, r)
}
if state.QType() != dns.TypeA && state.QType() != dns.TypeAAAA {
return plugin.NextOrFailure(m.Name(), m.Next, ctx, w, r)
}
msg.Answer = []dns.RR{}
addresses, _, _, err := m.Resolver.ResolveHostname(ctx, 0, hostName, syscall.AF_UNSPEC, 0)
if err != nil {
// Usually the error will say that it couldn't find a host with that name
// There may be uncommon errors though so not swallowing it while debugging
log.Debug(err)
} else {
m.AddARecord(msg, &state, hostName, addresses)
log.Debug(msg)
}
// Plugin only processes A and AAAA type multicast queries (.local or single name)
// Whether an answer was found or not this is end of the line, do not forward to external resolvers
// Always return NOERROR since we are not authoritative for this domain
w.WriteMsg(msg)
return dns.RcodeSuccess, nil
}
func GetPrimaryInterface(ctx context.Context, resolver *resolve1.Manager) int32 {
names, _, err := resolver.ResolveAddress(ctx, 0, syscall.AF_INET, []byte{8, 8, 8, 8}, 0)
if err != nil {
log.Error("could not locate primary interface due to: ", err)
return 0
}
if len(names) == 0 {
log.Error("could not locate primary interface, possible network issue")
return 0
}
return names[0].V0
}
func (m MDNS) Name() string { return "mdns" }