Skip to content

Commit

Permalink
Merge pull request #62 from richardschneider/legacy-unicast
Browse files Browse the repository at this point in the history
Legacy unicast support
  • Loading branch information
richardschneider authored Jun 8, 2019
2 parents f838192 + bb75b66 commit d9b2ea0
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions Spike/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions src/MessageEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public class MessageEventArgs : EventArgs
/// The endpoint from the message was received.
/// </value>
public IPEndPoint RemoteEndPoint { get; set; }

/// <summary>
/// Determines if the sender is using legacy unicast DNS.
/// </summary>
/// <value>
/// <b>false</b> if the sender is using port 5353.
/// </value>
public bool IsLegacyUnicast => RemoteEndPoint.Port != MulticastClient.MulticastPort;
}
}

9 changes: 8 additions & 1 deletion src/MulticastClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ class MulticastClient : IDisposable
{
static readonly ILog log = LogManager.GetLogger(typeof(MulticastClient));

const int MulticastPort = 5353;
/// <summary>
/// The port number assigned to Multicast DNS.
/// </summary>
/// <value>
/// Port number 5353.
/// </value>
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);
Expand Down
103 changes: 100 additions & 3 deletions src/MulticastService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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") };

Expand All @@ -51,6 +52,16 @@ public class MulticastService : IResolver, IDisposable
/// </summary>
MulticastClient client;

/// <summary>
/// Use to send unicast IPv4 answers.
/// </summary>
UdpClient unicastClientIp4 = new UdpClient(AddressFamily.InterNetwork);

/// <summary>
/// Use to send unicast IPv6 answers.
/// </summary>
UdpClient unicastClientIp6 = new UdpClient(AddressFamily.InterNetworkV6);

/// <summary>
/// Function used for listening filtered network interfaces.
/// </summary>
Expand Down Expand Up @@ -79,7 +90,6 @@ static MulticastService()
/// then forgotten.
/// </remarks>
/// <seealso cref="SendQuery(Message)"/>
/// <see cref="SendAnswer"/>
public event EventHandler<MessageEventArgs> QueryReceived;

/// <summary>
Expand Down Expand Up @@ -446,8 +456,10 @@ public void SendQuery(Message msg)
/// When the serialised <paramref name="answer"/> is too large.
/// </exception>
/// <remarks>
/// <para>
/// The <see cref="Message.AA"/> flag is set to true,
/// the <see cref="Message.Id"/> set to zero and any questions are removed.
/// </para>
/// <para>
/// The <paramref name="answer"/> is <see cref="Message.Truncate">truncated</see>
/// if exceeds the maximum packet length.
Expand All @@ -456,6 +468,10 @@ public void SendQuery(Message msg)
/// <paramref name="checkDuplicate"/> should always be <b>true</b> except
/// when <see href="https://tools.ietf.org/html/rfc6762#section-8.1">answering a probe</see>.
/// </para>
/// <note type="caution">
/// If possible the <see cref="SendAnswer(Message, MessageEventArgs, bool)"/>
/// method should be used, so that legacy unicast queries are supported.
/// </note>
/// </remarks>
/// <see cref="QueryReceived"/>
/// <seealso cref="Message.CreateResponse"/>
Expand All @@ -474,7 +490,77 @@ public void SendAnswer(Message answer, bool checkDuplicate = true)
Send(answer, checkDuplicate);
}

void Send(Message msg, bool checkDuplicate)
/// <summary>
/// Send an answer to a query.
/// </summary>
/// <param name="answer">
/// The answer message.
/// </param>
/// <param name="query">
/// The query that is being answered.
/// </param>
/// <param name="checkDuplicate">
/// If <b>true</b>, then if the same <paramref name="answer"/> was
/// recently sent it will not be sent again.
/// </param>
/// <exception cref="InvalidOperationException">
/// When the service has not started.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// When the serialised <paramref name="answer"/> is too large.
/// </exception>
/// <remarks>
/// <para>
/// If the <paramref name="query"/> is a standard multicast query (sent to port 5353), then
/// <see cref="SendAnswer(Message, bool)"/> is called.
/// </para>
/// <para>
/// Otherwise a legacy unicast reponse is sent to sender's end point.
/// The <see cref="Message.AA"/> flag is set to true,
/// the <see cref="Message.Id"/> is set to query's ID,
/// the <see cref="Message.Questions"/> is set to the query's questions,
/// and all resource record TTLs have a max value of 10 seconds.
/// </para>
/// <para>
/// The <paramref name="answer"/> is <see cref="Message.Truncate">truncated</see>
/// if exceeds the maximum packet length.
/// </para>
/// <para>
/// <paramref name="checkDuplicate"/> should always be <b>true</b> except
/// when <see href="https://tools.ietf.org/html/rfc6762#section-8.1">answering a probe</see>.
/// </para>
/// </remarks>
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)
Expand All @@ -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();
}
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/ServiceDiscovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
56 changes: 54 additions & 2 deletions test/MulticastServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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()
{
Expand Down

0 comments on commit d9b2ea0

Please sign in to comment.