From bd27d18147fcc5d00e3c02d5096eb61511302bb1 Mon Sep 17 00:00:00 2001 From: Ian Foster Date: Sun, 25 Apr 2021 20:13:23 -0700 Subject: [PATCH 1/2] implemented A and AAAA name resolution --- README.md | 1 + client.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++- service.go | 1 + 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f639e8cd..c4d45faf 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ See what needs to be done and submit a pull request :) * [x] Browse / Lookup / Register services * [x] Multiple IPv6 / IPv4 addresses support * [x] Send multiple probes (exp. back-off) if no service answers (*) +* [x] Resolve mdns host names * [ ] Timestamp entries for TTL checks * [ ] Compare new multicasts with already received services diff --git a/client.go b/client.go index e4f649bd..3a848fca 100644 --- a/client.go +++ b/client.go @@ -132,6 +132,35 @@ func (r *Resolver) Lookup(ctx context.Context, instance, service, domain string, return nil } +// Resolve resolves the provided name over mdns to A/AAAA IP +func (r *Resolver) Resolve(ctx context.Context, name string, qType uint16, entries chan<- *ServiceEntry) error { + if qType != dns.TypeA && qType != dns.TypeAAAA { + return fmt.Errorf("only A and AAAA records can be resolved") + } + lables := dns.SplitDomainName(name) + domain := lables[len(lables)-1] + hostname := trimDot(strings.TrimSuffix(name, domain+".")) + params := NewLookupParams("", hostname, domain, entries) + params.queryType = qType + ctx, cancel := context.WithCancel(ctx) + go r.c.mainloop(ctx, params) + err := r.c.query(params) + if err != nil { + // cancel mainloop + cancel() + return err + } + // If previous probe was ok, it should be fine now. In case of an error later on, + // the entries' queue is closed. + go func() { + if err := r.c.periodicQuery(ctx, params); err != nil { + cancel() + } + }() + + return nil +} + // defaultParams returns a default set of QueryParams. func defaultParams(service string) *LookupParams { return NewLookupParams("", service, "local", make(chan *ServiceEntry)) @@ -247,6 +276,34 @@ func (c *client) mainloop(ctx context.Context, params *LookupParams) { } entries[rr.Hdr.Name].Text = rr.Txt entries[rr.Hdr.Name].TTL = rr.Hdr.Ttl + case *dns.A: + if params.queryType != dns.TypeNone { + if params.ServiceName() != rr.Hdr.Name { + continue + } + if _, ok := entries[rr.Hdr.Name]; !ok { + entries[rr.Hdr.Name] = NewServiceEntry( + trimDot(strings.Replace(rr.Hdr.Name, params.ServiceName(), "", 1)), + params.Service, + params.Domain) + } + entries[rr.Hdr.Name].HostName = rr.Hdr.Name + entries[rr.Hdr.Name].TTL = rr.Hdr.Ttl + } + case *dns.AAAA: + if params.queryType != dns.TypeNone { + if params.ServiceName() != rr.Hdr.Name { + continue + } + if _, ok := entries[rr.Hdr.Name]; !ok { + entries[rr.Hdr.Name] = NewServiceEntry( + trimDot(strings.Replace(rr.Hdr.Name, params.ServiceName(), "", 1)), + params.Service, + params.Domain) + } + entries[rr.Hdr.Name].HostName = rr.Hdr.Name + entries[rr.Hdr.Name].TTL = rr.Hdr.Ttl + } } } // Associate IPs in a second round as other fields should be filled by now. @@ -410,7 +467,10 @@ func (c *client) query(params *LookupParams) error { // send the query m := new(dns.Msg) - if params.Instance != "" { // service instance name lookup + if params.queryType != dns.TypeNone { + // resolve lookup + m.SetQuestion(serviceName, params.queryType) + } else if params.Instance != "" { // service instance name lookup serviceInstanceName = fmt.Sprintf("%s.%s", params.Instance, serviceName) m.Question = []dns.Question{ dns.Question{serviceInstanceName, dns.TypeSRV, dns.ClassINET}, diff --git a/service.go b/service.go index 97e7ebd6..dc730f5b 100644 --- a/service.go +++ b/service.go @@ -72,6 +72,7 @@ type LookupParams struct { stopProbing chan struct{} once sync.Once + queryType uint16 // set in client.Resolve() for host lookup } // NewLookupParams constructs a LookupParams. From 7ba476ba571bc6c77c906a96611d05ce6d85cc5a Mon Sep 17 00:00:00 2001 From: Ian Foster Date: Sun, 25 Apr 2021 20:34:24 -0700 Subject: [PATCH 2/2] added ResolveOnce --- client.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/client.go b/client.go index 3a848fca..1cb7796f 100644 --- a/client.go +++ b/client.go @@ -161,6 +161,32 @@ func (r *Resolver) Resolve(ctx context.Context, name string, qType uint16, entri return nil } +// ResolveOnce like Resolve, but returns on the first response, which is usually expected if only one host on the network has a given name +func (r *Resolver) ResolveOnce(ctx context.Context, name string, qType uint16) ([]net.IP, error) { + localCtx, cancel := context.WithCancel(ctx) + defer cancel() + ip := make([]net.IP, 0, 1) + + entries := make(chan *ServiceEntry) + go func(results <-chan *ServiceEntry) { + for entry := range results { + if name == entry.HostName { + ip = append(ip, entry.AddrIPv4...) + ip = append(ip, entry.AddrIPv6...) + cancel() // limits this resolve to a single response. + } + } + }(entries) + + err := r.Resolve(localCtx, name, qType, entries) + if err != nil { + return nil, err + } + + <-ctx.Done() + return ip, nil +} + // defaultParams returns a default set of QueryParams. func defaultParams(service string) *LookupParams { return NewLookupParams("", service, "local", make(chan *ServiceEntry))