@ -1,7 +1,6 @@
#pragma warning disable CA1021 // Avoid out parameters
using System ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Globalization ;
using System.Linq ;
using System.Net ;
@ -13,14 +12,12 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net ;
using Microsoft.AspNetCore.Http ;
using Microsoft.Extensions.Logging ;
using NetCollection = System . Collections . ObjectModel . Collection < MediaBrowser . Common . Net . IPObject > ;
namespace Jellyfin.Networking.Manager
{
/// <summary>
/// Class to take care of network interface management.
///
/// Note: The normal collection methods and properties will not work with NetCollection. <see cref="MediaBrowser.Common.Net.NetworkExtensions"/>.
/// Note: The normal collection methods and properties will not work with Collection{IPObject}. <see cref="MediaBrowser.Common.Net.NetworkExtensions"/>.
/// </summary>
public class NetworkManager : INetworkManager , IDisposable
{
@ -37,7 +34,7 @@ namespace Jellyfin.Networking.Manager
/// <summary>
/// List of all interface addresses and masks.
/// </summary>
private readonly Net Collection _interfaceAddresses ;
private readonly Collection< IPObject > _interfaceAddresses ;
/// <summary>
/// List of all interface MAC addresses.
@ -61,30 +58,30 @@ namespace Jellyfin.Networking.Manager
private bool _eventfire ;
/// <summary>
/// Unfiltered user defined LAN subnets. ( Configuration.LocalNetworkSubnets).
/// Unfiltered user defined LAN subnets. ( <see cref="Network Configuration.LocalNetworkSubnets"/> )
/// or internal interface network subnets if undefined by user.
/// </summary>
private Net Collection _lanSubnets ;
private Collection< IPObject > _lanSubnets ;
/// <summary>
/// User defined list of subnets to excluded from the LAN.
/// </summary>
private Net Collection _excludedSubnets ;
private Collection< IPObject > _excludedSubnets ;
/// <summary>
/// List of interface addresses to bind the WS.
/// </summary>
private Net Collection _bindAddresses ;
private Collection< IPObject > _bindAddresses ;
/// <summary>
/// List of interface addresses to exclude from bind.
/// </summary>
private Net Collection _bindExclusions ;
private Collection< IPObject > _bindExclusions ;
/// <summary>
/// Caches list of all internal filtered interface addresses and masks.
/// </summary>
private Net Collection _internalInterfaces ;
private Collection< IPObject > _internalInterfaces ;
/// <summary>
/// Flag set when no custom LAN has been defined in the config.
@ -107,7 +104,7 @@ namespace Jellyfin.Networking.Manager
_logger = logger ? ? throw new ArgumentNullException ( nameof ( logger ) ) ;
_configurationManager = configurationManager ? ? throw new ArgumentNullException ( nameof ( configurationManager ) ) ;
_interfaceAddresses = new Net Collection( ) ;
_interfaceAddresses = new Collection< IPObject > ( ) ;
_macAddresses = new List < PhysicalAddress > ( ) ;
_interfaceNames = new Dictionary < string , int > ( ) ;
_publishedServerUrls = new Dictionary < IPNetAddress , string > ( ) ;
@ -143,7 +140,7 @@ namespace Jellyfin.Networking.Manager
public bool IsIP4Enabled { get ; set ; }
/// <inheritdoc/>
public Net Collection RemoteAddressFilter { get ; private set ; }
public Collection< IPObject > RemoteAddressFilter { get ; private set ; }
/// <summary>
/// Gets a value indicating whether is all IPv6 interfaces are trusted as internal.
@ -160,9 +157,9 @@ namespace Jellyfin.Networking.Manager
/// </summary>
/// <param name="source">Items to assign the collection, or null.</param>
/// <returns>The collection created.</returns>
public static Net Collection CreateCollection ( IEnumerable < IPObject > ? source = null )
public static Collection< IPObject > CreateCollection ( IEnumerable < IPObject > ? source = null )
{
var result = new Net Collection( ) ;
var result = new Collection< IPObject > ( ) ;
if ( source ! = null )
{
foreach ( var item in source )
@ -189,22 +186,22 @@ namespace Jellyfin.Networking.Manager
}
/// <inheritdoc/>
public bool IsGatewayInterface ( object ? addressObj )
public bool IsGatewayInterface ( IPObject ? addressObj )
{
var address = addressObj switch
{
IPAddress addressIp = > addressIp ,
IPObject addressIpObj = > addressIpObj . Address ,
_ = > IPAddress . None
} ;
var address = addressObj ? . Address ? ? IPAddress . None ;
return _internalInterfaces . Any ( i = > i . Address . Equals ( address ) & & i . Tag < 0 ) ;
}
/// <inheritdoc/>
public NetCollection GetLoopbacks ( )
public bool IsGatewayInterface ( IPAddress ? addressObj )
{
NetCollection nc = new NetCollection ( ) ;
return _internalInterfaces . Any ( i = > i . Address . Equals ( addressObj ? ? IPAddress . None ) & & i . Tag < 0 ) ;
}
/// <inheritdoc/>
public Collection < IPObject > GetLoopbacks ( )
{
Collection < IPObject > nc = new Collection < IPObject > ( ) ;
if ( IsIP4Enabled )
{
nc . AddItem ( IPAddress . Loopback ) ;
@ -231,9 +228,9 @@ namespace Jellyfin.Networking.Manager
}
/// <inheritdoc/>
public Net Collection CreateIPCollection ( string [ ] values , bool bracketed = false )
public Collection< IPObject > CreateIPCollection ( string [ ] values , bool bracketed = false )
{
Net Collection col = new Net Collection( ) ;
Collection< IPObject > col = new Collection< IPObject > ( ) ;
if ( values = = null )
{
return col ;
@ -256,7 +253,7 @@ namespace Jellyfin.Networking.Manager
{
if ( bracketed )
{
AddToCollection ( col , v .Substring ( 1 ) ) ;
AddToCollection ( col , v [1. . ] ) ;
}
}
else if ( ! bracketed )
@ -266,7 +263,7 @@ namespace Jellyfin.Networking.Manager
}
catch ( ArgumentException e )
{
_logger . Log Information( "Ignoring LAN value {value}. Reason : {reason}" , v , e . Message ) ;
_logger . Log Warning( e , "Ignoring LAN value {value}." , v ) ;
}
}
@ -274,7 +271,7 @@ namespace Jellyfin.Networking.Manager
}
/// <inheritdoc/>
public Net Collection GetAllBindInterfaces ( bool individualInterfaces = false )
public Collection< IPObject > GetAllBindInterfaces ( bool individualInterfaces = false )
{
int count = _bindAddresses . Count ;
@ -288,11 +285,11 @@ namespace Jellyfin.Networking.Manager
if ( individualInterfaces )
{
return new Net Collection( _interfaceAddresses ) ;
return new Collection< IPObject > ( _interfaceAddresses ) ;
}
// No bind address and no exclusions, so listen on all interfaces.
Net Collection result = new Net Collection( ) ;
Collection< IPObject > result = new Collection< IPObject > ( ) ;
if ( IsIP4Enabled )
{
@ -376,7 +373,7 @@ namespace Jellyfin.Networking.Manager
if ( MatchesPublishedServerUrl ( source , isExternal , out string res , out port ) )
{
_logger . LogInformation ( "{ 0}: Using BindAddress {1}:{2 }", source , res , port ) ;
_logger . LogInformation ( "{ Source}: Using BindAddress {Address}:{Port }", source , res , port ) ;
return res ;
}
}
@ -429,7 +426,7 @@ namespace Jellyfin.Networking.Manager
}
/// <inheritdoc/>
public Net Collection GetInternalBindAddresses ( )
public Collection< IPObject > GetInternalBindAddresses ( )
{
int count = _bindAddresses . Count ;
@ -445,7 +442,7 @@ namespace Jellyfin.Networking.Manager
return CreateCollection ( _internalInterfaces . Where ( p = > ! p . IsLoopback ( ) ) ) ;
}
return new Net Collection( _bindAddresses ) ;
return new Collection< IPObject > ( _bindAddresses ) ;
}
/// <inheritdoc/>
@ -526,7 +523,7 @@ namespace Jellyfin.Networking.Manager
}
/// <inheritdoc/>
public Net Collection GetFilteredLANSubnets ( Net Collection? filter = null )
public Collection< IPObject > GetFilteredLANSubnets ( Collection< IPObject > ? filter = null )
{
if ( filter = = null )
{
@ -543,7 +540,7 @@ namespace Jellyfin.Networking.Manager
}
/// <inheritdoc/>
public bool TryParseInterface ( string token , out Net Collection? result )
public bool TryParseInterface ( string token , out Collection< IPObject > ? result )
{
result = null ;
if ( string . IsNullOrEmpty ( token ) )
@ -553,16 +550,16 @@ namespace Jellyfin.Networking.Manager
if ( _interfaceNames ! = null & & _interfaceNames . TryGetValue ( token . ToLower ( CultureInfo . InvariantCulture ) , out int index ) )
{
result = new Net Collection( ) ;
result = new Collection< IPObject > ( ) ;
_logger . LogInformation ( "Interface {0} used in settings. Using its interface addresses." , token ) ;
// Replace interface tags with the interface IP's.
foreach ( IPNetAddress iface in _interfaceAddresses )
{
if ( Math . Abs ( iface . Tag ) = = index & &
(( IsIP4Enabled & & iface . Address . AddressFamily = = AddressFamily . InterNetwork ) | |
( IsIP6Enabled & & iface . Address . AddressFamily = = AddressFamily . InterNetworkV6 ) ) )
if ( Math . Abs ( iface . Tag ) = = index
&& (( IsIP4Enabled & & iface . Address . AddressFamily = = AddressFamily . InterNetwork )
| | ( IsIP6Enabled & & iface . Address . AddressFamily = = AddressFamily . InterNetworkV6 ) ) )
{
result . AddItem ( iface ) ;
}
@ -577,8 +574,8 @@ namespace Jellyfin.Networking.Manager
/// <summary>
/// Reloads all settings and re-initialises the instance.
/// </summary>
/// <param name="configuration">The configuration to use.</param>
public void UpdateSettings ( object configuration )
/// <param name="configuration">The <see cref="NetworkC onfiguration"/> to use.</param>
public void UpdateSettings ( NetworkConfiguration configuration )
{
NetworkConfiguration config = ( NetworkConfiguration ) configuration ? ? throw new ArgumentNullException ( nameof ( configuration ) ) ;
@ -622,7 +619,7 @@ namespace Jellyfin.Networking.Manager
/// <summary>
/// Protected implementation of Dispose pattern.
/// </summary>
/// <param name="disposing"> True to dispose the managed state.</param>
/// <param name="disposing"> <c> True</c> to dispose the managed state.</param>
protected virtual void Dispose ( bool disposing )
{
if ( ! _disposed )
@ -639,11 +636,11 @@ namespace Jellyfin.Networking.Manager
}
/// <summary>
/// Tr y s to identify the string and return an object of that class.
/// Tr ie s to identify the string and return an object of that class.
/// </summary>
/// <param name="addr">String to parse.</param>
/// <param name="result">IPObject to return.</param>
/// <returns> True if the value parsed successfully .</returns>
/// <returns> <c>true</c> if the value parsed successfully, <c>false</c> otherwise .</returns>
private static bool TryParse ( string addr , out IPObject result )
{
if ( ! string . IsNullOrEmpty ( addr ) )
@ -671,7 +668,7 @@ namespace Jellyfin.Networking.Manager
/// Ipv6 addresses are returned in [ ], with their scope removed.
/// </summary>
/// <param name="address">Address to convert.</param>
/// <returns>URI sa v e conversion of the address.</returns>
/// <returns>URI sa f e conversion of the address.</returns>
private static string FormatIP6String ( IPAddress address )
{
var str = address . ToString ( ) ;
@ -694,7 +691,7 @@ namespace Jellyfin.Networking.Manager
{
if ( evt . Key . Equals ( "network" , StringComparison . Ordinal ) )
{
UpdateSettings ( evt . NewConfiguration ) ;
UpdateSettings ( ( NetworkConfiguration ) evt . NewConfiguration ) ;
}
}
@ -703,7 +700,7 @@ namespace Jellyfin.Networking.Manager
/// </summary>
/// <param name="token">String to check.</param>
/// <param name="index">Interface index number.</param>
/// <returns> True if an interface name matches the token .</returns>
/// <returns> <c>true</c> if an interface name matches the token, <c>False</c> otherwise .</returns>
private bool IsInterface ( string token , out int index )
{
index = - 1 ;
@ -720,8 +717,8 @@ namespace Jellyfin.Networking.Manager
foreach ( ( string interfc , int interfcIndex ) in _interfaceNames )
{
if ( ( ! partial & & string . Equals ( interfc , token , StringComparison . OrdinalIgnoreCase ) ) | |
( partial & & interfc . StartsWith ( token , true , CultureInfo . InvariantCulture ) ) )
if ( ( ! partial & & string . Equals ( interfc , token , StringComparison . OrdinalIgnoreCase ) )
| | ( partial & & interfc . StartsWith ( token , true , CultureInfo . InvariantCulture ) ) )
{
index = interfcIndex ;
return true ;
@ -733,11 +730,11 @@ namespace Jellyfin.Networking.Manager
}
/// <summary>
/// Parses strings into the collection, replacing any interface references.
/// Parses a string and add s it into the the collection, replacing any interface references.
/// </summary>
/// <param name="col"> Collection.</param>
/// <param name="token">String to parse.</param>
private void AddToCollection ( Net Collection col , string token )
/// <param name="col"> <see cref="Collection{IPObject}"/> Collection.</param>
/// <param name="token">String value to parse.</param>
private void AddToCollection ( Collection< IPObject > col , string token )
{
// Is it the name of an interface (windows) eg, Wireless LAN adapter Wireless Network Connection 1.
// Null check required here for automated testing.
@ -748,9 +745,9 @@ namespace Jellyfin.Networking.Manager
// Replace interface tags with the interface IP's.
foreach ( IPNetAddress iface in _interfaceAddresses )
{
if ( Math . Abs ( iface . Tag ) = = index & &
(( IsIP4Enabled & & iface . Address . AddressFamily = = AddressFamily . InterNetwork ) | |
( IsIP6Enabled & & iface . Address . AddressFamily = = AddressFamily . InterNetworkV6 ) ) )
if ( Math . Abs ( iface . Tag ) = = index
&& (( IsIP4Enabled & & iface . Address . AddressFamily = = AddressFamily . InterNetwork )
| | ( IsIP6Enabled & & iface . Address . AddressFamily = = AddressFamily . InterNetworkV6 ) ) )
{
col . AddItem ( iface ) ;
}
@ -791,7 +788,7 @@ namespace Jellyfin.Networking.Manager
/// Handler for network change events.
/// </summary>
/// <param name="sender">Sender.</param>
/// <param name="e"> Network availability information.</param>
/// <param name="e"> A <see cref=" NetworkAvailabilityEventArgs"/> containing n etwork availability information.</param>
private void OnNetworkAvailabilityChanged ( object? sender , NetworkAvailabilityEventArgs e )
{
_logger . LogDebug ( "Network availability changed." ) ;
@ -802,7 +799,7 @@ namespace Jellyfin.Networking.Manager
/// Handler for network change events.
/// </summary>
/// <param name="sender">Sender.</param>
/// <param name="e"> Event arguments .</param>
/// <param name="e"> An <see cref="EventArgs"/> .</param>
private void OnNetworkAddressChanged ( object? sender , EventArgs e )
{
_logger . LogDebug ( "Network address change detected." ) ;
@ -812,7 +809,7 @@ namespace Jellyfin.Networking.Manager
/// <summary>
/// Async task that waits for 2 seconds before re-initialising the settings, as typically these events fire multiple times in succession.
/// </summary>
/// <returns> The network change async .</returns>
/// <returns> A <see cref="Task"/> representing the asynchronous operation .</returns>
private async Task OnNetworkChangeAsync ( )
{
try
@ -883,7 +880,7 @@ namespace Jellyfin.Networking.Manager
{
_publishedServerUrls [ new IPNetAddress ( IPAddress . Any ) ] = replacement ;
}
else if ( TryParseInterface ( parts [ 0 ] , out Net Collection? addresses ) & & addresses ! = null )
else if ( TryParseInterface ( parts [ 0 ] , out Collection< IPObject > ? addresses ) & & addresses ! = null )
{
foreach ( IPNetAddress na in addresses )
{
@ -903,6 +900,9 @@ namespace Jellyfin.Networking.Manager
}
}
/// <summary>
/// Initialises the network bind addresses.
/// </summary>
private void InitialiseBind ( NetworkConfiguration config )
{
string [ ] lanAddresses = config . LocalNetworkAddresses ;
@ -931,6 +931,9 @@ namespace Jellyfin.Networking.Manager
_logger . LogInformation ( "Using bind exclusions: {0}" , _bindExclusions . AsString ( ) ) ;
}
/// <summary>
/// Initialises the remote address values.
/// </summary>
private void InitialiseRemote ( NetworkConfiguration config )
{
RemoteAddressFilter = CreateIPCollection ( config . RemoteIPFilter ) ;
@ -964,7 +967,7 @@ namespace Jellyfin.Networking.Manager
_internalInterfaces = CreateCollection ( _interfaceAddresses . Where ( i = > IsPrivateAddressRange ( i ) & & ! _excludedSubnets . ContainsAddress ( i ) ) ) ;
// Subnets are the same as the calculated internal interface.
_lanSubnets = new Net Collection( ) ;
_lanSubnets = new Collection< IPObject > ( ) ;
// We must listen on loopback for LiveTV to function regardless of the settings.
if ( IsIP6Enabled )
@ -1131,7 +1134,7 @@ namespace Jellyfin.Networking.Manager
/// <param name="isExternal">True if the source is in the external subnet.</param>
/// <param name="bindPreference">The published server url that matches the source address.</param>
/// <param name="port">The resultant port, if one exists.</param>
/// <returns> True if a match is found .</returns>
/// <returns> <c>true</c> if a match is found, <c>false</c> otherwise .</returns>
private bool MatchesPublishedServerUrl ( IPObject source , bool isExternal , out string bindPreference , out int? port )
{
bindPreference = string . Empty ;
@ -1185,7 +1188,7 @@ namespace Jellyfin.Networking.Manager
/// <param name="source">IP source address to use.</param>
/// <param name="isExternal">True if the source is in the external subnet.</param>
/// <param name="result">The result, if a match is found.</param>
/// <returns> True if a match is found .</returns>
/// <returns> <c>true</c> if a match is found, <c>false</c> otherwise .</returns>
private bool MatchesBindInterface ( IPObject source , bool isExternal , out string result )
{
result = string . Empty ;
@ -1202,7 +1205,7 @@ namespace Jellyfin.Networking.Manager
{
// Check to see if any of the bind interfaces are in the same subnet.
Net Collection bindResult ;
Collection< IPObject > bindResult ;
IPAddress ? defaultGateway = null ;
IPAddress ? bindAddress ;
@ -1246,7 +1249,6 @@ namespace Jellyfin.Networking.Manager
if ( isExternal )
{
// TODO: remove this after testing.
_logger . LogWarning ( "{0}: External request received, however, only an internal interface bind found." , source ) ;
}
@ -1261,7 +1263,7 @@ namespace Jellyfin.Networking.Manager
/// </summary>
/// <param name="source">IP source address to use.</param>
/// <param name="result">The result, if a match is found.</param>
/// <returns> True if a match is found .</returns>
/// <returns> <c>true</c> if a match is found, <c>false</c> otherwise .</returns>
private bool MatchesExternalInterface ( IPObject source , out string result )
{
result = string . Empty ;
@ -1292,7 +1294,6 @@ namespace Jellyfin.Networking.Manager
// Have to return something, so return an internal address
// TODO: remove this after testing.
_logger . LogWarning ( "{0}: External request received, however, no WAN interface found." , source ) ;
return false ;
}