Skip to content

Commit

Permalink
feat(ServiceDiscovery): Only return reachable address records
Browse files Browse the repository at this point in the history
  • Loading branch information
richardschneider committed Aug 20, 2018
1 parent cd9b00c commit 723b659
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 1 deletion.
79 changes: 79 additions & 0 deletions src/IPAddressExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;

namespace Makaretu.Dns
{
/// <summary>
/// Extensions for <see cref="IPAddress"/>.
/// </summary>
public static class IPAddressExtensions
{
/// <summary>
/// Gets the subnet mask associated with the IP address.
/// </summary>
/// <param name="address">
/// An IP Addresses.
/// </param>
/// <returns>
/// The subnet mask; ror example "127.0.0.1" returns "255.0.0.0".
/// Or <b>null</b> When <paramref name="address"/> does not belong to
/// the localhost.
/// s</returns>
public static IPAddress GetSubnetMask(this IPAddress address)
{
return NetworkInterface.GetAllNetworkInterfaces()
.SelectMany(nic => nic.GetIPProperties().UnicastAddresses)
.Where(a => a.Address.Equals(address))
.Select(a => a.IPv4Mask)
.FirstOrDefault();
}

/// <summary>
/// Determines if the local IP address can be used by the
/// remote address.
/// </summary>
/// <param name="local"></param>
/// <param name="remote"></param>
/// <returns>
/// <b>true</b> if <paramref name="local"/> can be used by <paramref name="remote"/>;
/// otherwise, <b>false</b>.
/// </returns>
public static bool IsReachable(this IPAddress local, IPAddress remote)
{
// Loopback addresses are only reachable when the remote is
// the same host.
if (local.Equals(IPAddress.Loopback) || local.Equals(IPAddress.IPv6Loopback))
{
return MulticastService.GetIPAddresses().Contains(remote);
}

// IPv4 addresses are reachable when on the same subnet.
if (local.AddressFamily == AddressFamily.InterNetwork && remote.AddressFamily == AddressFamily.InterNetwork)
{
var mask = local.GetSubnetMask();
if (mask != null)
{
var network = IPNetwork.Parse(local, mask);
return network.Contains(remote);
}
}

// IPv6 link local addresses are reachabe when using the same scope id.
if (local.AddressFamily == AddressFamily.InterNetworkV6 && remote.AddressFamily == AddressFamily.InterNetworkV6)
{
if (local.IsIPv6LinkLocal || remote.IsIPv6LinkLocal)
{
return local.Equals(remote);
}
}

// Can not determine reachability, assume that network routing can do it.
return true;
}
}
}
14 changes: 13 additions & 1 deletion src/ServiceDiscovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ void OnQuery(object sender, MessageEventArgs e)
if (log.IsDebugEnabled)
log.Debug($"got query for {request.Questions[0].Name} {request.Questions[0].Type}");
var response = NameServer.ResolveAsync(request).Result;

if (response.Status == MessageStatus.NoError)
{
// Many bonjour browsers don't like DNS-SD response
Expand All @@ -152,14 +153,25 @@ void OnQuery(object sender, MessageEventArgs e)
response.AdditionalRecords.Clear();
}

// Only return address records that the querier can reach.
response.Answers.RemoveAll(rr => IsUnreachable(rr, e.RemoteEndPoint));
response.AuthorityRecords.RemoveAll(rr => IsUnreachable(rr, e.RemoteEndPoint));
response.AdditionalRecords.RemoveAll(rr => IsUnreachable(rr, e.RemoteEndPoint));

Mdns.SendAnswer(response);
if (log.IsDebugEnabled)
log.Debug($"sent answer {response.Answers[0]}");
//Console.WriteLine($"Response time {(DateTime.Now - request.CreationTime).TotalMilliseconds}ms");
}
}

#region IDisposable Support
bool IsUnreachable(ResourceRecord rr, IPEndPoint sender)
{
var arecord = rr as AddressRecord;
return !arecord?.Address.IsReachable(sender.Address) ?? false;
}

#region IDisposable Support

/// <inheritdoc />
protected virtual void Dispose(bool disposing)
Expand Down
105 changes: 105 additions & 0 deletions test/IPAddressExtensionsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using Makaretu.Dns;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;

namespace Makaretu.Dns
{

[TestClass]
public class IPAddressExtensionsTest
{
[TestMethod]
public void SubnetMask_Ipv4Loopback()
{
var mask = IPAddress.Loopback.GetSubnetMask();
Assert.AreEqual(IPAddress.Parse("255.0.0.0"), mask);
var network = IPNetwork.Parse(IPAddress.Loopback, mask);
Console.Write(network.ToString());
}

[TestMethod]
public void SubnetMask_Ipv6Loopback()
{
var mask = IPAddress.IPv6Loopback.GetSubnetMask();
Assert.AreEqual(IPAddress.Parse("0.0.0.0"), mask);
}

[TestMethod]
public void SubmetMask_NotLocalhost()
{
var mask = IPAddress.Parse("1.1.1.1").GetSubnetMask();
Assert.IsNull(mask);
}

[TestMethod]
public void SubnetMask_All()
{
foreach (var a in MulticastService.GetIPAddresses())
{
var network = IPNetwork.Parse(a, a.GetSubnetMask());

Console.WriteLine($"{a} mask {a.GetSubnetMask()} {network}");

Assert.IsTrue(network.Contains(a), $"{a} is not reachable");
}
}

[TestMethod]
public void LinkLocal()
{
foreach (var a in MulticastService.GetIPAddresses())
{
Console.WriteLine($"{a} ll={a.IsIPv6LinkLocal} ss={a.IsIPv6SiteLocal}");
}
}

[TestMethod]
public void Reachable_Loopback_From_Localhost()
{
var me = IPAddress.Loopback;
foreach (var a in MulticastService.GetIPAddresses())
{
Assert.IsTrue(me.IsReachable(a), $"{a}");
}
Assert.IsFalse(me.IsReachable(IPAddress.Parse("1.1.1.1")));
Assert.IsFalse(me.IsReachable(IPAddress.Parse("2606:4700:4700::1111")));

me = IPAddress.IPv6Loopback;
foreach (var a in MulticastService.GetIPAddresses())
{
Assert.IsTrue(me.IsReachable(a), $"{a}");
}
Assert.IsFalse(me.IsReachable(IPAddress.Parse("1.1.1.1")));
Assert.IsFalse(me.IsReachable(IPAddress.Parse("2606:4700:4700::1111")));
}

[TestMethod]
public void Reachable_Ipv4()
{
var me = MulticastService.GetIPAddresses()
.First(a => a.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(a));
Assert.IsTrue(me.IsReachable(me));
Assert.IsFalse(me.IsReachable(IPAddress.Parse("1.1.1.1")));

var nat = IPAddress.Parse("165.84.19.151"); // NAT PCP assigned address
Assert.IsTrue(nat.IsReachable(IPAddress.Parse("1.1.1.1")));
}

[TestMethod]
public void Reachable_Ipv6_LinkLocal()
{
var me1 = IPAddress.Parse("fe80::1:2:3:4%1");
var me2 = IPAddress.Parse("fe80::1:2:3:4%2");
var me5 = IPAddress.Parse("fe80::1:2:3:5%1");
Assert.IsTrue(me1.IsReachable(me1));
Assert.IsTrue(me2.IsReachable(me2));
Assert.IsFalse(me1.IsReachable(me2));
Assert.IsFalse(me1.IsReachable(me5));
}
}
}

0 comments on commit 723b659

Please sign in to comment.