Skip to content

Commit

Permalink
Escl: Continuous mDNS querying and duplicate prevention
Browse files Browse the repository at this point in the history
  • Loading branch information
cyanfish committed Dec 12, 2023
1 parent ef64c48 commit b8cc5a8
Showing 1 changed file with 28 additions and 18 deletions.
46 changes: 28 additions & 18 deletions NAPS2.Escl/Client/EsclServiceLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ namespace NAPS2.Escl.Client;
public class EsclServiceLocator : IDisposable
{
private readonly ServiceDiscovery _discovery;
private readonly HashSet<ServiceKey> _locatedServices = new();
private bool _started;
private int _nextQueryInterval = 1000;

public EsclServiceLocator(Action<EsclService> serviceCallback)
{
Expand All @@ -18,6 +20,12 @@ public EsclServiceLocator(Action<EsclService> serviceCallback)
try
{
var service = ParseService(args);
var serviceKey = new ServiceKey(service.ScannerName, service.Uuid, service.Port, service.IpV4, service.IpV6);
if (!_locatedServices.Add(serviceKey))
{
// Don't callback for duplicates
return;
}
Logger.LogDebug("Discovered ESCL Service: {Name}, instance {Instance}, endpoint {Endpoint}, ipv4 {Ipv4}, ipv6 {IpV6}, host {Host}, port {Port}, uuid {Uuid}",
service.ScannerName, args.ServiceInstanceName, args.RemoteEndPoint, service.IpV4, service.IpV6, service.Host, service.Port, service.Uuid);
serviceCallback(service);
Expand All @@ -36,33 +44,33 @@ public void Start()
if (_started) throw new InvalidOperationException("Already started");
_started = true;

Query();
}

private void Query()
{
if (_discovery.Mdns == null)
{
return;
}
// TODO: De-duplicate http/https services?
_discovery.QueryServiceInstances("_uscan._tcp");
_discovery.QueryServiceInstances("_uscans._tcp");

// We query once when we start, then once again after 1s to account for race conditions where there was a
// We query once when we start, then again after 1s, 2s, etc. to account for race conditions where there was a
// previous query/answer on the network just before we started listening, which would prevent us from receiving
// a response. See the following:
//
// "When retransmitting Multicast DNS queries to implement continuous monitoring, the interval between the first
// two queries MUST be at least one second."
// two queries MUST be at least one second, and the intervals between successive queries MUST increase by at
// least a factor of two."
// https://datatracker.ietf.org/doc/html/rfc6762#section-5.2
//
// "A Multicast DNS responder MUST NOT multicast a record on a given interface until at least one second has
// elapsed since the last time that record was multicast on that particular interface."
// https://datatracker.ietf.org/doc/html/rfc6762#section-6
Query();
Task.Delay(1000).ContinueWith(_ =>
{
if (_discovery.Mdns != null)
{
Query();
}
});
}

private void Query()
{
// TODO: De-duplicate http/https services?
_discovery.QueryServiceInstances("_uscan._tcp");
_discovery.QueryServiceInstances("_uscans._tcp");
Task.Delay(_nextQueryInterval).ContinueWith(_ => Query());
_nextQueryInterval *= 2;
}

private EsclService ParseService(ServiceInstanceDiscoveryEventArgs args)
Expand Down Expand Up @@ -140,4 +148,6 @@ public void Dispose()
{
_discovery.Dispose();
}
}

private record ServiceKey(string? ScannerName, string? Uuid, int Port, IPAddress? IpV4, IPAddress? IpV6);
}

0 comments on commit b8cc5a8

Please sign in to comment.