From 9ef79d190b2490a03c566bfaaf963fbba7d124a9 Mon Sep 17 00:00:00 2001 From: Jim Cartlidge Date: Sat, 12 Sep 2020 16:41:37 +0100 Subject: Large number of files --- MediaBrowser.Common/Net/INetworkManager.cs | 97 ------------------------------ 1 file changed, 97 deletions(-) delete mode 100644 MediaBrowser.Common/Net/INetworkManager.cs (limited to 'MediaBrowser.Common/Net') diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs deleted file mode 100644 index a0330afeff..0000000000 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ /dev/null @@ -1,97 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.NetworkInformation; - -namespace MediaBrowser.Common.Net -{ - public interface INetworkManager - { - event EventHandler NetworkChanged; - - /// - /// Gets or sets a function to return the list of user defined LAN addresses. - /// - Func LocalSubnetsFn { get; set; } - - /// - /// Gets a random port TCP number that is currently available. - /// - /// System.Int32. - int GetRandomUnusedTcpPort(); - - /// - /// Gets a random port UDP number that is currently available. - /// - /// System.Int32. - int GetRandomUnusedUdpPort(); - - /// - /// Returns the MAC Address from first Network Card in Computer. - /// - /// The MAC Address. - List GetMacAddresses(); - - /// - /// Determines whether [is in private address space] [the specified endpoint]. - /// - /// The endpoint. - /// true if [is in private address space] [the specified endpoint]; otherwise, false. - bool IsInPrivateAddressSpace(string endpoint); - - /// - /// Determines whether [is in private address space 10.x.x.x] [the specified endpoint] and exists in the subnets returned by GetSubnets(). - /// - /// The endpoint. - /// true if [is in private address space 10.x.x.x] [the specified endpoint]; otherwise, false. - bool IsInPrivateAddressSpaceAndLocalSubnet(string endpoint); - - /// - /// Determines whether [is in local network] [the specified endpoint]. - /// - /// The endpoint. - /// true if [is in local network] [the specified endpoint]; otherwise, false. - bool IsInLocalNetwork(string endpoint); - - /// - /// Investigates an caches a list of interface addresses, excluding local link and LAN excluded addresses. - /// - /// The list of ipaddresses. - IPAddress[] GetLocalIpAddresses(); - - /// - /// Checks if the given address falls within the ranges given in [subnets]. The addresses in subnets can be hosts or subnets in the CIDR format. - /// - /// The address to check. - /// If true, check against addresses in the LAN settings surrounded by brackets ([]). - /// trueif the address is in at least one of the given subnets, false otherwise. - bool IsAddressInSubnets(string addressString, string[] subnets); - - /// - /// Returns true if address is in the LAN list in the config file. - /// - /// The address to check. - /// If true, check against addresses in the LAN settings which have [] arroud and return true if it matches the address give in address. - /// If true, returns false if address is in the 127.x.x.x or 169.128.x.x range. - /// falseif the address isn't in the LAN list, true if the address has been defined as a LAN address. - bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC); - - /// - /// Checks if address is in the LAN list in the config file. - /// - /// Source address to check. - /// Destination address to check against. - /// Destination subnet to check against. - /// true/falsedepending on whether address1 is in the same subnet as IPAddress2 with subnetMask. - bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask); - - /// - /// Returns the subnet mask of an interface with the given address. - /// - /// The address to check. - /// Returns the subnet mask of an interface with the given address, or null if an interface match cannot be found. - IPAddress GetLocalIpSubnetMask(IPAddress address); - } -} -- cgit v1.2.3 From b44455ad0d6e78b5baed535c06a7f7d49116d1ee Mon Sep 17 00:00:00 2001 From: Jim Cartlidge Date: Mon, 14 Sep 2020 15:46:38 +0100 Subject: Update based on PR1 changes. --- Emby.Dlna/Main/DlnaEntryPoint.cs | 2 +- Emby.Server.Implementations/ApplicationHost.cs | 62 ++- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 2 +- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 2 +- .../LiveTv/TunerHosts/M3UTunerHost.cs | 2 +- Jellyfin.Api/Auth/BaseAuthorizationHandler.cs | 2 +- .../DefaultAuthorizationHandler.cs | 2 +- .../Auth/DownloadPolicy/DownloadHandler.cs | 2 +- ...FirstTimeOrIgnoreParentalControlSetupHandler.cs | 2 +- .../FirstTimeSetupOrDefaultHandler.cs | 2 +- .../FirstTimeSetupOrElevatedHandler.cs | 2 +- .../IgnoreParentalControlHandler.cs | 2 +- .../LocalAccessOrRequiresElevationHandler.cs | 2 +- .../Auth/LocalAccessPolicy/LocalAccessHandler.cs | 2 +- .../RequiresElevationHandler.cs | 2 +- Jellyfin.Api/Controllers/SystemController.cs | 2 +- Jellyfin.Api/Controllers/UserController.cs | 2 +- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 2 +- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 2 +- Jellyfin.Networking/Manager/INetworkManager.cs | 189 --------- Jellyfin.Networking/Manager/NetworkManager.cs | 463 +++++++++++---------- .../Users/UserManager.cs | 3 +- Jellyfin.Server/CoreAppHost.cs | 2 +- .../IpBasedAccessValidationMiddleware.cs | 2 +- .../Middleware/LanFilteringMiddleware.cs | 2 +- Jellyfin.Server/Program.cs | 4 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 3 +- MediaBrowser.Common/Net/INetworkManager.cs | 221 ++++++++++ MediaBrowser.Controller/IServerApplicationHost.cs | 26 +- RSSDP/SsdpCommunicationsServer.cs | 2 +- RSSDP/SsdpDevicePublisher.cs | 2 +- .../LocalAccessPolicy/LocalAccessHandlerTests.cs | 2 +- 32 files changed, 572 insertions(+), 447 deletions(-) delete mode 100644 Jellyfin.Networking/Manager/INetworkManager.cs create mode 100644 MediaBrowser.Common/Net/INetworkManager.cs (limited to 'MediaBrowser.Common/Net') diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 98f50c09af..a5da2fc5c8 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -9,7 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; using Emby.Dlna.Ssdp; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index cc04cb03f5..67a352fc93 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -160,6 +160,11 @@ namespace Emby.Server.Implementations } } + /// + /// Gets the singleton instance. + /// + public INetworkManager NetManager { get; internal set; } + /// /// Occurs when [has pending restart changed]. /// @@ -189,11 +194,6 @@ namespace Emby.Server.Implementations /// The plugins. public IReadOnlyList Plugins => _plugins; - /// - /// Gets the NetworkManager object. - /// - private readonly INetworkManager _networkManager; - /// /// Gets the logger factory. /// @@ -267,7 +267,7 @@ namespace Emby.Server.Implementations ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager); - _networkManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger()); + NetManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger()); Logger = LoggerFactory.CreateLogger(); @@ -524,7 +524,7 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton(_fileSystemManager); ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(_networkManager); + ServiceCollection.AddSingleton(NetManager); ServiceCollection.AddSingleton(); @@ -1116,7 +1116,7 @@ namespace Emby.Server.Implementations } public IEnumerable GetWakeOnLanInfo() - => _networkManager.GetMacAddresses() + => NetManager.GetMacAddresses() .Select(i => new WakeOnLanInfo(i)) .ToList(); @@ -1138,7 +1138,47 @@ namespace Emby.Server.Implementations public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps; /// - public string GetSmartApiUrl(object source) + public string GetSmartApiUrl(IPAddress ipAddress, int? port = null) + { + // Published server ends with a / + if (_startupOptions.PublishedServerUrl != null) + { + // Published server ends with a '/', so we need to remove it. + return _startupOptions.PublishedServerUrl.ToString().Trim('/'); + } + + string smart = NetManager.GetBindInterface(ipAddress, out port); + // If the smartAPI doesn't start with http then treat it as a host or ip. + if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + return smart.Trim('/'); + } + + return GetLocalApiUrl(smart.Trim('/'), null, port); + } + + /// + public string GetSmartApiUrl(HttpRequest request, int? port = null) + { + // Published server ends with a / + if (_startupOptions.PublishedServerUrl != null) + { + // Published server ends with a '/', so we need to remove it. + return _startupOptions.PublishedServerUrl.ToString().Trim('/'); + } + + string smart = NetManager.GetBindInterface(request, out port); + // If the smartAPI doesn't start with http then treat it as a host or ip. + if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + return smart.Trim('/'); + } + + return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port); + } + + /// + public string GetSmartApiUrl(string hostname, int? port = null) { // Published server ends with a / if (_startupOptions.PublishedServerUrl != null) @@ -1147,7 +1187,7 @@ namespace Emby.Server.Implementations return _startupOptions.PublishedServerUrl.ToString().Trim('/'); } - string smart = _networkManager.GetBindInterface(source, out int? port); + string smart = NetManager.GetBindInterface(hostname, out port); // If the smartAPI doesn't start with http then treat it as a host or ip. if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase)) @@ -1155,7 +1195,7 @@ namespace Emby.Server.Implementations return smart.Trim('/'); } - return GetLocalApiUrl(smart.Trim('/'), source is HttpRequest request ? request.Scheme : null, port); + return GetLocalApiUrl(smart.Trim('/'), null, port); } /// diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 1cf129ad2d..27937d2f84 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -10,7 +10,7 @@ using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 02ee302d0d..efb6d162ac 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -7,7 +7,7 @@ using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index f297ecd5df..531a785a07 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -8,7 +8,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; diff --git a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs index 08746b346b..bd76b93bf4 100644 --- a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs +++ b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs @@ -1,7 +1,7 @@ using System.Security.Claims; using Jellyfin.Api.Helpers; using Jellyfin.Data.Enums; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; diff --git a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs index 69e6a8fb2d..dfa366796f 100644 --- a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs +++ b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs b/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs index d1297119cd..3183d13186 100644 --- a/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs +++ b/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs b/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs index 53b5d47787..1cee962e9f 100644 --- a/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs +++ b/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs index abdf2858d0..214198e001 100644 --- a/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs +++ b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs index ada8a0d4ec..9867ea4ca6 100644 --- a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs +++ b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using Jellyfin.Api.Constants; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; diff --git a/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs index 475e3cdac4..affd955515 100644 --- a/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs +++ b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs index d022c9067f..fab464b503 100644 --- a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs +++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using Jellyfin.Api.Constants; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs b/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs index 418d63de68..801ee2f4c6 100644 --- a/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs +++ b/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs index a1cddbca3e..7adf72c3d3 100644 --- a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs +++ b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using Jellyfin.Api.Constants; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 6876b47b44..b87e275d0f 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 152e650bc0..c60497e304 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -7,7 +7,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.UserDtos; using Jellyfin.Data.Enums; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Authentication; diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 3be8734b94..f81fb88fb1 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -8,7 +8,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.StreamingDtos; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index d63e3ab118..9e4def774a 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; diff --git a/Jellyfin.Networking/Manager/INetworkManager.cs b/Jellyfin.Networking/Manager/INetworkManager.cs deleted file mode 100644 index ba571750b9..0000000000 --- a/Jellyfin.Networking/Manager/INetworkManager.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.NetworkInformation; -using MediaBrowser.Model.Configuration; -using NetworkCollection; - -namespace Jellyfin.Networking.Manager -{ - /// - /// Interface for the NetworkManager class. - /// - public interface INetworkManager - { - /// - /// Event triggered on network changes. - /// - event EventHandler NetworkChanged; - - /// - /// Gets the Published server override list. - /// - Dictionary PublishedServerOverrides { get; } - - /// - /// Gets a value indicating whether is all IPv6 interfaces are trusted as internal. - /// - public bool TrustAllIP6Interfaces { get; } - - /// - /// Gets returns the remote address filter. - /// - NetCollection RemoteAddressFilter { get; } - - /// - /// Calculates the list of interfaces to use for Kestrel. - /// - /// A NetCollection object containing all the interfaces to bind. - /// If all the interfaces are specified, and none are excluded, it returns zero items - /// to represent any address. - NetCollection GetAllBindInterfaces(); - - /// - /// Returns a collection containing the loopback interfaces. - /// - /// Netcollection. - public NetCollection GetLoopbacks(); - - /// - /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo) - /// If no bind addresses are specified, an internal interface address is selected. - /// The priority of selection is as follows:- - /// - /// The value contained in the startup parameter --published-server-url. - /// - /// If the user specified custom subnet overrides, the correct subnet for the source address. - /// - /// If the user specified bind interfaces to use:- - /// The bind interface that contains the source subnet. - /// The first bind interface specified that suits best first the source's endpoint. eg. external or internal. - /// - /// If the source is from a public subnet address range and the user hasn't specified any bind addresses:- - /// The first public interface that isn't a loopback and contains the source subnet. - /// The first public interface that isn't a loopback. Priority is given to interfaces with gateways. - /// An internal interface if there are no public ip addresses. - /// - /// If the source is from a private subnet address range and the user hasn't specified any bind addresses:- - /// The first private interface that contains the source subnet. - /// The first private interface that isn't a loopback. Priority is given to interfaces with gateways. - /// - /// If no interfaces meet any of these criteria, then a loopback address is returned. - /// - /// Interface that have been specifically excluded from binding are not used in any of the calculations. - /// - /// Source of the request. - /// Optional port returned, if it's part of an override. - /// IP Address to use, or loopback address if all else fails. - string GetBindInterface(object? source, out int? port); - - /// - /// Checks to see if the ip address is specifically excluded in LocalNetworkAddresses. - /// - /// IP address to check. - /// True if it is. - bool IsExcludedInterface(IPAddress address); - - /// - /// Get a list of all the MAC addresses associated with active interfaces. - /// - /// List of MAC addresses. - List GetMacAddresses(); - - /// - /// Checks to see if the IP Address provided matches an interface that has a gateway. - /// - /// IP to check. Can be an IPAddress or an IPObject. - /// Result of the check. - public bool IsGatewayInterface(object? addressObj); - - /// - /// Returns true if the address is a private address. - /// The config option TrustIP6Interfaces overrides this functions behaviour. - /// - /// Address to check. - /// True or False. - bool IsPrivateAddressRange(IPObject address); - - /// - /// Returns true if the address is part of the user defined LAN. - /// The config option TrustIP6Interfaces overrides this functions behaviour. - /// - /// IP to check. - /// True if endpoint is within the LAN range. - bool IsInLocalNetwork(string address); - - /// - /// Returns true if the address is part of the user defined LAN. - /// The config option TrustIP6Interfaces overrides this functions behaviour. - /// - /// IP to check. - /// True if endpoint is within the LAN range. - bool IsInLocalNetwork(IPObject address); - - /// - /// Returns true if the address is part of the user defined LAN. - /// The config option TrustIP6Interfaces overrides this functions behaviour. - /// - /// IP to check. - /// True if endpoint is within the LAN range. - bool IsInLocalNetwork(IPAddress address); - - /// - /// Attempts to convert the token to an IP address, permitting for interface descriptions and indexes. - /// eg. "eth1", or "TP-LINK Wireless USB Adapter". - /// - /// Token to parse. - /// Resultant object if successful. - /// Success of the operation. - bool TryParseInterface(string token, out IPNetAddress result); - - /// - /// Parses an array of strings into a NetCollection. - /// - /// Values to parse. - /// When true, only include values in []. When false, ignore bracketed values. - /// IPCollection object containing the value strings. - NetCollection CreateIPCollection(string[] values, bool bracketed = false); - - /// - /// Returns all the internal Bind interface addresses. - /// - /// An internal list of interfaces addresses. - NetCollection GetInternalBindAddresses(); - - /// - /// Checks to see if an IP address is still a valid interface address. - /// - /// IP address to check. - /// True if it is. - bool IsValidInterfaceAddress(IPAddress address); - - /// - /// Returns true if the IP address is in the excluded list. - /// - /// IP to check. - /// True if excluded. - bool IsExcluded(IPAddress ip); - - /// - /// Returns true if the IP address is in the excluded list. - /// - /// IP to check. - /// True if excluded. - bool IsExcluded(EndPoint ip); - - /// - /// Gets the filtered LAN ip addresses. - /// - /// Optional filter for the list. - /// Returns a filtered list of LAN addresses. - NetCollection GetFilteredLANSubnets(NetCollection? filter = null); - - /// - /// Reloads all settings and re-initialises the instance. - /// - /// to use. - public void UpdateSettings(ServerConfiguration config); - } -} diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 36a0a94a09..760938c40f 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -7,6 +7,7 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Model.Configuration; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -19,8 +20,6 @@ namespace Jellyfin.Networking.Manager /// public class NetworkManager : INetworkManager, IDisposable { - private static NetworkManager? _instance; - /// /// Contains the description of the interface along with its index. /// @@ -48,7 +47,7 @@ namespace Jellyfin.Networking.Manager /// /// Holds the bind address overrides. /// - private readonly Dictionary _overrideUrls; + private readonly Dictionary _publishedServerUrls; /// /// Used to stop "event-racing conditions". @@ -105,7 +104,7 @@ namespace Jellyfin.Networking.Manager _interfaceAddresses = new NetCollection(unique: false); _macAddresses = new List(); _interfaceNames = new SortedList(); - _overrideUrls = new Dictionary(); + _publishedServerUrls = new Dictionary(); UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration); if (!IsIP6Enabled && !IsIP4Enabled) @@ -117,8 +116,6 @@ namespace Jellyfin.Networking.Manager NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged; _configurationManager.ConfigurationUpdated += ConfigurationUpdated; - - Instance = this; } #pragma warning restore CS8618 // Non-nullable field is uninitialized. @@ -127,19 +124,6 @@ namespace Jellyfin.Networking.Manager /// public event EventHandler? NetworkChanged; - /// - /// Gets the singleton of this object. - /// - public static NetworkManager Instance - { - get => GetInstance(); - - internal set - { - _instance = value; - } - } - /// /// Gets the unique network location signature, which is updated on every network change. /// @@ -176,7 +160,7 @@ namespace Jellyfin.Networking.Manager /// /// Gets the Published server override list. /// - public Dictionary PublishedServerOverrides => _overrideUrls; + public Dictionary PublishedServerUrls => _publishedServerUrls; /// public void Dispose() @@ -198,9 +182,12 @@ namespace Jellyfin.Networking.Manager /// public bool IsGatewayInterface(object? addressObj) { - var address = (addressObj is IPAddress addressIP) ? - addressIP : (addressObj is IPObject addressIPObj) ? - addressIPObj.Address : IPAddress.None; + var address = addressObj switch + { + IPAddress addressIp => addressIp, + IPObject addressIpObj => addressIpObj.Address, + _ => IPAddress.None + }; lock (_intLock) { @@ -320,243 +307,127 @@ namespace Jellyfin.Networking.Manager } /// - public string GetBindInterface(object? source, out int? port) + public string GetBindInterface(string source, out int? port) { - bool chromeCast = false; - port = null; - // Parse the source object in an attempt to discover where the request originated. - IPObject sourceAddr; - if (source is HttpRequest sourceReq) + if (!string.IsNullOrEmpty(source)) { - port = sourceReq.Host.Port; - if (IPHost.TryParse(sourceReq.Host.Host, out IPHost host)) + if (string.Equals(source, "chromecast", StringComparison.OrdinalIgnoreCase)) { - sourceAddr = host; - } - else - { - // Assume it's external, as we cannot resolve the host. - sourceAddr = IPHost.None; - } - } - else if (source is string sourceStr && !string.IsNullOrEmpty(sourceStr)) - { - if (string.Equals(sourceStr, "chromecast", StringComparison.OrdinalIgnoreCase)) - { - chromeCast = true; // Just assign a variable so has source = true; - sourceAddr = IPNetAddress.IP4Loopback; + return GetBindInterface(IPNetAddress.IP4Loopback, out port); } - if (IPHost.TryParse(sourceStr, out IPHost host)) + if (IPHost.TryParse(source, out IPHost host)) { - sourceAddr = host; - } - else - { - // Assume it's external, as we cannot resolve the host. - sourceAddr = IPHost.None; + return GetBindInterface(host, out port); } } - else if (source is IPAddress sourceIP) + + return GetBindInterface(IPHost.None, out port); + } + + /// + public string GetBindInterface(IPAddress source, out int? port) + { + return GetBindInterface(new IPNetAddress(source), out port); + } + + /// + public string GetBindInterface(HttpRequest source, out int? port) + { + string result; + + if (source != null && IPHost.TryParse(source.Host.Host, out IPHost host)) { - sourceAddr = new IPNetAddress(sourceIP); + result = GetBindInterface(host, out port); + port ??= source.Host.Port; } else { - // If we have no idea, then assume it came from an external address. - sourceAddr = IPHost.None; + result = GetBindInterface(IPNetAddress.None, out port); + port ??= source?.Host.Port; } + return result; + } + + /// + public string GetBindInterface(IPObject source, out int? port) + { + port = null; + bool isChromeCast = source == IPNetAddress.IP4Loopback; // Do we have a source? - bool haveSource = !sourceAddr.Address.Equals(IPAddress.None); + bool haveSource = !source.Address.Equals(IPAddress.None); + bool isExternal = false; if (haveSource) { - if (!IsIP6Enabled && sourceAddr.AddressFamily == AddressFamily.InterNetworkV6) + if (!IsIP6Enabled && source.AddressFamily == AddressFamily.InterNetworkV6) { _logger.LogWarning("IPv6 is disabled in JellyFin, but enabled in the OS. This may affect how the interface is selected."); } - if (!IsIP4Enabled && sourceAddr.AddressFamily == AddressFamily.InterNetwork) + if (!IsIP4Enabled && source.AddressFamily == AddressFamily.InterNetwork) { _logger.LogWarning("IPv4 is disabled in JellyFin, but enabled in the OS. This may affect how the interface is selected."); } - } - bool isExternal = haveSource && !IsInLocalNetwork(sourceAddr); + isExternal = !IsInLocalNetwork(source); - string bindPreference = string.Empty; - if (haveSource) - { - // Check for user override. - foreach (var addr in _overrideUrls) + if (MatchesPublishedServerUrl(source, isExternal, isChromeCast, out string result, out port)) { - // Remaining. Match anything. - if (addr.Key.Equals(IPAddress.Broadcast)) - { - bindPreference = addr.Value; - break; - } - else if ((addr.Key.Equals(IPAddress.Any) || addr.Key.Equals(IPAddress.IPv6Any)) && (isExternal || chromeCast)) - { - // External. - bindPreference = addr.Value; - break; - } - else if (addr.Key.Contains(sourceAddr)) - { - // Match ip address. - bindPreference = addr.Value; - break; - } + _logger.LogInformation("{0}: Using BindAddress {1}:{2}", source, result, port); + return result; } } _logger.LogDebug("GetBindInterface: Souce: {0}, External: {1}:", haveSource, isExternal); - if (!string.IsNullOrEmpty(bindPreference)) - { - // Has it got a port defined? - var parts = bindPreference.Split(':'); - if (parts.Length > 1) - { - if (int.TryParse(parts[1], out int p)) - { - bindPreference = parts[0]; - port = p; - } - } - - _logger.LogInformation("{0}: Using BindAddress {1}:{2}", sourceAddr, bindPreference, port); - return bindPreference; - } - - string ipresult; - // No preference given, so move on to bind addresses. lock (_intLock) { - var nc = _bindAddresses.Exclude(_bindExclusions).Where(p => !p.IsLoopback()); - - int count = nc.Count(); - if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses.Equals(IPAddress.IPv6Any))) - { - // Ignore IPAny addresses. - count = 0; - } - - if (count != 0) + if (MatchesBindInterface(source, isExternal, out string result)) { - // Check to see if any of the bind interfaces are in the same subnet. - - IEnumerable bindResult; - IPAddress? defaultGateway = null; - - if (isExternal) - { - // Find all external bind addresses. Store the default gateway, but check to see if there is a better match first. - bindResult = nc.Where(p => !IsInLocalNetwork(p)).OrderBy(p => p.Tag); - defaultGateway = bindResult.FirstOrDefault()?.Address; - bindResult = bindResult.Where(p => p.Contains(sourceAddr)).OrderBy(p => p.Tag); - } - else - { - // Look for the best internal address. - bindResult = nc.Where(p => IsInLocalNetwork(p) && p.Contains(sourceAddr)).OrderBy(p => p.Tag); - } - - if (bindResult.Any()) - { - ipresult = FormatIP6String(bindResult.First().Address); - _logger.LogDebug("{0}: GetBindInterface: Has source, found a match bind interface subnets. {1}", sourceAddr, ipresult); - return ipresult; - } - - if (isExternal && defaultGateway != null) - { - ipresult = FormatIP6String(defaultGateway); - _logger.LogDebug("{0}: GetBindInterface: Using first user defined external interface. {1}", sourceAddr, ipresult); - return ipresult; - } - - ipresult = FormatIP6String(nc.First().Address); - _logger.LogDebug("{0}: GetBindInterface: Selected first user defined interface. {1}", sourceAddr, ipresult); - - if (isExternal) - { - // TODO: remove this after testing. - _logger.LogWarning("{0}: External request received, however, only an internal interface bind found.", sourceAddr); - } - - return ipresult; + return result; } - if (isExternal) + if (isExternal && MatchesExternalInterface(source, out result)) { - // Get the first WAN interface address that isn't a loopback. - var extResult = _interfaceAddresses - .Exclude(_bindExclusions) - .Where(p => !IsInLocalNetwork(p)) - .OrderBy(p => p.Tag); - - if (extResult.Any()) - { - // Does the request originate in one of the interface subnets? - // (For systems with multiple internal network cards, and multiple subnets) - foreach (var intf in extResult) - { - if (!IsInLocalNetwork(intf) && intf.Contains(sourceAddr)) - { - ipresult = FormatIP6String(intf.Address); - _logger.LogDebug("{0}: GetBindInterface: Selected best external on interface on range. {1}", sourceAddr, ipresult); - return ipresult; - } - } - - ipresult = FormatIP6String(extResult.First().Address); - _logger.LogDebug("{0}: GetBindInterface: Selected first external interface. {0}", sourceAddr, ipresult); - return ipresult; - } - - // 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.", sourceAddr); + return result; } // Get the first LAN interface address that isn't a loopback. - var result = _interfaceAddresses + var interfaces = new NetCollection(_interfaceAddresses .Exclude(_bindExclusions) .Where(p => IsInLocalNetwork(p)) - .OrderBy(p => p.Tag); + .OrderBy(p => p.Tag)); - if (result.Any()) + if (interfaces.Count > 0) { if (haveSource) { // Does the request originate in one of the interface subnets? // (For systems with multiple internal network cards, and multiple subnets) - foreach (var intf in result) + foreach (var intf in interfaces) { - if (intf.Contains(sourceAddr)) + if (intf.Contains(source)) { - ipresult = FormatIP6String(intf.Address); - _logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", sourceAddr, ipresult); - return ipresult; + result = FormatIP6String(intf.Address); + _logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", source, result); + return result; } } } - ipresult = FormatIP6String(result.First().Address); - _logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", sourceAddr, ipresult); - return ipresult; + result = FormatIP6String(interfaces.First().Address); + _logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", source, result); + return result; } // There isn't any others, so we'll use the loopback. - ipresult = IsIP6Enabled ? "::" : "127.0.0.1"; - _logger.LogWarning("{0}: GetBindInterface: Loopback return.", sourceAddr, ipresult); - return ipresult; + result = IsIP6Enabled ? "::" : "127.0.0.1"; + _logger.LogWarning("{0}: GetBindInterface: Loopback return.", source, result); + return result; } } @@ -771,16 +642,6 @@ namespace Jellyfin.Networking.Manager } } - private static NetworkManager GetInstance() - { - if (_instance == null) - { - throw new ApplicationException("NetworkManager is not initialised."); - } - - return _instance; - } - private void ConfigurationUpdated(object? sender, EventArgs args) { UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration); @@ -944,7 +805,7 @@ namespace Jellyfin.Networking.Manager { lock (_intLock) { - _overrideUrls.Clear(); + _publishedServerUrls.Clear(); } return; @@ -952,7 +813,7 @@ namespace Jellyfin.Networking.Manager lock (_intLock) { - _overrideUrls.Clear(); + _publishedServerUrls.Clear(); foreach (var entry in overrides) { @@ -966,15 +827,15 @@ namespace Jellyfin.Networking.Manager var replacement = parts[1].Trim(); if (string.Equals(parts[0], "remaining", StringComparison.OrdinalIgnoreCase)) { - _overrideUrls[new IPNetAddress(IPAddress.Broadcast)] = replacement; + _publishedServerUrls[new IPNetAddress(IPAddress.Broadcast)] = replacement; } else if (string.Equals(parts[0], "external", StringComparison.OrdinalIgnoreCase)) { - _overrideUrls[new IPNetAddress(IPAddress.Any)] = replacement; + _publishedServerUrls[new IPNetAddress(IPAddress.Any)] = replacement; } else if (TryParseInterface(parts[0], out IPNetAddress address)) { - _overrideUrls[address] = replacement; + _publishedServerUrls[address] = replacement; } else { @@ -1199,5 +1060,179 @@ namespace Jellyfin.Networking.Manager } } } + + /// + /// Attempts to match the source against a user defined bind interface. + /// + /// IP source address to use. + /// True if the source is in the external subnet. + /// True if the request is for a chromecast device. + /// The published server url that matches the source address. + /// The resultant port, if one exists. + /// True if a match is found. + private bool MatchesPublishedServerUrl(IPObject source, bool isExternal, bool isChromeCast, out string bindPreference, out int? port) + { + bindPreference = string.Empty; + port = null; + + // Check for user override. + foreach (var addr in _publishedServerUrls) + { + // Remaining. Match anything. + if (addr.Key.Equals(IPAddress.Broadcast)) + { + bindPreference = addr.Value; + break; + } + else if ((addr.Key.Equals(IPAddress.Any) || addr.Key.Equals(IPAddress.IPv6Any)) && (isExternal || isChromeCast)) + { + // External. + bindPreference = addr.Value; + break; + } + else if (addr.Key.Contains(source)) + { + // Match ip address. + bindPreference = addr.Value; + break; + } + } + + if (!string.IsNullOrEmpty(bindPreference)) + { + // Has it got a port defined? + var parts = bindPreference.Split(':'); + if (parts.Length > 1) + { + if (int.TryParse(parts[1], out int p)) + { + bindPreference = parts[0]; + port = p; + } + } + + return true; + } + + return false; + } + + /// + /// Attempts to match the source against a user defined bind interface. + /// + /// IP source address to use. + /// True if the source is in the external subnet. + /// The result, if a match is found. + /// True if a match is found. + private bool MatchesBindInterface(IPObject source, bool isExternal, out string result) + { + result = string.Empty; + var nc = new NetCollection(_bindAddresses.Exclude(_bindExclusions).Where(p => !p.IsLoopback())); + + int count = nc.Count; + if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses[0].Equals(IPAddress.IPv6Any))) + { + // Ignore IPAny addresses. + count = 0; + } + + if (count != 0) + { + // Check to see if any of the bind interfaces are in the same subnet. + + NetCollection bindResult; + IPAddress? defaultGateway = null; + IPAddress? bindAddress; + + if (isExternal) + { + // Find all external bind addresses. Store the default gateway, but check to see if there is a better match first. + bindResult = new NetCollection(nc + .Where(p => !IsInLocalNetwork(p)) + .OrderBy(p => p.Tag)); + defaultGateway = bindResult.FirstOrDefault()?.Address; + bindAddress = bindResult + .Where(p => p.Contains(source)) + .OrderBy(p => p.Tag) + .FirstOrDefault()?.Address; + } + else + { + // Look for the best internal address. + bindAddress = nc + .Where(p => IsInLocalNetwork(p) && (p.Contains(source) || p.Equals(IPAddress.None))) + .OrderBy(p => p.Tag) + .FirstOrDefault()?.Address; + } + + if (bindAddress != null) + { + result = FormatIP6String(bindAddress); + _logger.LogDebug("{0}: GetBindInterface: Has source, found a match bind interface subnets. {1}", source, result); + return true; + } + + if (isExternal && defaultGateway != null) + { + result = FormatIP6String(defaultGateway); + _logger.LogDebug("{0}: GetBindInterface: Using first user defined external interface. {1}", source, result); + return true; + } + + result = FormatIP6String(nc.First().Address); + _logger.LogDebug("{0}: GetBindInterface: Selected first user defined interface. {1}", source, result); + + if (isExternal) + { + // TODO: remove this after testing. + _logger.LogWarning("{0}: External request received, however, only an internal interface bind found.", source); + } + + return true; + } + + return false; + } + + /// + /// Attempts to match the source against am external interface. + /// + /// IP source address to use. + /// The result, if a match is found. + /// True if a match is found. + private bool MatchesExternalInterface(IPObject source, out string result) + { + result = string.Empty; + // Get the first WAN interface address that isn't a loopback. + var extResult = new NetCollection(_interfaceAddresses + .Exclude(_bindExclusions) + .Where(p => !IsInLocalNetwork(p)) + .OrderBy(p => p.Tag)); + + if (extResult.Count > 0) + { + // Does the request originate in one of the interface subnets? + // (For systems with multiple internal network cards, and multiple subnets) + foreach (var intf in extResult) + { + if (!IsInLocalNetwork(intf) && intf.Contains(source)) + { + result = FormatIP6String(intf.Address); + _logger.LogDebug("{0}: GetBindInterface: Selected best external on interface on range. {1}", source, result); + return true; + } + } + + result = FormatIP6String(extResult.First().Address); + _logger.LogDebug("{0}: GetBindInterface: Selected first external interface. {0}", source, result); + return true; + } + + // 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; + } } } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 8c19665cc1..f73c917a0b 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -12,10 +12,11 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using Jellyfin.Data.Events.Users; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Events; diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 566ba0ad85..e2d7305af4 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -5,7 +5,7 @@ using System.Reflection; using Emby.Drawing; using Emby.Server.Implementations; using Jellyfin.Drawing.Skia; -using Jellyfin.Networking.Manager; + using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Events; diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs index ff82fe6cc6..e927a147aa 100644 --- a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs +++ b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs @@ -1,6 +1,6 @@ using System.Linq; using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; diff --git a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs index 87c82bf583..fa34a167b3 100644 --- a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs +++ b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs @@ -2,7 +2,7 @@ using System; using System.Linq; using System.Net; using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 8549e39dbc..939f61656f 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -13,7 +13,7 @@ using CommandLine; using Emby.Server.Implementations; using Emby.Server.Implementations.IO; using Jellyfin.Api.Controllers; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Extensions; using Microsoft.AspNetCore.Hosting; @@ -272,7 +272,7 @@ namespace Jellyfin.Server return builder .UseKestrel((builderContext, options) => { - NetCollection addresses = NetworkManager.Instance.GetAllBindInterfaces(); + NetCollection addresses = appHost.NetManager.GetAllBindInterfaces(); bool flagged = false; foreach (IPObject netAdd in addresses) diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 70dcc2397c..0fda47788b 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -20,8 +20,9 @@ - + + diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs new file mode 100644 index 0000000000..32c017aee6 --- /dev/null +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -0,0 +1,221 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.NetworkInformation; +using MediaBrowser.Model.Configuration; +using Microsoft.AspNetCore.Http; +using NetworkCollection; + +namespace MediaBrowser.Common.Net +{ + /// + /// Interface for the NetworkManager class. + /// + public interface INetworkManager + { + /// + /// Event triggered on network changes. + /// + event EventHandler NetworkChanged; + + /// + /// Gets the published server urls list. + /// + Dictionary PublishedServerUrls { get; } + + /// + /// Gets a value indicating whether is all IPv6 interfaces are trusted as internal. + /// + public bool TrustAllIP6Interfaces { get; } + + /// + /// Gets the remote address filter. + /// + NetCollection RemoteAddressFilter { get; } + + /// + /// Calculates the list of interfaces to use for Kestrel. + /// + /// A NetCollection object containing all the interfaces to bind. + /// If all the interfaces are specified, and none are excluded, it returns zero items + /// to represent any address. + NetCollection GetAllBindInterfaces(); + + /// + /// Returns a collection containing the loopback interfaces. + /// + /// Netcollection. + public NetCollection GetLoopbacks(); + + /// + /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo) + /// If no bind addresses are specified, an internal interface address is selected. + /// The priority of selection is as follows:- + /// + /// The value contained in the startup parameter --published-server-url. + /// + /// If the user specified custom subnet overrides, the correct subnet for the source address. + /// + /// If the user specified bind interfaces to use:- + /// The bind interface that contains the source subnet. + /// The first bind interface specified that suits best first the source's endpoint. eg. external or internal. + /// + /// If the source is from a public subnet address range and the user hasn't specified any bind addresses:- + /// The first public interface that isn't a loopback and contains the source subnet. + /// The first public interface that isn't a loopback. Priority is given to interfaces with gateways. + /// An internal interface if there are no public ip addresses. + /// + /// If the source is from a private subnet address range and the user hasn't specified any bind addresses:- + /// The first private interface that contains the source subnet. + /// The first private interface that isn't a loopback. Priority is given to interfaces with gateways. + /// + /// If no interfaces meet any of these criteria, then a loopback address is returned. + /// + /// Interface that have been specifically excluded from binding are not used in any of the calculations. + /// + /// Source of the request. + /// Optional port returned, if it's part of an override. + /// IP Address to use, or loopback address if all else fails. + string GetBindInterface(IPObject source, out int? port); + + /// + /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo) + /// If no bind addresses are specified, an internal interface address is selected. + /// (See above). + /// + /// Source of the request. + /// Optional port returned, if it's part of an override. + /// IP Address to use, or loopback address if all else fails. + string GetBindInterface(HttpRequest source, out int? port); + + /// + /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo) + /// If no bind addresses are specified, an internal interface address is selected. + /// (See above). + /// + /// IP address of the request. + /// Optional port returned, if it's part of an override. + /// IP Address to use, or loopback address if all else fails. + string GetBindInterface(IPAddress source, out int? port); + + /// + /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo) + /// If no bind addresses are specified, an internal interface address is selected. + /// (See above). + /// + /// Source of the request. + /// Optional port returned, if it's part of an override. + /// IP Address to use, or loopback address if all else fails. + string GetBindInterface(string source, out int? port); + + /// + /// Checks to see if the ip address is specifically excluded in LocalNetworkAddresses. + /// + /// IP address to check. + /// True if it is. + bool IsExcludedInterface(IPAddress address); + + /// + /// Get a list of all the MAC addresses associated with active interfaces. + /// + /// List of MAC addresses. + List GetMacAddresses(); + + /// + /// Checks to see if the IP Address provided matches an interface that has a gateway. + /// + /// IP to check. Can be an IPAddress or an IPObject. + /// Result of the check. + public bool IsGatewayInterface(object? addressObj); + + /// + /// Returns true if the address is a private address. + /// The config option TrustIP6Interfaces overrides this functions behaviour. + /// + /// Address to check. + /// True or False. + bool IsPrivateAddressRange(IPObject address); + + /// + /// Returns true if the address is part of the user defined LAN. + /// The config option TrustIP6Interfaces overrides this functions behaviour. + /// + /// IP to check. + /// True if endpoint is within the LAN range. + bool IsInLocalNetwork(string address); + + /// + /// Returns true if the address is part of the user defined LAN. + /// The config option TrustIP6Interfaces overrides this functions behaviour. + /// + /// IP to check. + /// True if endpoint is within the LAN range. + bool IsInLocalNetwork(IPObject address); + + /// + /// Returns true if the address is part of the user defined LAN. + /// The config option TrustIP6Interfaces overrides this functions behaviour. + /// + /// IP to check. + /// True if endpoint is within the LAN range. + bool IsInLocalNetwork(IPAddress address); + + /// + /// Attempts to convert the token to an IP address, permitting for interface descriptions and indexes. + /// eg. "eth1", or "TP-LINK Wireless USB Adapter". + /// + /// Token to parse. + /// Resultant object if successful. + /// Success of the operation. + bool TryParseInterface(string token, out IPNetAddress result); + + /// + /// Parses an array of strings into a NetCollection. + /// + /// Values to parse. + /// When true, only include values in []. When false, ignore bracketed values. + /// IPCollection object containing the value strings. + NetCollection CreateIPCollection(string[] values, bool bracketed = false); + + /// + /// Returns all the internal Bind interface addresses. + /// + /// An internal list of interfaces addresses. + NetCollection GetInternalBindAddresses(); + + /// + /// Checks to see if an IP address is still a valid interface address. + /// + /// IP address to check. + /// True if it is. + bool IsValidInterfaceAddress(IPAddress address); + + /// + /// Returns true if the IP address is in the excluded list. + /// + /// IP to check. + /// True if excluded. + bool IsExcluded(IPAddress ip); + + /// + /// Returns true if the IP address is in the excluded list. + /// + /// IP to check. + /// True if excluded. + bool IsExcluded(EndPoint ip); + + /// + /// Gets the filtered LAN ip addresses. + /// + /// Optional filter for the list. + /// Returns a filtered list of LAN addresses. + NetCollection GetFilteredLANSubnets(NetCollection? filter = null); + + /// + /// Reloads all settings and re-initialises the instance. + /// + /// to use. + public void UpdateSettings(ServerConfiguration config); + } +} diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index f147e6a86c..7bc1006fc0 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -63,12 +63,28 @@ namespace MediaBrowser.Controller PublicSystemInfo GetPublicSystemInfo(IPAddress address); /// - /// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured - /// HTTPS will be preferred when available. + /// Gets a URL specific for the request. /// - /// The source of the request. - /// The server URL. - string GetSmartApiUrl(object source); + /// The instance. + /// Optional port number. + /// An accessible URL. + string GetSmartApiUrl(HttpRequest request, int? port = null); + + /// + /// Gets a URL specific for the request. + /// + /// The remote of the connection. + /// Optional port number. + /// An accessible URL. + string GetSmartApiUrl(IPAddress remoteAddr, int? port = null); + + /// + /// Gets a URL specific for the request. + /// + /// The hostname used in the connection. + /// Optional port number. + /// An accessible URL. + string GetSmartApiUrl(string hostname, int? port = null); /// /// Gets a localhost URL that can be used to access the API using the loop-back IP address. diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index e28a2f48d4..932f6bbb40 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -7,7 +7,7 @@ using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Net; using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index 43fccdad42..84456f7dcd 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Net; using NetworkCollection; diff --git a/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs index 2c7f0c4f9b..05dd8f325e 100644 --- a/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs @@ -4,7 +4,7 @@ using AutoFixture; using AutoFixture.AutoMoq; using Jellyfin.Api.Auth.LocalAccessPolicy; using Jellyfin.Api.Constants; -using Jellyfin.Networking.Manager; + using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; -- cgit v1.2.3 From bbe2400b59563991f1689c68ef36d623afd39f0a Mon Sep 17 00:00:00 2001 From: Jim Cartlidge Date: Wed, 30 Sep 2020 17:51:17 +0100 Subject: Updating to NetCollection 1.03 --- Jellyfin.Networking/Jellyfin.Networking.csproj | 3 +- Jellyfin.Networking/Manager/NetworkManager.cs | 150 ++++++++++++++------- MediaBrowser.Common/Net/INetworkManager.cs | 17 ++- .../Configuration/ServerConfiguration.cs | 23 ++-- 4 files changed, 128 insertions(+), 65 deletions(-) (limited to 'MediaBrowser.Common/Net') diff --git a/Jellyfin.Networking/Jellyfin.Networking.csproj b/Jellyfin.Networking/Jellyfin.Networking.csproj index 454dd9b7df..06d387dc87 100644 --- a/Jellyfin.Networking/Jellyfin.Networking.csproj +++ b/Jellyfin.Networking/Jellyfin.Networking.csproj @@ -28,8 +28,7 @@ - - + diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 83661d6b31..6f2970ef89 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -12,6 +12,7 @@ using MediaBrowser.Model.Configuration; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using NetworkCollection; +using NetworkCollection.Udp; namespace Jellyfin.Networking.Manager { @@ -125,29 +126,19 @@ namespace Jellyfin.Networking.Manager public event EventHandler? NetworkChanged; /// - /// Gets the unique network location signature, which is updated on every network change. + /// Gets or sets a value indicating whether testing is taking place. /// - public static string NetworkLocationSignature { get; internal set; } = Guid.NewGuid().ToString(); + public static string MockNetworkSettings { get; set; } = string.Empty; /// - /// Gets a value indicating whether IP6 is enabled. + /// Gets or sets a value indicating whether IP6 is enabled. /// - public static bool IsIP6Enabled { get; internal set; } + public bool IsIP6Enabled { get; set; } /// - /// Gets a value indicating whether IP4 is enabled. + /// Gets or sets a value indicating whether IP4 is enabled. /// - public static bool IsIP4Enabled { get; internal set; } = true; - - /// - /// Gets a value indicating whether is multi-socket binding available. - /// - public static bool EnableMultiSocketBinding { get; internal set; } = true; - - /// - /// Gets the number of times the network address has changed. - /// - public static int NetworkChangeCount { get; internal set; } = 1; + public bool IsIP4Enabled { get; set; } /// public NetCollection RemoteAddressFilter { get; private set; } @@ -271,7 +262,7 @@ namespace Jellyfin.Networking.Manager } /// - public NetCollection GetAllBindInterfaces() + public NetCollection GetAllBindInterfaces(bool individualInterfaces = false) { lock (_intLock) { @@ -285,6 +276,11 @@ namespace Jellyfin.Networking.Manager return _interfaceAddresses.Exclude(_bindExclusions); } + if (individualInterfaces) + { + return new NetCollection(_interfaceAddresses); + } + // No bind address and no exclusions, so listen on all interfaces. NetCollection result = new NetCollection(); @@ -311,12 +307,6 @@ namespace Jellyfin.Networking.Manager { if (!string.IsNullOrEmpty(source)) { - if (string.Equals(source, "chromecast", StringComparison.OrdinalIgnoreCase)) - { - // Just assign a variable so has source = true; - return GetBindInterface(IPNetAddress.IP4Loopback, out port); - } - if (IPHost.TryParse(source, out IPHost host)) { return GetBindInterface(host, out port); @@ -572,16 +562,18 @@ namespace Jellyfin.Networking.Manager } /// - public bool TryParseInterface(string token, out IPNetAddress result) + public bool TryParseInterface(string token, out NetCollection? result) { + result = null; if (string.IsNullOrEmpty(token)) { - result = IPNetAddress.None; return false; } if (_interfaceNames != null && _interfaceNames.TryGetValue(token.ToLower(CultureInfo.InvariantCulture), out int index)) { + result = new NetCollection(); + _logger.LogInformation("Interface {0} used in settings. Using its interface addresses.", token); // Replace interface tags with the interface IP's. @@ -591,13 +583,14 @@ namespace Jellyfin.Networking.Manager ((IsIP4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork) || (IsIP6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6))) { - result = iface; - return true; + result.Add(iface); } } + + return true; } - return IPNetAddress.TryParse(token, out result); + return false; } /// @@ -614,9 +607,27 @@ namespace Jellyfin.Networking.Manager IsIP4Enabled = Socket.OSSupportsIPv6 && config.EnableIPV4; IsIP6Enabled = Socket.OSSupportsIPv6 && config.EnableIPV6; TrustAllIP6Interfaces = config.TrustAllIP6Interfaces; - EnableMultiSocketBinding = config.EnableMultiSocketBinding; + UdpHelper.EnableMultiSocketBinding = config.EnableMultiSocketBinding; + + if (string.IsNullOrEmpty(MockNetworkSettings)) + { + InitialiseInterfaces(); + } + else // Used in testing only. + { + // Format is ,,: . Set index to -ve to simulate a gateway. + var interfaceList = MockNetworkSettings.Split(':'); + foreach (var details in interfaceList) + { + var parts = details.Split(','); + var address = IPNetAddress.Parse(parts[0]); + var index = int.Parse(parts[1], CultureInfo.InvariantCulture); + address.Tag = index; + _interfaceAddresses.Add(address); + _interfaceNames.Add(parts[2], Math.Abs(index)); + } + } - InitialiseInterfaces(); InitialiseLAN(config); InitialiseBind(config); InitialiseRemote(config); @@ -671,6 +682,40 @@ namespace Jellyfin.Networking.Manager return str; } + /// + /// Checks the string to see if it matches any interface names. + /// + /// String to check. + /// Interface index number. + /// True if an interface name matches the token. + private bool IsInterface(string token, out int index) + { + index = -1; + + // Is it the name of an interface (windows) eg, Wireless LAN adapter Wireless Network Connection 1. + // Null check required here for automated testing. + if (_interfaceNames != null && token.Length > 1) + { + bool partial = token[^1] == '*'; + if (partial) + { + token = token[0..^1]; + } + + foreach ((string interfc, int interfcIndex) in _interfaceNames) + { + if ((!partial && string.Equals(interfc, token, StringComparison.OrdinalIgnoreCase)) || + (partial && interfc.StartsWith(token, true, CultureInfo.InvariantCulture))) + { + index = interfcIndex; + return true; + } + } + } + + return false; + } + /// /// Parses strings into the collection, replacing any interface references. /// @@ -680,7 +725,7 @@ namespace Jellyfin.Networking.Manager { // Is it the name of an interface (windows) eg, Wireless LAN adapter Wireless Network Connection 1. // Null check required here for automated testing. - if (_interfaceNames != null && _interfaceNames.TryGetValue(token.ToLower(CultureInfo.InvariantCulture), out int index)) + if (IsInterface(token, out int index)) { _logger.LogInformation("Interface {0} used in settings. Using its interface addresses.", token); @@ -774,14 +819,6 @@ namespace Jellyfin.Networking.Manager /// private void OnNetworkChanged() { - // As per UPnP Device Architecture v1.0 Annex A - IPv6 Support. - NetworkLocationSignature = Guid.NewGuid().ToString(); - NetworkChangeCount++; - if (NetworkChangeCount > 99) - { - NetworkChangeCount = 1; - } - if (!_eventfire) { _logger.LogDebug("Network Address Change Event."); @@ -800,20 +837,14 @@ namespace Jellyfin.Networking.Manager /// private void InitialiseOverrides(ServerConfiguration config) { - string[] overrides = config.PublishedServerUriBySubnet; - if (overrides == null) - { - lock (_intLock) - { - _publishedServerUrls.Clear(); - } - - return; - } - lock (_intLock) { _publishedServerUrls.Clear(); + string[] overrides = config.PublishedServerUriBySubnet; + if (overrides == null) + { + return; + } foreach (var entry in overrides) { @@ -833,9 +864,16 @@ namespace Jellyfin.Networking.Manager { _publishedServerUrls[new IPNetAddress(IPAddress.Any)] = replacement; } - else if (TryParseInterface(parts[0], out IPNetAddress address)) + else if (TryParseInterface(parts[0], out NetCollection? addresses) && addresses != null) + { + foreach (IPNetAddress na in addresses) + { + _publishedServerUrls[na] = replacement; + } + } + else if (IPNetAddress.TryParse(parts[0], out IPNetAddress result)) { - _publishedServerUrls[address] = replacement; + _publishedServerUrls[result] = replacement; } else { @@ -859,6 +897,14 @@ namespace Jellyfin.Networking.Manager // TODO: end fix. + // Add virtual machine interface names to the list of bind exclusions, so that they are auto-excluded. + if (config.IgnoreVirtualInterfaces) + { + var newList = ba.ToList(); + newList.AddRange(config.VirtualInterfaceNames.Split(',').ToList()); + ba = newList.ToArray(); + } + // Read and parse bind addresses and exclusions, removing ones that don't exist. _bindAddresses = CreateIPCollection(ba).Union(_interfaceAddresses); _bindExclusions = CreateIPCollection(ba, true).Union(_interfaceAddresses); diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 32c017aee6..8789cd9d81 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -34,13 +34,24 @@ namespace MediaBrowser.Common.Net /// NetCollection RemoteAddressFilter { get; } + /// + /// Gets or sets a value indicating whether iP6 is enabled. + /// + public bool IsIP6Enabled { get; set; } + + /// + /// Gets or sets a value indicating whether iP4 is enabled. + /// + public bool IsIP4Enabled { get; set; } + /// /// Calculates the list of interfaces to use for Kestrel. /// /// A NetCollection object containing all the interfaces to bind. /// If all the interfaces are specified, and none are excluded, it returns zero items /// to represent any address. - NetCollection GetAllBindInterfaces(); + /// When false, return or for all interfaces. + NetCollection GetAllBindInterfaces(bool individualInterfaces = false); /// /// Returns a collection containing the loopback interfaces. @@ -166,9 +177,9 @@ namespace MediaBrowser.Common.Net /// eg. "eth1", or "TP-LINK Wireless USB Adapter". /// /// Token to parse. - /// Resultant object if successful. + /// Resultant object's ip addresses, if successful. /// Success of the operation. - bool TryParseInterface(string token, out IPNetAddress result); + bool TryParseInterface(string token, out NetCollection? result); /// /// Parses an array of strings into a NetCollection. diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 073a629829..dbfab1fad4 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable enable #pragma warning disable CS1591 #pragma warning disable CA1819 @@ -111,7 +111,7 @@ namespace MediaBrowser.Model.Configuration /// /// Gets or sets a value indicating whether detailed ssdp logs are sent to the console/log. - /// If the setting "Emby.Dlna": "Debug" msut be set in logging.default.json for this property to work. + /// "Emby.Dlna": "Debug" must be set in logging.default.json for this property to work. /// public bool EnableSSDPTracing { get; set; } = false; @@ -131,13 +131,23 @@ namespace MediaBrowser.Model.Configuration /// public int UDPSendDelay { get; set; } = 100; + /// + /// Gets or sets a value indicating whether address names that match should be Ignore for the purposes of binding. + /// + public bool IgnoreVirtualInterfaces { get; set; } = true; + + /// + /// Gets or sets a value indicating the interfaces that should be ignored. The list can be comma separated. . + /// + public string VirtualInterfaceNames { get; set; } = "vEthernet*"; + /// /// Gets or sets the time (in seconds) between the pings of SSDP gateway monitor. /// public int GatewayMonitorPeriod { get; set; } = 60; /// - /// Gets a value indicating whether is multi-socket binding available. + /// Gets a value indicating whether multi-socket binding is available. /// public bool EnableMultiSocketBinding { get; } = true; @@ -158,7 +168,7 @@ namespace MediaBrowser.Model.Configuration public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty(); /// - /// Gets or sets a value indicating whether gets or sets Autodiscovery tracing. + /// Gets or sets a value indicating whether Autodiscovery tracing is enabled. /// public bool AutoDiscoveryTracing { get; set; } = false; @@ -216,9 +226,7 @@ namespace MediaBrowser.Model.Configuration /// Gets or sets a value indicating whether quick connect is available for use on this server. /// public bool QuickConnectAvailable { get; set; } = false; - - public bool AutoRunWebApp { get; set; } = true; - + /// /// Gets or sets a value indicating whether access outside of the LAN is permitted. /// @@ -419,6 +427,5 @@ namespace MediaBrowser.Model.Configuration /// Gets or sets the known proxies. /// public string[] KnownProxies { get; set; } = Array.Empty(); - } } -- cgit v1.2.3 From a3f0843ac97c64a8a412d707baa9870d0e53dcff Mon Sep 17 00:00:00 2001 From: Greenback Date: Thu, 8 Oct 2020 19:00:30 +0100 Subject: Updated NetworkManager to PR1 --- .../Configuration/NetworkConfiguration.cs | 223 +++++++++++++++++++++ .../NetworkConfigurationExtensions.cs | 21 ++ .../Configuration/NetworkConfigurationFactory.cs | 27 +++ Jellyfin.Networking/Manager/NetworkManager.cs | 127 +++++------- MediaBrowser.Common/Net/INetworkManager.cs | 21 +- 5 files changed, 336 insertions(+), 83 deletions(-) create mode 100644 Jellyfin.Networking/Configuration/NetworkConfiguration.cs create mode 100644 Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs create mode 100644 Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs (limited to 'MediaBrowser.Common/Net') diff --git a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs new file mode 100644 index 0000000000..38f11153ab --- /dev/null +++ b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs @@ -0,0 +1,223 @@ +#pragma warning disable CA1819 // Properties should not return arrays + +using System; +using MediaBrowser.Model.Configuration; + +namespace Jellyfin.Networking.Configuration +{ + /// + /// Defines the . + /// + public class NetworkConfiguration + { + private string _baseUrl = string.Empty; + + /// + /// Gets the default http port. + /// + public const int DefaultHttpPort = 8096; + + /// + /// Gets the default https port. + /// + public const int DefaultHttpsPort = 8920; + + /// + /// Gets or sets a value indicating whether the server should force connections over HTTPS. + /// + public bool RequireHttps { get; set; } = false; + + /// + /// Gets or sets a value used to specify the URL prefix that your Jellyfin instance can be accessed at. + /// + public string BaseUrl + { + get => _baseUrl; + set + { + // Normalize the start of the string + if (string.IsNullOrWhiteSpace(value)) + { + // If baseUrl is empty, set an empty prefix string + _baseUrl = string.Empty; + return; + } + + if (value[0] != '/') + { + // If baseUrl was not configured with a leading slash, append one for consistency + value = "/" + value; + } + + // Normalize the end of the string + if (value[value.Length - 1] == '/') + { + // If baseUrl was configured with a trailing slash, remove it for consistency + value = value.Remove(value.Length - 1); + } + + _baseUrl = value; + } + } + + /// + /// Gets or sets the public HTTPS port. + /// + /// The public HTTPS port. + public int PublicHttpsPort { get; set; } = DefaultHttpsPort; + + /// + /// Gets or sets the HTTP server port number. + /// + /// The HTTP server port number. + public int HttpServerPortNumber { get; set; } = DefaultHttpPort; + + /// + /// Gets or sets the HTTPS server port number. + /// + /// The HTTPS server port number. + public int HttpsPortNumber { get; set; } = DefaultHttpsPort; + + /// + /// Gets or sets a value indicating whether to use HTTPS. + /// + /// + /// In order for HTTPS to be used, in addition to setting this to true, valid values must also be + /// provided for and . + /// + public bool EnableHttps { get; set; } = false; + + /// + /// Gets or sets the public mapped port. + /// + /// The public mapped port. + public int PublicPort { get; set; } = DefaultHttpPort; + + /// + /// Gets or sets a value indicating whether the http port should be mapped as part of UPnP automatic port forwarding.. + /// + public bool UPnPCreateHttpPortMap { get; set; } = false; + + /// + /// Gets or sets the UDPPortRange + /// Gets or sets client udp port range.. + /// + public string UDPPortRange { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether gets or sets IPV6 capability.. + /// + public bool EnableIPV6 { get; set; } = false; + + /// + /// Gets or sets a value indicating whether gets or sets IPV4 capability.. + /// + public bool EnableIPV4 { get; set; } = true; + + /// + /// Gets or sets a value indicating whether detailed ssdp logs are sent to the console/log. + /// "Emby.Dlna": "Debug" must be set in logging.default.json for this property to work.. + /// + public bool EnableSSDPTracing { get; set; } = false; + + /// + /// Gets or sets the SSDPTracingFilter + /// Gets or sets a value indicating whether an IP address is to be used to filter the detailed ssdp logs that are being sent to the console/log. + /// If the setting "Emby.Dlna": "Debug" msut be set in logging.default.json for this property to work.. + /// + public string SSDPTracingFilter { get; set; } = string.Empty; + + /// + /// Gets or sets the number of times SSDP UDP messages are sent.. + /// + public int UDPSendCount { get; set; } = 2; + + /// + /// Gets or sets the delay between each groups of SSDP messages (in ms).. + /// + public int UDPSendDelay { get; set; } = 100; + + /// + /// Gets or sets a value indicating whether address names that match should be Ignore for the purposes of binding.. + /// + public bool IgnoreVirtualInterfaces { get; set; } = true; + + /// + /// Gets or sets the VirtualInterfaceNames + /// Gets or sets a value indicating the interfaces that should be ignored. The list can be comma separated. .. + /// + public string VirtualInterfaceNames { get; set; } = "vEthernet*"; + + /// + /// Gets or sets the time (in seconds) between the pings of SSDP gateway monitor.. + /// + public int GatewayMonitorPeriod { get; set; } = 60; + + /// + /// Gets a value indicating whether multi-socket binding is available.. + /// + public bool EnableMultiSocketBinding { get; } = true; + + /// + /// Gets or sets a value indicating whether all IPv6 interfaces should be treated as on the internal network. + /// Depending on the address range implemented ULA ranges might not be used.. + /// + public bool TrustAllIP6Interfaces { get; set; } = false; + + /// + /// Gets or sets the ports that HDHomerun uses.. + /// + public string HDHomerunPortRange { get; set; } = string.Empty; + + /// + /// Gets or sets the PublishedServerUriBySubnet + /// Gets or sets PublishedServerUri to advertise for specific subnets.. + /// + public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty(); + + /// + /// Gets or sets a value indicating whether Autodiscovery tracing is enabled.. + /// + public bool AutoDiscoveryTracing { get; set; } = false; + + /// + /// Gets or sets a value indicating whether Autodiscovery is enabled.. + /// + public bool AutoDiscovery { get; set; } = true; + + /// + /// Gets or sets the filter for remote IP connectivity. Used in conjuntion with .. + /// + public string[] RemoteIPFilter { get; set; } = Array.Empty(); + + /// + /// Gets or sets a value indicating whether contains a blacklist or a whitelist. Default is a whitelist.. + /// + public bool IsRemoteIPFilterBlacklist { get; set; } = false; + + /// + /// Gets or sets a value indicating whether to enable automatic port forwarding.. + /// + public bool EnableUPnP { get; set; } = false; + + /// + /// Gets or sets a value indicating whether access outside of the LAN is permitted.. + /// + public bool EnableRemoteAccess { get; set; } = true; + + /// + /// Gets or sets the subnets that are deemed to make up the LAN.. + /// + public string[] LocalNetworkSubnets { get; set; } = Array.Empty(); + + /// + /// Gets or sets the interface addresses which Jellyfin will bind to. If empty, all interfaces will be used.. + /// + public string[] LocalNetworkAddresses { get; set; } = Array.Empty(); + + /// + /// Gets or sets the known proxies. + /// + public string[] KnownProxies { get; set; } = Array.Empty(); + } +} diff --git a/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs b/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs new file mode 100644 index 0000000000..e77b17ba92 --- /dev/null +++ b/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs @@ -0,0 +1,21 @@ +using Jellyfin.Networking.Configuration; +using MediaBrowser.Common.Configuration; + +namespace Jellyfin.Networking.Configuration +{ + /// + /// Defines the . + /// + public static class NetworkConfigurationExtensions + { + /// + /// Retrieves the network configuration. + /// + /// The . + /// The . + public static NetworkConfiguration GetNetworkConfiguration(this IConfigurationManager config) + { + return config.GetConfiguration("network"); + } + } +} diff --git a/Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs b/Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs new file mode 100644 index 0000000000..ac0485d871 --- /dev/null +++ b/Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; + +namespace Jellyfin.Networking.Configuration +{ + /// + /// Defines the . + /// + public class NetworkConfigurationFactory : IConfigurationFactory + { + /// + /// The GetConfigurations. + /// + /// The . + public IEnumerable GetConfigurations() + { + return new[] + { + new ConfigurationStore + { + Key = "network", + ConfigurationType = typeof(NetworkConfiguration) + } + }; + } + } +} diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 6f2970ef89..491fe824da 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -6,9 +6,9 @@ using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading.Tasks; +using Jellyfin.Networking.Configuration; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; -using MediaBrowser.Model.Configuration; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using NetworkCollection; @@ -24,7 +24,7 @@ namespace Jellyfin.Networking.Manager /// /// Contains the description of the interface along with its index. /// - private readonly SortedList _interfaceNames; + private readonly Dictionary _interfaceNames; /// /// Threading lock for network interfaces. @@ -96,7 +96,7 @@ namespace Jellyfin.Networking.Manager /// /// IServerConfigurationManager instance. /// Logger to use for messages. -#pragma warning disable CS8618 // Non-nullable field is uninitialized. : Values are set in InitialiseLAN function. Compiler doesn't yet recognise this. +#pragma warning disable CS8618 // Non-nullable field is uninitialized. : Values are set in UpdateSettings function. Compiler doesn't yet recognise this. public NetworkManager(IConfigurationManager configurationManager, ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -104,15 +104,9 @@ namespace Jellyfin.Networking.Manager _interfaceAddresses = new NetCollection(unique: false); _macAddresses = new List(); - _interfaceNames = new SortedList(); + _interfaceNames = new Dictionary(); _publishedServerUrls = new Dictionary(); - UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration); - if (!IsIP6Enabled && !IsIP4Enabled) - { - throw new ApplicationException("IPv4 and IPv6 cannot both be disabled."); - } - NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged; NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged; @@ -182,7 +176,7 @@ namespace Jellyfin.Networking.Manager lock (_intLock) { - return _internalInterfaces.Where(i => i.Address.Equals(address) && (i.Tag < 0)).Any(); + return _internalInterfaces.Where(i => i.Address.Equals(address) && i.Tag < 0).Any(); } } @@ -212,50 +206,47 @@ namespace Jellyfin.Networking.Manager /// public bool IsExcluded(EndPoint ip) { - if (ip != null) - { - return _excludedSubnets.Contains(((IPEndPoint)ip).Address); - } - - return false; + return ip != null && IsExcluded(((IPEndPoint)ip).Address); } /// public NetCollection CreateIPCollection(string[] values, bool bracketed = false) { NetCollection col = new NetCollection(); - if (values != null) + if (values == null) { - for (int a = 0; a < values.Length; a++) - { - string v = values[a].Trim(); + return col; + } - try + for (int a = 0; a < values.Length; a++) + { + string v = values[a].Trim(); + + try + { + if (v.StartsWith("[", StringComparison.OrdinalIgnoreCase) && v.EndsWith("]", StringComparison.OrdinalIgnoreCase)) { - if (v.StartsWith("[", StringComparison.OrdinalIgnoreCase) && v.EndsWith("]", StringComparison.OrdinalIgnoreCase)) + if (bracketed) { - if (bracketed) - { - AddToCollection(col, v.Remove(v.Length - 1).Substring(1)); - } + AddToCollection(col, v.Remove(v.Length - 1).Substring(1)); } - else if (v.StartsWith("!", StringComparison.OrdinalIgnoreCase)) - { - if (bracketed) - { - AddToCollection(col, v.Substring(1)); - } - } - else if (!bracketed) + } + else if (v.StartsWith("!", StringComparison.OrdinalIgnoreCase)) + { + if (bracketed) { - AddToCollection(col, v); + AddToCollection(col, v.Substring(1)); } } - catch (ArgumentException e) + else if (!bracketed) { - _logger.LogInformation("Ignoring LAN value {value}. Reason : {reason}", v, e.Message); + AddToCollection(col, v); } } + catch (ArgumentException e) + { + _logger.LogInformation("Ignoring LAN value {value}. Reason : {reason}", v, e.Message); + } } return col; @@ -305,12 +296,9 @@ namespace Jellyfin.Networking.Manager /// public string GetBindInterface(string source, out int? port) { - if (!string.IsNullOrEmpty(source)) + if (!string.IsNullOrEmpty(source) && IPHost.TryParse(source, out IPHost host)) { - if (IPHost.TryParse(source, out IPHost host)) - { - return GetBindInterface(host, out port); - } + return GetBindInterface(host, out port); } return GetBindInterface(IPHost.None, out port); @@ -345,7 +333,6 @@ namespace Jellyfin.Networking.Manager public string GetBindInterface(IPObject source, out int? port) { port = null; - bool isChromeCast = source == IPNetAddress.IP4Loopback; // Do we have a source? bool haveSource = !source.Address.Equals(IPAddress.None); bool isExternal = false; @@ -354,7 +341,7 @@ namespace Jellyfin.Networking.Manager { if (!IsIP6Enabled && source.AddressFamily == AddressFamily.InterNetworkV6) { - _logger.LogWarning("IPv6 is disabled in JellyFin, but enabled in the OS. This may affect how the interface is selected."); + _logger.LogWarning("IPv6 is disabled in Jellyfin, but enabled in the OS. This may affect how the interface is selected."); } if (!IsIP4Enabled && source.AddressFamily == AddressFamily.InterNetwork) @@ -364,7 +351,7 @@ namespace Jellyfin.Networking.Manager isExternal = !IsInLocalNetwork(source); - if (MatchesPublishedServerUrl(source, isExternal, isChromeCast, out string result, out port)) + if (MatchesPublishedServerUrl(source, isExternal, out string result, out port)) { _logger.LogInformation("{0}: Using BindAddress {1}:{2}", source, result, port); return result; @@ -529,12 +516,7 @@ namespace Jellyfin.Networking.Manager { lock (_intLock) { - if (_bindExclusions.Count > 0) - { - return _bindExclusions.Contains(address); - } - - return false; + return _bindExclusions.Contains(address); } } @@ -596,16 +578,20 @@ namespace Jellyfin.Networking.Manager /// /// Reloads all settings and re-initialises the instance. /// - /// to use. - public void UpdateSettings(ServerConfiguration config) + /// The configuration to use. + public void UpdateSettings(object configuration) { - if (config == null) - { - throw new ArgumentNullException(nameof(config)); - } + NetworkConfiguration config = (NetworkConfiguration)configuration ?? throw new ArgumentNullException(nameof(configuration)); IsIP4Enabled = Socket.OSSupportsIPv6 && config.EnableIPV4; IsIP6Enabled = Socket.OSSupportsIPv6 && config.EnableIPV6; + + if (!IsIP6Enabled && !IsIP4Enabled) + { + _logger.LogError("IPv4 and IPv6 cannot both be disabled."); + IsIP4Enabled = true; + } + TrustAllIP6Interfaces = config.TrustAllIP6Interfaces; UdpHelper.EnableMultiSocketBinding = config.EnableMultiSocketBinding; @@ -655,7 +641,7 @@ namespace Jellyfin.Networking.Manager private void ConfigurationUpdated(object? sender, EventArgs args) { - UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration); + UpdateSettings(_configurationManager.GetNetworkConfiguration()); } /// @@ -804,7 +790,7 @@ namespace Jellyfin.Networking.Manager await Task.Delay(2000).ConfigureAwait(false); InitialiseInterfaces(); // Recalculate LAN caches. - InitialiseLAN((ServerConfiguration)_configurationManager.CommonConfiguration); + InitialiseLAN(_configurationManager.GetNetworkConfiguration()); NetworkChanged?.Invoke(this, EventArgs.Empty); } @@ -835,7 +821,7 @@ namespace Jellyfin.Networking.Manager /// format is subnet=ipaddress|host|uri /// when subnet = 0.0.0.0, any external address matches. /// - private void InitialiseOverrides(ServerConfiguration config) + private void InitialiseOverrides(NetworkConfiguration config) { lock (_intLock) { @@ -884,7 +870,7 @@ namespace Jellyfin.Networking.Manager } } - private void InitialiseBind(ServerConfiguration config) + private void InitialiseBind(NetworkConfiguration config) { string[] ba = config.LocalNetworkAddresses; @@ -912,7 +898,7 @@ namespace Jellyfin.Networking.Manager _logger.LogInformation("Using bind exclusions: {0}", _bindExclusions); } - private void InitialiseRemote(ServerConfiguration config) + private void InitialiseRemote(NetworkConfiguration config) { RemoteAddressFilter = CreateIPCollection(config.RemoteIPFilter); } @@ -920,7 +906,7 @@ namespace Jellyfin.Networking.Manager /// /// Initialises internal LAN cache settings. /// - private void InitialiseLAN(ServerConfiguration config) + private void InitialiseLAN(NetworkConfiguration config) { lock (_intLock) { @@ -1029,8 +1015,7 @@ namespace Jellyfin.Networking.Manager }; int tag = nw.Tag; - /* Mono on OSX doesn't give any gateway addresses, so check DNS entries */ - if ((ipProperties.GatewayAddresses.Count > 0 || ipProperties.DnsAddresses.Count > 0) && !nw.IsLoopback()) + if ((ipProperties.GatewayAddresses.Count > 0) && !nw.IsLoopback()) { // -ve Tags signify the interface has a gateway. nw.Tag *= -1; @@ -1051,8 +1036,7 @@ namespace Jellyfin.Networking.Manager }; int tag = nw.Tag; - /* Mono on OSX doesn't give any gateway addresses, so check DNS entries */ - if ((ipProperties.GatewayAddresses.Count > 0 || ipProperties.DnsAddresses.Count > 0) && !nw.IsLoopback()) + if ((ipProperties.GatewayAddresses.Count > 0) && !nw.IsLoopback()) { // -ve Tags signify the interface has a gateway. nw.Tag *= -1; @@ -1112,11 +1096,10 @@ namespace Jellyfin.Networking.Manager /// /// IP source address to use. /// True if the source is in the external subnet. - /// True if the request is for a chromecast device. /// The published server url that matches the source address. /// The resultant port, if one exists. /// True if a match is found. - private bool MatchesPublishedServerUrl(IPObject source, bool isExternal, bool isChromeCast, out string bindPreference, out int? port) + private bool MatchesPublishedServerUrl(IPObject source, bool isExternal, out string bindPreference, out int? port) { bindPreference = string.Empty; port = null; @@ -1130,7 +1113,7 @@ namespace Jellyfin.Networking.Manager bindPreference = addr.Value; break; } - else if ((addr.Key.Equals(IPAddress.Any) || addr.Key.Equals(IPAddress.IPv6Any)) && (isExternal || isChromeCast)) + else if ((addr.Key.Equals(IPAddress.Any) || addr.Key.Equals(IPAddress.IPv6Any)) && isExternal) { // External. bindPreference = addr.Value; @@ -1241,7 +1224,7 @@ namespace Jellyfin.Networking.Manager } /// - /// Attempts to match the source against am external interface. + /// Attempts to match the source against an external interface. /// /// IP source address to use. /// The result, if a match is found. diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 8789cd9d81..f60f369d6a 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Net; using System.Net.NetworkInformation; -using MediaBrowser.Model.Configuration; using Microsoft.AspNetCore.Http; using NetworkCollection; @@ -27,7 +26,7 @@ namespace MediaBrowser.Common.Net /// /// Gets a value indicating whether is all IPv6 interfaces are trusted as internal. /// - public bool TrustAllIP6Interfaces { get; } + bool TrustAllIP6Interfaces { get; } /// /// Gets the remote address filter. @@ -37,12 +36,12 @@ namespace MediaBrowser.Common.Net /// /// Gets or sets a value indicating whether iP6 is enabled. /// - public bool IsIP6Enabled { get; set; } + bool IsIP6Enabled { get; set; } /// /// Gets or sets a value indicating whether iP4 is enabled. /// - public bool IsIP4Enabled { get; set; } + bool IsIP4Enabled { get; set; } /// /// Calculates the list of interfaces to use for Kestrel. @@ -57,7 +56,7 @@ namespace MediaBrowser.Common.Net /// Returns a collection containing the loopback interfaces. /// /// Netcollection. - public NetCollection GetLoopbacks(); + NetCollection GetLoopbacks(); /// /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo) @@ -93,7 +92,7 @@ namespace MediaBrowser.Common.Net /// /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo) /// If no bind addresses are specified, an internal interface address is selected. - /// (See above). + /// (See . /// /// Source of the request. /// Optional port returned, if it's part of an override. @@ -103,7 +102,7 @@ namespace MediaBrowser.Common.Net /// /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo) /// If no bind addresses are specified, an internal interface address is selected. - /// (See above). + /// (See . /// /// IP address of the request. /// Optional port returned, if it's part of an override. @@ -113,7 +112,7 @@ namespace MediaBrowser.Common.Net /// /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo) /// If no bind addresses are specified, an internal interface address is selected. - /// (See above). + /// (See . /// /// Source of the request. /// Optional port returned, if it's part of an override. @@ -138,7 +137,7 @@ namespace MediaBrowser.Common.Net /// /// IP to check. Can be an IPAddress or an IPObject. /// Result of the check. - public bool IsGatewayInterface(object? addressObj); + bool IsGatewayInterface(object? addressObj); /// /// Returns true if the address is a private address. @@ -226,7 +225,7 @@ namespace MediaBrowser.Common.Net /// /// Reloads all settings and re-initialises the instance. /// - /// to use. - public void UpdateSettings(ServerConfiguration config); + /// The configuration to use. + void UpdateSettings(object configuration); } } -- cgit v1.2.3 From 83af636c610744903b709117f7f2a7b7e34da1f0 Mon Sep 17 00:00:00 2001 From: Greenback Date: Sat, 31 Oct 2020 18:21:46 +0000 Subject: Updated with new NetManager --- Emby.Dlna/Main/DlnaEntryPoint.cs | 4 +- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 25 +- Jellyfin.Networking/Jellyfin.Networking.csproj | 4 - Jellyfin.Networking/Manager/NetworkManager.cs | 49 ++- .../IpBasedAccessValidationMiddleware.cs | 3 +- .../Middleware/LanFilteringMiddleware.cs | 1 - Jellyfin.Server/Program.cs | 4 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 1 - MediaBrowser.Common/Net/INetworkManager.cs | 4 +- MediaBrowser.Common/Net/IPHost.cs | 447 +++++++++++++++++++++ MediaBrowser.Common/Net/IPNetAddress.cs | 277 +++++++++++++ MediaBrowser.Common/Net/IPObject.cs | 395 ++++++++++++++++++ MediaBrowser.Common/Net/NetworkExtensions.cs | 254 ++++++++++++ MediaBrowser.sln | 7 + RSSDP/SsdpDevicePublisher.cs | 1 - .../Jellyfin.Networking.Tests.csproj | 28 ++ .../NetworkTesting/UnitTesting.cs | 425 ++++++++++++++++++++ 17 files changed, 1895 insertions(+), 34 deletions(-) create mode 100644 MediaBrowser.Common/Net/IPHost.cs create mode 100644 MediaBrowser.Common/Net/IPNetAddress.cs create mode 100644 MediaBrowser.Common/Net/IPObject.cs create mode 100644 MediaBrowser.Common/Net/NetworkExtensions.cs create mode 100644 tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj create mode 100644 tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs (limited to 'MediaBrowser.Common/Net') diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 504bde996b..be618be2b3 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; using Emby.Dlna.Ssdp; +using Jellyfin.Networking.Manager; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -26,7 +27,6 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; -using NetworkCollection; using Rssdp; using Rssdp.Infrastructure; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; @@ -261,7 +261,7 @@ namespace Emby.Dlna.Main { var udn = CreateUuid(_appHost.SystemId); - var bindAddresses = new NetCollection( + var bindAddresses = NetworkManager.CreateCollection( _networkManager.GetInternalBindAddresses() .Where(i => i.AddressFamily == AddressFamily.InterNetwork || (i.AddressFamily == AddressFamily.InterNetworkV6 && i.Address.ScopeId != 0))); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index cfc5278ece..c4176eb7ba 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; +using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -16,7 +18,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; -using NetworkCollection.Udp; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { @@ -51,6 +52,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun EnableStreamSharing = true; } + /// + /// Returns an unused UDP port number in the range specified. + /// + /// Upper and Lower boundary of ports to select. + /// System.Int32. + private static int GetUdpPortFromRange((int Min, int Max) range) + { + var properties = IPGlobalProperties.GetIPGlobalProperties(); + + // Get active udp listeners. + var udpListenerPorts = properties.GetActiveUdpListeners() + .Where(n => n.Port >= range.Min && n.Port <= range.Max) + .Select(n => n.Port); + + return Enumerable.Range(range.Min, range.Max) + .Where(i => !udpListenerPorts.Contains(i)) + .FirstOrDefault(); + } + public override async Task Open(CancellationToken openCancellationToken) { LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); @@ -58,7 +78,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var mediaSource = OriginalMediaSource; var uri = new Uri(mediaSource.Path); - var localPort = UdpHelper.GetRandomUnusedUdpPort(); + // Temporary Code to reduce PR size. + var localPort = GetUdpPortFromRange((49152, 65535)); Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath)); diff --git a/Jellyfin.Networking/Jellyfin.Networking.csproj b/Jellyfin.Networking/Jellyfin.Networking.csproj index 330d36a80d..1747a1dc7a 100644 --- a/Jellyfin.Networking/Jellyfin.Networking.csproj +++ b/Jellyfin.Networking/Jellyfin.Networking.csproj @@ -23,10 +23,6 @@ ../jellyfin.ruleset - - - - diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 616774d043..76ac02d791 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -13,8 +13,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using NetworkCollection; -using NetworkCollection.Udp; +using NetCollection = System.Collections.ObjectModel.Collection; namespace Jellyfin.Networking.Manager { @@ -154,6 +153,22 @@ namespace Jellyfin.Networking.Manager /// public Dictionary PublishedServerUrls => _publishedServerUrls; + /// + /// Creates a new network collection. + /// + /// Items to assign the collection, or null. + /// The collection created. + public static NetCollection CreateCollection(IEnumerable? source) + { + var result = new NetCollection(); + if (source != null) + { + return result.AddRange(source); + } + + return result; + } + /// public void Dispose() { @@ -162,10 +177,10 @@ namespace Jellyfin.Networking.Manager } /// - public List GetMacAddresses() + public IReadOnlyCollection GetMacAddresses() { // Populated in construction - so always has values. - return _macAddresses.ToList(); + return _macAddresses.AsReadOnly(); } /// @@ -187,12 +202,12 @@ namespace Jellyfin.Networking.Manager NetCollection nc = new NetCollection(); if (IsIP4Enabled) { - nc.Add(IPAddress.Loopback); + nc.AddItem(IPAddress.Loopback); } if (IsIP6Enabled) { - nc.Add(IPAddress.IPv6Loopback); + nc.AddItem(IPAddress.IPv6Loopback); } return nc; @@ -276,12 +291,12 @@ namespace Jellyfin.Networking.Manager if (IsIP4Enabled) { - result.Add(IPAddress.Any); + result.AddItem(IPAddress.Any); } if (IsIP6Enabled) { - result.Add(IPAddress.IPv6Any); + result.AddItem(IPAddress.IPv6Any); } return result; @@ -375,7 +390,7 @@ namespace Jellyfin.Networking.Manager } // Get the first LAN interface address that isn't a loopback. - var interfaces = new NetCollection(_interfaceAddresses + var interfaces = CreateCollection(_interfaceAddresses .Exclude(_bindExclusions) .Where(p => IsInLocalNetwork(p)) .OrderBy(p => p.Tag)); @@ -418,11 +433,11 @@ namespace Jellyfin.Networking.Manager if (_bindExclusions.Count > 0) { // Return all the internal interfaces except the ones excluded. - return new NetCollection(_internalInterfaces.Where(p => !_bindExclusions.Contains(p))); + return CreateCollection(_internalInterfaces.Where(p => !_bindExclusions.Contains(p))); } // No bind address, so return all internal interfaces. - return new NetCollection(_internalInterfaces.Where(p => !p.IsLoopback())); + return CreateCollection(_internalInterfaces.Where(p => !p.IsLoopback())); } return new NetCollection(_bindAddresses); @@ -572,7 +587,7 @@ namespace Jellyfin.Networking.Manager } TrustAllIP6Interfaces = config.TrustAllIP6Interfaces; - UdpHelper.EnableMultiSocketBinding = config.EnableMultiSocketBinding; + // UdpHelper.EnableMultiSocketBinding = config.EnableMultiSocketBinding; if (string.IsNullOrEmpty(MockNetworkSettings)) { @@ -941,7 +956,7 @@ namespace Jellyfin.Networking.Manager { _logger.LogDebug("Using LAN interface addresses as user provided no LAN details."); // Internal interfaces must be private and not excluded. - _internalInterfaces = new NetCollection(_interfaceAddresses.Where(i => IsPrivateAddressRange(i) && !_excludedSubnets.Contains(i))); + _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsPrivateAddressRange(i) && !_excludedSubnets.Contains(i))); // Subnets are the same as the calculated internal interface. _lanSubnets = new NetCollection(); @@ -976,7 +991,7 @@ namespace Jellyfin.Networking.Manager } // Internal interfaces must be private, not excluded and part of the LocalNetworkSubnet. - _internalInterfaces = new NetCollection(_interfaceAddresses.Where(i => IsInLocalNetwork(i) && !_excludedSubnets.Contains(i) && _lanSubnets.Contains(i))); + _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsInLocalNetwork(i) && !_excludedSubnets.Contains(i) && _lanSubnets.Contains(i))); } _logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets); @@ -1082,7 +1097,7 @@ namespace Jellyfin.Networking.Manager IPHost host = new IPHost(Dns.GetHostName()); foreach (var a in host.GetAddresses()) { - _interfaceAddresses.Add(a); + _interfaceAddresses.AddItem(a); } if (_interfaceAddresses.Count == 0) @@ -1189,7 +1204,7 @@ namespace Jellyfin.Networking.Manager if (isExternal) { // Find all external bind addresses. Store the default gateway, but check to see if there is a better match first. - bindResult = new NetCollection(nc + bindResult = CreateCollection(nc .Where(p => !IsInLocalNetwork(p)) .OrderBy(p => p.Tag)); defaultGateway = bindResult.FirstOrDefault()?.Address; @@ -1246,7 +1261,7 @@ namespace Jellyfin.Networking.Manager { result = string.Empty; // Get the first WAN interface address that isn't a loopback. - var extResult = new NetCollection(_interfaceAddresses + var extResult = CreateCollection(_interfaceAddresses .Exclude(_bindExclusions) .Where(p => !IsInLocalNetwork(p)) .OrderBy(p => p.Tag)); diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs index c3533d7956..525cd9ffe2 100644 --- a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs +++ b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs @@ -5,7 +5,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Http; -using NetworkCollection; namespace Jellyfin.Server.Middleware { @@ -47,7 +46,7 @@ namespace Jellyfin.Server.Middleware { // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. // If left blank, all remote addresses will be allowed. - NetCollection remoteAddressFilter = networkManager.RemoteAddressFilter; + var remoteAddressFilter = networkManager.RemoteAddressFilter; if (remoteAddressFilter.Count > 0 && !networkManager.IsInLocalNetwork(remoteIp)) { diff --git a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs index 1f4e80053d..8065054a1e 100644 --- a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs +++ b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs @@ -7,7 +7,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Http; -using NetworkCollection; namespace Jellyfin.Server.Middleware { diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index fd300da7f1..61f7da16a6 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -14,6 +14,7 @@ using Emby.Server.Implementations; using Emby.Server.Implementations.IO; using Jellyfin.Api.Controllers; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Extensions; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -23,7 +24,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using NetworkCollection; using Serilog; using Serilog.Extensions.Logging; using SQLitePCL; @@ -271,7 +271,7 @@ namespace Jellyfin.Server return builder .UseKestrel((builderContext, options) => { - NetCollection addresses = appHost.NetManager.GetAllBindInterfaces(); + var addresses = appHost.NetManager.GetAllBindInterfaces(); bool flagged = false; foreach (IPObject netAdd in addresses) diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 0b8f431c04..777136f8bf 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -22,7 +22,6 @@ - diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index f60f369d6a..a7beabbdcb 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Net; using System.Net.NetworkInformation; using Microsoft.AspNetCore.Http; -using NetworkCollection; +using NetCollection = System.Collections.ObjectModel.Collection; namespace MediaBrowser.Common.Net { @@ -130,7 +130,7 @@ namespace MediaBrowser.Common.Net /// Get a list of all the MAC addresses associated with active interfaces. /// /// List of MAC addresses. - List GetMacAddresses(); + IReadOnlyCollection GetMacAddresses(); /// /// Checks to see if the IP Address provided matches an interface that has a gateway. diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs new file mode 100644 index 0000000000..80052727af --- /dev/null +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -0,0 +1,447 @@ +#nullable enable +using System; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace MediaBrowser.Common.Net +{ + /// + /// Object that holds a host name. + /// + public class IPHost : IPObject + { + /// + /// Represents an IPHost that has no value. + /// + public static readonly IPHost None = new IPHost(string.Empty, IPAddress.None); + + /// + /// Time when last resolved. + /// + private long _lastResolved; + + /// + /// Gets the IP Addresses, attempting to resolve the name, if there are none. + /// + private IPAddress[] _addresses; + + /// + /// Initializes a new instance of the class. + /// + /// Host name to assign. + public IPHost(string name) + { + HostName = name ?? throw new ArgumentNullException(nameof(name)); + _addresses = Array.Empty(); + Resolved = false; + } + + /// + /// Initializes a new instance of the class. + /// + /// Host name to assign. + /// Address to assign. + private IPHost(string name, IPAddress address) + { + HostName = name ?? throw new ArgumentNullException(nameof(name)); + _addresses = new IPAddress[] { address ?? throw new ArgumentNullException(nameof(address)) }; + Resolved = !address.Equals(IPAddress.None); + } + + /// + /// Gets or sets the object's first IP address. + /// + public override IPAddress Address + { + get + { + return ResolveHost() ? this[0] : IPAddress.None; + } + + set + { + // Not implemented. + } + } + + /// + /// Gets or sets the object's first IP's subnet prefix. + /// The setter does nothing, but shouldn't raise an exception. + /// + public override byte PrefixLength + { + get + { + return (byte)(ResolveHost() ? 128 : 0); + } + + set + { + // Not implemented. + } + } + + /// + /// Gets or sets timeout value before resolve required, in minutes. + /// + public byte Timeout { get; set; } = 30; + + /// + /// Gets a value indicating whether the address has a value. + /// + public bool HasAddress + { + get + { + return _addresses.Length > 0; + } + } + + /// + /// Gets the host name of this object. + /// + public string HostName { get; } + + /// + /// Gets a value indicating whether this host has attempted to be resolved. + /// + public bool Resolved { get; private set; } + + /// + /// Gets or sets the IP Addresses associated with this object. + /// + /// Index of address. + public IPAddress this[int index] + { + get + { + ResolveHost(); + return index >= 0 && index < _addresses.Length ? _addresses[index] : IPAddress.None; + } + } + + /// + /// Attempts to parse the host string. + /// + /// Host name to parse. + /// Object representing the string, if it has successfully been parsed. + /// Success result of the parsing. + public static bool TryParse(string host, out IPHost hostObj) + { + if (!string.IsNullOrEmpty(host)) + { + // See if it's an IPv6 with port address e.g. [::1]:120. + int i = host.IndexOf("]:", StringComparison.OrdinalIgnoreCase); + if (i != -1) + { + return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj); + } + else + { + // See if it's an IPv6 in [] with no port. + i = host.IndexOf("]", StringComparison.OrdinalIgnoreCase); + if (i != -1) + { + return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj); + } + + // Is it a host or IPv4 with port? + string[] hosts = host.Split(':'); + + if (hosts.Length > 2) + { + hostObj = new IPHost(string.Empty, IPAddress.None); + return false; + } + + // Remove port from IPv4 if it exists. + host = hosts[0]; + + if (string.Equals("localhost", host, StringComparison.OrdinalIgnoreCase)) + { + hostObj = new IPHost(host, new IPAddress(Ipv4Loopback)); + return true; + } + + if (IPNetAddress.TryParse(host, out IPNetAddress netIP)) + { + // Host name is an ip address, so fake resolve. + hostObj = new IPHost(host, netIP.Address); + return true; + } + } + + // Only thing left is to see if it's a host string. + if (!string.IsNullOrEmpty(host)) + { + // Use regular expression as CheckHostName isn't RFC5892 compliant. + // Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation + Regex re = new Regex(@"^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)$", RegexOptions.IgnoreCase | RegexOptions.Multiline); + if (re.Match(host).Success) + { + hostObj = new IPHost(host); + return true; + } + } + } + + hostObj = IPHost.None; + return false; + } + + /// + /// Attempts to parse the host string. + /// + /// Host name to parse. + /// Object representing the string, if it has successfully been parsed. + public static IPHost Parse(string host) + { + if (!string.IsNullOrEmpty(host) && IPHost.TryParse(host, out IPHost res)) + { + return res; + } + + throw new InvalidCastException("Host does not contain a valid value. {host}"); + } + + /// + /// Attempts to parse the host string, ensuring that it resolves only to a specific IP type. + /// + /// Host name to parse. + /// Addressfamily filter. + /// Object representing the string, if it has successfully been parsed. + public static IPHost Parse(string host, AddressFamily family) + { + if (!string.IsNullOrEmpty(host) && IPHost.TryParse(host, out IPHost res)) + { + if (family == AddressFamily.InterNetwork) + { + res.Remove(AddressFamily.InterNetworkV6); + } + else + { + res.Remove(AddressFamily.InterNetwork); + } + + return res; + } + + throw new InvalidCastException("Host does not contain a valid value. {host}"); + } + + /// + /// Returns the Addresses that this item resolved to. + /// + /// IPAddress Array. + public IPAddress[] GetAddresses() + { + ResolveHost(); + return _addresses; + } + + /// + public override bool Contains(IPAddress address) + { + if (address != null && !Address.Equals(IPAddress.None)) + { + if (address.IsIPv4MappedToIPv6) + { + address = address.MapToIPv4(); + } + + foreach (var addr in GetAddresses()) + { + if (address.Equals(addr)) + { + return true; + } + } + } + + return false; + } + + /// + public override bool Equals(IPObject? other) + { + if (other is IPHost otherObj) + { + // Do we have the name Hostname? + if (string.Equals(otherObj.HostName, HostName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (!ResolveHost() || !otherObj.ResolveHost()) + { + return false; + } + + // Do any of our IP addresses match? + foreach (IPAddress addr in _addresses) + { + foreach (IPAddress otherAddress in otherObj._addresses) + { + if (addr.Equals(otherAddress)) + { + return true; + } + } + } + } + + return false; + } + + /// + public override bool IsIP6() + { + // Returns true if interfaces are only IP6. + if (ResolveHost()) + { + foreach (IPAddress i in _addresses) + { + if (i.AddressFamily != AddressFamily.InterNetworkV6) + { + return false; + } + } + + return true; + } + + return false; + } + + /// + public override string ToString() + { + // StringBuilder not optimum here. + string output = string.Empty; + if (_addresses.Length > 0) + { + bool moreThanOne = _addresses.Length > 1; + if (moreThanOne) + { + output = "["; + } + + foreach (var i in _addresses) + { + if (Address.Equals(IPAddress.None) && Address.AddressFamily == AddressFamily.Unspecified) + { + output += HostName + ","; + } + else if (i.Equals(IPAddress.Any)) + { + output += "Any IP4 Address,"; + } + else if (Address.Equals(IPAddress.IPv6Any)) + { + output += "Any IP6 Address,"; + } + else if (i.Equals(IPAddress.Broadcast)) + { + output += "Any Address,"; + } + else + { + output += $"{i}/32,"; + } + } + + output = output[0..^1]; + + if (moreThanOne) + { + output += "]"; + } + } + else + { + output = HostName; + } + + return output; + } + + /// + public override void Remove(AddressFamily family) + { + if (ResolveHost()) + { + _addresses = _addresses.Where(p => p.AddressFamily != family).ToArray(); + } + } + + /// + public override bool Contains(IPObject address) + { + // An IPHost cannot contain another IPObject, it can only be equal. + return Equals(address); + } + + /// + protected override IPObject CalculateNetworkAddress() + { + var netAddr = NetworkAddressOf(this[0], PrefixLength); + return new IPNetAddress(netAddr.Address, netAddr.PrefixLength); + } + + /// + /// Attempt to resolve the ip address of a host. + /// + /// The result of the comparison function. + private bool ResolveHost() + { + // When was the last time we resolved? + if (_lastResolved == 0) + { + _lastResolved = DateTime.Now.Ticks; + } + + // If we haven't resolved before, or out timer has run out... + if ((_addresses.Length == 0 && !Resolved) || (TimeSpan.FromTicks(DateTime.Now.Ticks - _lastResolved).TotalMinutes > Timeout)) + { + _lastResolved = DateTime.Now.Ticks; + ResolveHostInternal().GetAwaiter().GetResult(); + Resolved = true; + } + + return _addresses.Length > 0; + } + + /// + /// Task that looks up a Host name and returns its IP addresses. + /// + /// Array of IPAddress objects. + private async Task ResolveHostInternal() + { + if (!string.IsNullOrEmpty(HostName)) + { + // Resolves the host name - so save a DNS lookup. + if (string.Equals(HostName, "localhost", StringComparison.OrdinalIgnoreCase)) + { + _addresses = new IPAddress[] { new IPAddress(Ipv4Loopback), new IPAddress(Ipv6Loopback) }; + return; + } + + if (Uri.CheckHostName(HostName).Equals(UriHostNameType.Dns)) + { + try + { + IPHostEntry ip = await Dns.GetHostEntryAsync(HostName).ConfigureAwait(false); + _addresses = ip.AddressList; + } + catch (SocketException) + { + // Ignore socket errors, as the result value will just be an empty array. + } + } + } + } + } +} diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs new file mode 100644 index 0000000000..bcd049f3d5 --- /dev/null +++ b/MediaBrowser.Common/Net/IPNetAddress.cs @@ -0,0 +1,277 @@ +#nullable enable +using System; +using System.Net; +using System.Net.Sockets; + +namespace MediaBrowser.Common.Net +{ + /// + /// An object that holds and IP address and subnet mask. + /// + public class IPNetAddress : IPObject + { + /// + /// Represents an IPNetAddress that has no value. + /// + public static readonly IPNetAddress None = new IPNetAddress(IPAddress.None); + + /// + /// IPv4 multicast address. + /// + public static readonly IPAddress MulticastIPv4 = IPAddress.Parse("239.255.255.250"); + + /// + /// IPv6 local link multicast address. + /// + public static readonly IPAddress MulticastIPv6LinkLocal = IPAddress.Parse("ff02::C"); + + /// + /// IPv6 site local multicast address. + /// + public static readonly IPAddress MulticastIPv6SiteLocal = IPAddress.Parse("ff05::C"); + + /// + /// IP4Loopback address host. + /// + public static readonly IPNetAddress IP4Loopback = IPNetAddress.Parse("127.0.0.1/32"); + + /// + /// IP6Loopback address host. + /// + public static readonly IPNetAddress IP6Loopback = IPNetAddress.Parse("::1"); + + /// + /// Object's IP address. + /// + private IPAddress _address; + + /// + /// Initializes a new instance of the class. + /// + /// Address to assign. + public IPNetAddress(IPAddress address) + { + _address = address ?? throw new ArgumentNullException(nameof(address)); + PrefixLength = (byte)(address.AddressFamily == AddressFamily.InterNetwork ? 32 : 128); + } + + /// + /// Initializes a new instance of the class. + /// + /// IP Address. + /// Mask as a CIDR. + public IPNetAddress(IPAddress address, byte prefixLength) + { + if (address?.IsIPv4MappedToIPv6 ?? throw new ArgumentNullException(nameof(address))) + { + _address = address.MapToIPv4(); + } + else + { + _address = address; + } + + PrefixLength = prefixLength; + } + + /// + /// Gets or sets the object's IP address. + /// + public override IPAddress Address + { + get + { + return _address; + } + + set + { + _address = value ?? IPAddress.None; + } + } + + /// + public override byte PrefixLength { get; set; } + + /// + /// Try to parse the address and subnet strings into an IPNetAddress object. + /// + /// IP address to parse. Can be CIDR or X.X.X.X notation. + /// Resultant object. + /// True if the values parsed successfully. False if not, resulting in the IP being null. + public static bool TryParse(string addr, out IPNetAddress ip) + { + if (!string.IsNullOrEmpty(addr)) + { + addr = addr.Trim(); + + // Try to parse it as is. + if (IPAddress.TryParse(addr, out IPAddress res)) + { + ip = new IPNetAddress(res); + return true; + } + + // Is it a network? + string[] tokens = addr.Split("/"); + + if (tokens.Length == 2) + { + tokens[0] = tokens[0].TrimEnd(); + tokens[1] = tokens[1].TrimStart(); + + if (IPAddress.TryParse(tokens[0], out res)) + { + // Is the subnet part a cidr? + if (byte.TryParse(tokens[1], out byte cidr)) + { + ip = new IPNetAddress(res, cidr); + return true; + } + + // Is the subnet in x.y.a.b form? + if (IPAddress.TryParse(tokens[1], out IPAddress mask)) + { + ip = new IPNetAddress(res, MaskToCidr(mask)); + return true; + } + } + } + } + + ip = None; + return false; + } + + /// + /// Parses the string provided, throwing an exception if it is badly formed. + /// + /// String to parse. + /// IPNetAddress object. + public static IPNetAddress Parse(string addr) + { + if (TryParse(addr, out IPNetAddress o)) + { + return o; + } + + throw new ArgumentException("Unable to recognise object :" + addr); + } + + /// + public override bool Contains(IPAddress address) + { + if (address == null) + { + throw new ArgumentNullException(nameof(address)); + } + + if (address.IsIPv4MappedToIPv6) + { + address = address.MapToIPv4(); + } + + var altAddress = NetworkAddressOf(address, PrefixLength); + return NetworkAddress.Address.Equals(altAddress.Address) && NetworkAddress.PrefixLength >= altAddress.PrefixLength; + } + + /// + public override bool Contains(IPObject address) + { + if (address is IPHost addressObj && addressObj.HasAddress) + { + foreach (IPAddress addr in addressObj.GetAddresses()) + { + if (Contains(addr)) + { + return true; + } + } + } + else if (address is IPNetAddress netaddrObj) + { + // Have the same network address, but different subnets? + if (NetworkAddress.Address.Equals(netaddrObj.NetworkAddress.Address)) + { + return NetworkAddress.PrefixLength <= netaddrObj.PrefixLength; + } + + var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength); + return NetworkAddress.Address.Equals(altAddress.Address); + } + + return false; + } + + /// + public override bool Equals(IPObject? other) + { + if (other is IPNetAddress otherObj && !Address.Equals(IPAddress.None) && !otherObj.Address.Equals(IPAddress.None)) + { + return Address.Equals(otherObj.Address) && + PrefixLength == otherObj.PrefixLength; + } + + return false; + } + + /// + public override bool Equals(IPAddress address) + { + if (address != null && !address.Equals(IPAddress.None) && !Address.Equals(IPAddress.None)) + { + return address.Equals(Address); + } + + return false; + } + + /// + public override string ToString() + { + return ToString(false); + } + + /// + /// Returns a textual representation of this object. + /// + /// Set to true, if the subnet is to be included as part of the address. + /// String representation of this object. + public string ToString(bool shortVersion) + { + if (!Address.Equals(IPAddress.None)) + { + if (Address.Equals(IPAddress.Any)) + { + return "Any IP4 Address"; + } + + if (Address.Equals(IPAddress.IPv6Any)) + { + return "Any IP6 Address"; + } + + if (Address.Equals(IPAddress.Broadcast)) + { + return "Any Address"; + } + + if (shortVersion) + { + return Address.ToString(); + } + + return $"{Address}/{PrefixLength}"; + } + + return string.Empty; + } + + /// + protected override IPObject CalculateNetworkAddress() + { + var value = NetworkAddressOf(_address, PrefixLength); + return new IPNetAddress(value.Address, value.PrefixLength); + } + } +} diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs new file mode 100644 index 0000000000..a08694c266 --- /dev/null +++ b/MediaBrowser.Common/Net/IPObject.cs @@ -0,0 +1,395 @@ +#nullable enable +using System; +using System.Net; +using System.Net.Sockets; + +namespace MediaBrowser.Common.Net +{ + /// + /// Base network object class. + /// + public abstract class IPObject : IEquatable + { + /// + /// IPv6 Loopback address. + /// + protected static readonly byte[] Ipv6Loopback = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; + + /// + /// IPv4 Loopback address. + /// + protected static readonly byte[] Ipv4Loopback = { 127, 0, 0, 1 }; + + /// + /// The network address of this object. + /// + private IPObject? _networkAddress; + + /// + /// Gets or sets the user defined functions that need storage in this object. + /// + public int Tag { get; set; } + + /// + /// Gets or sets the object's IP address. + /// + public abstract IPAddress Address { get; set; } + + /// + /// Gets the object's network address. + /// + public IPObject NetworkAddress + { + get + { + if (_networkAddress == null) + { + _networkAddress = CalculateNetworkAddress(); + } + + return _networkAddress; + } + } + + /// + /// Gets or sets the object's IP address. + /// + public abstract byte PrefixLength { get; set; } + + /// + /// Gets the AddressFamily of this object. + /// + public AddressFamily AddressFamily + { + get + { + // Keep terms separate as Address performs other functions in inherited objects. + IPAddress address = Address; + return address.Equals(IPAddress.None) ? AddressFamily.Unspecified : address.AddressFamily; + } + } + + /// + /// Returns the network address of an object. + /// + /// IP Address to convert. + /// Subnet prefix. + /// IPAddress. + public static (IPAddress Address, byte PrefixLength) NetworkAddressOf(IPAddress address, byte prefixLength) + { + if (address == null) + { + throw new ArgumentNullException(nameof(address)); + } + + if (address.IsIPv4MappedToIPv6) + { + address = address.MapToIPv4(); + } + + if (IsLoopback(address)) + { + return (Address: address, PrefixLength: prefixLength); + } + + byte[] addressBytes = address.GetAddressBytes(); + + int div = prefixLength / 8; + int mod = prefixLength % 8; + if (mod != 0) + { + mod = 8 - mod; + addressBytes[div] = (byte)((int)addressBytes[div] >> mod << mod); + div++; + } + + for (int octet = div; octet < addressBytes.Length; octet++) + { + addressBytes[octet] = 0; + } + + return (Address: new IPAddress(addressBytes), PrefixLength: prefixLength); + } + + /// + /// Tests to see if the ip address is a Loopback address. + /// + /// Value to test. + /// True if it is. + public static bool IsLoopback(IPAddress address) + { + if (address == null) + { + throw new ArgumentNullException(nameof(address)); + } + + if (!address.Equals(IPAddress.None)) + { + if (address.IsIPv4MappedToIPv6) + { + address = address.MapToIPv4(); + } + + return address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback); + } + + return false; + } + + /// + /// Tests to see if the ip address is an IP6 address. + /// + /// Value to test. + /// True if it is. + public static bool IsIP6(IPAddress address) + { + if (address == null) + { + throw new ArgumentNullException(nameof(address)); + } + + if (address.IsIPv4MappedToIPv6) + { + address = address.MapToIPv4(); + } + + return !address.Equals(IPAddress.None) && (address.AddressFamily == AddressFamily.InterNetworkV6); + } + + /// + /// Tests to see if the address in the private address range. + /// + /// Object to test. + /// True if it contains a private address. + public static bool IsPrivateAddressRange(IPAddress address) + { + if (address == null) + { + throw new ArgumentNullException(nameof(address)); + } + + if (!address.Equals(IPAddress.None)) + { + if (address.AddressFamily == AddressFamily.InterNetwork) + { + if (address.IsIPv4MappedToIPv6) + { + address = address.MapToIPv4(); + } + + byte[] octet = address.GetAddressBytes(); + + return (octet[0] == 10) || + (octet[0] == 172 && octet[1] >= 16 && octet[1] <= 31) || // RFC1918 + (octet[0] == 192 && octet[1] == 168) || // RFC1918 + (octet[0] == 127); // RFC1122 + } + else + { + byte[] octet = address.GetAddressBytes(); + uint word = (uint)(octet[0] << 8) + octet[1]; + + return (word >= 0xfe80 && word <= 0xfebf) || // fe80::/10 :Local link. + (word >= 0xfc00 && word <= 0xfdff); // fc00::/7 :Unique local address. + } + } + + return false; + } + + /// + /// Returns true if the IPAddress contains an IP6 Local link address. + /// + /// IPAddress object to check. + /// True if it is a local link address. + /// See https://stackoverflow.com/questions/6459928/explain-the-instance-properties-of-system-net-ipaddress + /// it appears that the IPAddress.IsIPv6LinkLocal is out of date. + /// + public static bool IsIPv6LinkLocal(IPAddress address) + { + if (address == null) + { + throw new ArgumentNullException(nameof(address)); + } + + if (address.IsIPv4MappedToIPv6) + { + address = address.MapToIPv4(); + } + + if (address.AddressFamily != AddressFamily.InterNetworkV6) + { + return false; + } + + byte[] octet = address.GetAddressBytes(); + uint word = (uint)(octet[0] << 8) + octet[1]; + + return word >= 0xfe80 && word <= 0xfebf; // fe80::/10 :Local link. + } + + /// + /// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only. + /// + /// Subnet mask in CIDR notation. + /// IPv4 or IPv6 family. + /// String value of the subnet mask in dotted decimal notation. + public static IPAddress CidrToMask(byte cidr, AddressFamily family) + { + uint addr = 0xFFFFFFFF << (family == AddressFamily.InterNetwork ? 32 : 128 - cidr); + addr = + ((addr & 0xff000000) >> 24) | + ((addr & 0x00ff0000) >> 8) | + ((addr & 0x0000ff00) << 8) | + ((addr & 0x000000ff) << 24); + return new IPAddress(addr); + } + + /// + /// Convert a mask to a CIDR. IPv4 only. + /// https://stackoverflow.com/questions/36954345/get-cidr-from-netmask. + /// + /// Subnet mask. + /// Byte CIDR representing the mask. + public static byte MaskToCidr(IPAddress mask) + { + if (mask == null) + { + throw new ArgumentNullException(nameof(mask)); + } + + byte cidrnet = 0; + if (!mask.Equals(IPAddress.Any)) + { + byte[] bytes = mask.GetAddressBytes(); + + var zeroed = false; + for (var i = 0; i < bytes.Length; i++) + { + for (int v = bytes[i]; (v & 0xFF) != 0; v <<= 1) + { + if (zeroed) + { + // Invalid netmask. + return (byte)~cidrnet; + } + + if ((v & 0x80) == 0) + { + zeroed = true; + } + else + { + cidrnet++; + } + } + } + } + + return cidrnet; + } + + /// + /// Tests to see if this object is a Loopback address. + /// + /// True if it is. + public virtual bool IsLoopback() + { + return IsLoopback(Address); + } + + /// + /// Removes all addresses of a specific type from this object. + /// + /// Type of address to remove. + public virtual void Remove(AddressFamily family) + { + // This method only peforms a function in the IPHost implementation of IPObject. + } + + /// + /// Tests to see if this object is an IPv6 address. + /// + /// True if it is. + public virtual bool IsIP6() + { + return IsIP6(Address); + } + + /// + /// Returns true if this IP address is in the RFC private address range. + /// + /// True this object has a private address. + public virtual bool IsPrivateAddressRange() + { + return IsPrivateAddressRange(Address); + } + + /// + /// Compares this to the object passed as a parameter. + /// + /// Object to compare to. + /// Equality result. + public virtual bool Equals(IPAddress ip) + { + if (ip != null) + { + if (ip.IsIPv4MappedToIPv6) + { + ip = ip.MapToIPv4(); + } + + return !Address.Equals(IPAddress.None) && Address.Equals(ip); + } + + return false; + } + + /// + /// Compares this to the object passed as a parameter. + /// + /// Object to compare to. + /// Equality result. + public virtual bool Equals(IPObject? other) + { + if (other != null && other is IPObject otherObj) + { + return !Address.Equals(IPAddress.None) && Address.Equals(otherObj.Address); + } + + return false; + } + + /// + /// Compares the address in this object and the address in the object passed as a parameter. + /// + /// Object's IP address to compare to. + /// Comparison result. + public abstract bool Contains(IPObject address); + + /// + /// Compares the address in this object and the address in the object passed as a parameter. + /// + /// Object's IP address to compare to. + /// Comparison result. + public abstract bool Contains(IPAddress address); + + /// + public override int GetHashCode() + { + return Address.GetHashCode(); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as IPObject); + } + + /// + /// Calculates the network address of this object. + /// + /// Returns the network address of this object. + protected abstract IPObject CalculateNetworkAddress(); + } +} diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs new file mode 100644 index 0000000000..6e9cb46dc1 --- /dev/null +++ b/MediaBrowser.Common/Net/NetworkExtensions.cs @@ -0,0 +1,254 @@ +#pragma warning disable CA1062 // Validate arguments of public methods +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Runtime.CompilerServices; +using NetCollection = System.Collections.ObjectModel.Collection; + +namespace MediaBrowser.Common.Net +{ + /// + /// Defines the . + /// + public static class NetworkExtensions + { + /// + /// Add an address to the collection. + /// + /// The . + /// Item to add. + public static void AddItem(this NetCollection source, IPAddress ip) + { + if (!source.ContainsAddress(ip)) + { + source.Add(new IPNetAddress(ip, 32)); + } + } + + /// + /// Add multiple items to the collection. + /// + /// The . + /// Item to add. + /// Return the collection. + public static NetCollection AddRange(this NetCollection destination, IEnumerable source) + { + foreach (var item in source) + { + destination.Add(item); + } + + return destination; + } + + /// + /// Adds a network to the collection. + /// + /// The . + /// Item to add. + public static void AddItem(this NetCollection source, IPObject item) + { + if (!source.ContainsAddress(item)) + { + source.Add(item); + } + } + + /// + /// Converts this object to a string. + /// + /// The . + /// Returns a string representation of this object. + public static string Readable(this NetCollection source) + { + string output = "["; + if (source.Count > 0) + { + foreach (var i in source) + { + output += $"{i},"; + } + + output = output[0..^1]; + } + + return $"{output}]"; + } + + /// + /// Returns true if the collection contains an item with the ip address, + /// or the ip address falls within any of the collection's network ranges. + /// + /// The . + /// The item to look for. + /// True if the collection contains the item. + public static bool ContainsAddress(this NetCollection source, IPAddress item) + { + if (source.Count == 0) + { + return false; + } + + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + if (item.IsIPv4MappedToIPv6) + { + item = item.MapToIPv4(); + } + + foreach (var i in source) + { + if (i.Contains(item)) + { + return true; + } + } + + return false; + } + + /// + /// Returns true if the collection contains an item with the ip address, + /// or the ip address falls within any of the collection's network ranges. + /// + /// The . + /// The item to look for. + /// True if the collection contains the item. + public static bool ContainsAddress(this NetCollection source, IPObject item) + { + if (source.Count == 0) + { + return false; + } + + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + foreach (var i in source) + { + if (i.Contains(item)) + { + return true; + } + } + + return false; + } + + /// + /// Returns a collection containing the subnets of this collection given. + /// + /// The . + /// NetCollection object containing the subnets. + public static NetCollection AsNetworks(this NetCollection source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + NetCollection res = new NetCollection(); + + foreach (IPObject i in source) + { + if (i is IPNetAddress nw) + { + // Add the subnet calculated from the interface address/mask. + var na = nw.NetworkAddress; + na.Tag = i.Tag; + res.Add(na); + } + else + { + // Flatten out IPHost and add all its ip addresses. + foreach (var addr in ((IPHost)i).GetAddresses()) + { + IPNetAddress host = new IPNetAddress(addr) + { + Tag = i.Tag + }; + + res.Add(host); + } + } + } + + return res; + } + + /// + /// Excludes all the items from this list that are found in excludeList. + /// + /// The . + /// Items to exclude. + /// A new collection, with the items excluded. + public static NetCollection Exclude(this NetCollection source, NetCollection excludeList) + { + if (source.Count == 0 || excludeList == null) + { + return new NetCollection(source); + } + + NetCollection results = new NetCollection(); + + bool found; + foreach (var outer in source) + { + found = false; + + foreach (var inner in excludeList) + { + if (outer.Equals(inner)) + { + found = true; + break; + } + } + + if (!found) + { + results.Add(outer); + } + } + + return results; + } + + /// + /// Returns all items that co-exist in this object and target. + /// + /// The . + /// Collection to compare with. + /// A collection containing all the matches. + public static NetCollection Union(this NetCollection source, NetCollection target) + { + if (source.Count == 0) + { + return new NetCollection(); + } + + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + NetCollection nc = new NetCollection(); + + foreach (IPObject i in source) + { + if (target.ContainsAddress(i)) + { + nc.Add(i); + } + } + + return nc; + } + } +} diff --git a/MediaBrowser.sln b/MediaBrowser.sln index d460c0ab0c..cb204137bd 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -68,6 +68,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jellyfin.Networking\Jellyfin.Networking.csproj", "{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\NetworkTesting\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -182,6 +184,10 @@ Global {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.Build.0 = Release|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -193,6 +199,7 @@ Global {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index ae175d8c9d..3e89d7f602 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -6,7 +6,6 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; -using NetworkCollection; namespace Rssdp.Infrastructure { diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj new file mode 100644 index 0000000000..fa18316dff --- /dev/null +++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj @@ -0,0 +1,28 @@ + + + + + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} + + + + netcoreapp3.1 + false + true + enable + + + + + + + + + + + + + + + + diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs b/tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs new file mode 100644 index 0000000000..9e7e8d3ac3 --- /dev/null +++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs @@ -0,0 +1,425 @@ +using System; +using System.Net; +using Emby.Dlna.PlayTo; +using Jellyfin.Networking.Configuration; +using Jellyfin.Networking.Manager; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using Moq; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; +using NetCollection = System.Collections.ObjectModel.Collection; +using XMLProperties = System.Collections.Generic.Dictionary; + +namespace NetworkTesting +{ + public class NetTesting + { + /// + /// Trys to identify the string and return an object of that class. + /// + /// String to parse. + /// IPObject to return. + /// True if the value parsed successfully. + private static bool TryParse(string addr, out IPObject result) + { + if (!string.IsNullOrEmpty(addr)) + { + // Is it an IP address + if (IPNetAddress.TryParse(addr, out IPNetAddress nw)) + { + result = nw; + return true; + } + + if (IPHost.TryParse(addr, out IPHost h)) + { + result = h; + return true; + } + } + + result = IPNetAddress.None; + return false; + } + + + private IConfigurationManager GetMockConfig(NetworkConfiguration conf) + { + var configManager = new Mock + { + CallBase = true + }; + configManager.Setup(x => x.GetConfiguration(It.IsAny())).Returns(conf); + return (IConfigurationManager)configManager.Object; + } + + [Theory] + [InlineData("192.168.1.208/24,-16,eth16:200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.0/24", "[192.168.1.208/24,200.200.200.200/24]")] + [InlineData("192.168.1.208/24,-16,eth16:200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")] + [InlineData("192.168.1.208/24,-16,vEthernet1:192.168.1.208/24,-16,vEthernet212;200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")] + public void IgnoreVirtualInterfaces(string interfaces, string lan, string value) + { + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true, + LocalNetworkSubnets = lan.Split(';') + }; + + NetworkManager.MockNetworkSettings = interfaces; + var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + NetworkManager.MockNetworkSettings = string.Empty; + + Assert.True(string.Equals(nm.GetInternalBindAddresses().ToString(), value, StringComparison.Ordinal)); + } + + [Theory] + [InlineData("192.168.10.0/24, !192.168.10.60/32", "192.168.10.60")] + public void TextIsInNetwork(string network, string value) + { + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true, + LocalNetworkSubnets = network.Split(',') + }; + + var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + + Assert.True(!nm.IsInLocalNetwork(value)); + } + + [Theory] + [InlineData("127.0.0.1")] + [InlineData("127.0.0.1:123")] + [InlineData("localhost")] + [InlineData("localhost:1345")] + [InlineData("www.google.co.uk")] + [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")] + [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")] + [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]:124")] + [InlineData("fe80::7add:12ff:febb:c67b%16")] + [InlineData("[fe80::7add:12ff:febb:c67b%16]:123")] + [InlineData("192.168.1.2/255.255.255.0")] + [InlineData("192.168.1.2/24")] + + public void TestCollectionCreation(string address) + { + Assert.True(TryParse(address, out _)); + } + + [Theory] + [InlineData("256.128.0.0.0.1")] + [InlineData("127.0.0.1#")] + [InlineData("localhost!")] + [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517:1231")] + public void TestInvalidCollectionCreation(string address) + { + Assert.False(TryParse(address, out _)); + } + + [Theory] + // Src, IncIP6, incIP4, exIP6, ecIP4, net + [InlineData("127.0.0.1#", + "[]", + "[]", + "[]", + "[]", + "[]")] + [InlineData("[127.0.0.1]", + "[]", + "[]", + "[127.0.0.1/32]", + "[127.0.0.1/32]", + "[]")] + [InlineData("", + "[]", + "[]", + "[]", + "[]", + "[]")] + [InlineData("192.158.1.2/255.255.0.0,192.169.1.2/8", + "[192.158.1.2/16,192.169.1.2/8]", + "[192.158.1.2/16,192.169.1.2/8]", + "[]", + "[]", + "[192.158.0.0/16,192.0.0.0/8]")] + [InlineData("192.158.1.2/16, localhost, fd23:184f:2029:0:3139:7386:67d7:d517, [10.10.10.10]", + "[192.158.1.2/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]", + "[192.158.1.2/16,127.0.0.1/32]", + "[10.10.10.10/32]", + "[10.10.10.10/32]", + "[192.158.0.0/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]")] + public void TestCollections(string settings, string result1, string result2, string result3, string result4, string result5) + { + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true, + }; + + var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + + // Test included, IP6. + NetCollection nc = nm.CreateIPCollection(settings.Split(","), false); + Assert.True(string.Equals(nc.ToString(), result1, System.StringComparison.OrdinalIgnoreCase)); + + // Text excluded, non IP6. + nc = nm.CreateIPCollection(settings.Split(","), true); + Assert.True(string.Equals(nc?.ToString(), result3, System.StringComparison.OrdinalIgnoreCase)); + + conf.EnableIPV6 = false; + nm.UpdateSettings(conf); + + // Test included, non IP6. + nc = nm.CreateIPCollection(settings.Split(","), false); + Assert.True(string.Equals(nc.ToString(), result2, System.StringComparison.OrdinalIgnoreCase)); + + // Test excluded, including IPv6. + nc = nm.CreateIPCollection(settings.Split(","), true); + Assert.True(string.Equals(nc.ToString(), result4, System.StringComparison.OrdinalIgnoreCase)); + + conf.EnableIPV6 = true; + nm.UpdateSettings(conf); + + // Test network addresses of collection. + nc = nm.CreateIPCollection(settings.Split(","), false); + nc = nc.AsNetworks(); + Assert.True(string.Equals(nc.ToString(), result5, System.StringComparison.OrdinalIgnoreCase)); + } + + [Theory] + [InlineData("127.0.0.1", "fd23:184f:2029:0:3139:7386:67d7:d517/64,fd23:184f:2029:0:c0f0:8a8a:7605:fffa/128,fe80::3139:7386:67d7:d517%16/64,192.168.1.208/24,::1/128,127.0.0.1/8", "[127.0.0.1/32]")] + [InlineData("127.0.0.1", "127.0.0.1/8", "[127.0.0.1/32]")] + public void UnionCheck(string settings, string compare, string result) + { + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true, + }; + + var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + + NetCollection nc1 = nm.CreateIPCollection(settings.Split(","), false); + NetCollection nc2 = nm.CreateIPCollection(compare.Split(","), false); + + Assert.True(nc1.Union(nc2).ToString() == result); + } + + [Theory] + [InlineData("192.168.5.85/24", "192.168.5.1")] + [InlineData("192.168.5.85/24", "192.168.5.254")] + [InlineData("10.128.240.50/30", "10.128.240.48")] + [InlineData("10.128.240.50/30", "10.128.240.49")] + [InlineData("10.128.240.50/30", "10.128.240.50")] + [InlineData("10.128.240.50/30", "10.128.240.51")] + [InlineData("127.0.0.1/8", "127.0.0.1")] + public void IpV4SubnetMaskMatchesValidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPNetAddress.Parse(netMask); + Assert.True(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); + } + + [Theory] + [InlineData("192.168.5.85/24", "192.168.4.254")] + [InlineData("192.168.5.85/24", "191.168.5.254")] + [InlineData("10.128.240.50/30", "10.128.240.47")] + [InlineData("10.128.240.50/30", "10.128.240.52")] + [InlineData("10.128.240.50/30", "10.128.239.50")] + [InlineData("10.128.240.50/30", "10.127.240.51")] + public void IpV4SubnetMaskDoesNotMatchInvalidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPNetAddress.Parse(netMask); + Assert.False(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); + } + + [Theory] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:0000:0000:0000:0000")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFFF")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:0001:0000:0000:0000")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFF0")] + [InlineData("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0000")] + public void IpV6SubnetMaskMatchesValidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPNetAddress.Parse(netMask); + Assert.True(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); + } + + [Theory] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0011:FFFF:FFFF:FFFF:FFFF")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0013:0000:0000:0000:0000")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0013:0001:0000:0000:0000")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0011:FFFF:FFFF:FFFF:FFF0")] + [InlineData("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0001")] + public void IpV6SubnetMaskDoesNotMatchInvalidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPNetAddress.Parse(netMask); + Assert.False(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); + } + + [Theory] + [InlineData("10.0.0.0/255.0.0.0", "10.10.10.1/32")] + [InlineData("10.0.0.0/8", "10.10.10.1/32")] + [InlineData("10.0.0.0/255.0.0.0", "10.10.10.1")] + + [InlineData("10.10.0.0/255.255.0.0", "10.10.10.1/32")] + [InlineData("10.10.0.0/16", "10.10.10.1/32")] + [InlineData("10.10.0.0/255.255.0.0", "10.10.10.1")] + + [InlineData("10.10.10.0/255.255.255.0", "10.10.10.1/32")] + [InlineData("10.10.10.0/24", "10.10.10.1/32")] + [InlineData("10.10.10.0/255.255.255.0", "10.10.10.1")] + + public void TestSubnets(string network, string ip) + { + Assert.True(TryParse(network, out IPObject? networkObj)); + Assert.True(TryParse(ip, out IPObject? ipObj)); + +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8604 // Possible null reference argument. + Assert.True(networkObj.Contains(ipObj)); +#pragma warning restore CS8604 // Possible null reference argument. +#pragma warning restore CS8602 // Dereference of a possibly null reference. + } + + [Theory] + [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "172.168.1.2/24", "172.168.1.2/24")] + [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "172.168.1.2/24, 10.10.10.1", "172.168.1.2/24,10.10.10.1/24")] + [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "192.168.1.2/255.255.255.0, 10.10.10.1", "192.168.1.2/24,10.10.10.1/24")] + [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "192.168.1.2/24, 100.10.10.1", "192.168.1.2/24")] + [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "194.168.1.2/24, 100.10.10.1", "")] + + public void TestMatches(string source, string dest, string result) + { + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true + }; + + var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + + // Test included, IP6. + NetCollection ncSource = nm.CreateIPCollection(source.Split(",")); + NetCollection ncDest = nm.CreateIPCollection(dest.Split(",")); + NetCollection ncResult = ncSource.Union(ncDest); + NetCollection resultCollection = nm.CreateIPCollection(result.Split(",")); + Assert.True(ncResult.Equals(resultCollection)); + } + + + [Theory] + [InlineData("10.1.1.1/32", "10.1.1.1")] + [InlineData("192.168.1.254/32", "192.168.1.254/255.255.255.255")] + + public void TestEquals(string source, string dest) + { + Assert.True(IPNetAddress.Parse(source).Equals(IPNetAddress.Parse(dest))); + Assert.True(IPNetAddress.Parse(dest).Equals(IPNetAddress.Parse(source))); + } + + [Theory] + + // Testing bind interfaces. These are set for my system so won't work elsewhere. + // On my system eth16 is internal, eth11 external (Windows defines the indexes). + // + // This test is to replicate how DNLA requests work throughout the system. + + // User on internal network, we're bound internal and external - so result is internal. + [InlineData("192.168.1.1", "eth16,eth11", false, "eth16")] + // User on external network, we're bound internal and external - so result is external. + [InlineData("8.8.8.8", "eth16,eth11", false, "eth11")] + // User on internal network, we're bound internal only - so result is internal. + [InlineData("10.10.10.10", "eth16", false, "eth16")] + // User on internal network, no binding specified - so result is the 1st internal. + [InlineData("192.168.1.1", "", false, "eth16")] + // User on external network, internal binding only - so result is the 1st internal. + [InlineData("jellyfin.org", "eth16", false, "eth16")] + // User on external network, no binding - so result is the 1st external. + [InlineData("jellyfin.org", "", false, "eth11")] + // User assumed to be internal, no binding - so result is the 1st internal. + [InlineData("", "", false, "eth16")] + public void TestBindInterfaces(string source, string bindAddresses, bool ipv6enabled, string result) + { + var conf = new NetworkConfiguration() + { + LocalNetworkAddresses = bindAddresses.Split(','), + EnableIPV6 = ipv6enabled, + EnableIPV4 = true + }; + + NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16:200.200.200.200/24,11,eth11"; + var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + NetworkManager.MockNetworkSettings = string.Empty; + + _ = nm.TryParseInterface(result, out NetCollection? resultObj); + + if (resultObj != null) + { + result = ((IPNetAddress)resultObj[0]).ToString(true); + var intf = nm.GetBindInterface(source, out int? _); + + Assert.True(string.Equals(intf, result, System.StringComparison.OrdinalIgnoreCase)); + } + } + + [Theory] + + // Testing bind interfaces. These are set for my system so won't work elsewhere. + // On my system eth16 is internal, eth11 external (Windows defines the indexes). + // + // This test is to replicate how subnet bound ServerPublisherUri work throughout the system. + + // User on internal network, we're bound internal and external - so result is internal override. + [InlineData("192.168.1.1", "192.168.1.0/24", "eth16,eth11", false, "192.168.1.0/24=internal.jellyfin", "internal.jellyfin")] + + // User on external network, we're bound internal and external - so result is override. + [InlineData("8.8.8.8", "192.168.1.0/24", "eth16,eth11", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")] + + // User on internal network, we're bound internal only, but the address isn't in the LAN - so return the override. + [InlineData("10.10.10.10", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://internalButNotDefinedAsLan.com", "http://internalButNotDefinedAsLan.com")] + + // User on internal network, no binding specified - so result is the 1st internal. + [InlineData("192.168.1.1", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")] + + // User on external network, internal binding only - so asumption is a proxy forward, return external override. + [InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")] + + // User on external network, no binding - so result is the 1st external which is overriden. + [InlineData("jellyfin.org", "192.168.1.0/24", "", false, "0.0.0.0 = http://helloworld.com", "http://helloworld.com")] + + // User assumed to be internal, no binding - so result is the 1st internal. + [InlineData("", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")] + + // User is internal, no binding - so result is the 1st internal, which is then overridden. + [InlineData("192.168.1.1", "192.168.1.0/24", "", false, "eth16=http://helloworld.com", "http://helloworld.com")] + + public void TestBindInterfaceOverrides(string source, string lan, string bindAddresses, bool ipv6enabled, string publishedServers, string result) + { + var conf = new NetworkConfiguration() + { + LocalNetworkSubnets = lan.Split(','), + LocalNetworkAddresses = bindAddresses.Split(','), + EnableIPV6 = ipv6enabled, + EnableIPV4 = true, + PublishedServerUriBySubnet = new string[] { publishedServers } + }; + + NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16:200.200.200.200/24,11,eth11"; + var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + NetworkManager.MockNetworkSettings = string.Empty; + + if (nm.TryParseInterface(result, out NetCollection? resultObj) && resultObj != null) + { + // Parse out IPAddresses so we can do a string comparison. (Ignore subnet masks). + result = ((IPNetAddress)resultObj[0]).ToString(true); + } + + var intf = nm.GetBindInterface(source, out int? _); + + Assert.True(string.Equals(intf, result, System.StringComparison.OrdinalIgnoreCase)); + } + } +} -- cgit v1.2.3 From f06e4826c764c1214478a741b0f93315a8bba76b Mon Sep 17 00:00:00 2001 From: Greenback Date: Sat, 31 Oct 2020 19:16:28 +0000 Subject: Fixed testing units --- Jellyfin.Networking/Manager/NetworkManager.cs | 55 ++++++++++---------- MediaBrowser.Common/Net/NetworkExtensions.cs | 60 ++++++++++++++-------- .../NetworkTesting/UnitTesting.cs | 16 +++--- 3 files changed, 77 insertions(+), 54 deletions(-) (limited to 'MediaBrowser.Common/Net') diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 76ac02d791..2ec2a0ba52 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -158,12 +158,15 @@ namespace Jellyfin.Networking.Manager /// /// Items to assign the collection, or null. /// The collection created. - public static NetCollection CreateCollection(IEnumerable? source) + public static NetCollection CreateCollection(IEnumerable? source = null) { var result = new NetCollection(); if (source != null) { - return result.AddRange(source); + foreach (var item in source) + { + result.AddItem(item); + } } return result; @@ -433,7 +436,7 @@ namespace Jellyfin.Networking.Manager if (_bindExclusions.Count > 0) { // Return all the internal interfaces except the ones excluded. - return CreateCollection(_internalInterfaces.Where(p => !_bindExclusions.Contains(p))); + return CreateCollection(_internalInterfaces.Where(p => !_bindExclusions.ContainsAddress(p))); } // No bind address, so return all internal interfaces. @@ -463,7 +466,7 @@ namespace Jellyfin.Networking.Manager } // As private addresses can be redefined by Configuration.LocalNetworkAddresses - return _lanSubnets.Contains(address) && !_excludedSubnets.Contains(address); + return _lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address); } /// @@ -471,7 +474,7 @@ namespace Jellyfin.Networking.Manager { if (IPHost.TryParse(address, out IPHost ep)) { - return _lanSubnets.Contains(ep) && !_excludedSubnets.Contains(ep); + return _lanSubnets.ContainsAddress(ep) && !_excludedSubnets.ContainsAddress(ep); } return false; @@ -559,7 +562,7 @@ namespace Jellyfin.Networking.Manager ((IsIP4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork) || (IsIP6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6))) { - result.Add(iface); + result.AddItem(iface); } } @@ -603,7 +606,7 @@ namespace Jellyfin.Networking.Manager var address = IPNetAddress.Parse(parts[0]); var index = int.Parse(parts[1], CultureInfo.InvariantCulture); address.Tag = index; - _interfaceAddresses.Add(address); + _interfaceAddresses.AddItem(address); _interfaceNames.Add(parts[2], Math.Abs(index)); } } @@ -747,7 +750,7 @@ namespace Jellyfin.Networking.Manager ((IsIP4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork) || (IsIP6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6))) { - col.Add(iface); + col.AddItem(iface); } } } @@ -759,7 +762,7 @@ namespace Jellyfin.Networking.Manager obj.Remove(AddressFamily.InterNetworkV6); if (!obj.IsIP6()) { - col.Add(obj); + col.AddItem(obj); } } else if (!IsIP4Enabled) @@ -768,12 +771,12 @@ namespace Jellyfin.Networking.Manager obj.Remove(AddressFamily.InterNetwork); if (obj.IsIP6()) { - col.Add(obj); + col.AddItem(obj); } } else { - col.Add(obj); + col.AddItem(obj); } } else @@ -956,7 +959,7 @@ namespace Jellyfin.Networking.Manager { _logger.LogDebug("Using LAN interface addresses as user provided no LAN details."); // Internal interfaces must be private and not excluded. - _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsPrivateAddressRange(i) && !_excludedSubnets.Contains(i))); + _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsPrivateAddressRange(i) && !_excludedSubnets.ContainsAddress(i))); // Subnets are the same as the calculated internal interface. _lanSubnets = new NetCollection(); @@ -964,17 +967,17 @@ namespace Jellyfin.Networking.Manager // We must listen on loopback for LiveTV to function regardless of the settings. if (IsIP6Enabled) { - _lanSubnets.Add(IPNetAddress.IP6Loopback); - _lanSubnets.Add(IPNetAddress.Parse("fc00::/7")); // ULA - _lanSubnets.Add(IPNetAddress.Parse("fe80::/10")); // Site local + _lanSubnets.AddItem(IPNetAddress.IP6Loopback); + _lanSubnets.AddItem(IPNetAddress.Parse("fc00::/7")); // ULA + _lanSubnets.AddItem(IPNetAddress.Parse("fe80::/10")); // Site local } if (IsIP4Enabled) { - _lanSubnets.Add(IPNetAddress.IP4Loopback); - _lanSubnets.Add(IPNetAddress.Parse("10.0.0.0/8")); - _lanSubnets.Add(IPNetAddress.Parse("172.16.0.0/12")); - _lanSubnets.Add(IPNetAddress.Parse("192.168.0.0/16")); + _lanSubnets.AddItem(IPNetAddress.IP4Loopback); + _lanSubnets.AddItem(IPNetAddress.Parse("10.0.0.0/8")); + _lanSubnets.AddItem(IPNetAddress.Parse("172.16.0.0/12")); + _lanSubnets.AddItem(IPNetAddress.Parse("192.168.0.0/16")); } } else @@ -982,16 +985,16 @@ namespace Jellyfin.Networking.Manager // We must listen on loopback for LiveTV to function regardless of the settings. if (IsIP6Enabled) { - _lanSubnets.Add(IPNetAddress.IP6Loopback); + _lanSubnets.AddItem(IPNetAddress.IP6Loopback); } if (IsIP4Enabled) { - _lanSubnets.Add(IPNetAddress.IP4Loopback); + _lanSubnets.AddItem(IPNetAddress.IP4Loopback); } // Internal interfaces must be private, not excluded and part of the LocalNetworkSubnet. - _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsInLocalNetwork(i) && !_excludedSubnets.Contains(i) && _lanSubnets.Contains(i))); + _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsInLocalNetwork(i))); } _logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets); @@ -1049,7 +1052,7 @@ namespace Jellyfin.Networking.Manager nw.Tag *= -1; } - _interfaceAddresses.Add(nw); + _interfaceAddresses.AddItem(nw); // Store interface name so we can use the name in Collections. _interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag; @@ -1070,7 +1073,7 @@ namespace Jellyfin.Networking.Manager nw.Tag *= -1; } - _interfaceAddresses.Add(nw); + _interfaceAddresses.AddItem(nw); // Store interface name so we can use the name in Collections. _interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag; @@ -1104,10 +1107,10 @@ namespace Jellyfin.Networking.Manager { _logger.LogError("No interfaces information available. Resolving DNS name."); // Last ditch attempt - use loopback address. - _interfaceAddresses.Add(IPNetAddress.IP4Loopback); + _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback); if (IsIP6Enabled) { - _interfaceAddresses.Add(IPNetAddress.IP6Loopback); + _interfaceAddresses.AddItem(IPNetAddress.IP6Loopback); } } } diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs index 6e9cb46dc1..39499460ea 100644 --- a/MediaBrowser.Common/Net/NetworkExtensions.cs +++ b/MediaBrowser.Common/Net/NetworkExtensions.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Net; using System.Runtime.CompilerServices; +using System.Text; using NetCollection = System.Collections.ObjectModel.Collection; namespace MediaBrowser.Common.Net @@ -26,22 +27,6 @@ namespace MediaBrowser.Common.Net } } - /// - /// Add multiple items to the collection. - /// - /// The . - /// Item to add. - /// Return the collection. - public static NetCollection AddRange(this NetCollection destination, IEnumerable source) - { - foreach (var item in source) - { - destination.Add(item); - } - - return destination; - } - /// /// Adds a network to the collection. /// @@ -62,6 +47,7 @@ namespace MediaBrowser.Common.Net /// Returns a string representation of this object. public static string Readable(this NetCollection source) { + var sb = new StringBuilder(); string output = "["; if (source.Count > 0) { @@ -141,6 +127,40 @@ namespace MediaBrowser.Common.Net return false; } + /// + /// Compares two NetCollection objects. The order is ignored. + /// + /// The . + /// Item to compare to. + /// True if both are equal. + public static bool Compare(this NetCollection source, NetCollection dest) + { + if (dest == null || source.Count != dest.Count) + { + return false; + } + + foreach (var sourceItem in source) + { + bool found = false; + foreach (var destItem in dest) + { + if (sourceItem.Equals(destItem)) + { + found = true; + break; + } + } + + if (!found) + { + return false; + } + } + + return true; + } + /// /// Returns a collection containing the subnets of this collection given. /// @@ -162,7 +182,7 @@ namespace MediaBrowser.Common.Net // Add the subnet calculated from the interface address/mask. var na = nw.NetworkAddress; na.Tag = i.Tag; - res.Add(na); + res.AddItem(na); } else { @@ -174,7 +194,7 @@ namespace MediaBrowser.Common.Net Tag = i.Tag }; - res.Add(host); + res.AddItem(host); } } } @@ -213,7 +233,7 @@ namespace MediaBrowser.Common.Net if (!found) { - results.Add(outer); + results.AddItem(outer); } } @@ -244,7 +264,7 @@ namespace MediaBrowser.Common.Net { if (target.ContainsAddress(i)) { - nc.Add(i); + nc.AddItem(i); } } diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs b/tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs index 9e7e8d3ac3..fe64d1cfce 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs @@ -71,7 +71,7 @@ namespace NetworkTesting var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); NetworkManager.MockNetworkSettings = string.Empty; - Assert.True(string.Equals(nm.GetInternalBindAddresses().ToString(), value, StringComparison.Ordinal)); + Assert.True(string.Equals(nm.GetInternalBindAddresses().Readable(), value, StringComparison.Ordinal)); } [Theory] @@ -163,22 +163,22 @@ namespace NetworkTesting // Test included, IP6. NetCollection nc = nm.CreateIPCollection(settings.Split(","), false); - Assert.True(string.Equals(nc.ToString(), result1, System.StringComparison.OrdinalIgnoreCase)); + Assert.True(string.Equals(nc.Readable(), result1, System.StringComparison.OrdinalIgnoreCase)); // Text excluded, non IP6. nc = nm.CreateIPCollection(settings.Split(","), true); - Assert.True(string.Equals(nc?.ToString(), result3, System.StringComparison.OrdinalIgnoreCase)); + Assert.True(string.Equals(nc?.Readable(), result3, System.StringComparison.OrdinalIgnoreCase)); conf.EnableIPV6 = false; nm.UpdateSettings(conf); // Test included, non IP6. nc = nm.CreateIPCollection(settings.Split(","), false); - Assert.True(string.Equals(nc.ToString(), result2, System.StringComparison.OrdinalIgnoreCase)); + Assert.True(string.Equals(nc.Readable(), result2, System.StringComparison.OrdinalIgnoreCase)); // Test excluded, including IPv6. nc = nm.CreateIPCollection(settings.Split(","), true); - Assert.True(string.Equals(nc.ToString(), result4, System.StringComparison.OrdinalIgnoreCase)); + Assert.True(string.Equals(nc.Readable(), result4, System.StringComparison.OrdinalIgnoreCase)); conf.EnableIPV6 = true; nm.UpdateSettings(conf); @@ -186,7 +186,7 @@ namespace NetworkTesting // Test network addresses of collection. nc = nm.CreateIPCollection(settings.Split(","), false); nc = nc.AsNetworks(); - Assert.True(string.Equals(nc.ToString(), result5, System.StringComparison.OrdinalIgnoreCase)); + Assert.True(string.Equals(nc.Readable(), result5, System.StringComparison.OrdinalIgnoreCase)); } [Theory] @@ -205,7 +205,7 @@ namespace NetworkTesting NetCollection nc1 = nm.CreateIPCollection(settings.Split(","), false); NetCollection nc2 = nm.CreateIPCollection(compare.Split(","), false); - Assert.True(nc1.Union(nc2).ToString() == result); + Assert.True(nc1.Union(nc2).Readable() == result); } [Theory] @@ -306,7 +306,7 @@ namespace NetworkTesting NetCollection ncDest = nm.CreateIPCollection(dest.Split(",")); NetCollection ncResult = ncSource.Union(ncDest); NetCollection resultCollection = nm.CreateIPCollection(result.Split(",")); - Assert.True(ncResult.Equals(resultCollection)); + Assert.True(ncResult.Compare(resultCollection)); } -- cgit v1.2.3 From 3e62557959b1233311ab5d4e8f578f84438580d9 Mon Sep 17 00:00:00 2001 From: Greenback Date: Wed, 4 Nov 2020 20:29:55 +0000 Subject: amended testing unit. --- Jellyfin.Networking/Manager/NetworkManager.cs | 14 ++++++++------ MediaBrowser.Common/Net/NetworkExtensions.cs | 2 +- .../NetworkTesting/UnitTesting.cs | 14 +++++++------- 3 files changed, 16 insertions(+), 14 deletions(-) (limited to 'MediaBrowser.Common/Net') diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 2ec2a0ba52..289b1dc720 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -19,6 +19,8 @@ namespace Jellyfin.Networking.Manager { /// /// Class to take care of network interface management. + /// + /// Note: The normal collection methods and properties will not work with NetCollection. . /// public class NetworkManager : INetworkManager, IDisposable { @@ -925,8 +927,8 @@ namespace Jellyfin.Networking.Manager // Read and parse bind addresses and exclusions, removing ones that don't exist. _bindAddresses = CreateIPCollection(lanAddresses).Union(_interfaceAddresses); _bindExclusions = CreateIPCollection(lanAddresses, true).Union(_interfaceAddresses); - _logger.LogInformation("Using bind addresses: {0}", _bindAddresses); - _logger.LogInformation("Using bind exclusions: {0}", _bindExclusions); + _logger.LogInformation("Using bind addresses: {0}", _bindAddresses.AsString()); + _logger.LogInformation("Using bind exclusions: {0}", _bindExclusions.AsString()); } private void InitialiseRemote(NetworkConfiguration config) @@ -997,9 +999,9 @@ namespace Jellyfin.Networking.Manager _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsInLocalNetwork(i))); } - _logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets); - _logger.LogInformation("Defined LAN exclusions : {0}", _excludedSubnets); - _logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Exclude(_excludedSubnets).AsNetworks()); + _logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets.AsString()); + _logger.LogInformation("Defined LAN exclusions : {0}", _excludedSubnets.AsString()); + _logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Exclude(_excludedSubnets).AsNetworks().AsString()); } } @@ -1090,7 +1092,7 @@ namespace Jellyfin.Networking.Manager } _logger.LogDebug("Discovered {0} interfaces.", _interfaceAddresses.Count); - _logger.LogDebug("Interfaces addresses : {0}", _interfaceAddresses); + _logger.LogDebug("Interfaces addresses : {0}", _interfaceAddresses.AsString()); // If for some reason we don't have an interface info, resolve our DNS name. if (_interfaceAddresses.Count == 0) diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs index 39499460ea..e801de5eb8 100644 --- a/MediaBrowser.Common/Net/NetworkExtensions.cs +++ b/MediaBrowser.Common/Net/NetworkExtensions.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.Common.Net /// /// The . /// Returns a string representation of this object. - public static string Readable(this NetCollection source) + public static string AsString(this NetCollection source) { var sb = new StringBuilder(); string output = "["; diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs b/tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs index fe64d1cfce..c96c7defd7 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/UnitTesting.cs @@ -71,7 +71,7 @@ namespace NetworkTesting var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); NetworkManager.MockNetworkSettings = string.Empty; - Assert.True(string.Equals(nm.GetInternalBindAddresses().Readable(), value, StringComparison.Ordinal)); + Assert.True(string.Equals(nm.GetInternalBindAddresses().AsString(), value, StringComparison.Ordinal)); } [Theory] @@ -163,22 +163,22 @@ namespace NetworkTesting // Test included, IP6. NetCollection nc = nm.CreateIPCollection(settings.Split(","), false); - Assert.True(string.Equals(nc.Readable(), result1, System.StringComparison.OrdinalIgnoreCase)); + Assert.True(string.Equals(nc?.AsString(), result1, System.StringComparison.OrdinalIgnoreCase)); // Text excluded, non IP6. nc = nm.CreateIPCollection(settings.Split(","), true); - Assert.True(string.Equals(nc?.Readable(), result3, System.StringComparison.OrdinalIgnoreCase)); + Assert.True(string.Equals(nc?.AsString(), result3, System.StringComparison.OrdinalIgnoreCase)); conf.EnableIPV6 = false; nm.UpdateSettings(conf); // Test included, non IP6. nc = nm.CreateIPCollection(settings.Split(","), false); - Assert.True(string.Equals(nc.Readable(), result2, System.StringComparison.OrdinalIgnoreCase)); + Assert.True(string.Equals(nc?.AsString(), result2, System.StringComparison.OrdinalIgnoreCase)); // Test excluded, including IPv6. nc = nm.CreateIPCollection(settings.Split(","), true); - Assert.True(string.Equals(nc.Readable(), result4, System.StringComparison.OrdinalIgnoreCase)); + Assert.True(string.Equals(nc?.AsString(), result4, System.StringComparison.OrdinalIgnoreCase)); conf.EnableIPV6 = true; nm.UpdateSettings(conf); @@ -186,7 +186,7 @@ namespace NetworkTesting // Test network addresses of collection. nc = nm.CreateIPCollection(settings.Split(","), false); nc = nc.AsNetworks(); - Assert.True(string.Equals(nc.Readable(), result5, System.StringComparison.OrdinalIgnoreCase)); + Assert.True(string.Equals(nc?.AsString(), result5, System.StringComparison.OrdinalIgnoreCase)); } [Theory] @@ -205,7 +205,7 @@ namespace NetworkTesting NetCollection nc1 = nm.CreateIPCollection(settings.Split(","), false); NetCollection nc2 = nm.CreateIPCollection(compare.Split(","), false); - Assert.True(nc1.Union(nc2).Readable() == result); + Assert.True(nc1.Union(nc2).AsString() == result); } [Theory] -- cgit v1.2.3 From 9481fd07476a99c30a0db99f4c6171e2fbc84a8f Mon Sep 17 00:00:00 2001 From: Greenback Date: Mon, 16 Nov 2020 17:17:49 +0000 Subject: Upgraded to .Net5 --- MediaBrowser.Common/Configuration/IConfigurationManager.cs | 2 ++ MediaBrowser.Common/Net/IPNetAddress.cs | 4 ++-- MediaBrowser.Common/Net/IPObject.cs | 2 +- MediaBrowser.Controller/IServerApplicationHost.cs | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Common/Net') diff --git a/MediaBrowser.Common/Configuration/IConfigurationManager.cs b/MediaBrowser.Common/Configuration/IConfigurationManager.cs index 4c2b3a742a..790121274e 100644 --- a/MediaBrowser.Common/Configuration/IConfigurationManager.cs +++ b/MediaBrowser.Common/Configuration/IConfigurationManager.cs @@ -56,6 +56,8 @@ namespace MediaBrowser.Common.Configuration /// /// Gets the configuration. /// + /// /// The key. + /// System.Object. object GetConfiguration(string key); /// diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs index bcd049f3d5..0d28c35cb8 100644 --- a/MediaBrowser.Common/Net/IPNetAddress.cs +++ b/MediaBrowser.Common/Net/IPNetAddress.cs @@ -106,7 +106,7 @@ namespace MediaBrowser.Common.Net addr = addr.Trim(); // Try to parse it as is. - if (IPAddress.TryParse(addr, out IPAddress res)) + if (IPAddress.TryParse(addr, out IPAddress? res)) { ip = new IPNetAddress(res); return true; @@ -130,7 +130,7 @@ namespace MediaBrowser.Common.Net } // Is the subnet in x.y.a.b form? - if (IPAddress.TryParse(tokens[1], out IPAddress mask)) + if (IPAddress.TryParse(tokens[1], out IPAddress? mask)) { ip = new IPNetAddress(res, MaskToCidr(mask)); return true; diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs index a08694c266..36f3357cce 100644 --- a/MediaBrowser.Common/Net/IPObject.cs +++ b/MediaBrowser.Common/Net/IPObject.cs @@ -381,7 +381,7 @@ namespace MediaBrowser.Common.Net } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return Equals(obj as IPObject); } diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index eced9bf697..2456da826f 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller { -- cgit v1.2.3 From 978aa38f3bd19a55ed4f3587bf5fd7d583c317f5 Mon Sep 17 00:00:00 2001 From: Greenback Date: Mon, 16 Nov 2020 19:37:38 +0000 Subject: Updated PR1 code. --- .../Configuration/NetworkConfiguration.cs | 50 ++++---- Jellyfin.Networking/Manager/NetworkManager.cs | 141 +++++++++++---------- MediaBrowser.Common/Net/INetworkManager.cs | 38 +++--- MediaBrowser.Common/Net/IPHost.cs | 33 +++-- MediaBrowser.Common/Net/IPObject.cs | 56 ++++---- MediaBrowser.Common/Net/NetworkExtensions.cs | 70 +++++----- 6 files changed, 188 insertions(+), 200 deletions(-) (limited to 'MediaBrowser.Common/Net') diff --git a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs index 301e432511..e710eb3c76 100644 --- a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs +++ b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs @@ -11,12 +11,12 @@ namespace Jellyfin.Networking.Configuration public class NetworkConfiguration { /// - /// Gets the default http port. + /// The default value for . /// public const int DefaultHttpPort = 8096; /// - /// Gets the default https port. + /// The default value for and . /// public const int DefaultHttpsPort = 8920; @@ -94,124 +94,124 @@ namespace Jellyfin.Networking.Configuration public int PublicPort { get; set; } = DefaultHttpPort; /// - /// Gets or sets a value indicating whether the http port should be mapped as part of UPnP automatic port forwarding.. + /// Gets or sets a value indicating whether the http port should be mapped as part of UPnP automatic port forwarding. /// public bool UPnPCreateHttpPortMap { get; set; } /// /// Gets or sets the UDPPortRange - /// Gets or sets client udp port range.. + /// Gets or sets client udp port range. /// public string UDPPortRange { get; set; } = string.Empty; /// - /// Gets or sets a value indicating whether gets or sets IPV6 capability.. + /// Gets or sets a value indicating whether gets or sets IPV6 capability. /// public bool EnableIPV6 { get; set; } /// - /// Gets or sets a value indicating whether gets or sets IPV4 capability.. + /// Gets or sets a value indicating whether gets or sets IPV4 capability. /// public bool EnableIPV4 { get; set; } = true; /// /// Gets or sets a value indicating whether detailed ssdp logs are sent to the console/log. - /// "Emby.Dlna": "Debug" must be set in logging.default.json for this property to work.. + /// "Emby.Dlna": "Debug" must be set in logging.default.json for this property to work. /// public bool EnableSSDPTracing { get; set; } /// /// Gets or sets the SSDPTracingFilter /// Gets or sets a value indicating whether an IP address is to be used to filter the detailed ssdp logs that are being sent to the console/log. - /// If the setting "Emby.Dlna": "Debug" msut be set in logging.default.json for this property to work.. + /// If the setting "Emby.Dlna": "Debug" msut be set in logging.default.json for this property to work. /// public string SSDPTracingFilter { get; set; } = string.Empty; /// - /// Gets or sets the number of times SSDP UDP messages are sent.. + /// Gets or sets the number of times SSDP UDP messages are sent. /// public int UDPSendCount { get; set; } = 2; /// - /// Gets or sets the delay between each groups of SSDP messages (in ms).. + /// Gets or sets the delay between each groups of SSDP messages (in ms). /// public int UDPSendDelay { get; set; } = 100; /// - /// Gets or sets a value indicating whether address names that match should be Ignore for the purposes of binding.. + /// Gets or sets a value indicating whether address names that match should be Ignore for the purposes of binding. /// public bool IgnoreVirtualInterfaces { get; set; } = true; /// /// Gets or sets the VirtualInterfaceNames - /// Gets or sets a value indicating the interfaces that should be ignored. The list can be comma separated. .. + /// Gets or sets a value indicating the interfaces that should be ignored. The list can be comma separated. . /// public string VirtualInterfaceNames { get; set; } = "vEthernet*"; /// - /// Gets or sets the time (in seconds) between the pings of SSDP gateway monitor.. + /// Gets or sets the time (in seconds) between the pings of SSDP gateway monitor. /// public int GatewayMonitorPeriod { get; set; } = 60; /// - /// Gets a value indicating whether multi-socket binding is available.. + /// Gets a value indicating whether multi-socket binding is available. /// public bool EnableMultiSocketBinding { get; } = true; /// /// Gets or sets a value indicating whether all IPv6 interfaces should be treated as on the internal network. - /// Depending on the address range implemented ULA ranges might not be used.. + /// Depending on the address range implemented ULA ranges might not be used. /// public bool TrustAllIP6Interfaces { get; set; } /// - /// Gets or sets the ports that HDHomerun uses.. + /// Gets or sets the ports that HDHomerun uses. /// public string HDHomerunPortRange { get; set; } = string.Empty; /// /// Gets or sets the PublishedServerUriBySubnet - /// Gets or sets PublishedServerUri to advertise for specific subnets.. + /// Gets or sets PublishedServerUri to advertise for specific subnets. /// public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty(); /// - /// Gets or sets a value indicating whether Autodiscovery tracing is enabled.. + /// Gets or sets a value indicating whether Autodiscovery tracing is enabled. /// public bool AutoDiscoveryTracing { get; set; } /// - /// Gets or sets a value indicating whether Autodiscovery is enabled.. + /// Gets or sets a value indicating whether Autodiscovery is enabled. /// public bool AutoDiscovery { get; set; } = true; /// - /// Gets or sets the filter for remote IP connectivity. Used in conjuntion with .. + /// Gets or sets the filter for remote IP connectivity. Used in conjuntion with . /// public string[] RemoteIPFilter { get; set; } = Array.Empty(); /// - /// Gets or sets a value indicating whether contains a blacklist or a whitelist. Default is a whitelist.. + /// Gets or sets a value indicating whether contains a blacklist or a whitelist. Default is a whitelist. /// public bool IsRemoteIPFilterBlacklist { get; set; } /// - /// Gets or sets a value indicating whether to enable automatic port forwarding.. + /// Gets or sets a value indicating whether to enable automatic port forwarding. /// public bool EnableUPnP { get; set; } /// - /// Gets or sets a value indicating whether access outside of the LAN is permitted.. + /// Gets or sets a value indicating whether access outside of the LAN is permitted. /// public bool EnableRemoteAccess { get; set; } = true; /// - /// Gets or sets the subnets that are deemed to make up the LAN.. + /// Gets or sets the subnets that are deemed to make up the LAN. /// public string[] LocalNetworkSubnets { get; set; } = Array.Empty(); /// - /// Gets or sets the interface addresses which Jellyfin will bind to. If empty, all interfaces will be used.. + /// Gets or sets the interface addresses which Jellyfin will bind to. If empty, all interfaces will be used. /// public string[] LocalNetworkAddresses { get; set; } = Array.Empty(); diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 289b1dc720..00711f1620 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -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; namespace Jellyfin.Networking.Manager { /// /// Class to take care of network interface management. - /// - /// Note: The normal collection methods and properties will not work with NetCollection. . + /// Note: The normal collection methods and properties will not work with Collection{IPObject}. . /// public class NetworkManager : INetworkManager, IDisposable { @@ -37,7 +34,7 @@ namespace Jellyfin.Networking.Manager /// /// List of all interface addresses and masks. /// - private readonly NetCollection _interfaceAddresses; + private readonly Collection _interfaceAddresses; /// /// List of all interface MAC addresses. @@ -61,30 +58,30 @@ namespace Jellyfin.Networking.Manager private bool _eventfire; /// - /// Unfiltered user defined LAN subnets. (Configuration.LocalNetworkSubnets). + /// Unfiltered user defined LAN subnets. () /// or internal interface network subnets if undefined by user. /// - private NetCollection _lanSubnets; + private Collection _lanSubnets; /// /// User defined list of subnets to excluded from the LAN. /// - private NetCollection _excludedSubnets; + private Collection _excludedSubnets; /// /// List of interface addresses to bind the WS. /// - private NetCollection _bindAddresses; + private Collection _bindAddresses; /// /// List of interface addresses to exclude from bind. /// - private NetCollection _bindExclusions; + private Collection _bindExclusions; /// /// Caches list of all internal filtered interface addresses and masks. /// - private NetCollection _internalInterfaces; + private Collection _internalInterfaces; /// /// 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 NetCollection(); + _interfaceAddresses = new Collection(); _macAddresses = new List(); _interfaceNames = new Dictionary(); _publishedServerUrls = new Dictionary(); @@ -143,7 +140,7 @@ namespace Jellyfin.Networking.Manager public bool IsIP4Enabled { get; set; } /// - public NetCollection RemoteAddressFilter { get; private set; } + public Collection RemoteAddressFilter { get; private set; } /// /// Gets a value indicating whether is all IPv6 interfaces are trusted as internal. @@ -160,9 +157,9 @@ namespace Jellyfin.Networking.Manager /// /// Items to assign the collection, or null. /// The collection created. - public static NetCollection CreateCollection(IEnumerable? source = null) + public static Collection CreateCollection(IEnumerable? source = null) { - var result = new NetCollection(); + var result = new Collection(); if (source != null) { foreach (var item in source) @@ -189,22 +186,22 @@ namespace Jellyfin.Networking.Manager } /// - 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); } /// - 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); + } + + /// + public Collection GetLoopbacks() + { + Collection nc = new Collection(); if (IsIP4Enabled) { nc.AddItem(IPAddress.Loopback); @@ -231,9 +228,9 @@ namespace Jellyfin.Networking.Manager } /// - public NetCollection CreateIPCollection(string[] values, bool bracketed = false) + public Collection CreateIPCollection(string[] values, bool bracketed = false) { - NetCollection col = new NetCollection(); + Collection col = new Collection(); 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.LogInformation("Ignoring LAN value {value}. Reason : {reason}", v, e.Message); + _logger.LogWarning(e, "Ignoring LAN value {value}.", v); } } @@ -274,7 +271,7 @@ namespace Jellyfin.Networking.Manager } /// - public NetCollection GetAllBindInterfaces(bool individualInterfaces = false) + public Collection GetAllBindInterfaces(bool individualInterfaces = false) { int count = _bindAddresses.Count; @@ -288,11 +285,11 @@ namespace Jellyfin.Networking.Manager if (individualInterfaces) { - return new NetCollection(_interfaceAddresses); + return new Collection(_interfaceAddresses); } // No bind address and no exclusions, so listen on all interfaces. - NetCollection result = new NetCollection(); + Collection result = new Collection(); 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 } /// - public NetCollection GetInternalBindAddresses() + public Collection GetInternalBindAddresses() { int count = _bindAddresses.Count; @@ -445,7 +442,7 @@ namespace Jellyfin.Networking.Manager return CreateCollection(_internalInterfaces.Where(p => !p.IsLoopback())); } - return new NetCollection(_bindAddresses); + return new Collection(_bindAddresses); } /// @@ -526,7 +523,7 @@ namespace Jellyfin.Networking.Manager } /// - public NetCollection GetFilteredLANSubnets(NetCollection? filter = null) + public Collection GetFilteredLANSubnets(Collection? filter = null) { if (filter == null) { @@ -543,7 +540,7 @@ namespace Jellyfin.Networking.Manager } /// - public bool TryParseInterface(string token, out NetCollection? result) + public bool TryParseInterface(string token, out Collection? 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 NetCollection(); + result = new Collection(); _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 /// /// Reloads all settings and re-initialises the instance. /// - /// The configuration to use. - public void UpdateSettings(object configuration) + /// The to use. + public void UpdateSettings(NetworkConfiguration configuration) { NetworkConfiguration config = (NetworkConfiguration)configuration ?? throw new ArgumentNullException(nameof(configuration)); @@ -622,7 +619,7 @@ namespace Jellyfin.Networking.Manager /// /// Protected implementation of Dispose pattern. /// - /// True to dispose the managed state. + /// True to dispose the managed state. protected virtual void Dispose(bool disposing) { if (!_disposed) @@ -639,11 +636,11 @@ namespace Jellyfin.Networking.Manager } /// - /// Trys to identify the string and return an object of that class. + /// Tries to identify the string and return an object of that class. /// /// String to parse. /// IPObject to return. - /// True if the value parsed successfully. + /// true if the value parsed successfully, false otherwise. 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. /// /// Address to convert. - /// URI save conversion of the address. + /// URI safe conversion of the address. 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 /// /// String to check. /// Interface index number. - /// True if an interface name matches the token. + /// true if an interface name matches the token, False otherwise. 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 } /// - /// Parses strings into the collection, replacing any interface references. + /// Parses a string and adds it into the the collection, replacing any interface references. /// - /// Collection. - /// String to parse. - private void AddToCollection(NetCollection col, string token) + /// Collection. + /// String value to parse. + private void AddToCollection(Collection 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. /// /// Sender. - /// Network availability information. + /// A containing network availability information. 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. /// /// Sender. - /// Event arguments. + /// An . private void OnNetworkAddressChanged(object? sender, EventArgs e) { _logger.LogDebug("Network address change detected."); @@ -812,7 +809,7 @@ namespace Jellyfin.Networking.Manager /// /// Async task that waits for 2 seconds before re-initialising the settings, as typically these events fire multiple times in succession. /// - /// The network change async. + /// A representing the asynchronous operation. 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 NetCollection? addresses) && addresses != null) + else if (TryParseInterface(parts[0], out Collection? addresses) && addresses != null) { foreach (IPNetAddress na in addresses) { @@ -903,6 +900,9 @@ namespace Jellyfin.Networking.Manager } } + /// + /// Initialises the network bind addresses. + /// 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()); } + /// + /// Initialises the remote address values. + /// 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 NetCollection(); + _lanSubnets = new Collection(); // We must listen on loopback for LiveTV to function regardless of the settings. if (IsIP6Enabled) @@ -1131,7 +1134,7 @@ namespace Jellyfin.Networking.Manager /// True if the source is in the external subnet. /// The published server url that matches the source address. /// The resultant port, if one exists. - /// True if a match is found. + /// true if a match is found, false otherwise. private bool MatchesPublishedServerUrl(IPObject source, bool isExternal, out string bindPreference, out int? port) { bindPreference = string.Empty; @@ -1185,7 +1188,7 @@ namespace Jellyfin.Networking.Manager /// IP source address to use. /// True if the source is in the external subnet. /// The result, if a match is found. - /// True if a match is found. + /// true if a match is found, false otherwise. 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. - NetCollection bindResult; + Collection 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 /// /// IP source address to use. /// The result, if a match is found. - /// True if a match is found. + /// true if a match is found, false otherwise. 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; } diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index a7beabbdcb..43562afe38 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -1,10 +1,11 @@ #nullable enable using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Net; using System.Net.NetworkInformation; +using MediaBrowser.Common.Net; using Microsoft.AspNetCore.Http; -using NetCollection = System.Collections.ObjectModel.Collection; namespace MediaBrowser.Common.Net { @@ -31,7 +32,7 @@ namespace MediaBrowser.Common.Net /// /// Gets the remote address filter. /// - NetCollection RemoteAddressFilter { get; } + Collection RemoteAddressFilter { get; } /// /// Gets or sets a value indicating whether iP6 is enabled. @@ -46,17 +47,17 @@ namespace MediaBrowser.Common.Net /// /// Calculates the list of interfaces to use for Kestrel. /// - /// A NetCollection object containing all the interfaces to bind. + /// A Collection{IPObject} object containing all the interfaces to bind. /// If all the interfaces are specified, and none are excluded, it returns zero items /// to represent any address. /// When false, return or for all interfaces. - NetCollection GetAllBindInterfaces(bool individualInterfaces = false); + Collection GetAllBindInterfaces(bool individualInterfaces = false); /// /// Returns a collection containing the loopback interfaces. /// - /// Netcollection. - NetCollection GetLoopbacks(); + /// Collection{IPObject}. + Collection GetLoopbacks(); /// /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo) @@ -137,7 +138,14 @@ namespace MediaBrowser.Common.Net /// /// IP to check. Can be an IPAddress or an IPObject. /// Result of the check. - bool IsGatewayInterface(object? addressObj); + bool IsGatewayInterface(IPObject? addressObj); + + /// + /// Checks to see if the IP Address provided matches an interface that has a gateway. + /// + /// IP to check. Can be an IPAddress or an IPObject. + /// Result of the check. + bool IsGatewayInterface(IPAddress? addressObj); /// /// Returns true if the address is a private address. @@ -178,21 +186,21 @@ namespace MediaBrowser.Common.Net /// Token to parse. /// Resultant object's ip addresses, if successful. /// Success of the operation. - bool TryParseInterface(string token, out NetCollection? result); + bool TryParseInterface(string token, out Collection? result); /// - /// Parses an array of strings into a NetCollection. + /// Parses an array of strings into a Collection{IPObject}. /// /// Values to parse. /// When true, only include values in []. When false, ignore bracketed values. /// IPCollection object containing the value strings. - NetCollection CreateIPCollection(string[] values, bool bracketed = false); + Collection CreateIPCollection(string[] values, bool bracketed = false); /// /// Returns all the internal Bind interface addresses. /// /// An internal list of interfaces addresses. - NetCollection GetInternalBindAddresses(); + Collection GetInternalBindAddresses(); /// /// Checks to see if an IP address is still a valid interface address. @@ -220,12 +228,6 @@ namespace MediaBrowser.Common.Net /// /// Optional filter for the list. /// Returns a filtered list of LAN addresses. - NetCollection GetFilteredLANSubnets(NetCollection? filter = null); - - /// - /// Reloads all settings and re-initialises the instance. - /// - /// The configuration to use. - void UpdateSettings(object configuration); + Collection GetFilteredLANSubnets(Collection? filter = null); } } diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index 80052727af..f9e1568efc 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Common.Net public static readonly IPHost None = new IPHost(string.Empty, IPAddress.None); /// - /// Time when last resolved. + /// Time when last resolved in ticks. /// private long _lastResolved; @@ -63,7 +63,8 @@ namespace MediaBrowser.Common.Net set { - // Not implemented. + // Not implemented, as a host's address is determined by DNS. + throw new NotImplementedException("The address of a host is determined by DNS."); } } @@ -75,12 +76,14 @@ namespace MediaBrowser.Common.Net { get { - return (byte)(ResolveHost() ? 128 : 0); + return (byte)(ResolveHost() ? 128 : 32); } set { - // Not implemented. + // Not implemented, as a host object can only have a prefix length of 128 (IPv6) or 32 (IPv4) prefix length, + // which is automatically determined by it's IP type. Anything else is meaningless. + throw new NotImplementedException("The prefix length on a host cannot be set."); } } @@ -92,13 +95,7 @@ namespace MediaBrowser.Common.Net /// /// Gets a value indicating whether the address has a value. /// - public bool HasAddress - { - get - { - return _addresses.Length > 0; - } - } + public bool HasAddress => _addresses.Length != 0; /// /// Gets the host name of this object. @@ -128,7 +125,7 @@ namespace MediaBrowser.Common.Net /// /// Host name to parse. /// Object representing the string, if it has successfully been parsed. - /// Success result of the parsing. + /// true if the parsing is successful, false if not. public static bool TryParse(string host, out IPHost hostObj) { if (!string.IsNullOrEmpty(host)) @@ -142,7 +139,7 @@ namespace MediaBrowser.Common.Net else { // See if it's an IPv6 in [] with no port. - i = host.IndexOf("]", StringComparison.OrdinalIgnoreCase); + i = host.IndexOf(']', StringComparison.OrdinalIgnoreCase); if (i != -1) { return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj); @@ -394,19 +391,19 @@ namespace MediaBrowser.Common.Net /// /// Attempt to resolve the ip address of a host. /// - /// The result of the comparison function. + /// true if any addresses have been resolved, otherwise false. private bool ResolveHost() { // When was the last time we resolved? if (_lastResolved == 0) { - _lastResolved = DateTime.Now.Ticks; + _lastResolved = DateTime.UtcNow.Ticks; } // If we haven't resolved before, or out timer has run out... - if ((_addresses.Length == 0 && !Resolved) || (TimeSpan.FromTicks(DateTime.Now.Ticks - _lastResolved).TotalMinutes > Timeout)) + if ((_addresses.Length == 0 && !Resolved) || (TimeSpan.FromTicks(DateTime.UtcNow.Ticks - _lastResolved).TotalMinutes > Timeout)) { - _lastResolved = DateTime.Now.Ticks; + _lastResolved = DateTime.UtcNow.Ticks; ResolveHostInternal().GetAwaiter().GetResult(); Resolved = true; } @@ -417,7 +414,7 @@ namespace MediaBrowser.Common.Net /// /// Task that looks up a Host name and returns its IP addresses. /// - /// Array of IPAddress objects. + /// A representing the asynchronous operation. private async Task ResolveHostInternal() { if (!string.IsNullOrEmpty(HostName)) diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs index 36f3357cce..d18ac98933 100644 --- a/MediaBrowser.Common/Net/IPObject.cs +++ b/MediaBrowser.Common/Net/IPObject.cs @@ -26,7 +26,7 @@ namespace MediaBrowser.Common.Net private IPObject? _networkAddress; /// - /// Gets or sets the user defined functions that need storage in this object. + /// Gets or sets a user defined value that is associated with this object. /// public int Tag { get; set; } @@ -38,18 +38,7 @@ namespace MediaBrowser.Common.Net /// /// Gets the object's network address. /// - public IPObject NetworkAddress - { - get - { - if (_networkAddress == null) - { - _networkAddress = CalculateNetworkAddress(); - } - - return _networkAddress; - } - } + public IPObject NetworkAddress => _networkAddress ??= CalculateNetworkAddress(); /// /// Gets or sets the object's IP address. @@ -92,22 +81,33 @@ namespace MediaBrowser.Common.Net return (Address: address, PrefixLength: prefixLength); } + // An ip address is just a list of bytes, each one representing a segment on the network. + // This separates the IP address into octets and calculates how many octets will need to be altered or set to zero dependant upon the + // prefix length value. eg. /16 on a 4 octet ip4 address (192.168.2.240) will result in the 2 and the 240 being zeroed out. + // Where there is not an exact boundary (eg /23), mod is used to calculate how many bits of this value are to be kept. + byte[] addressBytes = address.GetAddressBytes(); int div = prefixLength / 8; int mod = prefixLength % 8; if (mod != 0) { + // Prefix length is counted right to left, so subtract 8 so we know how many bits to clear. mod = 8 - mod; + + // Shift out the bits from the octet that we don't want, by moving right then back left. addressBytes[div] = (byte)((int)addressBytes[div] >> mod << mod); + // Move on the next byte. div++; } + // Blank out the remaining octets from mod + 1 to the end of the byte array. (192.168.2.240/16 becomes 192.168.0.0) for (int octet = div; octet < addressBytes.Length; octet++) { addressBytes[octet] = 0; } + // Return the network address for the prefix. return (Address: new IPAddress(addressBytes), PrefixLength: prefixLength); } @@ -179,18 +179,18 @@ namespace MediaBrowser.Common.Net byte[] octet = address.GetAddressBytes(); - return (octet[0] == 10) || - (octet[0] == 172 && octet[1] >= 16 && octet[1] <= 31) || // RFC1918 - (octet[0] == 192 && octet[1] == 168) || // RFC1918 - (octet[0] == 127); // RFC1122 + return (octet[0] == 10) + || (octet[0] == 172 && octet[1] >= 16 && octet[1] <= 31) // RFC1918 + || (octet[0] == 192 && octet[1] == 168) // RFC1918 + || (octet[0] == 127); // RFC1122 } else { byte[] octet = address.GetAddressBytes(); uint word = (uint)(octet[0] << 8) + octet[1]; - return (word >= 0xfe80 && word <= 0xfebf) || // fe80::/10 :Local link. - (word >= 0xfc00 && word <= 0xfdff); // fc00::/7 :Unique local address. + return (word >= 0xfe80 && word <= 0xfebf) // fe80::/10 :Local link. + || (word >= 0xfc00 && word <= 0xfdff); // fc00::/7 :Unique local address. } } @@ -202,7 +202,8 @@ namespace MediaBrowser.Common.Net /// /// IPAddress object to check. /// True if it is a local link address. - /// See https://stackoverflow.com/questions/6459928/explain-the-instance-properties-of-system-net-ipaddress + /// + /// See https://stackoverflow.com/questions/6459928/explain-the-instance-properties-of-system-net-ipaddress /// it appears that the IPAddress.IsIPv6LinkLocal is out of date. /// public static bool IsIPv6LinkLocal(IPAddress address) @@ -237,11 +238,10 @@ namespace MediaBrowser.Common.Net public static IPAddress CidrToMask(byte cidr, AddressFamily family) { uint addr = 0xFFFFFFFF << (family == AddressFamily.InterNetwork ? 32 : 128 - cidr); - addr = - ((addr & 0xff000000) >> 24) | - ((addr & 0x00ff0000) >> 8) | - ((addr & 0x0000ff00) << 8) | - ((addr & 0x000000ff) << 24); + addr = ((addr & 0xff000000) >> 24) + | ((addr & 0x00ff0000) >> 8) + | ((addr & 0x0000ff00) << 8) + | ((addr & 0x000000ff) << 24); return new IPAddress(addr); } @@ -304,7 +304,7 @@ namespace MediaBrowser.Common.Net /// Type of address to remove. public virtual void Remove(AddressFamily family) { - // This method only peforms a function in the IPHost implementation of IPObject. + // This method only performs a function in the IPHost implementation of IPObject. } /// @@ -352,9 +352,9 @@ namespace MediaBrowser.Common.Net /// Equality result. public virtual bool Equals(IPObject? other) { - if (other != null && other is IPObject otherObj) + if (other != null) { - return !Address.Equals(IPAddress.None) && Address.Equals(otherObj.Address); + return !Address.Equals(IPAddress.None) && Address.Equals(other.Address); } return false; diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs index e801de5eb8..d07bba249b 100644 --- a/MediaBrowser.Common/Net/NetworkExtensions.cs +++ b/MediaBrowser.Common/Net/NetworkExtensions.cs @@ -2,10 +2,10 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Net; using System.Runtime.CompilerServices; using System.Text; -using NetCollection = System.Collections.ObjectModel.Collection; namespace MediaBrowser.Common.Net { @@ -17,9 +17,9 @@ namespace MediaBrowser.Common.Net /// /// Add an address to the collection. /// - /// The . + /// The . /// Item to add. - public static void AddItem(this NetCollection source, IPAddress ip) + public static void AddItem(this Collection source, IPAddress ip) { if (!source.ContainsAddress(ip)) { @@ -30,9 +30,9 @@ namespace MediaBrowser.Common.Net /// /// Adds a network to the collection. /// - /// The . + /// The . /// Item to add. - public static void AddItem(this NetCollection source, IPObject item) + public static void AddItem(this Collection source, IPObject item) { if (!source.ContainsAddress(item)) { @@ -43,33 +43,21 @@ namespace MediaBrowser.Common.Net /// /// Converts this object to a string. /// - /// The . + /// The . /// Returns a string representation of this object. - public static string AsString(this NetCollection source) + public static string AsString(this Collection source) { - var sb = new StringBuilder(); - string output = "["; - if (source.Count > 0) - { - foreach (var i in source) - { - output += $"{i},"; - } - - output = output[0..^1]; - } - - return $"{output}]"; + return $"[{string.Join(',', source)}]"; } /// /// Returns true if the collection contains an item with the ip address, /// or the ip address falls within any of the collection's network ranges. /// - /// The . + /// The . /// The item to look for. /// True if the collection contains the item. - public static bool ContainsAddress(this NetCollection source, IPAddress item) + public static bool ContainsAddress(this Collection source, IPAddress item) { if (source.Count == 0) { @@ -101,10 +89,10 @@ namespace MediaBrowser.Common.Net /// Returns true if the collection contains an item with the ip address, /// or the ip address falls within any of the collection's network ranges. /// - /// The . + /// The . /// The item to look for. /// True if the collection contains the item. - public static bool ContainsAddress(this NetCollection source, IPObject item) + public static bool ContainsAddress(this Collection source, IPObject item) { if (source.Count == 0) { @@ -128,12 +116,12 @@ namespace MediaBrowser.Common.Net } /// - /// Compares two NetCollection objects. The order is ignored. + /// Compares two Collection{IPObject} objects. The order is ignored. /// - /// The . + /// The . /// Item to compare to. /// True if both are equal. - public static bool Compare(this NetCollection source, NetCollection dest) + public static bool Compare(this Collection source, Collection dest) { if (dest == null || source.Count != dest.Count) { @@ -164,16 +152,16 @@ namespace MediaBrowser.Common.Net /// /// Returns a collection containing the subnets of this collection given. /// - /// The . - /// NetCollection object containing the subnets. - public static NetCollection AsNetworks(this NetCollection source) + /// The . + /// Collection{IPObject} object containing the subnets. + public static Collection AsNetworks(this Collection source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } - NetCollection res = new NetCollection(); + Collection res = new Collection(); foreach (IPObject i in source) { @@ -184,10 +172,10 @@ namespace MediaBrowser.Common.Net na.Tag = i.Tag; res.AddItem(na); } - else + else if (i is IPHost ipHost) { // Flatten out IPHost and add all its ip addresses. - foreach (var addr in ((IPHost)i).GetAddresses()) + foreach (var addr in ipHost.GetAddresses()) { IPNetAddress host = new IPNetAddress(addr) { @@ -205,17 +193,17 @@ namespace MediaBrowser.Common.Net /// /// Excludes all the items from this list that are found in excludeList. /// - /// The . + /// The . /// Items to exclude. /// A new collection, with the items excluded. - public static NetCollection Exclude(this NetCollection source, NetCollection excludeList) + public static Collection Exclude(this Collection source, Collection excludeList) { if (source.Count == 0 || excludeList == null) { - return new NetCollection(source); + return new Collection(source); } - NetCollection results = new NetCollection(); + Collection results = new Collection(); bool found; foreach (var outer in source) @@ -243,14 +231,14 @@ namespace MediaBrowser.Common.Net /// /// Returns all items that co-exist in this object and target. /// - /// The . + /// The . /// Collection to compare with. /// A collection containing all the matches. - public static NetCollection Union(this NetCollection source, NetCollection target) + public static Collection Union(this Collection source, Collection target) { if (source.Count == 0) { - return new NetCollection(); + return new Collection(); } if (target == null) @@ -258,7 +246,7 @@ namespace MediaBrowser.Common.Net throw new ArgumentNullException(nameof(target)); } - NetCollection nc = new NetCollection(); + Collection nc = new Collection(); foreach (IPObject i in source) { -- cgit v1.2.3 From 5fa4cce4cce2bcc6ac2a2cca884af5e659c836f0 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 17 Nov 2020 06:57:25 -0700 Subject: Use ALL the decompression methods. --- MediaBrowser.Common/Net/DefaultHttpClientHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'MediaBrowser.Common/Net') diff --git a/MediaBrowser.Common/Net/DefaultHttpClientHandler.cs b/MediaBrowser.Common/Net/DefaultHttpClientHandler.cs index e189d6e706..f1c5f24772 100644 --- a/MediaBrowser.Common/Net/DefaultHttpClientHandler.cs +++ b/MediaBrowser.Common/Net/DefaultHttpClientHandler.cs @@ -13,8 +13,7 @@ namespace MediaBrowser.Common.Net /// public DefaultHttpClientHandler() { - // TODO change to DecompressionMethods.All with .NET5 - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + AutomaticDecompression = DecompressionMethods.All; } } } -- cgit v1.2.3 From 9a9b2bfb2ea6a39d1a46f16355b42d930b307177 Mon Sep 17 00:00:00 2001 From: Greenback Date: Sat, 21 Nov 2020 00:34:09 +0000 Subject: Updated to the latest --- .../Configuration/NetworkConfiguration.cs | 8 +- Jellyfin.Networking/Manager/NetworkManager.cs | 184 +++++++++++---------- MediaBrowser.Common/Net/IPHost.cs | 29 ++-- MediaBrowser.Common/Net/IPNetAddress.cs | 8 +- MediaBrowser.Common/Net/IPObject.cs | 31 ++-- 5 files changed, 144 insertions(+), 116 deletions(-) (limited to 'MediaBrowser.Common/Net') diff --git a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs index e710eb3c76..df420f48a2 100644 --- a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs +++ b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs @@ -99,8 +99,7 @@ namespace Jellyfin.Networking.Configuration public bool UPnPCreateHttpPortMap { get; set; } /// - /// Gets or sets the UDPPortRange - /// Gets or sets client udp port range. + /// Gets or sets the UDPPortRange. /// public string UDPPortRange { get; set; } = string.Empty; @@ -115,8 +114,8 @@ namespace Jellyfin.Networking.Configuration public bool EnableIPV4 { get; set; } = true; /// - /// Gets or sets a value indicating whether detailed ssdp logs are sent to the console/log. - /// "Emby.Dlna": "Debug" must be set in logging.default.json for this property to work. + /// Gets or sets a value indicating whether detailed SSDP logs are sent to the console/log. + /// "Emby.Dlna": "Debug" must be set in logging.default.json for this property to have any effect. /// public bool EnableSSDPTracing { get; set; } @@ -143,7 +142,6 @@ namespace Jellyfin.Networking.Configuration public bool IgnoreVirtualInterfaces { get; set; } = true; /// - /// Gets or sets the VirtualInterfaceNames /// Gets or sets a value indicating the interfaces that should be ignored. The list can be comma separated. . /// public string VirtualInterfaceNames { get; set; } = "vEthernet*"; diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 00711f1620..515ae669a1 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -182,7 +182,7 @@ namespace Jellyfin.Networking.Manager public IReadOnlyCollection GetMacAddresses() { // Populated in construction - so always has values. - return _macAddresses.AsReadOnly(); + return _macAddresses; } /// @@ -378,7 +378,7 @@ namespace Jellyfin.Networking.Manager } } - _logger.LogDebug("GetBindInterface: Source: {0}, External: {1}:", haveSource, isExternal); + _logger.LogDebug("GetBindInterface: Source: {HaveSource}, External: {IsExternal}:", haveSource, isExternal); // No preference given, so move on to bind addresses. if (MatchesBindInterface(source, isExternal, out string result)) @@ -408,20 +408,20 @@ namespace Jellyfin.Networking.Manager if (intf.Contains(source)) { result = FormatIP6String(intf.Address); - _logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Has source, matched best internal interface on range. {Result}", source, result); return result; } } } result = FormatIP6String(interfaces.First().Address); - _logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Matched first internal interface. {Result}", source, result); return result; } // There isn't any others, so we'll use the loopback. result = IsIP6Enabled ? "::" : "127.0.0.1"; - _logger.LogWarning("{0}: GetBindInterface: Loopback return.", source, result); + _logger.LogWarning("{Source}: GetBindInterface: Loopback {Result} returned.", source, result); return result; } @@ -552,7 +552,7 @@ namespace Jellyfin.Networking.Manager { result = new Collection(); - _logger.LogInformation("Interface {0} used in settings. Using its interface addresses.", token); + _logger.LogInformation("Interface {Token} used in settings. Using its interface addresses.", token); // Replace interface tags with the interface IP's. foreach (IPNetAddress iface in _interfaceAddresses) @@ -575,7 +575,7 @@ namespace Jellyfin.Networking.Manager /// Reloads all settings and re-initialises the instance. /// /// The to use. - public void UpdateSettings(NetworkConfiguration configuration) + public void UpdateSettings(object configuration) { NetworkConfiguration config = (NetworkConfiguration)configuration ?? throw new ArgumentNullException(nameof(configuration)); @@ -740,7 +740,7 @@ namespace Jellyfin.Networking.Manager // Null check required here for automated testing. if (IsInterface(token, out int index)) { - _logger.LogInformation("Interface {0} used in settings. Using its interface addresses.", token); + _logger.LogInformation("Interface {Token} used in settings. Using its interface addresses.", token); // Replace interface tags with the interface IP's. foreach (IPNetAddress iface in _interfaceAddresses) @@ -780,7 +780,7 @@ namespace Jellyfin.Networking.Manager } else { - _logger.LogDebug("Invalid or unknown network {0}.", token); + _logger.LogDebug("Invalid or unknown network {Token}.", token); } } @@ -867,7 +867,7 @@ namespace Jellyfin.Networking.Manager var parts = entry.Split('='); if (parts.Length != 2) { - _logger.LogError("Unable to parse bind override. {0}", entry); + _logger.LogError("Unable to parse bind override: {Entry}", entry); } else { @@ -893,7 +893,7 @@ namespace Jellyfin.Networking.Manager } else { - _logger.LogError("Unable to parse bind ip address. {0}", parts[1]); + _logger.LogError("Unable to parse bind ip address. {Parts}", parts[1]); } } } @@ -905,30 +905,35 @@ namespace Jellyfin.Networking.Manager /// private void InitialiseBind(NetworkConfiguration config) { - string[] lanAddresses = config.LocalNetworkAddresses; + lock (_intLock) + { + string[] lanAddresses = config.LocalNetworkAddresses; - // TODO: remove when bug fixed: https://github.com/jellyfin/jellyfin-web/issues/1334 + // TODO: remove when bug fixed: https://github.com/jellyfin/jellyfin-web/issues/1334 - if (lanAddresses.Length == 1 && lanAddresses[0].IndexOf(',', StringComparison.OrdinalIgnoreCase) != -1) - { - lanAddresses = lanAddresses[0].Split(','); - } + if (lanAddresses.Length == 1 && lanAddresses[0].IndexOf(',', StringComparison.OrdinalIgnoreCase) != -1) + { + lanAddresses = lanAddresses[0].Split(','); + } - // TODO: end fix: https://github.com/jellyfin/jellyfin-web/issues/1334 + // TODO: end fix: https://github.com/jellyfin/jellyfin-web/issues/1334 - // Add virtual machine interface names to the list of bind exclusions, so that they are auto-excluded. - if (config.IgnoreVirtualInterfaces) - { - var newList = lanAddresses.ToList(); - newList.AddRange(config.VirtualInterfaceNames.Split(',').ToList()); - lanAddresses = newList.ToArray(); - } + // Add virtual machine interface names to the list of bind exclusions, so that they are auto-excluded. + if (config.IgnoreVirtualInterfaces) + { + var virtualInterfaceNames = config.VirtualInterfaceNames.Split(','); + var newList = new string[lanAddresses.Length + virtualInterfaceNames.Length]; + Array.Copy(lanAddresses, newList, lanAddresses.Length); + Array.Copy(virtualInterfaceNames, 0, newList, lanAddresses.Length, virtualInterfaceNames.Length); + lanAddresses = newList; + } - // Read and parse bind addresses and exclusions, removing ones that don't exist. - _bindAddresses = CreateIPCollection(lanAddresses).Union(_interfaceAddresses); - _bindExclusions = CreateIPCollection(lanAddresses, true).Union(_interfaceAddresses); - _logger.LogInformation("Using bind addresses: {0}", _bindAddresses.AsString()); - _logger.LogInformation("Using bind exclusions: {0}", _bindExclusions.AsString()); + // Read and parse bind addresses and exclusions, removing ones that don't exist. + _bindAddresses = CreateIPCollection(lanAddresses).Union(_interfaceAddresses); + _bindExclusions = CreateIPCollection(lanAddresses, true).Union(_interfaceAddresses); + _logger.LogInformation("Using bind addresses: {0}", _bindAddresses.AsString()); + _logger.LogInformation("Using bind exclusions: {0}", _bindExclusions.AsString()); + } } /// @@ -936,7 +941,10 @@ namespace Jellyfin.Networking.Manager /// private void InitialiseRemote(NetworkConfiguration config) { - RemoteAddressFilter = CreateIPCollection(config.RemoteIPFilter); + lock (_intLock) + { + RemoteAddressFilter = CreateIPCollection(config.RemoteIPFilter); + } } /// @@ -959,7 +967,8 @@ namespace Jellyfin.Networking.Manager // If no LAN addresses are specified - all private subnets are deemed to be the LAN _usingPrivateAddresses = _lanSubnets.Count == 0; - // NOTE: The order of the commands in this statement matters. + // NOTE: The order of the commands generating the collection in this statement matters. + // Altering the order will cause the collections to be created incorrectly. if (_usingPrivateAddresses) { _logger.LogDebug("Using LAN interface addresses as user provided no LAN details."); @@ -1020,6 +1029,7 @@ namespace Jellyfin.Networking.Manager _interfaceNames.Clear(); _interfaceAddresses.Clear(); + _macAddresses.Clear(); try { @@ -1051,7 +1061,7 @@ namespace Jellyfin.Networking.Manager }; int tag = nw.Tag; - if ((ipProperties.GatewayAddresses.Count > 0) && !nw.IsLoopback()) + if (ipProperties.GatewayAddresses.Count > 0 && !nw.IsLoopback()) { // -ve Tags signify the interface has a gateway. nw.Tag *= -1; @@ -1072,7 +1082,7 @@ namespace Jellyfin.Networking.Manager }; int tag = nw.Tag; - if ((ipProperties.GatewayAddresses.Count > 0) && !nw.IsLoopback()) + if (ipProperties.GatewayAddresses.Count > 0 && !nw.IsLoopback()) { // -ve Tags signify the interface has a gateway. nw.Tag *= -1; @@ -1087,9 +1097,10 @@ namespace Jellyfin.Networking.Manager } } #pragma warning disable CA1031 // Do not catch general exception types - catch + catch (Exception ex) { // Ignore error, and attempt to continue. + _logger.LogError(ex, "Error encountered parsing interfaces."); } #pragma warning restore CA1031 // Do not catch general exception types } @@ -1100,8 +1111,7 @@ namespace Jellyfin.Networking.Manager // If for some reason we don't have an interface info, resolve our DNS name. if (_interfaceAddresses.Count == 0) { - _logger.LogWarning("No interfaces information available. Using loopback."); - + _logger.LogError("No interfaces information available. Resolving DNS name."); IPHost host = new IPHost(Dns.GetHostName()); foreach (var a in host.GetAddresses()) { @@ -1110,7 +1120,7 @@ namespace Jellyfin.Networking.Manager if (_interfaceAddresses.Count == 0) { - _logger.LogError("No interfaces information available. Resolving DNS name."); + _logger.LogWarning("No interfaces information available. Using loopback."); // Last ditch attempt - use loopback address. _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback); if (IsIP6Enabled) @@ -1131,11 +1141,11 @@ namespace Jellyfin.Networking.Manager /// Attempts to match the source against a user defined bind interface. /// /// IP source address to use. - /// True if the source is in the external subnet. + /// True if the source is in the external subnet. /// The published server url that matches the source address. /// The resultant port, if one exists. /// true if a match is found, false otherwise. - private bool MatchesPublishedServerUrl(IPObject source, bool isExternal, out string bindPreference, out int? port) + private bool MatchesPublishedServerUrl(IPObject source, bool isInExternalSubnet, out string bindPreference, out int? port) { bindPreference = string.Empty; port = null; @@ -1144,12 +1154,12 @@ namespace Jellyfin.Networking.Manager foreach (var addr in _publishedServerUrls) { // Remaining. Match anything. - if (addr.Key.Equals(IPAddress.Broadcast)) + if (addr.Key.Address.Equals(IPAddress.Broadcast)) { bindPreference = addr.Value; break; } - else if ((addr.Key.Equals(IPAddress.Any) || addr.Key.Equals(IPAddress.IPv6Any)) && isExternal) + else if ((addr.Key.Address.Equals(IPAddress.Any) || addr.Key.Address.Equals(IPAddress.IPv6Any)) && isInExternalSubnet) { // External. bindPreference = addr.Value; @@ -1163,38 +1173,38 @@ namespace Jellyfin.Networking.Manager } } - if (!string.IsNullOrEmpty(bindPreference)) + if (string.IsNullOrEmpty(bindPreference)) + { + return false; + } + + // Has it got a port defined? + var parts = bindPreference.Split(':'); + if (parts.Length > 1) { - // Has it got a port defined? - var parts = bindPreference.Split(':'); - if (parts.Length > 1) + if (int.TryParse(parts[1], out int p)) { - if (int.TryParse(parts[1], out int p)) - { - bindPreference = parts[0]; - port = p; - } + bindPreference = parts[0]; + port = p; } - - return true; } - return false; + return true; } /// /// Attempts to match the source against a user defined bind interface. /// /// IP source address to use. - /// True if the source is in the external subnet. + /// True if the source is in the external subnet. /// The result, if a match is found. /// true if a match is found, false otherwise. - private bool MatchesBindInterface(IPObject source, bool isExternal, out string result) + private bool MatchesBindInterface(IPObject source, bool isInExternalSubnet, out string result) { result = string.Empty; - var nc = _bindAddresses.Exclude(_bindExclusions); + var addresses = _bindAddresses.Exclude(_bindExclusions); - int count = nc.Count; + int count = addresses.Count; if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses[0].Equals(IPAddress.IPv6Any))) { // Ignore IPAny addresses. @@ -1205,26 +1215,34 @@ namespace Jellyfin.Networking.Manager { // Check to see if any of the bind interfaces are in the same subnet. - Collection bindResult; IPAddress? defaultGateway = null; - IPAddress? bindAddress; + IPAddress? bindAddress = null; - if (isExternal) + if (isInExternalSubnet) { // Find all external bind addresses. Store the default gateway, but check to see if there is a better match first. - bindResult = CreateCollection(nc - .Where(p => !IsInLocalNetwork(p)) - .OrderBy(p => p.Tag)); - defaultGateway = bindResult.FirstOrDefault()?.Address; - bindAddress = bindResult - .Where(p => p.Contains(source)) - .OrderBy(p => p.Tag) - .FirstOrDefault()?.Address; + foreach (var addr in addresses.OrderBy(p => p.Tag)) + { + if (defaultGateway == null && !IsInLocalNetwork(addr)) + { + defaultGateway = addr.Address; + } + + if (bindAddress == null && addr.Contains(source)) + { + bindAddress = addr.Address; + } + + if (defaultGateway != null && bindAddress != null) + { + break; + } + } } else { // Look for the best internal address. - bindAddress = nc + bindAddress = addresses .Where(p => IsInLocalNetwork(p) && (p.Contains(source) || p.Equals(IPAddress.None))) .OrderBy(p => p.Tag) .FirstOrDefault()?.Address; @@ -1233,23 +1251,23 @@ namespace Jellyfin.Networking.Manager if (bindAddress != null) { result = FormatIP6String(bindAddress); - _logger.LogDebug("{0}: GetBindInterface: Has source, found a match bind interface subnets. {1}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Has source, found a match bind interface subnets. {Result}", source, result); return true; } - if (isExternal && defaultGateway != null) + if (isInExternalSubnet && defaultGateway != null) { result = FormatIP6String(defaultGateway); - _logger.LogDebug("{0}: GetBindInterface: Using first user defined external interface. {1}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Using first user defined external interface. {Result}", source, result); return true; } - result = FormatIP6String(nc.First().Address); - _logger.LogDebug("{0}: GetBindInterface: Selected first user defined interface. {1}", source, result); + result = FormatIP6String(addresses[0].Address); + _logger.LogDebug("{Source}: GetBindInterface: Selected first user defined interface. {Result}", source, result); - if (isExternal) + if (isInExternalSubnet) { - _logger.LogWarning("{0}: External request received, however, only an internal interface bind found.", source); + _logger.LogWarning("{Source}: External request received, however, only an internal interface bind found.", source); } return true; @@ -1268,12 +1286,12 @@ namespace Jellyfin.Networking.Manager { result = string.Empty; // Get the first WAN interface address that isn't a loopback. - var extResult = CreateCollection(_interfaceAddresses + var extResult = _interfaceAddresses .Exclude(_bindExclusions) .Where(p => !IsInLocalNetwork(p)) - .OrderBy(p => p.Tag)); + .OrderBy(p => p.Tag); - if (extResult.Count > 0) + if (extResult.Any()) { // Does the request originate in one of the interface subnets? // (For systems with multiple internal network cards, and multiple subnets) @@ -1282,19 +1300,19 @@ namespace Jellyfin.Networking.Manager if (!IsInLocalNetwork(intf) && intf.Contains(source)) { result = FormatIP6String(intf.Address); - _logger.LogDebug("{0}: GetBindInterface: Selected best external on interface on range. {1}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Selected best external on interface on range. {Result}", source, result); return true; } } result = FormatIP6String(extResult.First().Address); - _logger.LogDebug("{0}: GetBindInterface: Selected first external interface. {0}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Selected first external interface. {Result}", source, result); return true; } // Have to return something, so return an internal address - _logger.LogWarning("{0}: External request received, however, no WAN interface found.", source); + _logger.LogWarning("{Source}: External request received, however, no WAN interface found.", source); return false; } } diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index f9e1568efc..4cede9ab16 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; @@ -13,6 +14,11 @@ namespace MediaBrowser.Common.Net /// public class IPHost : IPObject { + /// + /// Gets or sets timeout value before resolve required, in minutes. + /// + public const int Timeout = 30; + /// /// Represents an IPHost that has no value. /// @@ -21,7 +27,7 @@ namespace MediaBrowser.Common.Net /// /// Time when last resolved in ticks. /// - private long _lastResolved; + private DateTime? _lastResolved = null; /// /// Gets the IP Addresses, attempting to resolve the name, if there are none. @@ -83,15 +89,9 @@ namespace MediaBrowser.Common.Net { // Not implemented, as a host object can only have a prefix length of 128 (IPv6) or 32 (IPv4) prefix length, // which is automatically determined by it's IP type. Anything else is meaningless. - throw new NotImplementedException("The prefix length on a host cannot be set."); } } - /// - /// Gets or sets timeout value before resolve required, in minutes. - /// - public byte Timeout { get; set; } = 30; - /// /// Gets a value indicating whether the address has a value. /// @@ -395,15 +395,15 @@ namespace MediaBrowser.Common.Net private bool ResolveHost() { // When was the last time we resolved? - if (_lastResolved == 0) + if (_lastResolved == null) { - _lastResolved = DateTime.UtcNow.Ticks; + _lastResolved = DateTime.UtcNow; } - // If we haven't resolved before, or out timer has run out... - if ((_addresses.Length == 0 && !Resolved) || (TimeSpan.FromTicks(DateTime.UtcNow.Ticks - _lastResolved).TotalMinutes > Timeout)) + // If we haven't resolved before, or our timer has run out... + if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved?.AddMinutes(Timeout))) { - _lastResolved = DateTime.UtcNow.Ticks; + _lastResolved = DateTime.UtcNow; ResolveHostInternal().GetAwaiter().GetResult(); Resolved = true; } @@ -433,9 +433,10 @@ namespace MediaBrowser.Common.Net IPHostEntry ip = await Dns.GetHostEntryAsync(HostName).ConfigureAwait(false); _addresses = ip.AddressList; } - catch (SocketException) + catch (SocketException ex) { - // Ignore socket errors, as the result value will just be an empty array. + // Log and then ignore socket errors, as the result value will just be an empty array. + Debug.WriteLine("GetHostEntryAsync failed with {Message}.", ex.Message); } } } diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs index 0d28c35cb8..a6f5fe4b37 100644 --- a/MediaBrowser.Common/Net/IPNetAddress.cs +++ b/MediaBrowser.Common/Net/IPNetAddress.cs @@ -18,17 +18,17 @@ namespace MediaBrowser.Common.Net /// /// IPv4 multicast address. /// - public static readonly IPAddress MulticastIPv4 = IPAddress.Parse("239.255.255.250"); + public static readonly IPAddress SSDPMulticastIPv4 = IPAddress.Parse("239.255.255.250"); /// /// IPv6 local link multicast address. /// - public static readonly IPAddress MulticastIPv6LinkLocal = IPAddress.Parse("ff02::C"); + public static readonly IPAddress SSDPMulticastIPv6LinkLocal = IPAddress.Parse("ff02::C"); /// /// IPv6 site local multicast address. /// - public static readonly IPAddress MulticastIPv6SiteLocal = IPAddress.Parse("ff05::C"); + public static readonly IPAddress SSDPMulticastIPv6SiteLocal = IPAddress.Parse("ff05::C"); /// /// IP4Loopback address host. @@ -235,7 +235,7 @@ namespace MediaBrowser.Common.Net /// /// Returns a textual representation of this object. /// - /// Set to true, if the subnet is to be included as part of the address. + /// Set to true, if the subnet is to be excluded as part of the address. /// String representation of this object. public string ToString(bool shortVersion) { diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs index d18ac98933..69cd57f8ae 100644 --- a/MediaBrowser.Common/Net/IPObject.cs +++ b/MediaBrowser.Common/Net/IPObject.cs @@ -86,7 +86,9 @@ namespace MediaBrowser.Common.Net // prefix length value. eg. /16 on a 4 octet ip4 address (192.168.2.240) will result in the 2 and the 240 being zeroed out. // Where there is not an exact boundary (eg /23), mod is used to calculate how many bits of this value are to be kept. - byte[] addressBytes = address.GetAddressBytes(); + // GetAddressBytes + Span addressBytes = stackalloc byte[address.AddressFamily == AddressFamily.InterNetwork ? 4 : 16]; + address.TryWriteBytes(addressBytes, out _); int div = prefixLength / 8; int mod = prefixLength % 8; @@ -170,14 +172,16 @@ namespace MediaBrowser.Common.Net if (!address.Equals(IPAddress.None)) { - if (address.AddressFamily == AddressFamily.InterNetwork) + if (address.IsIPv4MappedToIPv6) { - if (address.IsIPv4MappedToIPv6) - { - address = address.MapToIPv4(); - } + address = address.MapToIPv4(); + } - byte[] octet = address.GetAddressBytes(); + if (address.AddressFamily == AddressFamily.InterNetwork) + { + // GetAddressBytes + Span octet = stackalloc byte[4]; + address.TryWriteBytes(octet, out _); return (octet[0] == 10) || (octet[0] == 172 && octet[1] >= 16 && octet[1] <= 31) // RFC1918 @@ -186,7 +190,10 @@ namespace MediaBrowser.Common.Net } else { - byte[] octet = address.GetAddressBytes(); + // GetAddressBytes + Span octet = stackalloc byte[16]; + address.TryWriteBytes(octet, out _); + uint word = (uint)(octet[0] << 8) + octet[1]; return (word >= 0xfe80 && word <= 0xfebf) // fe80::/10 :Local link. @@ -223,7 +230,9 @@ namespace MediaBrowser.Common.Net return false; } - byte[] octet = address.GetAddressBytes(); + // GetAddressBytes + Span octet = stackalloc byte[16]; + address.TryWriteBytes(octet, out _); uint word = (uint)(octet[0] << 8) + octet[1]; return word >= 0xfe80 && word <= 0xfebf; // fe80::/10 :Local link. @@ -261,7 +270,9 @@ namespace MediaBrowser.Common.Net byte cidrnet = 0; if (!mask.Equals(IPAddress.Any)) { - byte[] bytes = mask.GetAddressBytes(); + // GetAddressBytes + Span bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? 4 : 16]; + mask.TryWriteBytes(bytes, out _); var zeroed = false; for (var i = 0; i < bytes.Length; i++) -- cgit v1.2.3 From 50e375020a580078ed309e2d59cf124d10b4a2ed Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 24 Nov 2020 05:11:02 +0000 Subject: [Fix] NetworkManager binding to [::] (#4549) * Autodiscovery enable/disable patch * Fixed [::] issue on bind. Altered test. * Update UdpServerEntryPoint.cs * Update Jellyfin.Networking.Tests.csproj * Update Jellyfin.Networking.Tests.csproj * Update INetworkManager.cs --- Jellyfin.Networking/Manager/NetworkManager.cs | 37 ++++++++++++---------- MediaBrowser.Common/Net/INetworkManager.cs | 12 +++---- .../NetworkTesting/NetworkParseTests.cs | 16 ++++++---- 3 files changed, 36 insertions(+), 29 deletions(-) (limited to 'MediaBrowser.Common/Net') diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 515ae669a1..1a5614b7b0 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -84,7 +84,7 @@ namespace Jellyfin.Networking.Manager private Collection _internalInterfaces; /// - /// Flag set when no custom LAN has been defined in the config. + /// Flag set when no custom LAN has been defined in the configuration. /// private bool _usingPrivateAddresses; @@ -228,7 +228,7 @@ namespace Jellyfin.Networking.Manager } /// - public Collection CreateIPCollection(string[] values, bool bracketed = false) + public Collection CreateIPCollection(string[] values, bool negated = false) { Collection col = new Collection(); if (values == null) @@ -242,21 +242,14 @@ namespace Jellyfin.Networking.Manager try { - if (v.StartsWith('[') && v.EndsWith(']')) + if (v.StartsWith('!')) { - if (bracketed) - { - AddToCollection(col, v[1..^1]); - } - } - else if (v.StartsWith('!')) - { - if (bracketed) + if (negated) { AddToCollection(col, v[1..]); } } - else if (!bracketed) + else if (!negated) { AddToCollection(col, v); } @@ -730,7 +723,7 @@ namespace Jellyfin.Networking.Manager } /// - /// Parses a string and adds it into the the collection, replacing any interface references. + /// Parses a string and adds it into the collection, replacing any interface references. /// /// Collection. /// String value to parse. @@ -755,7 +748,19 @@ namespace Jellyfin.Networking.Manager } else if (TryParse(token, out IPObject obj)) { - if (!IsIP6Enabled) + // Expand if the ip address is "any". + if ((obj.Address.Equals(IPAddress.Any) && IsIP4Enabled) + || (obj.Address.Equals(IPAddress.IPv6Any) && IsIP6Enabled)) + { + foreach (IPNetAddress iface in _interfaceAddresses) + { + if (obj.AddressFamily == iface.AddressFamily) + { + col.AddItem(iface); + } + } + } + else if (!IsIP6Enabled) { // Remove IP6 addresses from multi-homed IPHosts. obj.Remove(AddressFamily.InterNetworkV6); @@ -872,7 +877,7 @@ namespace Jellyfin.Networking.Manager else { var replacement = parts[1].Trim(); - if (string.Equals(parts[0], "remaining", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(parts[0], "all", StringComparison.OrdinalIgnoreCase)) { _publishedServerUrls[new IPNetAddress(IPAddress.Broadcast)] = replacement; } @@ -956,7 +961,7 @@ namespace Jellyfin.Networking.Manager { _logger.LogDebug("Refreshing LAN information."); - // Get config options. + // Get configuration options. string[] subnets = config.LocalNetworkSubnets; // Create lists from user settings. diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 43562afe38..b6c390d239 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.Common.Net /// /// Returns true if the address is a private address. - /// The config option TrustIP6Interfaces overrides this functions behaviour. + /// The configuration option TrustIP6Interfaces overrides this functions behaviour. /// /// Address to check. /// True or False. @@ -157,7 +157,7 @@ namespace MediaBrowser.Common.Net /// /// Returns true if the address is part of the user defined LAN. - /// The config option TrustIP6Interfaces overrides this functions behaviour. + /// The configuration option TrustIP6Interfaces overrides this functions behaviour. /// /// IP to check. /// True if endpoint is within the LAN range. @@ -165,7 +165,7 @@ namespace MediaBrowser.Common.Net /// /// Returns true if the address is part of the user defined LAN. - /// The config option TrustIP6Interfaces overrides this functions behaviour. + /// The configuration option TrustIP6Interfaces overrides this functions behaviour. /// /// IP to check. /// True if endpoint is within the LAN range. @@ -173,7 +173,7 @@ namespace MediaBrowser.Common.Net /// /// Returns true if the address is part of the user defined LAN. - /// The config option TrustIP6Interfaces overrides this functions behaviour. + /// The configuration option TrustIP6Interfaces overrides this functions behaviour. /// /// IP to check. /// True if endpoint is within the LAN range. @@ -192,9 +192,9 @@ namespace MediaBrowser.Common.Net /// Parses an array of strings into a Collection{IPObject}. /// /// Values to parse. - /// When true, only include values in []. When false, ignore bracketed values. + /// When true, only include values beginning with !. When false, ignore ! values. /// IPCollection object containing the value strings. - Collection CreateIPCollection(string[] values, bool bracketed = false); + Collection CreateIPCollection(string[] values, bool negated = false); /// /// Returns all the internal Bind interface addresses. diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs index 56d11ef521..c350685af0 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs @@ -135,6 +135,7 @@ namespace Jellyfin.Networking.Tests [InlineData("127.0.0.1#")] [InlineData("localhost!")] [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517:1231")] + [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517:1231]")] public void InvalidAddressString(string address) { Assert.False(TryParse(address, out _)); @@ -157,7 +158,7 @@ namespace Jellyfin.Networking.Tests "[]", "[]", "[]")] - [InlineData("[127.0.0.1]", + [InlineData("!127.0.0.1", "[]", "[]", "[127.0.0.1/32]", @@ -169,18 +170,19 @@ namespace Jellyfin.Networking.Tests "[]", "[]", "[]")] + [InlineData( + "192.158.1.2/16, localhost, fd23:184f:2029:0:3139:7386:67d7:d517, !10.10.10.10", + "[192.158.1.2/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]", + "[192.158.1.2/16,127.0.0.1/32]", + "[10.10.10.10/32]", + "[10.10.10.10/32]", + "[192.158.0.0/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]")] [InlineData("192.158.1.2/255.255.0.0,192.169.1.2/8", "[192.158.1.2/16,192.169.1.2/8]", "[192.158.1.2/16,192.169.1.2/8]", "[]", "[]", "[192.158.0.0/16,192.0.0.0/8]")] - [InlineData("192.158.1.2/16, localhost, fd23:184f:2029:0:3139:7386:67d7:d517, [10.10.10.10]", - "[192.158.1.2/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]", - "[192.158.1.2/16,127.0.0.1/32]", - "[10.10.10.10/32]", - "[10.10.10.10/32]", - "[192.158.0.0/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]")] public void TestCollections(string settings, string result1, string result2, string result3, string result4, string result5) { if (settings == null) -- cgit v1.2.3