Skip to content
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

Reachable ip #100

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions src/IPAddressExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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>
/// <param name="searchOnNeighborAddresses">If other addresses from the same NIC should als be checked or not.</param>
/// <param name="nics">The available nics on the system. Providing them reduces computation time.</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, bool searchOnNeighborAddresses = true, NetworkInterface[] nics = null)
{
// Loopback addresses are only reachable when the remote is
// the same host.
if (local.Equals(IPAddress.Loopback) || local.Equals(IPAddress.IPv6Loopback))
//if (IPAddress.IsLoopback(local) && IPAddress.IsLoopback(remote))
{
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);
}

// A mask of null should not occur. Is the IP address available on the actual machine or are the local and remote addresses switched in the function call?
return false;
}

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

// only interested in link local addresses
return false;
}


// mix of IPv4 and IPv6 -> try if the remote is reachable from one of the other IP addresses of the same NIC
if (!searchOnNeighborAddresses)
return false;

if(nics == null)
nics = NetworkInterface.GetAllNetworkInterfaces();

foreach (var nic in nics)
{
var localAddressInformation = nic.GetIPProperties().UnicastAddresses.Where(a => a.Address.Equals(local)).FirstOrDefault();
if (localAddressInformation != null)
{
var otherAddresses = nic.GetIPProperties().UnicastAddresses.Where(a => a != localAddressInformation).Select(a => a.Address);
return otherAddresses.Any(otherAddress => otherAddress.IsReachable(remote, searchOnNeighborAddresses: false, nics));
}
}

// If nothing of the above matched, they are probably not reachable.
return false;
}
}
}
7 changes: 6 additions & 1 deletion src/Mdns.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@
<PackageTags>multicast mdns dns zeroconf</PackageTags>
<IncludeSymbols>True</IncludeSymbols>
<PackageProjectUrl>https://github.com/richardschneider/net-mdns</PackageProjectUrl>
<Nullable>disable</Nullable>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard14'">
<DefineConstants>NETSTANDARD14</DefineConstants>
<DefineConstants>DEBUG;NETSTANDARD14</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard20|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>

<ItemGroup>
Expand Down
69 changes: 69 additions & 0 deletions src/MessageExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;

namespace Makaretu.Dns
{
/// <summary>Class for extensions regarding the message class.</summary>
public static class MessageExtensions
{
/// <summary>Copies the specified original message.</summary>
/// <param name="originalMessage">The original message.</param>
/// <returns>A copy of the original message.</returns>
public static Message Copy(this Message originalMessage)
{
var message = new Message();
message.AA = originalMessage.AA;
message.AD = originalMessage.AD;
message.CD = originalMessage.AD;
message.DO = originalMessage.DO;
message.Id = originalMessage.Id;
message.QR = originalMessage.QR;
message.Opcode = originalMessage.Opcode;
message.RA = originalMessage.RA;
message.RD = originalMessage.RD;
message.TC = originalMessage.TC;
message.Status = originalMessage.Status;
message.Z = originalMessage.Z;

message.AdditionalRecords = new List<ResourceRecord>(originalMessage.AdditionalRecords);
message.AuthorityRecords = new List<ResourceRecord>(originalMessage.AdditionalRecords);
message.Answers = new List<ResourceRecord>(originalMessage.Answers);
message.Questions.AddRange(originalMessage.Questions);

return message;
}

/// <summary>Removes the unreachable records.</summary>
/// <param name="message">The message from which the unreachable records should be removed.</param>
/// <param name="address">The address which should be able to reach all the addresses contained in the address records.</param>
/// <param name="nics">The available nics on the system. Providing them reduces computation time.</param>
public static void RemoveUnreachableRecords(this Message message, IPAddress address, NetworkInterface[] nics = null)
{
// Only return address records that the querier can reach.
message.Answers.RemoveAll(rr => IsUnreachable(rr, address, nics));
message.AuthorityRecords.RemoveAll(rr => IsUnreachable(rr, address, nics));
message.AdditionalRecords.RemoveAll(rr => IsUnreachable(rr, address, nics));
}

/// <summary>Determines whether the message contains address records.</summary>
/// <param name="message">The message that should be checked for address records.</param>
/// <returns><c>true</c> if the specified message contains address records; otherwise, <c>false</c>.</returns>
public static bool ContainsAddressRecords(this Message message) =>
(message.Answers.Any((rr) => rr.Type == DnsType.A || rr.Type == DnsType.AAAA) ||
message.AuthorityRecords.Any((rr) => rr.Type == DnsType.A || rr.Type == DnsType.AAAA) ||
message.AdditionalRecords.Any((rr) => rr.Type == DnsType.A || rr.Type == DnsType.AAAA));

/// <summary>Determines whether the specified resource record is unreachable.</summary>
/// <param name="rr">The resource record.</param>
/// <param name="address">The address.</param>
/// <returns><c>true</c> if the specified resource records is unreachable; otherwise, <c>false</c>.</returns>
/// <param name="nics">The available nics on the system. Providing them reduces computation time.</param>
private static bool IsUnreachable(ResourceRecord rr, IPAddress address, NetworkInterface[] nics = null)
{
var addressRecord = rr as AddressRecord;
return !addressRecord?.Address.IsReachable(address, nics: nics) ?? false;
}
}
}
64 changes: 47 additions & 17 deletions src/MulticastClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Common.Logging;
using Common.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
Expand Down Expand Up @@ -104,12 +104,12 @@ public MulticastClient(bool useIPv4, bool useIpv6, IEnumerable<NetworkInterface>
sender.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastLoopback, true);
break;
case AddressFamily.InterNetworkV6:
receiver6.Client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.AddMembership, new IPv6MulticastOption(MulticastAddressIp6, address.ScopeId));
sender.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
sender.Client.Bind(localEndpoint);
sender.Client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.AddMembership, new IPv6MulticastOption(MulticastAddressIp6));
receiver6.Client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.AddMembership, new IPv6MulticastOption(MulticastAddressIp6, address.ScopeId));
sender.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
sender.Client.Bind(localEndpoint);
sender.Client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.AddMembership, new IPv6MulticastOption(MulticastAddressIp6));
sender.Client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.MulticastLoopback, true);
break;
break;
default:
throw new NotSupportedException($"Address family {address.AddressFamily}.");
}
Expand All @@ -127,39 +127,69 @@ public MulticastClient(bool useIPv4, bool useIpv6, IEnumerable<NetworkInterface>
}
catch (Exception e)
{
log.Error($"Cannot setup send socket for {address}: {e.Message}");
log.Error($"Cannot setup send socket for {address}: {e.Message}");
sender.Dispose();
}
}

// Start listening for messages.
// Start listening for messages.
foreach (var r in receivers)
{
Listen(r);
}
}

public async Task SendAsync(byte[] message)
public async Task SendAsync(Message message, IPAddress ipAddress = null, NetworkInterface[] nics = null)
{
foreach (var sender in senders)
{
try
foreach (var sender in senders)
{
// Make a copy of the message, so that its resource records can be removed without changing the actual message.
var messageCopy = message.Copy();

// Only return address records that the querier can reach.
if (ipAddress != null)
{
var reachable = sender.Key.IsReachable(ipAddress, nics: nics);
if (reachable == false)
{
log.Debug($"Suppressing a message because {ipAddress} (querier) and {sender.Key} are not on the same subnet.");
continue;
}
}
else if (!messageCopy.IsQuery)
{
// When the IP is null, the querier is not defined -> return only address records that remain in the same subnet as the sender.
messageCopy.RemoveUnreachableRecords(sender.Key, nics);

// If there is no address record left, the service is not running on the same subnet as the sender, so move to the next sender.
if (!messageCopy.ContainsAddressRecords())
{
log.Debug($"All address records have been removed for {sender.Key}, therefore no message is sent.");
continue;
}
}

var messageBytes = messageCopy.ToByteArray();

try
{
var endpoint = sender.Key.AddressFamily == AddressFamily.InterNetwork ? MdnsEndpointIp4 : MdnsEndpointIp6;

log.Debug($"Trying to send the message from {sender.Key} to {endpoint.Address}:{endpoint.Port}");
await sender.Value.SendAsync(
message, message.Length,
endpoint)
.ConfigureAwait(false);
messageBytes, messageBytes.Length,
endpoint)
.ConfigureAwait(false);
}
catch (Exception e)
{
log.Error($"Sender {sender.Key} failure: {e.Message}");
log.Error($"Sender {sender.Key} failure: {e.Message}");
// eat it.
}
}
}

void Listen(UdpClient receiver)
void Listen(UdpClient receiver)
{
// ReceiveAsync does not support cancellation. So the receiver is disposed
// to stop it. See https://github.com/dotnet/corefx/issues/9848
Expand Down
Loading