2019-01-06 20:50:43 +00:00
using System ;
2016-10-29 05:40:15 +00:00
using System.Collections.Generic ;
using System.Linq ;
using System.Net ;
using System.Net.NetworkInformation ;
using System.Net.Sockets ;
2016-10-29 18:46:14 +00:00
using System.Threading.Tasks ;
2017-08-16 06:43:41 +00:00
using MediaBrowser.Common.Net ;
2019-01-13 19:22:00 +00:00
using Microsoft.Extensions.Logging ;
2016-10-29 05:40:15 +00:00
2017-08-16 06:43:41 +00:00
namespace Emby.Server.Implementations.Networking
2016-10-29 05:40:15 +00:00
{
2020-04-29 11:24:01 +00:00
/// <summary>
/// Class to take care of network interface management.
/// </summary>
2016-11-11 07:24:36 +00:00
public class NetworkManager : INetworkManager
2016-10-29 05:40:15 +00:00
{
2019-07-07 19:03:26 +00:00
private readonly ILogger _logger ;
private readonly object _localIpAddressSyncLock = new object ( ) ;
2019-09-23 21:10:51 +00:00
private readonly object _subnetLookupLock = new object ( ) ;
2020-04-29 11:24:01 +00:00
private readonly Dictionary < string , List < string > > _subnetLookup = new Dictionary < string , List < string > > ( StringComparer . Ordinal ) ;
private IPAddress [ ] _localIpAddresses ;
private List < PhysicalAddress > _macAddresses ;
2019-09-23 21:10:51 +00:00
2020-04-29 11:24:01 +00:00
/// <summary>
/// Initializes a new instance of the <see cref="NetworkManager"/> class.
/// </summary>
/// <param name="logger">Logger to use for messages.</param>
2019-07-07 19:03:26 +00:00
public NetworkManager ( ILogger < NetworkManager > logger )
2016-10-29 05:40:15 +00:00
{
2019-07-07 19:03:26 +00:00
_logger = logger ;
2017-11-27 20:16:41 +00:00
2019-07-07 19:03:26 +00:00
NetworkChange . NetworkAddressChanged + = OnNetworkAddressChanged ;
NetworkChange . NetworkAvailabilityChanged + = OnNetworkAvailabilityChanged ;
2017-11-27 20:16:41 +00:00
}
2020-04-29 11:24:01 +00:00
/// <inheritdoc/>
2019-07-07 19:03:26 +00:00
public event EventHandler NetworkChanged ;
2020-04-29 11:24:01 +00:00
/// <inheritdoc/>
2019-09-23 21:10:51 +00:00
public Func < string [ ] > LocalSubnetsFn { get ; set ; }
2020-04-29 11:24:01 +00:00
/// <inheritdoc/>
public IPAddress [ ] GetLocalIpAddresses ( )
{
lock ( _localIpAddressSyncLock )
{
if ( _localIpAddresses = = null )
{
var addresses = GetLocalIpAddressesInternal ( ) . ToArray ( ) ;
_localIpAddresses = addresses ;
}
return _localIpAddresses ;
}
}
/// <inheritdoc/>
public bool IsInPrivateAddressSpace ( string endpoint )
{
return IsInPrivateAddressSpace ( endpoint , true ) ;
}
/// <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 IsInPrivateAddressSpaceAndLocalSubnet ( string endpoint )
{
if ( endpoint . StartsWith ( "10." , StringComparison . OrdinalIgnoreCase ) )
{
var endpointFirstPart = endpoint . Split ( '.' ) [ 0 ] ;
var subnets = GetSubnets ( endpointFirstPart ) ;
foreach ( var subnet_Match in subnets )
{
// logger.LogDebug("subnet_Match:" + subnet_Match);
if ( endpoint . StartsWith ( subnet_Match + "." , StringComparison . OrdinalIgnoreCase ) )
{
return true ;
}
}
}
return false ;
}
/// <summary>
/// Gets a random port number that is currently available.
/// </summary>
/// <returns>System.Int32.</returns>
public int GetRandomUnusedTcpPort ( )
{
var listener = new TcpListener ( IPAddress . Any , 0 ) ;
listener . Start ( ) ;
var port = ( ( IPEndPoint ) listener . LocalEndpoint ) . Port ;
listener . Stop ( ) ;
return port ;
}
/// <inheritdoc/>
public int GetRandomUnusedUdpPort ( )
{
var localEndPoint = new IPEndPoint ( IPAddress . Any , 0 ) ;
var udpClient = new UdpClient ( localEndPoint ) ;
using ( udpClient )
{
var port = ( ( IPEndPoint ) udpClient . Client . LocalEndPoint ) . Port ;
return port ;
}
}
/// <inheritdoc/>
public List < PhysicalAddress > GetMacAddresses ( )
{
if ( _macAddresses = = null )
{
_macAddresses = GetMacAddressesInternal ( ) . ToList ( ) ;
}
return _macAddresses ;
}
/// <inheritdoc/>
public bool IsInSameSubnet ( IPAddress address1 , IPAddress address2 , IPAddress subnetMask )
{
IPAddress network1 = GetNetworkAddress ( address1 , subnetMask ) ;
IPAddress network2 = GetNetworkAddress ( address2 , subnetMask ) ;
return network1 . Equals ( network2 ) ;
}
/// <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 ) ;
}
/// <inheritdoc/>
public IPAddress GetLocalIpSubnetMask ( IPAddress address )
{
NetworkInterface [ ] interfaces ;
try
{
var validStatuses = new [ ] { OperationalStatus . Up , OperationalStatus . Unknown } ;
interfaces = NetworkInterface . GetAllNetworkInterfaces ( )
. Where ( i = > validStatuses . Contains ( i . OperationalStatus ) )
. ToArray ( ) ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error in GetAllNetworkInterfaces" ) ;
return null ;
}
foreach ( NetworkInterface ni in interfaces )
{
foreach ( UnicastIPAddressInformation ip in ni . GetIPProperties ( ) . UnicastAddresses )
{
if ( ip . Address . Equals ( address ) & & ip . IPv4Mask ! = null )
{
return ip . IPv4Mask ;
}
}
}
return null ;
}
/// <summary>
/// Checks if the give address false within the ranges givin 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 ) )
{
try
{
var ipNetwork = IPNetwork . Parse ( normalizedSubnet ) ;
if ( ipNetwork . Contains ( address ) )
{
return true ;
}
}
catch
{
// Ignoring - invalid subnet passed encountered.
}
}
}
return false ;
}
private static Task < IPAddress [ ] > GetIpAddresses ( string hostName )
{
return Dns . GetHostAddressesAsync ( hostName ) ;
}
private static async Task < IEnumerable < IPAddress > > GetLocalIpAddressesFallback ( )
{
var host = await Dns . GetHostEntryAsync ( Dns . GetHostName ( ) ) . ConfigureAwait ( false ) ;
// Reverse them because the last one is usually the correct one
// It's not fool-proof so ultimately the consumer will have to examine them and decide
return host . AddressList
. Where ( i = > i . AddressFamily = = AddressFamily . InterNetwork | | i . AddressFamily = = AddressFamily . InterNetworkV6 )
. Reverse ( ) ;
}
private static IEnumerable < PhysicalAddress > GetMacAddressesInternal ( )
= > NetworkInterface . GetAllNetworkInterfaces ( )
. Where ( i = > i . NetworkInterfaceType ! = NetworkInterfaceType . Loopback )
. Select ( x = > x . GetPhysicalAddress ( ) )
. Where ( x = > x ! = null & & x ! = PhysicalAddress . None ) ;
2019-07-07 19:03:26 +00:00
private void OnNetworkAvailabilityChanged ( object sender , NetworkAvailabilityEventArgs e )
2017-11-27 20:16:41 +00:00
{
2019-07-07 19:03:26 +00:00
_logger . LogDebug ( "NetworkAvailabilityChanged" ) ;
2017-11-29 20:50:18 +00:00
OnNetworkChanged ( ) ;
2017-11-27 20:16:41 +00:00
}
2019-07-07 19:03:26 +00:00
private void OnNetworkAddressChanged ( object sender , EventArgs e )
2017-11-27 20:16:41 +00:00
{
2019-07-07 19:03:26 +00:00
_logger . LogDebug ( "NetworkAddressChanged" ) ;
2017-11-29 20:50:18 +00:00
OnNetworkChanged ( ) ;
}
private void OnNetworkChanged ( )
{
2017-12-01 17:03:40 +00:00
lock ( _localIpAddressSyncLock )
{
_localIpAddresses = null ;
2018-09-12 17:26:21 +00:00
_macAddresses = null ;
2017-12-01 17:03:40 +00:00
}
2019-07-07 19:03:26 +00:00
2019-11-28 16:46:06 +00:00
NetworkChanged ? . Invoke ( this , EventArgs . Empty ) ;
2016-10-29 05:40:15 +00:00
}
2020-04-28 20:57:39 +00:00
private List < IPAddress > GetLocalIpAddressesInternal ( )
2016-10-29 05:40:15 +00:00
{
2020-04-28 20:57:39 +00:00
var list = GetIPsDefault ( ) . ToList ( ) ;
2016-10-29 05:40:15 +00:00
if ( list . Count = = 0 )
{
2019-07-07 19:03:26 +00:00
list = GetLocalIpAddressesFallback ( ) . GetAwaiter ( ) . GetResult ( ) . ToList ( ) ;
2016-10-29 05:40:15 +00:00
}
2020-04-28 20:57:39 +00:00
var listClone = new List < IPAddress > ( ) ;
2017-11-10 21:22:38 +00:00
2020-04-28 20:57:39 +00:00
var subnets = LocalSubnetsFn ( ) ;
2016-10-29 05:40:15 +00:00
2020-04-28 20:57:39 +00:00
foreach ( var i in list )
2020-04-28 20:51:49 +00:00
{
2020-04-28 20:57:39 +00:00
if ( i . IsIPv6LinkLocal | | i . ToString ( ) . StartsWith ( "169.254." , StringComparison . OrdinalIgnoreCase ) )
{
continue ;
}
2016-10-29 05:40:15 +00:00
2020-04-28 20:57:39 +00:00
if ( Array . IndexOf ( subnets , "[" + i . ToString ( ) + "]" ) = = - 1 )
{
listClone . Add ( i ) ;
}
2020-04-28 20:51:49 +00:00
}
2020-04-28 20:57:39 +00:00
return listClone
. OrderBy ( i = > i . AddressFamily = = AddressFamily . InterNetwork ? 0 : 1 )
// .ThenBy(i => listClone.IndexOf(i))
. GroupBy ( i = > i . ToString ( ) )
. Select ( x = > x . First ( ) )
. ToList ( ) ;
2020-04-28 20:51:49 +00:00
}
2020-04-29 11:24:01 +00:00
// Checks if the address in endpoint is an RFC1918, RFC1122, or RFC3927 address
2018-09-12 17:26:21 +00:00
private bool IsInPrivateAddressSpace ( string endpoint , bool checkSubnets )
2016-10-29 05:40:15 +00:00
{
if ( string . Equals ( endpoint , "::1" , StringComparison . OrdinalIgnoreCase ) )
{
return true ;
}
2020-04-29 11:24:01 +00:00
// IPV6
2017-10-11 04:45:21 +00:00
if ( endpoint . Split ( '.' ) . Length > 4 )
{
// Handle ipv4 mapped to ipv6
var originalEndpoint = endpoint ;
2020-04-29 11:24:01 +00:00
endpoint = endpoint . Replace ( "::ffff:" , string . Empty , StringComparison . OrdinalIgnoreCase ) ;
2017-10-11 04:45:21 +00:00
if ( string . Equals ( endpoint , originalEndpoint , StringComparison . OrdinalIgnoreCase ) )
{
return false ;
}
}
2016-10-29 05:40:15 +00:00
// Private address space:
2020-04-29 11:24:01 +00:00
if ( string . Equals ( endpoint , "localhost" , StringComparison . OrdinalIgnoreCase ) )
2016-10-29 05:40:15 +00:00
{
2020-04-28 20:57:39 +00:00
return true ;
2016-10-29 05:40:15 +00:00
}
2020-04-29 11:24:01 +00:00
byte [ ] octet = IPAddress . Parse ( endpoint ) . GetAddressBytes ( ) ;
2020-04-28 20:51:49 +00:00
2020-04-29 11:24:01 +00:00
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
2018-09-12 17:26:21 +00:00
{
2020-04-29 11:24:01 +00:00
return false ;
2018-09-12 17:26:21 +00:00
}
if ( checkSubnets & & IsInPrivateAddressSpaceAndLocalSubnet ( endpoint ) )
{
return true ;
}
return false ;
2017-10-11 03:35:30 +00:00
}
2020-04-29 11:24:01 +00:00
// Gives a list of possible subnets from the system whose interface ip starts with endpointFirstPart
2017-10-27 04:14:45 +00:00
private List < string > GetSubnets ( string endpointFirstPart )
2017-10-11 04:45:21 +00:00
{
2019-09-23 21:10:51 +00:00
lock ( _subnetLookupLock )
2017-10-11 04:45:21 +00:00
{
2019-01-13 20:46:33 +00:00
if ( _subnetLookup . TryGetValue ( endpointFirstPart , out var subnets ) )
2017-10-11 04:45:21 +00:00
{
2017-10-27 04:14:45 +00:00
return subnets ;
2017-10-11 04:45:21 +00:00
}
2017-10-27 04:14:45 +00:00
subnets = new List < string > ( ) ;
2019-01-13 20:37:13 +00:00
foreach ( var adapter in NetworkInterface . GetAllNetworkInterfaces ( ) )
2017-10-27 04:14:45 +00:00
{
2019-01-13 20:37:13 +00:00
foreach ( var unicastIPAddressInformation in adapter . GetIPProperties ( ) . UnicastAddresses )
2017-10-27 04:14:45 +00:00
{
2017-10-11 04:45:21 +00:00
if ( unicastIPAddressInformation . Address . AddressFamily = = AddressFamily . InterNetwork & & endpointFirstPart = = unicastIPAddressInformation . Address . ToString ( ) . Split ( '.' ) [ 0 ] )
{
int subnet_Test = 0 ;
foreach ( string part in unicastIPAddressInformation . IPv4Mask . ToString ( ) . Split ( '.' ) )
{
2019-09-23 21:10:51 +00:00
if ( part . Equals ( "0" , StringComparison . Ordinal ) )
{
break ;
}
2017-10-11 04:45:21 +00:00
subnet_Test + + ;
}
2019-01-06 20:50:43 +00:00
var subnet_Match = string . Join ( "." , unicastIPAddressInformation . Address . ToString ( ) . Split ( '.' ) . Take ( subnet_Test ) . ToArray ( ) ) ;
2017-10-11 04:45:21 +00:00
2017-10-27 04:14:45 +00:00
// TODO: Is this check necessary?
if ( adapter . OperationalStatus = = OperationalStatus . Up )
{
subnets . Add ( subnet_Match ) ;
}
}
}
2017-10-11 04:45:21 +00:00
}
2017-10-27 04:14:45 +00:00
_subnetLookup [ endpointFirstPart ] = subnets ;
return subnets ;
}
2017-10-11 04:45:21 +00:00
}
2018-09-12 17:26:21 +00:00
private bool IsInLocalNetworkInternal ( string endpoint , bool resolveHost )
{
if ( string . IsNullOrEmpty ( endpoint ) )
2016-10-29 05:40:15 +00:00
{
2019-01-06 20:50:43 +00:00
throw new ArgumentNullException ( nameof ( endpoint ) ) ;
2016-10-29 05:40:15 +00:00
}
2019-01-13 20:46:33 +00:00
if ( IPAddress . TryParse ( endpoint , out var address ) )
2016-10-29 05:40:15 +00:00
{
var addressString = address . ToString ( ) ;
2018-09-12 17:26:21 +00:00
var localSubnetsFn = LocalSubnetsFn ;
if ( localSubnetsFn ! = null )
{
var localSubnets = localSubnetsFn ( ) ;
foreach ( var subnet in localSubnets )
{
2020-04-29 11:24:01 +00:00
// Only validate if there's at least one valid entry.
2018-09-12 17:26:21 +00:00
if ( ! string . IsNullOrWhiteSpace ( subnet ) )
{
return IsAddressInSubnets ( address , addressString , localSubnets ) | | IsInPrivateAddressSpace ( addressString , false ) ;
}
}
}
2016-10-29 05:40:15 +00:00
int lengthMatch = 100 ;
if ( address . AddressFamily = = AddressFamily . InterNetwork )
{
lengthMatch = 4 ;
2018-09-12 17:26:21 +00:00
if ( IsInPrivateAddressSpace ( addressString , true ) )
2016-10-29 05:40:15 +00:00
{
return true ;
}
}
else if ( address . AddressFamily = = AddressFamily . InterNetworkV6 )
{
2017-11-10 21:22:38 +00:00
lengthMatch = 9 ;
2018-09-12 17:26:21 +00:00
if ( IsInPrivateAddressSpace ( endpoint , true ) )
2016-10-29 05:40:15 +00:00
{
return true ;
}
}
// Should be even be doing this with ipv6?
if ( addressString . Length > = lengthMatch )
{
var prefix = addressString . Substring ( 0 , lengthMatch ) ;
2016-11-04 23:57:21 +00:00
if ( GetLocalIpAddresses ( ) . Any ( i = > i . ToString ( ) . StartsWith ( prefix , StringComparison . OrdinalIgnoreCase ) ) )
2016-10-29 05:40:15 +00:00
{
return true ;
}
}
2016-11-04 23:57:21 +00:00
}
2016-10-29 05:40:15 +00:00
else if ( resolveHost )
{
2019-01-13 20:46:33 +00:00
if ( Uri . TryCreate ( endpoint , UriKind . RelativeOrAbsolute , out var uri ) )
2016-10-29 05:40:15 +00:00
{
try
{
var host = uri . DnsSafeHost ;
2019-07-07 19:03:26 +00:00
_logger . LogDebug ( "Resolving host {0}" , host ) ;
2016-10-29 05:40:15 +00:00
2016-10-29 18:46:14 +00:00
address = GetIpAddresses ( host ) . Result . FirstOrDefault ( ) ;
2016-10-29 05:40:15 +00:00
if ( address ! = null )
{
2019-07-07 19:03:26 +00:00
_logger . LogDebug ( "{0} resolved to {1}" , host , address ) ;
2016-10-29 05:40:15 +00:00
return IsInLocalNetworkInternal ( address . ToString ( ) , false ) ;
}
}
catch ( InvalidOperationException )
{
2020-04-29 11:24:01 +00:00
// Can happen with reverse proxy or IIS url rewriting?
2016-10-29 05:40:15 +00:00
}
catch ( Exception ex )
{
2019-07-07 19:03:26 +00:00
_logger . LogError ( ex , "Error resolving hostname" ) ;
2016-10-29 05:40:15 +00:00
}
}
}
return false ;
}
2020-04-28 20:57:39 +00:00
private IEnumerable < IPAddress > GetIPsDefault ( )
2016-11-04 23:57:21 +00:00
{
2019-07-07 19:03:26 +00:00
IEnumerable < NetworkInterface > interfaces ;
2016-10-29 05:40:15 +00:00
2016-11-04 23:57:21 +00:00
try
{
interfaces = NetworkInterface . GetAllNetworkInterfaces ( )
2019-07-07 19:03:26 +00:00
. Where ( x = > x . OperationalStatus = = OperationalStatus . Up
| | x . OperationalStatus = = OperationalStatus . Unknown ) ;
2016-11-04 23:57:21 +00:00
}
2019-07-07 19:03:26 +00:00
catch ( NetworkInformationException ex )
2016-11-04 23:57:21 +00:00
{
2019-07-07 19:03:26 +00:00
_logger . LogError ( ex , "Error in GetAllNetworkInterfaces" ) ;
return Enumerable . Empty < IPAddress > ( ) ;
2016-11-04 23:57:21 +00:00
}
return interfaces . SelectMany ( network = >
{
2019-07-07 19:03:26 +00:00
var ipProperties = network . GetIPProperties ( ) ;
2016-10-29 05:40:15 +00:00
2020-04-28 20:57:39 +00:00
// Exclude any addresses if they appear in the LAN list in [ ]
2016-11-04 23:57:21 +00:00
2019-07-07 19:03:26 +00:00
return ipProperties . UnicastAddresses
. Select ( i = > i . Address )
. Where ( i = > i . AddressFamily = = AddressFamily . InterNetwork | | i . AddressFamily = = AddressFamily . InterNetworkV6 ) ;
2019-02-02 11:27:06 +00:00
} ) . GroupBy ( i = > i . ToString ( ) )
2019-07-07 19:03:26 +00:00
. Select ( x = > x . First ( ) ) ;
2016-11-04 23:57:21 +00:00
}
2019-02-22 04:06:49 +00:00
private IPAddress GetNetworkAddress ( IPAddress address , IPAddress subnetMask )
{
byte [ ] ipAdressBytes = address . GetAddressBytes ( ) ;
byte [ ] subnetMaskBytes = subnetMask . GetAddressBytes ( ) ;
if ( ipAdressBytes . Length ! = subnetMaskBytes . Length )
{
throw new ArgumentException ( "Lengths of IP address and subnet mask do not match." ) ;
}
byte [ ] broadcastAddress = new byte [ ipAdressBytes . Length ] ;
for ( int i = 0 ; i < broadcastAddress . Length ; i + + )
{
2019-11-28 16:46:06 +00:00
broadcastAddress [ i ] = ( byte ) ( ipAdressBytes [ i ] & subnetMaskBytes [ i ] ) ;
2019-02-22 04:06:49 +00:00
}
2019-07-07 19:03:26 +00:00
2019-02-22 04:06:49 +00:00
return new IPAddress ( broadcastAddress ) ;
}
2016-10-29 05:40:15 +00:00
}
}