Skip to content

Commit

Permalink
Merge pull request #51 from richardschneider/qu-question
Browse files Browse the repository at this point in the history
fix: process questions requesting unicast responses #50
  • Loading branch information
richardschneider authored Mar 5, 2019
2 parents e68cad9 + f633e88 commit 6ab780d
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 2 deletions.
37 changes: 37 additions & 0 deletions src/MulticastService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,43 @@ public void SendQuery(string name, DnsClass klass = DnsClass.IN, DnsType type =
SendQuery(msg);
}

/// <summary>
/// Ask for answers about a name and accept unicast and/or broadcast response.
/// </summary>
/// <param name="name">
/// A domain name that should end with ".local", e.g. "myservice.local".
/// </param>
/// <param name="klass">
/// The class, defaults to <see cref="DnsClass.IN"/>.
/// </param>
/// <param name="type">
/// The question type, defaults to <see cref="DnsType.ANY"/>.
/// </param>
/// <remarks>
/// Send a "QU" question (unicast). The most significat bit of the Class is set.
/// Answers to any query are obtained on the <see cref="AnswerReceived"/>
/// event.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// When the service has not started.
/// </exception>
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);
}

/// <summary>
/// Ask for answers.
/// </summary>
Expand Down
52 changes: 50 additions & 2 deletions src/ServiceDiscovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ public void QueryAllServices()
Mdns.SendQuery(ServiceName, type: DnsType.PTR);
}

/// <summary>
/// Asks other MDNS services to send their service names;
/// accepts unicast and/or broadcast answers.
/// </summary>
/// <remarks>
/// When an answer is received the <see cref="ServiceDiscovered"/> event is raised.
/// </remarks>
public void QueryUnicastAllServices()
{
Mdns.SendUnicastQuery(ServiceName, type: DnsType.PTR);
}

/// <summary>
/// Asks instances of the specified service to send details.
/// </summary>
Expand All @@ -142,6 +154,22 @@ public void QueryServiceInstances(string service)
Mdns.SendQuery(service + ".local", type: DnsType.PTR);
}

/// <summary>
/// Asks instances of the specified service to send details.
/// accepts unicast and/or broadcast answers.
/// </summary>
/// <param name="service">
/// The service name to query. Typically of the form "_<i>service</i>._tcp".
/// </param>
/// <remarks>
/// When an answer is received the <see cref="ServiceInstanceDiscovered"/> event is raised.
/// </remarks>
/// <seealso cref="ServiceProfile.ServiceName"/>
public void QueryUnicastServiceInstances(string service)
{
Mdns.SendUnicastQuery(service + ".local", type: DnsType.PTR);
}

/// <summary>
/// Advertise a service profile.
/// </summary>
Expand Down Expand Up @@ -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;
Expand All @@ -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)
{
Expand Down
29 changes: 29 additions & 0 deletions test/MulticastServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
63 changes: 63 additions & 0 deletions test/ServiceDiscoveryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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()
{
Expand Down

0 comments on commit 6ab780d

Please sign in to comment.