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:
BaronGreenback 2020-04-28 21:57:39 +01:00
parent a3140f83c6
commit ebd589aa86
6 changed files with 102 additions and 77 deletions

View file

@ -180,7 +180,7 @@ namespace Emby.Dlna.Main
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows || var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
OperatingSystem.Id == OperatingSystemId.Linux; OperatingSystem.Id == OperatingSystemId.Linux;
_communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding) _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
{ {
IsShared = true IsShared = true
}; };
@ -266,6 +266,12 @@ namespace Emby.Dlna.Main
continue; continue;
} }
// Limit to LAN addresses only
if (!_networkManager.IsAddressInSubnets(address, true, true))
{
continue;
}
var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);

View file

@ -1274,7 +1274,7 @@ namespace Emby.Server.Implementations
if (addresses.Count == 0) if (addresses.Count == 0)
{ {
addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces)); addresses.AddRange(_networkManager.GetLocalIpAddresses());
} }
var resultList = new List<IPAddress>(); var resultList = new List<IPAddress>();

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
@ -56,13 +55,13 @@ namespace Emby.Server.Implementations.Networking
NetworkChanged?.Invoke(this, EventArgs.Empty); NetworkChanged?.Invoke(this, EventArgs.Empty);
} }
public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true) public IPAddress[] GetLocalIpAddresses()
{ {
lock (_localIpAddressSyncLock) lock (_localIpAddressSyncLock)
{ {
if (_localIpAddresses == null) if (_localIpAddresses == null)
{ {
var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).ToArray(); var addresses = GetLocalIpAddressesInternal().ToArray();
_localIpAddresses = addresses; _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) if (list.Count == 0)
{ {
list = GetLocalIpAddressesFallback().GetAwaiter().GetResult().ToList(); 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) .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1)
.ThenBy(i => listClone.IndexOf(i)) // .ThenBy(i => listClone.IndexOf(i))
.Where(FilterIpAddress)
.GroupBy(i => i.ToString()) .GroupBy(i => i.ToString())
.Select(x => x.First()) .Select(x => x.First())
.ToList(); .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) public bool IsInPrivateAddressSpace(string endpoint)
{ {
return IsInPrivateAddressSpace(endpoint, true); return IsInPrivateAddressSpace(endpoint, true);
} }
// checks if the address in endpoint is an RFC1918, RFC1122, or RFC3927 address
private bool IsInPrivateAddressSpace(string endpoint, bool checkSubnets) private bool IsInPrivateAddressSpace(string endpoint, bool checkSubnets)
{ {
if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase)) if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase))
@ -128,23 +130,28 @@ namespace Emby.Server.Implementations.Networking
} }
// Private address space: // Private address space:
// http://en.wikipedia.org/wiki/Private_network
if (endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase)) if (endpoint.ToLower() == "localhost")
{
return Is172AddressPrivate(endpoint);
}
if (endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) ||
endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) ||
endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase))
{ {
return true; 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)) if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint))
@ -177,6 +184,7 @@ namespace Emby.Server.Implementations.Networking
return false; return false;
} }
// Gives a list of possible subnets from the system whose interface ip starts with endpointFirstPart
private List<string> GetSubnets(string endpointFirstPart) private List<string> GetSubnets(string endpointFirstPart)
{ {
lock (_subnetLookupLock) 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) public bool IsInLocalNetwork(string endpoint)
{ {
return IsInLocalNetworkInternal(endpoint, true); return IsInLocalNetworkInternal(endpoint, true);
@ -245,23 +240,57 @@ namespace Emby.Server.Implementations.Networking
return IsAddressInSubnets(IPAddress.Parse(addressString), addressString, subnets); 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) private static bool IsAddressInSubnets(IPAddress address, string addressString, string[] subnets)
{ {
foreach (var subnet in subnets) foreach (var subnet in subnets)
{ {
var normalizedSubnet = subnet.Trim(); 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)) if (string.Equals(normalizedSubnet, addressString, StringComparison.OrdinalIgnoreCase))
{ {
return true; return true;
} }
// parse CIDR subnets and see if address falls within it.
if (normalizedSubnet.Contains('/', StringComparison.Ordinal)) if (normalizedSubnet.Contains('/', StringComparison.Ordinal))
{ {
var ipNetwork = IPNetwork.Parse(normalizedSubnet); try
if (ipNetwork.Contains(address))
{ {
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); return Dns.GetHostAddressesAsync(hostName);
} }
private IEnumerable<IPAddress> GetIPsDefault(bool ignoreVirtualInterface) private IEnumerable<IPAddress> GetIPsDefault()
{ {
IEnumerable<NetworkInterface> interfaces; IEnumerable<NetworkInterface> interfaces;
@ -380,15 +409,7 @@ namespace Emby.Server.Implementations.Networking
{ {
var ipProperties = network.GetIPProperties(); var ipProperties = network.GetIPProperties();
// Try to exclude virtual adapters // Exclude any addresses if they appear in the LAN list in [ ]
// 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>();
}
return ipProperties.UnicastAddresses return ipProperties.UnicastAddresses
.Select(i => i.Address) .Select(i => i.Address)
@ -494,15 +515,12 @@ namespace Emby.Server.Implementations.Networking
foreach (NetworkInterface ni in interfaces) 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;
}
}
} }
} }

View file

@ -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> /// <returns><c>true</c> if [is in local network] [the specified endpoint]; otherwise, <c>false</c>.</returns>
bool IsInLocalNetwork(string endpoint); bool IsInLocalNetwork(string endpoint);
IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface); IPAddress[] GetLocalIpAddresses();
bool IsAddressInSubnets(string addressString, string[] subnets); bool IsAddressInSubnets(string addressString, string[] subnets);
bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC);
bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask); bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask);
IPAddress GetLocalIpSubnetMask(IPAddress address); IPAddress GetLocalIpSubnetMask(IPAddress address);

View file

@ -46,8 +46,7 @@ namespace Rssdp.Infrastructure
private HttpResponseParser _ResponseParser; private HttpResponseParser _ResponseParser;
private readonly ILogger _logger; private readonly ILogger _logger;
private ISocketFactory _SocketFactory; private ISocketFactory _SocketFactory;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly IServerConfigurationManager _config;
private int _LocalPort; private int _LocalPort;
private int _MulticastTtl; private int _MulticastTtl;
@ -77,11 +76,11 @@ namespace Rssdp.Infrastructure
/// Minimum constructor. /// Minimum constructor.
/// </summary> /// </summary>
/// <exception cref="ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception> /// <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) INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
: this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding) : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding)
{ {
_config = config;
} }
/// <summary> /// <summary>
@ -370,13 +369,13 @@ namespace Rssdp.Infrastructure
if (_enableMultiSocketBinding) if (_enableMultiSocketBinding)
{ {
foreach (var address in _networkManager.GetLocalIpAddresses(_config.Configuration.IgnoreVirtualInterfaces)) foreach (var address in _networkManager.GetLocalIpAddresses())
{ {
if (address.AddressFamily == AddressFamily.InterNetworkV6) if (address.AddressFamily == AddressFamily.InterNetworkV6)
{ {
// Not support IPv6 right now // Not support IPv6 right now
continue; continue;
} }
try try
{ {

View file

@ -357,7 +357,7 @@ namespace Rssdp.Infrastructure
private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress) private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress)
{ {
if (!message.IsSuccessStatusCode) return; if (!message.IsSuccessStatusCode) return;
var location = GetFirstHeaderUriValue("Location", message); var location = GetFirstHeaderUriValue("Location", message);
if (location != null) if (location != null)
{ {