From 2d856fa51618ba9168d95fe21f4b6055b621dfc7 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 4 Jul 2021 13:05:12 -0700 Subject: [PATCH 1/3] remove the Resolver --- README.md | 7 +-- client.go | 93 ++++++++++++++++----------------------- examples/resolv/client.go | 9 +--- service_test.go | 25 ++--------- 4 files changed, 44 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 6d0442ec..f93d9a25 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,6 @@ This package requires **Go 1.7** (context in std lib) or later. ## Browse for services in your local network ```go -// Discover all services on the network (e.g. _workstation._tcp) -resolver, err := zeroconf.NewResolver(nil) -if err != nil { - log.Fatalln("Failed to initialize resolver:", err.Error()) -} - entries := make(chan *zeroconf.ServiceEntry) go func(results <-chan *zeroconf.ServiceEntry) { for entry := range results { @@ -45,6 +39,7 @@ go func(results <-chan *zeroconf.ServiceEntry) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer cancel() +// Discover all services on the network (e.g. _workstation._tcp) err = resolver.Browse(ctx, "_workstation._tcp", "local.", entries) if err != nil { log.Fatalln("Failed to browse:", err.Error()) diff --git a/client.go b/client.go index 270394ab..34abd57f 100644 --- a/client.go +++ b/client.go @@ -26,6 +26,13 @@ const ( IPv4AndIPv6 = (IPv4 | IPv6) //< Default option. ) +// Client structure encapsulates both IPv4/IPv6 UDP connections. +type client struct { + ipv4conn *ipv4.PacketConn + ipv6conn *ipv6.PacketConn + ifaces []net.Interface +} + type clientOpts struct { listenOn IPType ifaces []net.Interface @@ -52,80 +59,61 @@ func SelectIfaces(ifaces []net.Interface) ClientOption { } } -// Resolver acts as entry point for service lookups and to browse the DNS-SD. -type Resolver struct { - c *client -} - -// NewResolver creates a new resolver and joins the UDP multicast groups to -// listen for mDNS messages. -func NewResolver(options ...ClientOption) (*Resolver, error) { - // Apply default configuration and load supplied options. - var conf = clientOpts{ - listenOn: IPv4AndIPv6, - } - for _, o := range options { - if o != nil { - o(&conf) - } - } - - c, err := newClient(conf) +// Browse for all services of a given type in a given domain. +func Browse(ctx context.Context, service, domain string, entries chan<- *ServiceEntry, opts ...ClientOption) error { + cl, err := newClient(applyOpts(opts...)) if err != nil { - return nil, err + return err } - return &Resolver{ - c: c, - }, nil -} - -// Browse for all services of a given type in a given domain. -func (r *Resolver) Browse(ctx context.Context, service, domain string, entries chan<- *ServiceEntry) error { params := defaultParams(service) if domain != "" { params.Domain = domain } params.Entries = entries params.isBrowsing = true - ctx, cancel := context.WithCancel(ctx) - go r.c.mainloop(ctx, params) + return cl.run(ctx, params) +} - err := r.c.query(params) +// Lookup a specific service by its name and type in a given domain. +func Lookup(ctx context.Context, instance, service, domain string, entries chan<- *ServiceEntry, opts ...ClientOption) error { + cl, err := newClient(applyOpts(opts...)) if err != nil { - 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 -} - -// Lookup a specific service by its name and type in a given domain. -func (r *Resolver) Lookup(ctx context.Context, instance, service, domain string, entries chan<- *ServiceEntry) error { params := defaultParams(service) params.Instance = instance if domain != "" { params.Domain = domain } params.Entries = entries + return cl.run(ctx, params) +} + +func applyOpts(options ...ClientOption) clientOpts { + // Apply default configuration and load supplied options. + var conf = clientOpts{ + listenOn: IPv4AndIPv6, + } + for _, o := range options { + if o != nil { + o(&conf) + } + } + return conf +} + +func (c *client) run(ctx context.Context, params *lookupParams) error { ctx, cancel := context.WithCancel(ctx) - go r.c.mainloop(ctx, params) - err := r.c.query(params) - if err != nil { - // cancel mainloop + go c.mainloop(ctx, params) + + if err := c.query(params); err != nil { 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 { + if err := c.periodicQuery(ctx, params); err != nil { cancel() } }() @@ -138,13 +126,6 @@ func defaultParams(service string) *lookupParams { return newLookupParams("", service, "local", false, make(chan *ServiceEntry)) } -// Client structure encapsulates both IPv4/IPv6 UDP connections. -type client struct { - ipv4conn *ipv4.PacketConn - ipv6conn *ipv6.PacketConn - ifaces []net.Interface -} - // Client structure constructor func newClient(opts clientOpts) (*client, error) { ifaces := opts.ifaces diff --git a/examples/resolv/client.go b/examples/resolv/client.go index 83186bc4..45332306 100644 --- a/examples/resolv/client.go +++ b/examples/resolv/client.go @@ -18,12 +18,6 @@ var ( func main() { flag.Parse() - // Discover all services on the network (e.g. _workstation._tcp) - resolver, err := zeroconf.NewResolver(nil) - if err != nil { - log.Fatalln("Failed to initialize resolver:", err.Error()) - } - entries := make(chan *zeroconf.ServiceEntry) go func(results <-chan *zeroconf.ServiceEntry) { for entry := range results { @@ -34,7 +28,8 @@ func main() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(*waitTime)) defer cancel() - err = resolver.Browse(ctx, *service, *domain, entries) + // Discover all services on the network (e.g. _workstation._tcp) + err := zeroconf.Browse(ctx, *service, *domain, entries) if err != nil { log.Fatalln("Failed to browse:", err.Error()) } diff --git a/service_test.go b/service_test.go index 2c5a23ed..f45d83de 100644 --- a/service_test.go +++ b/service_test.go @@ -40,12 +40,8 @@ func TestBasic(t *testing.T) { time.Sleep(time.Second) - resolver, err := NewResolver(nil) - if err != nil { - t.Fatalf("Expected create resolver success, but got %v", err) - } entries := make(chan *ServiceEntry, 100) - if err := resolver.Browse(ctx, mdnsService, mdnsDomain, entries); err != nil { + if err := Browse(ctx, mdnsService, mdnsDomain, entries); err != nil { t.Fatalf("Expected browse success, but got %v", err) } <-ctx.Done() @@ -69,11 +65,6 @@ func TestBasic(t *testing.T) { } func TestNoRegister(t *testing.T) { - resolver, err := NewResolver(nil) - if err != nil { - t.Fatalf("Expected create resolver success, but got %v", err) - } - // before register, mdns resolve shuold not have any entry entries := make(chan *ServiceEntry) go func(results <-chan *ServiceEntry) { @@ -84,7 +75,7 @@ func TestNoRegister(t *testing.T) { }(entries) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - if err := resolver.Browse(ctx, mdnsService, mdnsDomain, entries); err != nil { + if err := Browse(ctx, mdnsService, mdnsDomain, entries); err != nil { t.Fatalf("Expected browse success, but got %v", err) } <-ctx.Done() @@ -100,12 +91,8 @@ func TestSubtype(t *testing.T) { time.Sleep(time.Second) - resolver, err := NewResolver(nil) - if err != nil { - t.Fatalf("Expected create resolver success, but got %v", err) - } entries := make(chan *ServiceEntry, 100) - if err := resolver.Browse(ctx, mdnsSubtype, mdnsDomain, entries); err != nil { + if err := Browse(ctx, mdnsSubtype, mdnsDomain, entries); err != nil { t.Fatalf("Expected browse success, but got %v", err) } <-ctx.Done() @@ -136,12 +123,8 @@ func TestSubtype(t *testing.T) { time.Sleep(time.Second) - resolver, err := NewResolver(nil) - if err != nil { - t.Fatalf("Expected create resolver success, but got %v", err) - } entries := make(chan *ServiceEntry, 100) - if err := resolver.Browse(ctx, mdnsService, mdnsDomain, entries); err != nil { + if err := Browse(ctx, mdnsService, mdnsDomain, entries); err != nil { t.Fatalf("Expected browse success, but got %v", err) } <-ctx.Done() From 513aee2bcd8a198ce93d3859227ba77a1ee8299a Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 4 Jul 2021 14:28:19 -0700 Subject: [PATCH 2/3] make Lookup and Resolve blocking calls --- client.go | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/client.go b/client.go index 34abd57f..d6085b93 100644 --- a/client.go +++ b/client.go @@ -104,21 +104,12 @@ func applyOpts(options ...ClientOption) clientOpts { func (c *client) run(ctx context.Context, params *lookupParams) error { ctx, cancel := context.WithCancel(ctx) + defer cancel() go c.mainloop(ctx, params) - if err := c.query(params); err != nil { - 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 := c.periodicQuery(ctx, params); err != nil { - cancel() - } - }() - - return nil + return c.periodicQuery(ctx, params) } // defaultParams returns a default set of QueryParams. @@ -362,6 +353,10 @@ func (c *client) periodicQuery(ctx context.Context, params *lookupParams) error } }() for { + // Do periodic query. + if err := c.query(params); err != nil { + return err + } // Backoff and cancel logic. wait := bo.NextBackOff() if wait == backoff.Stop { @@ -372,6 +367,7 @@ func (c *client) periodicQuery(ctx context.Context, params *lookupParams) error } else { timer.Reset(wait) } + select { case <-timer.C: // Wait for next iteration. @@ -380,12 +376,11 @@ func (c *client) periodicQuery(ctx context.Context, params *lookupParams) error // Done here. Received a matching mDNS entry. return nil case <-ctx.Done(): + if params.isBrowsing { + return nil + } return ctx.Err() } - // Do periodic query. - if err := c.query(params); err != nil { - return err - } } } @@ -409,11 +404,7 @@ func (c *client) query(params *lookupParams) error { m.SetQuestion(serviceName, dns.TypePTR) } m.RecursionDesired = false - if err := c.sendQuery(m); err != nil { - return err - } - - return nil + return c.sendQuery(m) } // Pack the dns.Msg and write to available connections (multicast) From 5d79d014ea9ae891851792b403c5055b8377e8b3 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 4 Jul 2021 14:35:09 -0700 Subject: [PATCH 3/3] wait for the mainloop to complete before return Lookup and Browse --- client.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index d6085b93..a6787cd3 100644 --- a/client.go +++ b/client.go @@ -104,12 +104,18 @@ func applyOpts(options ...ClientOption) clientOpts { func (c *client) run(ctx context.Context, params *lookupParams) error { ctx, cancel := context.WithCancel(ctx) - defer cancel() - go c.mainloop(ctx, params) + done := make(chan struct{}) + go func() { + defer close(done) + c.mainloop(ctx, params) + }() // If previous probe was ok, it should be fine now. In case of an error later on, // the entries' queue is closed. - return c.periodicQuery(ctx, params) + err := c.periodicQuery(ctx, params) + cancel() + <-done + return err } // defaultParams returns a default set of QueryParams.