Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rework the client-side API, remove the Resolver, make Browse and Lookup package-level functions #100

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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())
Expand Down
124 changes: 51 additions & 73 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -52,99 +59,70 @@ 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
ctx, cancel := context.WithCancel(ctx)
go r.c.mainloop(ctx, params)
err := r.c.query(params)
if err != nil {
// cancel mainloop
cancel()
return err
return cl.run(ctx, params)
}

func applyOpts(options ...ClientOption) clientOpts {
// Apply default configuration and load supplied options.
var conf = clientOpts{
listenOn: IPv4AndIPv6,
}
// 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()
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)
done := make(chan struct{})
go func() {
defer close(done)
c.mainloop(ctx, params)
}()

return nil
// If previous probe was ok, it should be fine now. In case of an error later on,
// the entries' queue is closed.
err := c.periodicQuery(ctx, params)
cancel()
<-done
return err
}

// defaultParams returns a default set of QueryParams.
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
Expand Down Expand Up @@ -381,6 +359,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 {
Expand All @@ -391,6 +373,7 @@ func (c *client) periodicQuery(ctx context.Context, params *lookupParams) error
} else {
timer.Reset(wait)
}

select {
case <-timer.C:
// Wait for next iteration.
Expand All @@ -399,12 +382,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
}
}
}

Expand All @@ -428,11 +410,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)
Expand Down
9 changes: 2 additions & 7 deletions examples/resolv/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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())
}
Expand Down
25 changes: 4 additions & 21 deletions service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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) {
Expand All @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down