From 6d920aad4a61fa93fdfc53aaa83b99950b068bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Didrik=20Nordstr=C3=B6m?= Date: Mon, 31 Jul 2023 05:19:24 -0700 Subject: [PATCH 1/2] Refactor client loop for clarity - Added a couple of comments - Removed superfluous delete entries while iterating - Removed superfluous length check prior to iterating --- client.go | 53 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/client.go b/client.go index c0b2cae1..3356a053 100644 --- a/client.go +++ b/client.go @@ -177,12 +177,12 @@ func (c *client) mainloop(ctx context.Context, params *lookupParams) { } // Iterate through channels from listeners goroutines - var entries map[string]*ServiceEntry sentEntries := make(map[string]*ServiceEntry) ticker := time.NewTicker(cleanupFreq) defer ticker.Stop() for { + var entries map[string]*ServiceEntry var now time.Time select { case <-ctx.Done(): @@ -269,35 +269,34 @@ func (c *client) mainloop(ctx context.Context, params *lookupParams) { } } - if len(entries) > 0 { - for k, e := range entries { - if !e.Expiry.After(now) { - delete(entries, k) - delete(sentEntries, k) - continue - } - if _, ok := sentEntries[k]; ok { - continue - } + for k, e := range entries { + if !e.Expiry.After(now) { + // Implies TTL=0, meaning a "Goodbye Packet". + delete(sentEntries, k) + continue + } + if _, ok := sentEntries[k]; ok { + // Already sent, suppress duplicates + continue + } - // If this is an DNS-SD query do not throw PTR away. - // It is expected to have only PTR for enumeration - if params.ServiceRecord.ServiceTypeName() != params.ServiceRecord.ServiceName() { - // Require at least one resolved IP address for ServiceEntry - // TODO: wait some more time as chances are high both will arrive. - if len(e.AddrIPv4) == 0 && len(e.AddrIPv6) == 0 { - continue - } - } - // Submit entry to subscriber and cache it. - // This is also a point to possibly stop probing actively for a - // service entry. - params.Entries <- e - sentEntries[k] = e - if !params.isBrowsing { - params.disableProbing() + // If this is an DNS-SD query do not throw PTR away. + // It is expected to have only PTR for enumeration + if params.ServiceRecord.ServiceTypeName() != params.ServiceRecord.ServiceName() { + // Require at least one resolved IP address for ServiceEntry + // TODO: wait some more time as chances are high both will arrive. + if len(e.AddrIPv4) == 0 && len(e.AddrIPv6) == 0 { + continue } } + // Submit entry to subscriber and cache it. + // This is also a point to possibly stop probing actively for a + // service entry. + params.Entries <- e + sentEntries[k] = e + if !params.isBrowsing { + params.disableProbing() + } } } } From 8958d088b54152f469eaa582558c5a1c93199777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Didrik=20Nordstr=C3=B6m?= Date: Mon, 31 Jul 2023 05:47:19 -0700 Subject: [PATCH 2/2] Add `Unannouncements` client option to detect disconnected clients --- client.go | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/client.go b/client.go index 3356a053..e2644138 100644 --- a/client.go +++ b/client.go @@ -32,14 +32,16 @@ var initialQueryInterval = 4 * time.Second // Client structure encapsulates both IPv4/IPv6 UDP connections. type client struct { - ipv4conn *ipv4.PacketConn - ipv6conn *ipv6.PacketConn - ifaces []net.Interface + ipv4conn *ipv4.PacketConn + ipv6conn *ipv6.PacketConn + ifaces []net.Interface + unannouncements bool } type clientOpts struct { - listenOn IPType - ifaces []net.Interface + listenOn IPType + ifaces []net.Interface + unannouncements bool } // ClientOption fills the option struct to configure intefaces, etc. @@ -63,6 +65,14 @@ func SelectIfaces(ifaces []net.Interface) ClientOption { } } +// Emit an entry with an expiry in the past if a previously emitted entry is unannounced. +// This is never guaranteed to occur, but can speed up detection of disconnected clients. +func Unannouncements() ClientOption { + return func(o *clientOpts) { + o.unannouncements = true + } +} + // Browse for all services of a given type in a given domain. // Received entries are sent on the entries channel. // It blocks until the context is canceled (or an error occurs). @@ -157,9 +167,10 @@ func newClient(opts clientOpts) (*client, error) { } return &client{ - ipv4conn: ipv4conn, - ipv6conn: ipv6conn, - ifaces: ifaces, + ipv4conn: ipv4conn, + ipv6conn: ipv6conn, + ifaces: ifaces, + unannouncements: opts.unannouncements, }, nil } @@ -272,6 +283,9 @@ func (c *client) mainloop(ctx context.Context, params *lookupParams) { for k, e := range entries { if !e.Expiry.After(now) { // Implies TTL=0, meaning a "Goodbye Packet". + if _, ok := sentEntries[k]; ok && c.unannouncements { + params.Entries <- e + } delete(sentEntries, k) continue }