|
@@ -2,7 +2,6 @@
|
|
|
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
-using System.Globalization;
|
|
|
using System.Linq;
|
|
|
using System.Net;
|
|
|
using System.Net.NetworkInformation;
|
|
@@ -13,6 +12,9 @@ using Microsoft.Extensions.Logging;
|
|
|
|
|
|
namespace Emby.Server.Implementations.Networking
|
|
|
{
|
|
|
+ /// <summary>
|
|
|
+ /// Class to take care of network interface management.
|
|
|
+ /// </summary>
|
|
|
public class NetworkManager : INetworkManager
|
|
|
{
|
|
|
private readonly ILogger<NetworkManager> _logger;
|
|
@@ -21,8 +23,14 @@ namespace Emby.Server.Implementations.Networking
|
|
|
private readonly object _localIpAddressSyncLock = new object();
|
|
|
|
|
|
private readonly object _subnetLookupLock = new object();
|
|
|
- private Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
|
|
|
+ private readonly Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
|
|
|
|
|
|
+ private List<PhysicalAddress> _macAddresses;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes a new instance of the <see cref="NetworkManager"/> class.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="logger">Logger to use for messages.</param>
|
|
|
public NetworkManager(ILogger<NetworkManager> logger)
|
|
|
{
|
|
|
_logger = logger;
|
|
@@ -31,8 +39,10 @@ namespace Emby.Server.Implementations.Networking
|
|
|
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
public event EventHandler NetworkChanged;
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
public Func<string[]> LocalSubnetsFn { get; set; }
|
|
|
|
|
|
private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
|
|
@@ -58,13 +68,14 @@ namespace Emby.Server.Implementations.Networking
|
|
|
NetworkChanged?.Invoke(this, EventArgs.Empty);
|
|
|
}
|
|
|
|
|
|
- public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public IPAddress[] GetLocalIpAddresses()
|
|
|
{
|
|
|
lock (_localIpAddressSyncLock)
|
|
|
{
|
|
|
if (_localIpAddresses == null)
|
|
|
{
|
|
|
- var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).ToArray();
|
|
|
+ var addresses = GetLocalIpAddressesInternal().ToArray();
|
|
|
|
|
|
_localIpAddresses = addresses;
|
|
|
}
|
|
@@ -73,42 +84,47 @@ namespace Emby.Server.Implementations.Networking
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private List<IPAddress> GetLocalIpAddressesInternal(bool ignoreVirtualInterface)
|
|
|
+ private List<IPAddress> GetLocalIpAddressesInternal()
|
|
|
{
|
|
|
- var list = GetIPsDefault(ignoreVirtualInterface).ToList();
|
|
|
+ var list = GetIPsDefault().ToList();
|
|
|
|
|
|
if (list.Count == 0)
|
|
|
{
|
|
|
list = GetLocalIpAddressesFallback().GetAwaiter().GetResult().ToList();
|
|
|
}
|
|
|
|
|
|
- var listClone = list.ToList();
|
|
|
+ var listClone = new List<IPAddress>();
|
|
|
|
|
|
- return list
|
|
|
- .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1)
|
|
|
- .ThenBy(i => listClone.IndexOf(i))
|
|
|
- .Where(FilterIpAddress)
|
|
|
- .GroupBy(i => i.ToString())
|
|
|
- .Select(x => x.First())
|
|
|
- .ToList();
|
|
|
- }
|
|
|
+ var subnets = LocalSubnetsFn();
|
|
|
|
|
|
- private static bool FilterIpAddress(IPAddress address)
|
|
|
- {
|
|
|
- if (address.IsIPv6LinkLocal
|
|
|
- || address.ToString().StartsWith("169.", StringComparison.OrdinalIgnoreCase))
|
|
|
+ foreach (var i in list)
|
|
|
{
|
|
|
- return false;
|
|
|
+ if (i.IsIPv6LinkLocal || i.ToString().StartsWith("169.254.", StringComparison.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Array.IndexOf(subnets, $"[{i}]") == -1)
|
|
|
+ {
|
|
|
+ listClone.Add(i);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- return true;
|
|
|
+ return listClone
|
|
|
+ .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1)
|
|
|
+ // .ThenBy(i => listClone.IndexOf(i))
|
|
|
+ .GroupBy(i => i.ToString())
|
|
|
+ .Select(x => x.First())
|
|
|
+ .ToList();
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
public bool IsInPrivateAddressSpace(string endpoint)
|
|
|
{
|
|
|
return IsInPrivateAddressSpace(endpoint, true);
|
|
|
}
|
|
|
|
|
|
+ // Checks if the address in endpoint is an RFC1918, RFC1122, or RFC3927 address
|
|
|
private bool IsInPrivateAddressSpace(string endpoint, bool checkSubnets)
|
|
|
{
|
|
|
if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase))
|
|
@@ -116,12 +132,12 @@ namespace Emby.Server.Implementations.Networking
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- // ipv6
|
|
|
+ // IPV6
|
|
|
if (endpoint.Split('.').Length > 4)
|
|
|
{
|
|
|
// Handle ipv4 mapped to ipv6
|
|
|
var originalEndpoint = endpoint;
|
|
|
- endpoint = endpoint.Replace("::ffff:", string.Empty);
|
|
|
+ endpoint = endpoint.Replace("::ffff:", string.Empty, StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
|
if (string.Equals(endpoint, originalEndpoint, StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
@@ -130,23 +146,21 @@ namespace Emby.Server.Implementations.Networking
|
|
|
}
|
|
|
|
|
|
// Private address space:
|
|
|
- // http://en.wikipedia.org/wiki/Private_network
|
|
|
-
|
|
|
- if (endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- return Is172AddressPrivate(endpoint);
|
|
|
- }
|
|
|
|
|
|
- if (endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) ||
|
|
|
- endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) ||
|
|
|
- endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase))
|
|
|
+ if (string.Equals(endpoint, "localhost", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- if (checkSubnets && endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase))
|
|
|
+ byte[] octet = IPAddress.Parse(endpoint).GetAddressBytes();
|
|
|
+
|
|
|
+ if ((octet[0] == 10) ||
|
|
|
+ (octet[0] == 172 && (octet[1] >= 16 && octet[1] <= 31)) || // RFC1918
|
|
|
+ (octet[0] == 192 && octet[1] == 168) || // RFC1918
|
|
|
+ (octet[0] == 127) || // RFC1122
|
|
|
+ (octet[0] == 169 && octet[1] == 254)) // RFC3927
|
|
|
{
|
|
|
- return true;
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint))
|
|
@@ -157,6 +171,7 @@ namespace Emby.Server.Implementations.Networking
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
public bool IsInPrivateAddressSpaceAndLocalSubnet(string endpoint)
|
|
|
{
|
|
|
if (endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase))
|
|
@@ -179,6 +194,7 @@ namespace Emby.Server.Implementations.Networking
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ // Gives a list of possible subnets from the system whose interface ip starts with endpointFirstPart
|
|
|
private List<string> GetSubnets(string endpointFirstPart)
|
|
|
{
|
|
|
lock (_subnetLookupLock)
|
|
@@ -224,46 +240,75 @@ namespace Emby.Server.Implementations.Networking
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private static bool Is172AddressPrivate(string endpoint)
|
|
|
- {
|
|
|
- for (var i = 16; i <= 31; i++)
|
|
|
- {
|
|
|
- if (endpoint.StartsWith("172." + i.ToString(CultureInfo.InvariantCulture) + ".", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
+ /// <inheritdoc/>
|
|
|
public bool IsInLocalNetwork(string endpoint)
|
|
|
{
|
|
|
return IsInLocalNetworkInternal(endpoint, true);
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
public bool IsAddressInSubnets(string addressString, string[] subnets)
|
|
|
{
|
|
|
return IsAddressInSubnets(IPAddress.Parse(addressString), addressString, subnets);
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC)
|
|
|
+ {
|
|
|
+ byte[] octet = address.GetAddressBytes();
|
|
|
+
|
|
|
+ if ((octet[0] == 127) || // RFC1122
|
|
|
+ (octet[0] == 169 && octet[1] == 254)) // RFC3927
|
|
|
+ {
|
|
|
+ // don't use on loopback or 169 interfaces
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ string addressString = address.ToString();
|
|
|
+ string excludeAddress = "[" + addressString + "]";
|
|
|
+ var subnets = LocalSubnetsFn();
|
|
|
+
|
|
|
+ // Exclude any addresses if they appear in the LAN list in [ ]
|
|
|
+ if (Array.IndexOf(subnets, excludeAddress) != -1)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return IsAddressInSubnets(address, addressString, subnets);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Checks if the give address falls within the ranges given in [subnets]. The addresses in subnets can be hosts or subnets in the CIDR format.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="address">IPAddress version of the address.</param>
|
|
|
+ /// <param name="addressString">The address to check.</param>
|
|
|
+ /// <param name="subnets">If true, check against addresses in the LAN settings which have [] arroud and return true if it matches the address give in address.</param>
|
|
|
+ /// <returns><c>false</c>if the address isn't in the subnets, <c>true</c> otherwise.</returns>
|
|
|
private static bool IsAddressInSubnets(IPAddress address, string addressString, string[] subnets)
|
|
|
{
|
|
|
foreach (var subnet in subnets)
|
|
|
{
|
|
|
var normalizedSubnet = subnet.Trim();
|
|
|
-
|
|
|
+ // Is the subnet a host address and does it match the address being passes?
|
|
|
if (string.Equals(normalizedSubnet, addressString, StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ // Parse CIDR subnets and see if address falls within it.
|
|
|
if (normalizedSubnet.Contains('/', StringComparison.Ordinal))
|
|
|
{
|
|
|
- var ipNetwork = IPNetwork.Parse(normalizedSubnet);
|
|
|
- if (ipNetwork.Contains(address))
|
|
|
+ try
|
|
|
{
|
|
|
- return true;
|
|
|
+ var ipNetwork = IPNetwork.Parse(normalizedSubnet);
|
|
|
+ if (ipNetwork.Contains(address))
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ // Ignoring - invalid subnet passed encountered.
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -288,7 +333,7 @@ namespace Emby.Server.Implementations.Networking
|
|
|
var localSubnets = localSubnetsFn();
|
|
|
foreach (var subnet in localSubnets)
|
|
|
{
|
|
|
- // only validate if there's at least one valid entry
|
|
|
+ // Only validate if there's at least one valid entry.
|
|
|
if (!string.IsNullOrWhiteSpace(subnet))
|
|
|
{
|
|
|
return IsAddressInSubnets(address, addressString, localSubnets) || IsInPrivateAddressSpace(addressString, false);
|
|
@@ -345,7 +390,7 @@ namespace Emby.Server.Implementations.Networking
|
|
|
}
|
|
|
catch (InvalidOperationException)
|
|
|
{
|
|
|
- // Can happen with reverse proxy or IIS url rewriting
|
|
|
+ // Can happen with reverse proxy or IIS url rewriting?
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
@@ -362,7 +407,7 @@ namespace Emby.Server.Implementations.Networking
|
|
|
return Dns.GetHostAddressesAsync(hostName);
|
|
|
}
|
|
|
|
|
|
- private IEnumerable<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
|
|
|
+ private IEnumerable<IPAddress> GetIPsDefault()
|
|
|
{
|
|
|
IEnumerable<NetworkInterface> interfaces;
|
|
|
|
|
@@ -382,15 +427,7 @@ namespace Emby.Server.Implementations.Networking
|
|
|
{
|
|
|
var ipProperties = network.GetIPProperties();
|
|
|
|
|
|
- // Try to exclude virtual adapters
|
|
|
- // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms
|
|
|
- var addr = ipProperties.GatewayAddresses.FirstOrDefault();
|
|
|
- if (addr == null
|
|
|
- || (ignoreVirtualInterface
|
|
|
- && (addr.Address.Equals(IPAddress.Any) || addr.Address.Equals(IPAddress.IPv6Any))))
|
|
|
- {
|
|
|
- return Enumerable.Empty<IPAddress>();
|
|
|
- }
|
|
|
+ // Exclude any addresses if they appear in the LAN list in [ ]
|
|
|
|
|
|
return ipProperties.UnicastAddresses
|
|
|
.Select(i => i.Address)
|
|
@@ -423,33 +460,29 @@ namespace Emby.Server.Implementations.Networking
|
|
|
return port;
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
public int GetRandomUnusedUdpPort()
|
|
|
{
|
|
|
var localEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
|
|
using (var udpClient = new UdpClient(localEndPoint))
|
|
|
{
|
|
|
- var port = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port;
|
|
|
- return port;
|
|
|
+ return ((IPEndPoint)udpClient.Client.LocalEndPoint).Port;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private List<PhysicalAddress> _macAddresses;
|
|
|
+ /// <inheritdoc/>
|
|
|
public List<PhysicalAddress> GetMacAddresses()
|
|
|
{
|
|
|
- if (_macAddresses == null)
|
|
|
- {
|
|
|
- _macAddresses = GetMacAddressesInternal().ToList();
|
|
|
- }
|
|
|
-
|
|
|
- return _macAddresses;
|
|
|
+ return _macAddresses ??= GetMacAddressesInternal().ToList();
|
|
|
}
|
|
|
|
|
|
private static IEnumerable<PhysicalAddress> GetMacAddressesInternal()
|
|
|
=> NetworkInterface.GetAllNetworkInterfaces()
|
|
|
.Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
|
|
.Select(x => x.GetPhysicalAddress())
|
|
|
- .Where(x => x != null && x != PhysicalAddress.None);
|
|
|
+ .Where(x => !x.Equals(PhysicalAddress.None));
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
|
|
|
{
|
|
|
IPAddress network1 = GetNetworkAddress(address1, subnetMask);
|
|
@@ -476,6 +509,7 @@ namespace Emby.Server.Implementations.Networking
|
|
|
return new IPAddress(broadcastAddress);
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
public IPAddress GetLocalIpSubnetMask(IPAddress address)
|
|
|
{
|
|
|
NetworkInterface[] interfaces;
|
|
@@ -496,14 +530,11 @@ namespace Emby.Server.Implementations.Networking
|
|
|
|
|
|
foreach (NetworkInterface ni in interfaces)
|
|
|
{
|
|
|
- if (ni.GetIPProperties().GatewayAddresses.FirstOrDefault() != null)
|
|
|
+ foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
|
|
|
{
|
|
|
- foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
|
|
|
+ if (ip.Address.Equals(address) && ip.IPv4Mask != null)
|
|
|
{
|
|
|
- if (ip.Address.Equals(address) && ip.IPv4Mask != null)
|
|
|
- {
|
|
|
- return ip.IPv4Mask;
|
|
|
- }
|
|
|
+ return ip.IPv4Mask;
|
|
|
}
|
|
|
}
|
|
|
}
|