diff --git a/src/MulticastService.cs b/src/MulticastService.cs index b01917a..7b10ecf 100644 --- a/src/MulticastService.cs +++ b/src/MulticastService.cs @@ -371,6 +371,43 @@ public void SendQuery(string name, DnsClass klass = DnsClass.IN, DnsType type = SendQuery(msg); } + /// + /// Ask for answers about a name and accept unicast and/or broadcast response. + /// + /// + /// A domain name that should end with ".local", e.g. "myservice.local". + /// + /// + /// The class, defaults to . + /// + /// + /// The question type, defaults to . + /// + /// + /// Send a "QU" question (unicast). The most significat bit of the Class is set. + /// Answers to any query are obtained on the + /// event. + /// + /// + /// When the service has not started. + /// + public void SendUnicastQuery(string name, DnsClass klass = DnsClass.IN, DnsType type = DnsType.ANY) + { + var msg = new Message + { + Opcode = MessageOperation.Query, + QR = false + }; + msg.Questions.Add(new Question + { + Name = name, + Class = (DnsClass) ((ushort)klass | 0x8000), + Type = type + }); + + SendQuery(msg); + } + /// /// Ask for answers. /// diff --git a/src/ServiceDiscovery.cs b/src/ServiceDiscovery.cs index 8938131..307d3ff 100644 --- a/src/ServiceDiscovery.cs +++ b/src/ServiceDiscovery.cs @@ -127,6 +127,18 @@ public void QueryAllServices() Mdns.SendQuery(ServiceName, type: DnsType.PTR); } + /// + /// Asks other MDNS services to send their service names; + /// accepts unicast and/or broadcast answers. + /// + /// + /// When an answer is received the event is raised. + /// + public void QueryUnicastAllServices() + { + Mdns.SendUnicastQuery(ServiceName, type: DnsType.PTR); + } + /// /// Asks instances of the specified service to send details. /// @@ -142,6 +154,22 @@ public void QueryServiceInstances(string service) Mdns.SendQuery(service + ".local", type: DnsType.PTR); } + /// + /// Asks instances of the specified service to send details. + /// accepts unicast and/or broadcast answers. + /// + /// + /// The service name to query. Typically of the form "_service._tcp". + /// + /// + /// When an answer is received the event is raised. + /// + /// + public void QueryUnicastServiceInstances(string service) + { + Mdns.SendUnicastQuery(service + ".local", type: DnsType.PTR); + } + /// /// Advertise a service profile. /// @@ -200,7 +228,19 @@ void OnQuery(object sender, MessageEventArgs e) if (log.IsDebugEnabled) { - log.Debug($"got query from: {e.RemoteEndPoint.Address}, for {request.Questions[0].Name} {request.Questions[0].Type}"); + log.Debug($"got query from: {e.RemoteEndPoint}, for {request.Questions[0].Name} {request.Questions[0].Type}"); + } + + // Determine if this query is requesting a unicast response + // and normalise the Class. + var QU = false; // unicast query response? + foreach (var r in request.Questions) + { + if (((ushort)r.Class & 0x8000) != 0) + { + QU = true; + r.Class = (DnsClass)((ushort)r.Class & 0x7fff); + } } var response = NameServer.ResolveAsync(request).Result; @@ -222,7 +262,15 @@ void OnQuery(object sender, MessageEventArgs e) response.Answers.AddRange(response.AdditionalRecords); } - Mdns.SendAnswer(response); + if (QU) + { + // TODO: Send a Unicast response if required. + Mdns.SendAnswer(response); + } + else + { + Mdns.SendAnswer(response); + } if (log.IsDebugEnabled) { diff --git a/test/MulticastServiceTest.cs b/test/MulticastServiceTest.cs index 27df7f5..83aad39 100644 --- a/test/MulticastServiceTest.cs +++ b/test/MulticastServiceTest.cs @@ -58,6 +58,35 @@ public void SendQuery() } } + [TestMethod] + public void SendUnicastQuery() + { + var ready = new ManualResetEvent(false); + var done = new ManualResetEvent(false); + Message msg = null; + + var mdns = new MulticastService(); + mdns.NetworkInterfaceDiscovered += (s, e) => ready.Set(); + mdns.QueryReceived += (s, e) => + { + msg = e.Message; + done.Set(); + }; + try + { + mdns.Start(); + Assert.IsTrue(ready.WaitOne(TimeSpan.FromSeconds(1)), "ready timeout"); + mdns.SendUnicastQuery("some-service.local"); + Assert.IsTrue(done.WaitOne(TimeSpan.FromSeconds(1)), "query timeout"); + Assert.AreEqual("some-service.local", msg.Questions.First().Name); + Assert.AreEqual(DnsClass.IN + 0x8000, msg.Questions.First().Class); + } + finally + { + mdns.Stop(); + } + } + [TestMethod] public void ReceiveAnswer() { diff --git a/test/ServiceDiscoveryTest.cs b/test/ServiceDiscoveryTest.cs index ca29166..e677b9a 100644 --- a/test/ServiceDiscoveryTest.cs +++ b/test/ServiceDiscoveryTest.cs @@ -150,6 +150,35 @@ public void Discover_AllServices() } } + [TestMethod] + public void Discover_AllServices_Unicast() + { + var service = new ServiceProfile("x", "_sdtest-5._udp", 1024); + var done = new ManualResetEvent(false); + var mdns = new MulticastService(); + var sd = new ServiceDiscovery(mdns); + + mdns.NetworkInterfaceDiscovered += (s, e) => sd.QueryUnicastAllServices(); + sd.ServiceDiscovered += (s, serviceName) => + { + if (serviceName == service.QualifiedServiceName) + { + done.Set(); + } + }; + try + { + sd.Advertise(service); + mdns.Start(); + Assert.IsTrue(done.WaitOne(TimeSpan.FromSeconds(1)), "DNS-SD query timeout"); + } + finally + { + sd.Dispose(); + mdns.Stop(); + } + } + [TestMethod] public void Discover_ServiceInstance() { @@ -184,6 +213,40 @@ public void Discover_ServiceInstance() } } + [TestMethod] + public void Discover_ServiceInstance_Unicast() + { + var service = new ServiceProfile("y", "_sdtest-5._udp", 1024); + var done = new ManualResetEvent(false); + var mdns = new MulticastService(); + var sd = new ServiceDiscovery(mdns); + + mdns.NetworkInterfaceDiscovered += (s, e) => + { + sd.QueryUnicastServiceInstances(service.ServiceName); + }; + + sd.ServiceInstanceDiscovered += (s, e) => + { + if (e.ServiceInstanceName == service.FullyQualifiedName) + { + Assert.IsNotNull(e.Message); + done.Set(); + } + }; + try + { + sd.Advertise(service); + mdns.Start(); + Assert.IsTrue(done.WaitOne(TimeSpan.FromSeconds(1)), "instance not found"); + } + finally + { + sd.Dispose(); + mdns.Stop(); + } + } + [TestMethod] public void Discover_ServiceInstance_WithAnswersContainingAdditionRecords() {