[go: up one dir, main page]

Skip to content

Commit

Permalink
Whilst fixing issues with SSDP on devices with multiple interfaces, i…
Browse files Browse the repository at this point in the history
… 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 jellyfin#986 mod - as this was part of the issue of jellyfin#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
  • Loading branch information
BaronGreenback committed Apr 28, 2020
1 parent a3140f8 commit ebd589a
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 76 deletions.
8 changes: 7 additions & 1 deletion Emby.Dlna/Main/DlnaEntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ private void StartSsdpHandler()
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
};
Expand Down Expand Up @@ -266,6 +266,12 @@ private async Task RegisterServerEndpoints()
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);
Expand Down
2 changes: 1 addition & 1 deletion Emby.Server.Implementations/ApplicationHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1274,7 +1274,7 @@ private async Task<List<IPAddress>> GetLocalIpAddressesInternal(bool allowLoopba

if (addresses.Count == 0)
{
addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
addresses.AddRange(_networkManager.GetLocalIpAddresses());
}

var resultList = new List<IPAddress>();
Expand Down
150 changes: 84 additions & 66 deletions Emby.Server.Implementations/Networking/NetworkManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
Expand Down Expand Up @@ -56,13 +55,13 @@ private void OnNetworkChanged()
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;
}
Expand All @@ -71,42 +70,45 @@ public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
}
}

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.ToString() + "]") == -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();
}

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))
Expand All @@ -128,23 +130,28 @@ private bool IsInPrivateAddressSpace(string endpoint, bool checkSubnets)
}

// Private address space:
// http://en.wikipedia.org/wiki/Private_network

if (endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase))
if (endpoint.ToLower() == "localhost")
{
return Is172AddressPrivate(endpoint);
return true;
}

if (endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) ||
endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) ||
endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase))
try
{
return true;
}
byte[] octet = IPAddress.Parse(endpoint).GetAddressBytes();

if (checkSubnets && endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase))
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
{
return true;

}

if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint))
Expand Down Expand Up @@ -177,6 +184,7 @@ public bool IsInPrivateAddressSpaceAndLocalSubnet(string endpoint)
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)
Expand Down Expand Up @@ -222,19 +230,6 @@ private List<string> GetSubnets(string endpointFirstPart)
}
}

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);
Expand All @@ -245,23 +240,57 @@ public bool IsAddressInSubnets(string addressString, string[] 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)
{
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.
}
}
}
Expand Down Expand Up @@ -359,8 +388,8 @@ private static Task<IPAddress[]> GetIpAddresses(string hostName)
{
return Dns.GetHostAddressesAsync(hostName);
}

private IEnumerable<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
private IEnumerable<IPAddress> GetIPsDefault()
{
IEnumerable<NetworkInterface> interfaces;

Expand All @@ -380,15 +409,7 @@ private IEnumerable<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
{
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)
Expand Down Expand Up @@ -494,15 +515,12 @@ public IPAddress GetLocalIpSubnetMask(IPAddress address)

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;
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion MediaBrowser.Common/Net/INetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ public interface INetworkManager
/// <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);
Expand Down
11 changes: 5 additions & 6 deletions RSSDP/SsdpCommunicationsServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ public sealed class SsdpCommunicationsServer : DisposableManagedObjectBase, ISsd
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;
Expand Down Expand Up @@ -77,11 +76,11 @@ public sealed class SsdpCommunicationsServer : DisposableManagedObjectBase, ISsd
/// 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>
Expand Down Expand Up @@ -370,13 +369,13 @@ private List<ISocket> CreateSocketAndListenForResponsesAsync()

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
{
Expand Down
2 changes: 1 addition & 1 deletion RSSDP/SsdpDeviceLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ private Task BroadcastDiscoverMessage(string serviceType, TimeSpan mxValue, Canc
private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress)
{
if (!message.IsSuccessStatusCode) return;

var location = GetFirstHeaderUriValue("Location", message);
if (location != null)
{
Expand Down

0 comments on commit ebd589a

Please sign in to comment.