@ -2,7 +2,6 @@
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 ;
@ -13,6 +12,9 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Networking
namespace Emby.Server.Implementations.Networking
{
{
/// <summary>
/// Class to take care of network interface management.
/// </summary>
public class NetworkManager : INetworkManager
public class NetworkManager : INetworkManager
{
{
private readonly ILogger < NetworkManager > _logger ;
private readonly ILogger < NetworkManager > _logger ;
@ -21,8 +23,14 @@ namespace Emby.Server.Implementations.Networking
private readonly object _localIpAddressSyncLock = new object ( ) ;
private readonly object _localIpAddressSyncLock = new object ( ) ;
private readonly object _subnetLookupLock = 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 )
public NetworkManager ( ILogger < NetworkManager > logger )
{
{
_logger = logger ;
_logger = logger ;
@ -31,8 +39,10 @@ namespace Emby.Server.Implementations.Networking
NetworkChange . NetworkAvailabilityChanged + = OnNetworkAvailabilityChanged ;
NetworkChange . NetworkAvailabilityChanged + = OnNetworkAvailabilityChanged ;
}
}
/// <inheritdoc/>
public event EventHandler NetworkChanged ;
public event EventHandler NetworkChanged ;
/// <inheritdoc/>
public Func < string [ ] > LocalSubnetsFn { get ; set ; }
public Func < string [ ] > LocalSubnetsFn { get ; set ; }
private void OnNetworkAvailabilityChanged ( object sender , NetworkAvailabilityEventArgs e )
private void OnNetworkAvailabilityChanged ( object sender , NetworkAvailabilityEventArgs e )
@ -58,13 +68,14 @@ namespace Emby.Server.Implementations.Networking
NetworkChanged ? . Invoke ( this , EventArgs . Empty ) ;
NetworkChanged ? . Invoke ( this , EventArgs . Empty ) ;
}
}
public IPAddress [ ] GetLocalIpAddresses ( bool ignoreVirtualInterface = true )
/// <inheritdoc/>
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 ;
}
}
@ -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 )
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 ( ) ;
. OrderBy ( i = > i . AddressFamily = = AddressFamily . InterNetwork ? 0 : 1 )
. ThenBy ( i = > listClone . IndexOf ( i ) )
. Where ( FilterIpAddress )
. GroupBy ( i = > i . ToString ( ) )
. Select ( x = > x . First ( ) )
. ToList ( ) ;
}
private static bool FilterIpAddress ( IPAddress address )
foreach ( var i in list )
{
if ( address . IsIPv6LinkLocal
| | address . ToString ( ) . StartsWith ( "169." , StringComparison . OrdinalIgnoreCase ) )
{
{
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 )
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 ) )
@ -116,12 +132,12 @@ namespace Emby.Server.Implementations.Networking
return true ;
return true ;
}
}
// ipv 6
// IPV 6
if ( endpoint . Split ( '.' ) . Length > 4 )
if ( endpoint . Split ( '.' ) . Length > 4 )
{
{
// Handle ipv4 mapped to ipv6
// Handle ipv4 mapped to ipv6
var originalEndpoint = endpoint ;
var originalEndpoint = endpoint ;
endpoint = endpoint . Replace ( "::ffff:" , string . Empty );
endpoint = endpoint . Replace ( "::ffff:" , string . Empty , StringComparison . OrdinalIgnoreCase );
if ( string . Equals ( endpoint , originalEndpoint , StringComparison . OrdinalIgnoreCase ) )
if ( string . Equals ( endpoint , originalEndpoint , StringComparison . OrdinalIgnoreCase ) )
{
{
@ -130,23 +146,21 @@ namespace Emby.Server.Implementations.Networking
}
}
// Private address space:
// 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 ) | |
if ( string . Equals ( endpoint , "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 ) )
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 fals e;
}
}
if ( checkSubnets & & IsInPrivateAddressSpaceAndLocalSubnet ( endpoint ) )
if ( checkSubnets & & IsInPrivateAddressSpaceAndLocalSubnet ( endpoint ) )
@ -157,6 +171,7 @@ namespace Emby.Server.Implementations.Networking
return false ;
return false ;
}
}
/// <inheritdoc/>
public bool IsInPrivateAddressSpaceAndLocalSubnet ( string endpoint )
public bool IsInPrivateAddressSpaceAndLocalSubnet ( string endpoint )
{
{
if ( endpoint . StartsWith ( "10." , StringComparison . OrdinalIgnoreCase ) )
if ( endpoint . StartsWith ( "10." , StringComparison . OrdinalIgnoreCase ) )
@ -179,6 +194,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 )
@ -224,46 +240,75 @@ namespace Emby.Server.Implementations.Networking
}
}
}
}
private static bool Is172AddressPrivate ( string endpoint )
/// <inheritdoc/>
{
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 ) ;
}
}
/// <inheritdoc/>
public bool IsAddressInSubnets ( string addressString , string [ ] subnets )
public bool IsAddressInSubnets ( string addressString , string [ ] subnets )
{
{
return IsAddressInSubnets ( IPAddress . Parse ( addressString ) , addressString , 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 )
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.
}
}
}
}
}
}
@ -288,7 +333,7 @@ namespace Emby.Server.Implementations.Networking
var localSubnets = localSubnetsFn ( ) ;
var localSubnets = localSubnetsFn ( ) ;
foreach ( var subnet in localSubnets )
foreach ( var subnet in localSubnets )
{
{
// o nly validate if there's at least one valid entry
// O nly validate if there's at least one valid entry.
if ( ! string . IsNullOrWhiteSpace ( subnet ) )
if ( ! string . IsNullOrWhiteSpace ( subnet ) )
{
{
return IsAddressInSubnets ( address , addressString , localSubnets ) | | IsInPrivateAddressSpace ( addressString , false ) ;
return IsAddressInSubnets ( address , addressString , localSubnets ) | | IsInPrivateAddressSpace ( addressString , false ) ;
@ -345,7 +390,7 @@ namespace Emby.Server.Implementations.Networking
}
}
catch ( InvalidOperationException )
catch ( InvalidOperationException )
{
{
// Can happen with reverse proxy or IIS url rewriting
// Can happen with reverse proxy or IIS url rewriting ?
}
}
catch ( Exception ex )
catch ( Exception ex )
{
{
@ -362,7 +407,7 @@ 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 ;
@ -382,15 +427,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 )
@ -423,33 +460,29 @@ namespace Emby.Server.Implementations.Networking
return port ;
return port ;
}
}
/// <inheritdoc/>
public int GetRandomUnusedUdpPort ( )
public int GetRandomUnusedUdpPort ( )
{
{
var localEndPoint = new IPEndPoint ( IPAddress . Any , 0 ) ;
var localEndPoint = new IPEndPoint ( IPAddress . Any , 0 ) ;
using ( var udpClient = new UdpClient ( localEndPoint ) )
using ( var udpClient = new UdpClient ( localEndPoint ) )
{
{
var port = ( ( IPEndPoint ) udpClient . Client . LocalEndPoint ) . Port ;
return ( ( IPEndPoint ) udpClient . Client . LocalEndPoint ) . Port ;
return port ;
}
}
}
}
private List < PhysicalAddress > _macAddresses ;
/// <inheritdoc/>
public List < PhysicalAddress > GetMacAddresses ( )
public List < PhysicalAddress > GetMacAddresses ( )
{
{
if ( _macAddresses = = null )
return _macAddresses ? ? = GetMacAddressesInternal ( ) . ToList ( ) ;
{
_macAddresses = GetMacAddressesInternal ( ) . ToList ( ) ;
}
return _macAddresses ;
}
}
private static IEnumerable < PhysicalAddress > GetMacAddressesInternal ( )
private static IEnumerable < PhysicalAddress > GetMacAddressesInternal ( )
= > NetworkInterface . GetAllNetworkInterfaces ( )
= > NetworkInterface . GetAllNetworkInterfaces ( )
. Where ( i = > i . NetworkInterfaceType ! = NetworkInterfaceType . Loopback )
. Where ( i = > i . NetworkInterfaceType ! = NetworkInterfaceType . Loopback )
. Select ( x = > x . GetPhysicalAddress ( ) )
. 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 )
public bool IsInSameSubnet ( IPAddress address1 , IPAddress address2 , IPAddress subnetMask )
{
{
IPAddress network1 = GetNetworkAddress ( address1 , subnetMask ) ;
IPAddress network1 = GetNetworkAddress ( address1 , subnetMask ) ;
@ -476,6 +509,7 @@ namespace Emby.Server.Implementations.Networking
return new IPAddress ( broadcastAddress ) ;
return new IPAddress ( broadcastAddress ) ;
}
}
/// <inheritdoc/>
public IPAddress GetLocalIpSubnetMask ( IPAddress address )
public IPAddress GetLocalIpSubnetMask ( IPAddress address )
{
{
NetworkInterface [ ] interfaces ;
NetworkInterface [ ] interfaces ;
@ -496,14 +530,11 @@ namespace Emby.Server.Implementations.Networking
foreach ( NetworkInterface ni in interfaces )
foreach ( NetworkInterface ni in interfaces )
{
{
i f ( 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 ;
}
}
}
}
}
}
}