Whilst fixing issues with SSDP on devices with multiple interfaces, i came across a design issue in the current code - namely interfaces without a gateway were ignored.
Fixing this required the removal of the code that attempted to detect virtual interfaces. Not wanting to remove functionality, but not able to keep the code in place, I implemented a work around solution (see 4 below). Whilst in the area, I also fixed a few minor bugs i encountered (1, 5, 6 below) and stopped SSDP messages from going out on non-LAN interfaces (3) All these changes are related. Changes 1 IsInPrivateAddressSpace - improved subnet code checking 2 interfaces with no gateway were being excluded from SSDP blasts 3 filtered SSDP blasts from not LAN addresses as defined on the network page. 4 removed #986 mod - as this was part of the issue of #2986. Interfaces can be excluded from the LAN by putting the LAN address in brackets. eg. [10.1.1.1] will exclude an interface with ip address 10.1.1.1 from SSDP 5 fixed a problem where an invalid LAN address causing the SSDP to crash 6 corrected local link filter (FilterIPAddress) to filter on 169.254. addresses
This commit is contained in:
parent
a3140f83c6
commit
ebd589aa86
|
@ -180,7 +180,7 @@ namespace Emby.Dlna.Main
|
|||
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
|
||||
OperatingSystem.Id == OperatingSystemId.Linux;
|
||||
|
||||
_communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
||||
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
||||
{
|
||||
IsShared = true
|
||||
};
|
||||
|
@ -266,6 +266,12 @@ namespace Emby.Dlna.Main
|
|||
continue;
|
||||
}
|
||||
|
||||
// Limit to LAN addresses only
|
||||
if (!_networkManager.IsAddressInSubnets(address, true, true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
||||
|
||||
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
|
||||
|
|
|
@ -1274,7 +1274,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
if (addresses.Count == 0)
|
||||
{
|
||||
addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
|
||||
addresses.AddRange(_networkManager.GetLocalIpAddresses());
|
||||
}
|
||||
|
||||
var resultList = new List<IPAddress>();
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
|
@ -56,13 +55,13 @@ namespace Emby.Server.Implementations.Networking
|
|||
NetworkChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
|
||||
public IPAddress[] GetLocalIpAddresses()
|
||||
{
|
||||
lock (_localIpAddressSyncLock)
|
||||
{
|
||||
if (_localIpAddresses == null)
|
||||
{
|
||||
var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).ToArray();
|
||||
var addresses = GetLocalIpAddressesInternal().ToArray();
|
||||
|
||||
_localIpAddresses = addresses;
|
||||
}
|
||||
|
@ -71,42 +70,45 @@ 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
|
||||
var subnets = LocalSubnetsFn();
|
||||
|
||||
foreach (var i in list)
|
||||
{
|
||||
if (i.IsIPv6LinkLocal || i.ToString().StartsWith("169.254.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Array.IndexOf(subnets, "[" + i.ToString() + "]") == -1)
|
||||
{
|
||||
listClone.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
return listClone
|
||||
.OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1)
|
||||
.ThenBy(i => listClone.IndexOf(i))
|
||||
.Where(FilterIpAddress)
|
||||
// .ThenBy(i => listClone.IndexOf(i))
|
||||
.GroupBy(i => i.ToString())
|
||||
.Select(x => x.First())
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static bool FilterIpAddress(IPAddress address)
|
||||
{
|
||||
if (address.IsIPv6LinkLocal
|
||||
|| address.ToString().StartsWith("169.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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))
|
||||
|
@ -128,23 +130,28 @@ 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 (endpoint.ToLower() == "localhost")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (checkSubnets && endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase))
|
||||
try
|
||||
{
|
||||
return true;
|
||||
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 false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint))
|
||||
|
@ -177,6 +184,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)
|
||||
|
@ -222,19 +230,6 @@ 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;
|
||||
}
|
||||
|
||||
public bool IsInLocalNetwork(string endpoint)
|
||||
{
|
||||
return IsInLocalNetworkInternal(endpoint, true);
|
||||
|
@ -245,23 +240,57 @@ namespace Emby.Server.Implementations.Networking
|
|||
return IsAddressInSubnets(IPAddress.Parse(addressString), addressString, subnets);
|
||||
}
|
||||
|
||||
// returns true if address is in the LAN list in the config file
|
||||
// always returns false if address has been excluded from the LAN if excludeInterfaces is true
|
||||
// and excludes RFC addresses if excludeRFC is true
|
||||
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);
|
||||
}
|
||||
|
||||
// Checks to see if address/addressString (same but different type) falls within subnets[]
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -359,8 +388,8 @@ namespace Emby.Server.Implementations.Networking
|
|||
{
|
||||
return Dns.GetHostAddressesAsync(hostName);
|
||||
}
|
||||
|
||||
private IEnumerable<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
|
||||
|
||||
private IEnumerable<IPAddress> GetIPsDefault()
|
||||
{
|
||||
IEnumerable<NetworkInterface> interfaces;
|
||||
|
||||
|
@ -380,15 +409,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)
|
||||
|
@ -494,15 +515,12 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,10 +41,12 @@ namespace MediaBrowser.Common.Net
|
|||
/// <returns><c>true</c> if [is in local network] [the specified endpoint]; otherwise, <c>false</c>.</returns>
|
||||
bool IsInLocalNetwork(string endpoint);
|
||||
|
||||
IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface);
|
||||
IPAddress[] GetLocalIpAddresses();
|
||||
|
||||
bool IsAddressInSubnets(string addressString, string[] subnets);
|
||||
|
||||
bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC);
|
||||
|
||||
bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask);
|
||||
|
||||
IPAddress GetLocalIpSubnetMask(IPAddress address);
|
||||
|
|
|
@ -46,8 +46,7 @@ namespace Rssdp.Infrastructure
|
|||
private HttpResponseParser _ResponseParser;
|
||||
private readonly ILogger _logger;
|
||||
private ISocketFactory _SocketFactory;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly INetworkManager _networkManager;
|
||||
|
||||
private int _LocalPort;
|
||||
private int _MulticastTtl;
|
||||
|
@ -77,11 +76,11 @@ namespace Rssdp.Infrastructure
|
|||
/// Minimum constructor.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception>
|
||||
public SsdpCommunicationsServer(IServerConfigurationManager config, ISocketFactory socketFactory,
|
||||
public SsdpCommunicationsServer(ISocketFactory socketFactory,
|
||||
INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
|
||||
: this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding)
|
||||
{
|
||||
_config = config;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -370,13 +369,13 @@ namespace Rssdp.Infrastructure
|
|||
|
||||
if (_enableMultiSocketBinding)
|
||||
{
|
||||
foreach (var address in _networkManager.GetLocalIpAddresses(_config.Configuration.IgnoreVirtualInterfaces))
|
||||
foreach (var address in _networkManager.GetLocalIpAddresses())
|
||||
{
|
||||
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
// Not support IPv6 right now
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
@ -357,7 +357,7 @@ namespace Rssdp.Infrastructure
|
|||
private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress)
|
||||
{
|
||||
if (!message.IsSuccessStatusCode) return;
|
||||
|
||||
|
||||
var location = GetFirstHeaderUriValue("Location", message);
|
||||
if (location != null)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue
Block a user