diff --git a/README.md b/README.md
index 7c46fd2..0ed1a18 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ service or service instance.
- CI on Circle (Debian GNU/Linux), Travis (Ubuntu Xenial and OSX) and AppVeyor (Windows Server 2016)
- Detects new and/or removed network interfaces
- Supports multicasting on multiple network interfaces
+- Handles legacy unicast queries, see #61
## Getting started
diff --git a/Spike/Program.cs b/Spike/Program.cs
index 219c1d4..a500ed1 100644
--- a/Spike/Program.cs
+++ b/Spike/Program.cs
@@ -52,6 +52,7 @@ static void Main(string[] args)
};
var sd = new ServiceDiscovery(mdns);
+ sd.Advertise(new ServiceProfile("ipfs1", "_ipfs-discovery._udp", 5010));
sd.Advertise(new ServiceProfile("x1", "_xservice._tcp", 5011));
sd.Advertise(new ServiceProfile("x2", "_xservice._tcp", 666));
var z1 = new ServiceProfile("z1", "_zservice._udp", 5012);
diff --git a/src/MessageEventArgs.cs b/src/MessageEventArgs.cs
index 9aaba5c..c9e5c54 100644
--- a/src/MessageEventArgs.cs
+++ b/src/MessageEventArgs.cs
@@ -24,6 +24,14 @@ public class MessageEventArgs : EventArgs
/// The endpoint from the message was received.
///
public IPEndPoint RemoteEndPoint { get; set; }
+
+ ///
+ /// Determines if the sender is using legacy unicast DNS.
+ ///
+ ///
+ /// false if the sender is using port 5353.
+ ///
+ public bool IsLegacyUnicast => RemoteEndPoint.Port != MulticastClient.MulticastPort;
}
}
diff --git a/src/MulticastClient.cs b/src/MulticastClient.cs
index 5856232..4af58ac 100644
--- a/src/MulticastClient.cs
+++ b/src/MulticastClient.cs
@@ -19,7 +19,14 @@ class MulticastClient : IDisposable
{
static readonly ILog log = LogManager.GetLogger(typeof(MulticastClient));
- const int MulticastPort = 5353;
+ ///
+ /// The port number assigned to Multicast DNS.
+ ///
+ ///
+ /// Port number 5353.
+ ///
+ public static readonly int MulticastPort = 5353;
+
static readonly IPAddress MulticastAddressIp4 = IPAddress.Parse("224.0.0.251");
static readonly IPAddress MulticastAddressIp6 = IPAddress.Parse("FF02::FB");
static readonly IPEndPoint MdnsEndpointIp6 = new IPEndPoint(MulticastAddressIp6, MulticastPort);
diff --git a/src/MulticastService.cs b/src/MulticastService.cs
index 5d48fc3..2a9a348 100644
--- a/src/MulticastService.cs
+++ b/src/MulticastService.cs
@@ -30,6 +30,7 @@ public class MulticastService : IResolver, IDisposable
const int packetOverhead = 48;
const int maxDatagramSize = Message.MaxLength;
+ static readonly TimeSpan maxLegacyUnicastTTL = TimeSpan.FromSeconds(10);
static readonly ILog log = LogManager.GetLogger(typeof(MulticastService));
static readonly IPNetwork[] LinkLocalNetworks = new[] { IPNetwork.Parse("169.254.0.0/16"), IPNetwork.Parse("fe80::/10") };
@@ -51,6 +52,16 @@ public class MulticastService : IResolver, IDisposable
///
MulticastClient client;
+ ///
+ /// Use to send unicast IPv4 answers.
+ ///
+ UdpClient unicastClientIp4 = new UdpClient(AddressFamily.InterNetwork);
+
+ ///
+ /// Use to send unicast IPv6 answers.
+ ///
+ UdpClient unicastClientIp6 = new UdpClient(AddressFamily.InterNetworkV6);
+
///
/// Function used for listening filtered network interfaces.
///
@@ -79,7 +90,6 @@ static MulticastService()
/// then forgotten.
///
///
- ///
public event EventHandler QueryReceived;
///
@@ -446,8 +456,10 @@ public void SendQuery(Message msg)
/// When the serialised is too large.
///
///
+ ///
/// The flag is set to true,
/// the set to zero and any questions are removed.
+ ///
///
/// The is truncated
/// if exceeds the maximum packet length.
@@ -456,6 +468,10 @@ public void SendQuery(Message msg)
/// should always be true except
/// when answering a probe.
///
+ ///
+ /// If possible the
+ /// method should be used, so that legacy unicast queries are supported.
+ ///
///
///
///
@@ -474,7 +490,77 @@ public void SendAnswer(Message answer, bool checkDuplicate = true)
Send(answer, checkDuplicate);
}
- void Send(Message msg, bool checkDuplicate)
+ ///
+ /// Send an answer to a query.
+ ///
+ ///
+ /// The answer message.
+ ///
+ ///
+ /// The query that is being answered.
+ ///
+ ///
+ /// If true, then if the same was
+ /// recently sent it will not be sent again.
+ ///
+ ///
+ /// When the service has not started.
+ ///
+ ///
+ /// When the serialised is too large.
+ ///
+ ///
+ ///
+ /// If the is a standard multicast query (sent to port 5353), then
+ /// is called.
+ ///
+ ///
+ /// Otherwise a legacy unicast reponse is sent to sender's end point.
+ /// The flag is set to true,
+ /// the is set to query's ID,
+ /// the is set to the query's questions,
+ /// and all resource record TTLs have a max value of 10 seconds.
+ ///
+ ///
+ /// The is truncated
+ /// if exceeds the maximum packet length.
+ ///
+ ///
+ /// should always be true except
+ /// when answering a probe.
+ ///
+ ///
+ public void SendAnswer(Message answer, MessageEventArgs query, bool checkDuplicate = true)
+ {
+ if (!query.IsLegacyUnicast)
+ {
+ SendAnswer(answer, checkDuplicate);
+ return;
+ }
+
+ answer.AA = true;
+ answer.Id = query.Message.Id;
+ answer.Questions.Clear();
+ answer.Questions.AddRange(query.Message.Questions);
+ answer.Truncate(maxPacketSize);
+
+ foreach (var r in answer.Answers)
+ {
+ r.TTL = (r.TTL > maxLegacyUnicastTTL) ? maxLegacyUnicastTTL : r.TTL;
+ }
+ foreach (var r in answer.AdditionalRecords)
+ {
+ r.TTL = (r.TTL > maxLegacyUnicastTTL) ? maxLegacyUnicastTTL : r.TTL;
+ }
+ foreach (var r in answer.AdditionalRecords)
+ {
+ r.TTL = (r.TTL > maxLegacyUnicastTTL) ? maxLegacyUnicastTTL : r.TTL;
+ }
+
+ Send(answer, checkDuplicate, query.RemoteEndPoint);
+ }
+
+ void Send(Message msg, bool checkDuplicate, IPEndPoint remoteEndPoint = null)
{
var packet = msg.ToByteArray();
if (packet.Length > maxPacketSize)
@@ -487,7 +573,18 @@ void Send(Message msg, bool checkDuplicate)
return;
}
- client?.SendAsync(packet).GetAwaiter().GetResult();
+ // Standard multicast reponse?
+ if (remoteEndPoint == null)
+ {
+ client?.SendAsync(packet).GetAwaiter().GetResult();
+ }
+ // Unicast response
+ else
+ {
+ var unicastClient = (remoteEndPoint.Address.AddressFamily == AddressFamily.InterNetwork)
+ ? unicastClientIp4 : unicastClientIp6;
+ unicastClient.SendAsync(packet, packet.Length, remoteEndPoint).GetAwaiter().GetResult();
+ }
}
///
diff --git a/src/ServiceDiscovery.cs b/src/ServiceDiscovery.cs
index 69bbaf8..4343a0e 100644
--- a/src/ServiceDiscovery.cs
+++ b/src/ServiceDiscovery.cs
@@ -330,11 +330,11 @@ void OnQuery(object sender, MessageEventArgs e)
if (QU)
{
// TODO: Send a Unicast response if required.
- Mdns.SendAnswer(response);
+ Mdns.SendAnswer(response, e);
}
else
{
- Mdns.SendAnswer(response);
+ Mdns.SendAnswer(response, e);
}
if (log.IsDebugEnabled)
diff --git a/test/MulticastServiceTest.cs b/test/MulticastServiceTest.cs
index 254c77e..d136cfa 100644
--- a/test/MulticastServiceTest.cs
+++ b/test/MulticastServiceTest.cs
@@ -40,8 +40,12 @@ public void SendQuery()
mdns.NetworkInterfaceDiscovered += (s, e) => ready.Set();
mdns.QueryReceived += (s, e) =>
{
- msg = e.Message;
- done.Set();
+ if ("some-service.local" == e.Message.Questions.First().Name)
+ {
+ msg = e.Message;
+ Assert.IsFalse(e.IsLegacyUnicast);
+ done.Set();
+ }
};
try
{
@@ -131,6 +135,54 @@ public void ReceiveAnswer()
}
}
+ [TestMethod]
+ public async Task ReceiveLegacyUnicastAnswer()
+ {
+ var service = Guid.NewGuid().ToString() + ".local";
+ var ready = new ManualResetEvent(false);
+
+ var query = new Message();
+ query.Questions.Add(new Question
+ {
+ Name = service,
+ Type = DnsType.A
+ });
+ var packet = query.ToByteArray();
+ var client = new UdpClient();
+ using (var mdns = new MulticastService())
+ {
+ mdns.NetworkInterfaceDiscovered += (s, e) => ready.Set();
+ mdns.QueryReceived += (s, e) =>
+ {
+ var msg = e.Message;
+ if (msg.Questions.Any(q => q.Name == service))
+ {
+ var res = msg.CreateResponse();
+ res.Answers.Add(new ARecord
+ {
+ Name = service,
+ Address = IPAddress.Parse("127.1.1.1")
+ });
+ mdns.SendAnswer(res, e);
+ }
+ };
+ mdns.Start();
+ Assert.IsTrue(ready.WaitOne(TimeSpan.FromSeconds(1)), "ready timeout");
+ await client.SendAsync(packet, packet.Length, "224.0.0.251", 5353);
+ var r = await client.ReceiveAsync();
+ var response = new Message();
+ response.Read(r.Buffer, 0, r.Buffer.Length);
+ Assert.IsTrue(response.IsResponse);
+ Assert.AreEqual(MessageStatus.NoError, response.Status);
+ Assert.IsTrue(response.AA);
+ Assert.AreEqual(1, response.Questions.Count);
+ var a = (ARecord)response.Answers[0];
+ Assert.AreEqual(IPAddress.Parse("127.1.1.1"), a.Address);
+ Assert.AreEqual(service, a.Name);
+ Assert.AreEqual(TimeSpan.FromSeconds(10), a.TTL);
+ }
+ }
+
[TestMethod]
public void ReceiveAnswer_IPv4()
{