-
Notifications
You must be signed in to change notification settings - Fork 83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Interface specific addresses #26
base: master
Are you sure you want to change the base?
Changes from all commits
e592d1d
0c470a7
07b08bc
db48ca4
b2f6bb3
153d97c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; for 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there no further heuristics we could use if all else fails? Maybe something like |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
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] | ||
[TestCategory("IPv6")] | ||
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)); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When testing this on multi-interface setup, I was still seeing some weird results, like my local Hyper-V interface addresses being used in answers to queries sent from remote machines on a completely different network, which I'd expect would be filtered out.