diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index 40c2cc0e0a..98f50c09af 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -2,12 +2,14 @@
using System;
using System.Globalization;
+using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
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;
@@ -25,6 +27,7 @@ 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;
@@ -134,20 +137,20 @@ namespace Emby.Dlna.Main
{
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
- await ReloadComponents().ConfigureAwait(false);
+ ReloadComponents();
_config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
}
- private async void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
+ private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{
if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
{
- await ReloadComponents().ConfigureAwait(false);
+ ReloadComponents();
}
}
- private async Task ReloadComponents()
+ private void ReloadComponents()
{
var options = _config.GetDlnaConfiguration();
@@ -155,7 +158,7 @@ namespace Emby.Dlna.Main
if (options.EnableServer)
{
- await StartDevicePublisher(options).ConfigureAwait(false);
+ StartDevicePublisher(options);
}
else
{
@@ -225,7 +228,7 @@ namespace Emby.Dlna.Main
}
}
- public async Task StartDevicePublisher(Configuration.DlnaOptions options)
+ public void StartDevicePublisher(Configuration.DlnaOptions options)
{
if (!options.BlastAliveMessages)
{
@@ -245,7 +248,7 @@ namespace Emby.Dlna.Main
SupportPnpRootDevice = false
};
- await RegisterServerEndpoints().ConfigureAwait(false);
+ RegisterServerEndpoints();
_publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
}
@@ -255,39 +258,46 @@ namespace Emby.Dlna.Main
}
}
- private async Task RegisterServerEndpoints()
+ private void RegisterServerEndpoints()
{
- var addresses = await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false);
+ var bindAddresses = _networkManager.GetInternalBindAddresses()
+ .Where(i => i.AddressFamily == AddressFamily.InterNetwork || (i.AddressFamily == AddressFamily.InterNetworkV6 && i.Address.ScopeId != 0));
var udn = CreateUuid(_appHost.SystemId);
- foreach (var address in addresses)
+ if (!bindAddresses.Any())
{
- if (address.AddressFamily == AddressFamily.InterNetworkV6)
+ // No interfaces returned, so use loopback.
+ bindAddresses = _networkManager.GetLoopbacks();
+ }
+
+ foreach (var addr in bindAddresses)
+ {
+ if (addr.AddressFamily == AddressFamily.InterNetworkV6)
{
// Not supporting IPv6 right now
continue;
}
// Limit to LAN addresses only
- if (!_networkManager.IsAddressInSubnets(address, true, true))
+ if (!_networkManager.IsInLocalNetwork(addr))
{
continue;
}
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
- _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
+ _logger.LogInformation("Registering publisher for {0} on {1}", fullService, addr);
var descriptorUri = "/dlna/" + udn + "/description.xml";
- var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri);
+ var uri = new Uri(_appHost.GetSmartApiUrl(addr.Address) + descriptorUri);
var device = new SsdpRootDevice
{
CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
Location = uri, // Must point to the URL that serves your devices UPnP description document.
- Address = address,
- SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
+ Address = addr.Address,
+ SubnetMask = ((IPNetAddress)addr).Mask, // MIGRATION: This fields is going.
FriendlyName = "Jellyfin",
Manufacturer = "Jellyfin",
ModelName = "Jellyfin Server",
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index 21877f121f..10887bf60d 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -177,15 +177,7 @@ namespace Emby.Dlna.PlayTo
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
- string serverAddress;
- if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IPAddress.Any) || info.LocalIpAddress.Equals(IPAddress.IPv6Any))
- {
- serverAddress = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
- }
- else
- {
- serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
- }
+ string serverAddress = _appHost.GetSmartApiUrl(info.LocalIpAddress);
controller = new PlayToController(
sessionInfo,
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 642e2fdbe9..cc04cb03f5 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -46,6 +46,7 @@ using Emby.Server.Implementations.SyncPlay;
using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates;
using Jellyfin.Api.Helpers;
+using Jellyfin.Networking.Manager;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
@@ -97,6 +98,7 @@ using MediaBrowser.Providers.Manager;
using MediaBrowser.Providers.Plugins.TheTvdb;
using MediaBrowser.Providers.Subtitles;
using MediaBrowser.XbmcMetadata.Providers;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -117,7 +119,6 @@ namespace Emby.Server.Implementations
private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
private readonly IFileSystem _fileSystemManager;
- private readonly INetworkManager _networkManager;
private readonly IXmlSerializer _xmlSerializer;
private readonly IStartupOptions _startupOptions;
@@ -188,6 +189,11 @@ namespace Emby.Server.Implementations
/// The plugins.
public IReadOnlyList Plugins => _plugins;
+ ///
+ /// Gets the NetworkManager object.
+ ///
+ private readonly INetworkManager _networkManager;
+
///
/// Gets the logger factory.
///
@@ -211,7 +217,7 @@ namespace Emby.Server.Implementations
private readonly List _disposableParts = new List();
///
- /// Gets the configuration manager.
+ /// Gets or sets the configuration manager.
///
/// The configuration manager.
protected IConfigurationManager ConfigurationManager { get; set; }
@@ -244,28 +250,25 @@ namespace Emby.Server.Implementations
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
- /// Instance of the interface.
/// Instance of the interface.
public ApplicationHost(
IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
IStartupOptions options,
IFileSystem fileSystem,
- INetworkManager networkManager,
IServiceCollection serviceCollection)
{
_xmlSerializer = new MyXmlSerializer();
ServiceCollection = serviceCollection;
- _networkManager = networkManager;
- networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
-
ApplicationPaths = applicationPaths;
LoggerFactory = loggerFactory;
_fileSystemManager = fileSystem;
ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
+ _networkManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger());
+
Logger = LoggerFactory.CreateLogger();
_startupOptions = options;
@@ -278,8 +281,6 @@ namespace Emby.Server.Implementations
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
- _networkManager.NetworkChanged += OnNetworkChanged;
-
CertificateInfo = new CertificateInfo
{
Path = ServerConfigurationManager.Configuration.CertificatePath,
@@ -308,16 +309,6 @@ namespace Emby.Server.Implementations
.Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase);
}
- private string[] GetConfiguredLocalSubnets()
- {
- return ServerConfigurationManager.Configuration.LocalNetworkSubnets;
- }
-
- private void OnNetworkChanged(object sender, EventArgs e)
- {
- _validAddressResults.Clear();
- }
-
///
public Version ApplicationVersion { get; }
@@ -398,7 +389,7 @@ namespace Emby.Server.Implementations
///
/// Resolves this instance.
///
- /// The type
+ /// The type.
/// ``0.
public T Resolve() => ServiceProvider.GetService();
@@ -1091,13 +1082,10 @@ namespace Emby.Server.Implementations
///
/// Gets the system status.
///
- /// The cancellation token.
+ /// Where this request originated.
/// SystemInfo.
- public async Task GetSystemInfo(CancellationToken cancellationToken)
+ public SystemInfo GetSystemInfo(IPAddress source)
{
- var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
- var transcodingTempPath = ConfigurationManager.GetTranscodePath();
-
return new SystemInfo
{
HasPendingRestart = HasPendingRestart,
@@ -1117,9 +1105,9 @@ namespace Emby.Server.Implementations
CanSelfRestart = CanSelfRestart,
CanLaunchWebBrowser = CanLaunchWebBrowser,
HasUpdateAvailable = HasUpdateAvailable,
- TranscodingTempPath = transcodingTempPath,
+ TranscodingTempPath = ConfigurationManager.GetTranscodePath(),
ServerName = FriendlyName,
- LocalAddress = localAddress,
+ LocalAddress = GetSmartApiUrl(source),
SupportsLibraryMonitor = true,
EncoderLocation = _mediaEncoder.EncoderLocation,
SystemArchitecture = RuntimeInformation.OSArchitecture,
@@ -1132,10 +1120,8 @@ namespace Emby.Server.Implementations
.Select(i => new WakeOnLanInfo(i))
.ToList();
- public async Task GetPublicSystemInfo(CancellationToken cancellationToken)
+ public PublicSystemInfo GetPublicSystemInfo(IPAddress source)
{
- var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
-
return new PublicSystemInfo
{
Version = ApplicationVersionString,
@@ -1143,8 +1129,8 @@ namespace Emby.Server.Implementations
Id = SystemId,
OperatingSystem = OperatingSystem.Id.ToString(),
ServerName = FriendlyName,
- LocalAddress = localAddress,
- StartupWizardCompleted = ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted
+ LocalAddress = GetSmartApiUrl(source),
+ StartupWizardCompleted = ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted
};
}
@@ -1152,186 +1138,51 @@ namespace Emby.Server.Implementations
public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps;
///
- public async Task GetLocalApiUrl(CancellationToken cancellationToken)
+ public string GetSmartApiUrl(object source)
{
- try
- {
- // Return the first matched address, if found, or the first known local address
- var addresses = await GetLocalIpAddressesInternal(false, 1, cancellationToken).ConfigureAwait(false);
- if (addresses.Count == 0)
- {
- return null;
- }
-
- return GetLocalApiUrl(addresses[0]);
- }
- catch (Exception ex)
+ // Published server ends with a /
+ if (_startupOptions.PublishedServerUrl != null)
{
- Logger.LogError(ex, "Error getting local Ip address information");
+ // Published server ends with a '/', so we need to remove it.
+ return _startupOptions.PublishedServerUrl.ToString().Trim('/');
}
- return null;
- }
+ string smart = _networkManager.GetBindInterface(source, out int? port);
- ///
- /// Removes the scope id from IPv6 addresses.
- ///
- /// The IPv6 address.
- /// The IPv6 address without the scope id.
- private ReadOnlySpan RemoveScopeId(ReadOnlySpan address)
- {
- var index = address.IndexOf('%');
- if (index == -1)
+ // If the smartAPI doesn't start with http then treat it as a host or ip.
+ if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
- return address;
+ return smart.Trim('/');
}
- return address.Slice(0, index);
+ return GetLocalApiUrl(smart.Trim('/'), source is HttpRequest request ? request.Scheme : null, port);
}
- ///
- public string GetLocalApiUrl(IPAddress ipAddress)
+ ///
+ public string GetLoopbackHttpApiUrl()
{
- if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
+ if (NetworkManager.IsIP6Enabled)
{
- var str = RemoveScopeId(ipAddress.ToString());
- Span span = new char[str.Length + 2];
- span[0] = '[';
- str.CopyTo(span.Slice(1));
- span[^1] = ']';
-
- return GetLocalApiUrl(span);
+ return GetLocalApiUrl("::1", Uri.UriSchemeHttp, HttpPort);
}
- return GetLocalApiUrl(ipAddress.ToString());
- }
-
- ///
- public string GetLoopbackHttpApiUrl()
- {
return GetLocalApiUrl("127.0.0.1", Uri.UriSchemeHttp, HttpPort);
}
///
- public string GetLocalApiUrl(ReadOnlySpan host, string scheme = null, int? port = null)
+ public string GetLocalApiUrl(string host, string scheme = null, int? port = null)
{
// NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
// not. For consistency, always trim the trailing slash.
return new UriBuilder
{
Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
- Host = host.ToString(),
+ Host = host,
Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort),
Path = ServerConfigurationManager.Configuration.BaseUrl
}.ToString().TrimEnd('/');
}
- public Task> GetLocalIpAddresses(CancellationToken cancellationToken)
- {
- return GetLocalIpAddressesInternal(true, 0, cancellationToken);
- }
-
- private async Task> GetLocalIpAddressesInternal(bool allowLoopback, int limit, CancellationToken cancellationToken)
- {
- var addresses = ServerConfigurationManager
- .Configuration
- .LocalNetworkAddresses
- .Select(x => NormalizeConfiguredLocalAddress(x))
- .Where(i => i != null)
- .ToList();
-
- if (addresses.Count == 0)
- {
- addresses.AddRange(_networkManager.GetLocalIpAddresses());
- }
-
- var resultList = new List();
-
- foreach (var address in addresses)
- {
- if (!allowLoopback)
- {
- if (address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback))
- {
- continue;
- }
- }
-
- if (await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false))
- {
- resultList.Add(address);
-
- if (limit > 0 && resultList.Count >= limit)
- {
- return resultList;
- }
- }
- }
-
- return resultList;
- }
-
- public IPAddress NormalizeConfiguredLocalAddress(ReadOnlySpan address)
- {
- var index = address.Trim('/').IndexOf('/');
- if (index != -1)
- {
- address = address.Slice(index + 1);
- }
-
- if (IPAddress.TryParse(address.Trim('/'), out IPAddress result))
- {
- return result;
- }
-
- return null;
- }
-
- private readonly ConcurrentDictionary _validAddressResults = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
-
- private async Task IsLocalIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken)
- {
- if (address.Equals(IPAddress.Loopback)
- || address.Equals(IPAddress.IPv6Loopback))
- {
- return true;
- }
-
- var apiUrl = GetLocalApiUrl(address) + "/system/ping";
-
- if (_validAddressResults.TryGetValue(apiUrl, out var cachedResult))
- {
- return cachedResult;
- }
-
- try
- {
- using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
- using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
-
- await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
- var result = await System.Text.Json.JsonSerializer.DeserializeAsync(stream, JsonDefaults.GetOptions(), cancellationToken).ConfigureAwait(false);
- var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
-
- _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
- Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid);
- return valid;
- }
- catch (OperationCanceledException)
- {
- Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled");
- throw;
- }
- catch (Exception ex)
- {
- Logger.LogDebug(ex, "Ping test result to {0}. Success: {1}", apiUrl, false);
-
- _validAddressResults.AddOrUpdate(apiUrl, false, (k, v) => false);
- return false;
- }
- }
-
public string FriendlyName =>
string.IsNullOrEmpty(ServerConfigurationManager.Configuration.ServerName)
? Environment.MachineName
@@ -1486,6 +1337,7 @@ namespace Emby.Server.Implementations
_disposed = true;
}
+
}
internal class CertificateInfo
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 0a348f0d00..db81167433 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -36,7 +36,7 @@
-
+
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index 8a0c0043a9..598cf0af7c 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
@@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv
}
var list = sources.ToList();
- var serverUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
+ var serverUrl = _appHost.GetSmartApiUrl(string.Empty);
foreach (var source in list)
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 28e30fac8b..1cf129ad2d 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -10,6 +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 6730751d50..02ee302d0d 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -7,6 +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;
@@ -57,7 +58,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var mediaSource = OriginalMediaSource;
var uri = new Uri(mediaSource.Path);
- var localPort = _networkManager.GetRandomUnusedUdpPort();
+ var localPort = 50000; // Will return to random after next PR.
Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 8107bc427b..f297ecd5df 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -8,6 +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/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs
deleted file mode 100644
index 089ec30e6b..0000000000
--- a/Emby.Server.Implementations/Networking/NetworkManager.cs
+++ /dev/null
@@ -1,556 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.NetworkInformation;
-using System.Net.Sockets;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.Networking
-{
- ///
- /// Class to take care of network interface management.
- ///
- public class NetworkManager : INetworkManager
- {
- private readonly ILogger _logger;
-
- private IPAddress[] _localIpAddresses;
- private readonly object _localIpAddressSyncLock = new object();
-
- private readonly object _subnetLookupLock = new object();
- private readonly Dictionary> _subnetLookup = new Dictionary>(StringComparer.Ordinal);
-
- private List _macAddresses;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Logger to use for messages.
- public NetworkManager(ILogger logger)
- {
- _logger = logger;
-
- NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged;
- NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
- }
-
- ///
- public event EventHandler NetworkChanged;
-
- ///
- public Func LocalSubnetsFn { get; set; }
-
- private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
- {
- _logger.LogDebug("NetworkAvailabilityChanged");
- OnNetworkChanged();
- }
-
- private void OnNetworkAddressChanged(object sender, EventArgs e)
- {
- _logger.LogDebug("NetworkAddressChanged");
- OnNetworkChanged();
- }
-
- private void OnNetworkChanged()
- {
- lock (_localIpAddressSyncLock)
- {
- _localIpAddresses = null;
- _macAddresses = null;
- }
-
- NetworkChanged?.Invoke(this, EventArgs.Empty);
- }
-
- ///
- public IPAddress[] GetLocalIpAddresses()
- {
- lock (_localIpAddressSyncLock)
- {
- if (_localIpAddresses == null)
- {
- var addresses = GetLocalIpAddressesInternal().ToArray();
-
- _localIpAddresses = addresses;
- }
-
- return _localIpAddresses;
- }
- }
-
- private List GetLocalIpAddressesInternal()
- {
- var list = GetIPsDefault().ToList();
-
- if (list.Count == 0)
- {
- list = GetLocalIpAddressesFallback().GetAwaiter().GetResult().ToList();
- }
-
- var listClone = new List();
-
- var subnets = LocalSubnetsFn();
-
- foreach (var i in list)
- {
- if (i.IsIPv6LinkLocal || i.ToString().StartsWith("169.254.", StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- if (Array.IndexOf(subnets, $"[{i}]") == -1)
- {
- listClone.Add(i);
- }
- }
-
- return listClone
- .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1)
- // .ThenBy(i => listClone.IndexOf(i))
- .GroupBy(i => i.ToString())
- .Select(x => x.First())
- .ToList();
- }
-
- ///
- public bool IsInPrivateAddressSpace(string endpoint)
- {
- return IsInPrivateAddressSpace(endpoint, true);
- }
-
- // Checks if the address in endpoint is an RFC1918, RFC1122, or RFC3927 address
- private bool IsInPrivateAddressSpace(string endpoint, bool checkSubnets)
- {
- if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- // IPV6
- if (endpoint.Split('.').Length > 4)
- {
- // Handle ipv4 mapped to ipv6
- var originalEndpoint = endpoint;
- endpoint = endpoint.Replace("::ffff:", string.Empty, StringComparison.OrdinalIgnoreCase);
-
- if (string.Equals(endpoint, originalEndpoint, StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- }
-
- // Private address space:
-
- if (string.Equals(endpoint, "localhost", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- if (!IPAddress.TryParse(endpoint, out var ipAddress))
- {
- return false;
- }
-
- byte[] octet = ipAddress.GetAddressBytes();
-
- if ((octet[0] == 10) ||
- (octet[0] == 172 && (octet[1] >= 16 && octet[1] <= 31)) || // RFC1918
- (octet[0] == 192 && octet[1] == 168) || // RFC1918
- (octet[0] == 127) || // RFC1122
- (octet[0] == 169 && octet[1] == 254)) // RFC3927
- {
- return true;
- }
-
- if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint))
- {
- return true;
- }
-
- return false;
- }
-
- ///
- public bool IsInPrivateAddressSpaceAndLocalSubnet(string endpoint)
- {
- if (endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase))
- {
- var endpointFirstPart = endpoint.Split('.')[0];
-
- var subnets = GetSubnets(endpointFirstPart);
-
- foreach (var subnet_Match in subnets)
- {
- // logger.LogDebug("subnet_Match:" + subnet_Match);
-
- if (endpoint.StartsWith(subnet_Match + ".", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
- }
-
- return false;
- }
-
- // Gives a list of possible subnets from the system whose interface ip starts with endpointFirstPart
- private List GetSubnets(string endpointFirstPart)
- {
- lock (_subnetLookupLock)
- {
- if (_subnetLookup.TryGetValue(endpointFirstPart, out var subnets))
- {
- return subnets;
- }
-
- subnets = new List();
-
- foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
- {
- foreach (var unicastIPAddressInformation in adapter.GetIPProperties().UnicastAddresses)
- {
- if (unicastIPAddressInformation.Address.AddressFamily == AddressFamily.InterNetwork && endpointFirstPart == unicastIPAddressInformation.Address.ToString().Split('.')[0])
- {
- int subnet_Test = 0;
- foreach (string part in unicastIPAddressInformation.IPv4Mask.ToString().Split('.'))
- {
- if (part.Equals("0", StringComparison.Ordinal))
- {
- break;
- }
-
- subnet_Test++;
- }
-
- var subnet_Match = string.Join(".", unicastIPAddressInformation.Address.ToString().Split('.').Take(subnet_Test).ToArray());
-
- // TODO: Is this check necessary?
- if (adapter.OperationalStatus == OperationalStatus.Up)
- {
- subnets.Add(subnet_Match);
- }
- }
- }
- }
-
- _subnetLookup[endpointFirstPart] = subnets;
-
- return subnets;
- }
- }
-
- ///
- public bool IsInLocalNetwork(string endpoint)
- {
- return IsInLocalNetworkInternal(endpoint, true);
- }
-
- ///
- public bool IsAddressInSubnets(string addressString, string[] subnets)
- {
- return IsAddressInSubnets(IPAddress.Parse(addressString), addressString, subnets);
- }
-
- ///
- public bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC)
- {
- byte[] octet = address.GetAddressBytes();
-
- if ((octet[0] == 127) || // RFC1122
- (octet[0] == 169 && octet[1] == 254)) // RFC3927
- {
- // don't use on loopback or 169 interfaces
- return false;
- }
-
- string addressString = address.ToString();
- string excludeAddress = "[" + addressString + "]";
- var subnets = LocalSubnetsFn();
-
- // Include any address if LAN subnets aren't specified
- if (subnets.Length == 0)
- {
- return true;
- }
-
- // Exclude any addresses if they appear in the LAN list in [ ]
- if (Array.IndexOf(subnets, excludeAddress) != -1)
- {
- return false;
- }
-
- return IsAddressInSubnets(address, addressString, subnets);
- }
-
- ///
- /// Checks if the give address falls within the ranges given in [subnets]. The addresses in subnets can be hosts or subnets in the CIDR format.
- ///
- /// IPAddress version of the address.
- /// 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.
- /// falseif the address isn't in the subnets, true otherwise.
- private static bool IsAddressInSubnets(IPAddress address, string addressString, string[] subnets)
- {
- foreach (var subnet in subnets)
- {
- var normalizedSubnet = subnet.Trim();
- // Is the subnet a host address and does it match the address being passes?
- if (string.Equals(normalizedSubnet, addressString, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- // Parse CIDR subnets and see if address falls within it.
- if (normalizedSubnet.Contains('/', StringComparison.Ordinal))
- {
- try
- {
- var ipNetwork = IPNetwork.Parse(normalizedSubnet);
- if (ipNetwork.Contains(address))
- {
- return true;
- }
- }
- catch
- {
- // Ignoring - invalid subnet passed encountered.
- }
- }
- }
-
- return false;
- }
-
- private bool IsInLocalNetworkInternal(string endpoint, bool resolveHost)
- {
- if (string.IsNullOrEmpty(endpoint))
- {
- throw new ArgumentNullException(nameof(endpoint));
- }
-
- if (IPAddress.TryParse(endpoint, out var address))
- {
- var addressString = address.ToString();
-
- var localSubnetsFn = LocalSubnetsFn;
- if (localSubnetsFn != null)
- {
- var localSubnets = localSubnetsFn();
- foreach (var subnet in localSubnets)
- {
- // Only validate if there's at least one valid entry.
- if (!string.IsNullOrWhiteSpace(subnet))
- {
- return IsAddressInSubnets(address, addressString, localSubnets) || IsInPrivateAddressSpace(addressString, false);
- }
- }
- }
-
- int lengthMatch = 100;
- if (address.AddressFamily == AddressFamily.InterNetwork)
- {
- lengthMatch = 4;
- if (IsInPrivateAddressSpace(addressString, true))
- {
- return true;
- }
- }
- else if (address.AddressFamily == AddressFamily.InterNetworkV6)
- {
- lengthMatch = 9;
- if (IsInPrivateAddressSpace(endpoint, true))
- {
- return true;
- }
- }
-
- // Should be even be doing this with ipv6?
- if (addressString.Length >= lengthMatch)
- {
- var prefix = addressString.Substring(0, lengthMatch);
-
- if (GetLocalIpAddresses().Any(i => i.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
- {
- return true;
- }
- }
- }
- else if (resolveHost)
- {
- if (Uri.TryCreate(endpoint, UriKind.RelativeOrAbsolute, out var uri))
- {
- try
- {
- var host = uri.DnsSafeHost;
- _logger.LogDebug("Resolving host {0}", host);
-
- address = GetIpAddresses(host).GetAwaiter().GetResult().FirstOrDefault();
-
- if (address != null)
- {
- _logger.LogDebug("{0} resolved to {1}", host, address);
-
- return IsInLocalNetworkInternal(address.ToString(), false);
- }
- }
- catch (InvalidOperationException)
- {
- // Can happen with reverse proxy or IIS url rewriting?
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error resolving hostname");
- }
- }
- }
-
- return false;
- }
-
- private static Task GetIpAddresses(string hostName)
- {
- return Dns.GetHostAddressesAsync(hostName);
- }
-
- private IEnumerable GetIPsDefault()
- {
- IEnumerable interfaces;
-
- try
- {
- interfaces = NetworkInterface.GetAllNetworkInterfaces()
- .Where(x => x.OperationalStatus == OperationalStatus.Up
- || x.OperationalStatus == OperationalStatus.Unknown);
- }
- catch (NetworkInformationException ex)
- {
- _logger.LogError(ex, "Error in GetAllNetworkInterfaces");
- return Enumerable.Empty();
- }
-
- return interfaces.SelectMany(network =>
- {
- var ipProperties = network.GetIPProperties();
-
- // Exclude any addresses if they appear in the LAN list in [ ]
-
- return ipProperties.UnicastAddresses
- .Select(i => i.Address)
- .Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6);
- }).GroupBy(i => i.ToString())
- .Select(x => x.First());
- }
-
- private static async Task> GetLocalIpAddressesFallback()
- {
- var host = await Dns.GetHostEntryAsync(Dns.GetHostName()).ConfigureAwait(false);
-
- // Reverse them because the last one is usually the correct one
- // It's not fool-proof so ultimately the consumer will have to examine them and decide
- return host.AddressList
- .Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6)
- .Reverse();
- }
-
- ///
- /// Gets a random port number that is currently available.
- ///
- /// System.Int32.
- public int GetRandomUnusedTcpPort()
- {
- var listener = new TcpListener(IPAddress.Any, 0);
- listener.Start();
- var port = ((IPEndPoint)listener.LocalEndpoint).Port;
- listener.Stop();
- return port;
- }
-
- ///
- public int GetRandomUnusedUdpPort()
- {
- var localEndPoint = new IPEndPoint(IPAddress.Any, 0);
- using (var udpClient = new UdpClient(localEndPoint))
- {
- return ((IPEndPoint)udpClient.Client.LocalEndPoint).Port;
- }
- }
-
- ///
- public List GetMacAddresses()
- {
- return _macAddresses ??= GetMacAddressesInternal().ToList();
- }
-
- private static IEnumerable GetMacAddressesInternal()
- => NetworkInterface.GetAllNetworkInterfaces()
- .Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback)
- .Select(x => x.GetPhysicalAddress())
- .Where(x => !x.Equals(PhysicalAddress.None));
-
- ///
- public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
- {
- IPAddress network1 = GetNetworkAddress(address1, subnetMask);
- IPAddress network2 = GetNetworkAddress(address2, subnetMask);
- return network1.Equals(network2);
- }
-
- private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
- {
- byte[] ipAdressBytes = address.GetAddressBytes();
- byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
-
- if (ipAdressBytes.Length != subnetMaskBytes.Length)
- {
- throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
- }
-
- byte[] broadcastAddress = new byte[ipAdressBytes.Length];
- for (int i = 0; i < broadcastAddress.Length; i++)
- {
- broadcastAddress[i] = (byte)(ipAdressBytes[i] & subnetMaskBytes[i]);
- }
-
- return new IPAddress(broadcastAddress);
- }
-
- ///
- public IPAddress GetLocalIpSubnetMask(IPAddress address)
- {
- NetworkInterface[] interfaces;
-
- try
- {
- var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown };
-
- interfaces = NetworkInterface.GetAllNetworkInterfaces()
- .Where(i => validStatuses.Contains(i.OperationalStatus))
- .ToArray();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error in GetAllNetworkInterfaces");
- return null;
- }
-
- foreach (NetworkInterface ni in interfaces)
- {
- foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
- {
- if (ip.Address.Equals(address) && ip.IPv4Mask != null)
- {
- return ip.IPv4Mask;
- }
- }
- }
-
- return null;
- }
- }
-}
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs
index b7a59cee2d..3dc34da5cf 100644
--- a/Emby.Server.Implementations/Udp/UdpServer.cs
+++ b/Emby.Server.Implementations/Udp/UdpServer.cs
@@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Udp
{
string localUrl = !string.IsNullOrEmpty(_config[AddressOverrideConfigKey])
? _config[AddressOverrideConfigKey]
- : await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
+ : _appHost.GetSmartApiUrl(string.Empty); // MIGRATION: Temp value.
if (!string.IsNullOrEmpty(localUrl))
{
diff --git a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
index d732b6bc6a..08746b346b 100644
--- a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
+++ b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
@@ -1,6 +1,7 @@
-using System.Security.Claims;
+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 b5913daab9..69e6a8fb2d 100644
--- a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs
+++ b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+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 b61680ab1a..d1297119cd 100644
--- a/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs
+++ b/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+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 31482a930f..53b5d47787 100644
--- a/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs
+++ b/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+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 9815e252ee..abdf2858d0 100644
--- a/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs
+++ b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs
@@ -1,4 +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 decbe0c035..ada8a0d4ec 100644
--- a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs
+++ b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs
@@ -1,5 +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 5213bc4cb7..475e3cdac4 100644
--- a/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs
+++ b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+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 14722aa57e..d022c9067f 100644
--- a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs
+++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs
@@ -1,5 +1,6 @@
-using System.Threading.Tasks;
+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 af73352bcc..418d63de68 100644
--- a/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs
+++ b/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+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 b235c4b63b..a1cddbca3e 100644
--- a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs
+++ b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs
@@ -1,5 +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 4cb1984a2f..6876b47b44 100644
--- a/Jellyfin.Api/Controllers/SystemController.cs
+++ b/Jellyfin.Api/Controllers/SystemController.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
@@ -8,6 +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;
@@ -64,9 +65,9 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Info")]
[Authorize(Policy = Policies.FirstTimeSetupOrIgnoreParentalControl)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task> GetSystemInfo()
+ public ActionResult GetSystemInfo()
{
- return await _appHost.GetSystemInfo(CancellationToken.None).ConfigureAwait(false);
+ return _appHost.GetSystemInfo(Request.HttpContext.Connection.RemoteIpAddress);
}
///
@@ -76,9 +77,9 @@ namespace Jellyfin.Api.Controllers
/// A with public info about the system.
[HttpGet("Info/Public")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task> GetPublicSystemInfo()
+ public ActionResult GetPublicSystemInfo()
{
- return await _appHost.GetPublicSystemInfo(CancellationToken.None).ConfigureAwait(false);
+ return _appHost.GetPublicSystemInfo(Request.HttpContext.Connection.RemoteIpAddress);
}
///
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 630e9df6ac..152e650bc0 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
@@ -7,6 +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 af0519ffa8..3be8734b94 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -8,6 +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 1207fb5134..d63e3ab118 100644
--- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs
+++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Globalization;
using System.Linq;
using System.Text.Json;
@@ -6,6 +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/Jellyfin.Networking.csproj b/Jellyfin.Networking/Jellyfin.Networking.csproj
new file mode 100644
index 0000000000..b6834cb0ee
--- /dev/null
+++ b/Jellyfin.Networking/Jellyfin.Networking.csproj
@@ -0,0 +1,39 @@
+
+
+ Jellyfin.Networking
+ Library
+ netstandard2.1
+ false
+ true
+ true
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ../jellyfin.ruleset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Jellyfin.Networking/Manager/INetworkManager.cs b/Jellyfin.Networking/Manager/INetworkManager.cs
new file mode 100644
index 0000000000..ba571750b9
--- /dev/null
+++ b/Jellyfin.Networking/Manager/INetworkManager.cs
@@ -0,0 +1,189 @@
+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
new file mode 100644
index 0000000000..36a0a94a09
--- /dev/null
+++ b/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -0,0 +1,1203 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using NetworkCollection;
+
+namespace Jellyfin.Networking.Manager
+{
+ ///
+ /// Class to take care of network interface management.
+ ///
+ public class NetworkManager : INetworkManager, IDisposable
+ {
+ private static NetworkManager? _instance;
+
+ ///
+ /// Contains the description of the interface along with its index.
+ ///
+ private readonly SortedList _interfaceNames;
+
+ ///
+ /// Threading lock for network interfaces.
+ ///
+ private readonly object _intLock = new object();
+
+ ///
+ /// List of all interface addresses and masks.
+ ///
+ private readonly NetCollection _interfaceAddresses;
+
+ ///
+ /// List of all interface MAC addresses.
+ ///
+ private readonly List _macAddresses;
+
+ private readonly ILogger _logger;
+
+ private readonly IConfigurationManager _configurationManager;
+
+ ///
+ /// Holds the bind address overrides.
+ ///
+ private readonly Dictionary _overrideUrls;
+
+ ///
+ /// Used to stop "event-racing conditions".
+ ///
+ private bool _eventfire;
+
+ ///
+ /// Unfiltered user defined LAN subnets. (Configuration.LocalNetworkSubnets).
+ /// or internal interface network subnets if undefined by user.
+ ///
+ private NetCollection _lanSubnets;
+
+ ///
+ /// User defined list of subnets to excluded from the LAN.
+ ///
+ private NetCollection _excludedSubnets;
+
+ ///
+ /// List of interface addresses to bind the WS.
+ ///
+ private NetCollection _bindAddresses;
+
+ ///
+ /// List of interface addresses to exclude from bind.
+ ///
+ private NetCollection _bindExclusions;
+
+ ///
+ /// Caches list of all internal filtered interface addresses and masks.
+ ///
+ private NetCollection _internalInterfaces;
+
+ ///
+ /// Flag set when no custom LAN has been defined in the config.
+ ///
+ private bool _usingPrivateAddresses;
+
+ ///
+ /// True if this object is disposed.
+ ///
+ private bool _disposed;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// 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.
+ public NetworkManager(IConfigurationManager configurationManager, ILogger logger)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _configurationManager = configurationManager ?? throw new ArgumentNullException(nameof(configurationManager));
+
+ _interfaceAddresses = new NetCollection(unique: false);
+ _macAddresses = new List();
+ _interfaceNames = new SortedList();
+ _overrideUrls = 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;
+
+ _configurationManager.ConfigurationUpdated += ConfigurationUpdated;
+
+ Instance = this;
+ }
+#pragma warning restore CS8618 // Non-nullable field is uninitialized.
+
+ ///
+ /// Event triggered on network changes.
+ ///
+ 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.
+ ///
+ public static string NetworkLocationSignature { get; internal set; } = Guid.NewGuid().ToString();
+
+ ///
+ /// Gets a value indicating whether IP6 is enabled.
+ ///
+ public static bool IsIP6Enabled { get; internal set; }
+
+ ///
+ /// Gets 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 NetCollection RemoteAddressFilter { get; private set; }
+
+ ///
+ /// Gets a value indicating whether is all IPv6 interfaces are trusted as internal.
+ ///
+ public bool TrustAllIP6Interfaces { get; internal set; }
+
+ ///
+ /// Gets the Published server override list.
+ ///
+ public Dictionary PublishedServerOverrides => _overrideUrls;
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ public List GetMacAddresses()
+ {
+ // Populated in construction - so always has values.
+ lock (_intLock)
+ {
+ return _macAddresses.ToList();
+ }
+ }
+
+ ///
+ public bool IsGatewayInterface(object? addressObj)
+ {
+ var address = (addressObj is IPAddress addressIP) ?
+ addressIP : (addressObj is IPObject addressIPObj) ?
+ addressIPObj.Address : IPAddress.None;
+
+ lock (_intLock)
+ {
+ return _internalInterfaces.Where(i => i.Address.Equals(address) && (i.Tag < 0)).Any();
+ }
+ }
+
+ ///
+ public NetCollection GetLoopbacks()
+ {
+ NetCollection nc = new NetCollection();
+ if (IsIP4Enabled)
+ {
+ nc.Add(IPAddress.Loopback);
+ }
+
+ if (IsIP6Enabled)
+ {
+ nc.Add(IPAddress.IPv6Loopback);
+ }
+
+ return nc;
+ }
+
+ ///
+ public bool IsExcluded(IPAddress ip)
+ {
+ return _excludedSubnets.Contains(ip);
+ }
+
+ ///
+ public bool IsExcluded(EndPoint ip)
+ {
+ if (ip != null)
+ {
+ return _excludedSubnets.Contains(((IPEndPoint)ip).Address);
+ }
+
+ return false;
+ }
+
+ ///
+ public NetCollection CreateIPCollection(string[] values, bool bracketed = false)
+ {
+ NetCollection col = new NetCollection();
+ if (values != null)
+ {
+ for (int a = 0; a < values.Length; a++)
+ {
+ string v = values[a].Trim();
+
+ try
+ {
+ if (v.StartsWith("[", StringComparison.OrdinalIgnoreCase) && v.EndsWith("]", StringComparison.OrdinalIgnoreCase))
+ {
+ if (bracketed)
+ {
+ 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)
+ {
+ AddToCollection(col, v);
+ }
+ }
+ catch (ArgumentException e)
+ {
+ _logger.LogInformation("Ignoring LAN value {value}. Reason : {reason}", v, e.Message);
+ }
+ }
+ }
+
+ return col;
+ }
+
+ ///
+ public NetCollection GetAllBindInterfaces()
+ {
+ lock (_intLock)
+ {
+ int count = _bindAddresses.Count;
+
+ if (count == 0)
+ {
+ if (_bindExclusions.Count > 0)
+ {
+ // Return all the interfaces except the ones specifically excluded.
+ return _interfaceAddresses.Exclude(_bindExclusions);
+ }
+
+ // No bind address and no exclusions, so listen on all interfaces.
+ NetCollection result = new NetCollection();
+
+ if (IsIP4Enabled)
+ {
+ result.Add(IPAddress.Any);
+ }
+
+ if (IsIP6Enabled)
+ {
+ result.Add(IPAddress.IPv6Any);
+ }
+
+ return result;
+ }
+
+ // Remove any excluded bind interfaces.
+ return _bindAddresses.Exclude(_bindExclusions);
+ }
+ }
+
+ ///
+ public string GetBindInterface(object? 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)
+ {
+ port = sourceReq.Host.Port;
+ if (IPHost.TryParse(sourceReq.Host.Host, out IPHost host))
+ {
+ 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;
+ }
+
+ if (IPHost.TryParse(sourceStr, out IPHost host))
+ {
+ sourceAddr = host;
+ }
+ else
+ {
+ // Assume it's external, as we cannot resolve the host.
+ sourceAddr = IPHost.None;
+ }
+ }
+ else if (source is IPAddress sourceIP)
+ {
+ sourceAddr = new IPNetAddress(sourceIP);
+ }
+ else
+ {
+ // If we have no idea, then assume it came from an external address.
+ sourceAddr = IPHost.None;
+ }
+
+ // Do we have a source?
+ bool haveSource = !sourceAddr.Address.Equals(IPAddress.None);
+
+ if (haveSource)
+ {
+ if (!IsIP6Enabled && sourceAddr.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)
+ {
+ _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);
+
+ string bindPreference = string.Empty;
+ if (haveSource)
+ {
+ // Check for user override.
+ foreach (var addr in _overrideUrls)
+ {
+ // 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.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)
+ {
+ // 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;
+ }
+
+ if (isExternal)
+ {
+ // 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);
+ }
+
+ // Get the first LAN interface address that isn't a loopback.
+ var result = _interfaceAddresses
+ .Exclude(_bindExclusions)
+ .Where(p => IsInLocalNetwork(p))
+ .OrderBy(p => p.Tag);
+
+ if (result.Any())
+ {
+ 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)
+ {
+ if (intf.Contains(sourceAddr))
+ {
+ ipresult = FormatIP6String(intf.Address);
+ _logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", sourceAddr, ipresult);
+ return ipresult;
+ }
+ }
+ }
+
+ ipresult = FormatIP6String(result.First().Address);
+ _logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", sourceAddr, ipresult);
+ return ipresult;
+ }
+
+ // 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;
+ }
+ }
+
+ ///
+ public NetCollection GetInternalBindAddresses()
+ {
+ lock (_intLock)
+ {
+ int count = _bindAddresses.Count;
+
+ if (count == 0)
+ {
+ if (_bindExclusions.Count > 0)
+ {
+ // Return all the internal interfaces except the ones excluded.
+ return new NetCollection(_internalInterfaces.Where(p => !_bindExclusions.Contains(p)));
+ }
+
+ // No bind address, so return all internal interfaces.
+ return new NetCollection(_internalInterfaces.Where(p => !p.IsLoopback()));
+ }
+
+ return new NetCollection(_bindAddresses.Where(p => !p.IsLoopback()));
+ }
+ }
+
+ ///
+ public bool IsInLocalNetwork(IPObject address)
+ {
+ if (address == null)
+ {
+ throw new ArgumentNullException(nameof(address));
+ }
+
+ if (address.Equals(IPAddress.None))
+ {
+ return false;
+ }
+
+ // See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
+ if (TrustAllIP6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ return true;
+ }
+
+ lock (_intLock)
+ {
+ // As private addresses can be redefined by Configuration.LocalNetworkAddresses
+ return _lanSubnets.Contains(address) && !_excludedSubnets.Contains(address);
+ }
+ }
+
+ ///
+ public bool IsInLocalNetwork(string address)
+ {
+ if (IPHost.TryParse(address, out IPHost ep))
+ {
+ lock (_intLock)
+ {
+ return _lanSubnets.Contains(ep) && !_excludedSubnets.Contains(ep);
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ public bool IsInLocalNetwork(IPAddress address)
+ {
+ if (address == null)
+ {
+ throw new ArgumentNullException(nameof(address));
+ }
+
+ // See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
+ if (TrustAllIP6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ return true;
+ }
+
+ lock (_intLock)
+ {
+ // As private addresses can be redefined by Configuration.LocalNetworkAddresses
+ return _lanSubnets.Contains(address) && !_excludedSubnets.Contains(address);
+ }
+ }
+
+ ///
+ public bool IsPrivateAddressRange(IPObject address)
+ {
+ if (address == null)
+ {
+ throw new ArgumentNullException(nameof(address));
+ }
+
+ // See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
+ if (TrustAllIP6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ return true;
+ }
+ else
+ {
+ return address.IsPrivateAddressRange();
+ }
+ }
+
+ ///
+ public bool IsExcludedInterface(IPAddress address)
+ {
+ lock (_intLock)
+ {
+ if (_bindExclusions.Count > 0)
+ {
+ return _bindExclusions.Contains(address);
+ }
+
+ return false;
+ }
+ }
+
+ ///
+ public NetCollection GetFilteredLANSubnets(NetCollection? filter = null)
+ {
+ lock (_intLock)
+ {
+ if (filter == null)
+ {
+ return NetCollection.AsNetworks(_lanSubnets.Exclude(_excludedSubnets));
+ }
+
+ return _lanSubnets.Exclude(filter);
+ }
+ }
+
+ ///
+ public bool IsValidInterfaceAddress(IPAddress address)
+ {
+ lock (_intLock)
+ {
+ return _interfaceAddresses.Contains(address);
+ }
+ }
+
+ ///
+ public bool TryParseInterface(string token, out IPNetAddress result)
+ {
+ if (string.IsNullOrEmpty(token))
+ {
+ result = IPNetAddress.None;
+ return false;
+ }
+
+ if (_interfaceNames != null && _interfaceNames.TryGetValue(token.ToLower(CultureInfo.InvariantCulture), out int index))
+ {
+ _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)))
+ {
+ result = iface;
+ return true;
+ }
+ }
+ }
+
+ return IPNetAddress.TryParse(token, out result);
+ }
+
+ ///
+ /// Reloads all settings and re-initialises the instance.
+ ///
+ /// to use.
+ public void UpdateSettings(ServerConfiguration config)
+ {
+ if (config == null)
+ {
+ throw new ArgumentNullException(nameof(config));
+ }
+
+ IsIP4Enabled = Socket.OSSupportsIPv6 && config.EnableIPV4;
+ IsIP6Enabled = Socket.OSSupportsIPv6 && config.EnableIPV6;
+ TrustAllIP6Interfaces = config.TrustAllIP6Interfaces;
+ EnableMultiSocketBinding = config.EnableMultiSocketBinding;
+
+ InitialiseInterfaces();
+ InitialiseLAN(config);
+ InitialiseBind(config);
+ InitialiseRemote(config);
+ InitialiseOverrides(config);
+ }
+
+ ///
+ /// Protected implementation of Dispose pattern.
+ ///
+ /// True to dispose the managed state.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _configurationManager.ConfigurationUpdated -= ConfigurationUpdated;
+ NetworkChange.NetworkAddressChanged -= OnNetworkAddressChanged;
+ NetworkChange.NetworkAvailabilityChanged -= OnNetworkAvailabilityChanged;
+ }
+
+ _disposed = true;
+ }
+ }
+
+ 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);
+ }
+
+ ///
+ /// Converts an IPAddress into a string.
+ /// Ipv6 addresses are returned in [ ], with their scope removed.
+ ///
+ /// Address to convert.
+ /// URI save conversion of the address.
+ private string FormatIP6String(IPAddress address)
+ {
+ var str = address.ToString();
+ if (address.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ int i = str.IndexOf("%", StringComparison.OrdinalIgnoreCase);
+
+ if (i != -1)
+ {
+ str = str.Substring(0, i);
+ }
+
+ return $"[{str}]";
+ }
+
+ return str;
+ }
+
+ ///
+ /// Parses strings into the collection, replacing any interface references.
+ ///
+ /// Collection.
+ /// String to parse.
+ private void AddToCollection(NetCollection 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.
+ if (_interfaceNames != null && _interfaceNames.TryGetValue(token.ToLower(CultureInfo.InvariantCulture), out int index))
+ {
+ _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)))
+ {
+ col.Add(iface);
+ }
+ }
+ }
+ else if (NetCollection.TryParse(token, out IPObject obj))
+ {
+ if (!IsIP6Enabled)
+ {
+ // Remove IP6 addresses from multi-homed IPHosts.
+ obj.Remove(AddressFamily.InterNetworkV6);
+ if (!obj.IsIP6())
+ {
+ col.Add(obj);
+ }
+ }
+ else if (!IsIP4Enabled)
+ {
+ // Remove IP4 addresses from multi-homed IPHosts.
+ obj.Remove(AddressFamily.InterNetwork);
+ if (obj.IsIP6())
+ {
+ col.Add(obj);
+ }
+ }
+ else
+ {
+ col.Add(obj);
+ }
+ }
+ else
+ {
+ _logger.LogDebug("Invalid or unknown network {0}.", token);
+ }
+ }
+
+ ///
+ /// Handler for network change events.
+ ///
+ /// Sender.
+ /// Network availablity information.
+ private void OnNetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs e)
+ {
+ _logger.LogDebug("Network availability changed.");
+ OnNetworkChanged();
+ }
+
+ ///
+ /// Handler for network change events.
+ ///
+ /// Sender.
+ /// Event arguments.
+ private void OnNetworkAddressChanged(object? sender, EventArgs e)
+ {
+ _logger.LogDebug("Network address change detected.");
+ OnNetworkChanged();
+ }
+
+ ///
+ /// 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.
+ private async Task OnNetworkChangeAsync()
+ {
+ try
+ {
+ await Task.Delay(2000).ConfigureAwait(false);
+ InitialiseInterfaces();
+ // Recalculate LAN caches.
+ InitialiseLAN((ServerConfiguration)_configurationManager.CommonConfiguration);
+
+ NetworkChanged?.Invoke(this, EventArgs.Empty);
+ }
+ finally
+ {
+ _eventfire = false;
+ }
+ }
+
+ ///
+ /// Triggers our event, and re-loads interface information.
+ ///
+ 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.");
+ // As network events tend to fire one after the other only fire once every second.
+ _eventfire = true;
+ _ = OnNetworkChangeAsync();
+ }
+ }
+
+ ///
+ /// Parses the user defined overrides into the dictionary object.
+ /// Overrides are the equivalent of localised publishedServerUrl, enabling
+ /// different addresses to be advertised over different subnets.
+ /// format is subnet=ipaddress|host|uri
+ /// when subnet = 0.0.0.0, any external address matches.
+ ///
+ private void InitialiseOverrides(ServerConfiguration config)
+ {
+ string[] overrides = config.PublishedServerUriBySubnet;
+ if (overrides == null)
+ {
+ lock (_intLock)
+ {
+ _overrideUrls.Clear();
+ }
+
+ return;
+ }
+
+ lock (_intLock)
+ {
+ _overrideUrls.Clear();
+
+ foreach (var entry in overrides)
+ {
+ var parts = entry.Split('=');
+ if (parts.Length != 2)
+ {
+ _logger.LogError("Unable to parse bind override. {0}", entry);
+ }
+ else
+ {
+ var replacement = parts[1].Trim();
+ if (string.Equals(parts[0], "remaining", StringComparison.OrdinalIgnoreCase))
+ {
+ _overrideUrls[new IPNetAddress(IPAddress.Broadcast)] = replacement;
+ }
+ else if (string.Equals(parts[0], "external", StringComparison.OrdinalIgnoreCase))
+ {
+ _overrideUrls[new IPNetAddress(IPAddress.Any)] = replacement;
+ }
+ else if (TryParseInterface(parts[0], out IPNetAddress address))
+ {
+ _overrideUrls[address] = replacement;
+ }
+ else
+ {
+ _logger.LogError("Unable to parse bind ip address. {0}", parts[1]);
+ }
+ }
+ }
+ }
+ }
+
+ private void InitialiseBind(ServerConfiguration config)
+ {
+ string[] ba = config.LocalNetworkAddresses;
+
+ // TODO: remove when bug fixed: https://github.com/jellyfin/jellyfin-web/issues/1334
+
+ if (ba.Length == 1 && ba[0].IndexOf(',', StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ ba = ba[0].Split(',');
+ }
+
+ // TODO: end fix.
+
+ // Read and parse bind addresses and exclusions, removing ones that don't exist.
+ _bindAddresses = CreateIPCollection(ba).Union(_interfaceAddresses);
+ _bindExclusions = CreateIPCollection(ba, true).Union(_interfaceAddresses);
+ _logger.LogInformation("Using bind addresses: {0}", _bindAddresses);
+ _logger.LogInformation("Using bind exclusions: {0}", _bindExclusions);
+ }
+
+ private void InitialiseRemote(ServerConfiguration config)
+ {
+ RemoteAddressFilter = CreateIPCollection(config.RemoteIPFilter);
+ }
+
+ ///
+ /// Initialises internal LAN cache settings.
+ ///
+ private void InitialiseLAN(ServerConfiguration config)
+ {
+ lock (_intLock)
+ {
+ _logger.LogDebug("Refreshing LAN information.");
+
+ // Get config options.
+ string[] subnets = config.LocalNetworkSubnets;
+
+ // Create lists from user settings.
+
+ _lanSubnets = CreateIPCollection(subnets);
+ _excludedSubnets = NetCollection.AsNetworks(CreateIPCollection(subnets, true));
+
+ // 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.
+ if (_usingPrivateAddresses)
+ {
+ _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)));
+
+ // Subnets are the same as the calculated internal interface.
+ _lanSubnets = new NetCollection();
+
+ // 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
+ }
+
+ 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"));
+ }
+ }
+ else
+ {
+ // We must listen on loopback for LiveTV to function regardless of the settings.
+ if (IsIP6Enabled)
+ {
+ _lanSubnets.Add(IPNetAddress.IP6Loopback);
+ }
+
+ if (IsIP4Enabled)
+ {
+ _lanSubnets.Add(IPNetAddress.IP4Loopback);
+ }
+
+ // 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)));
+ }
+
+ _logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets);
+ _logger.LogInformation("Defined LAN exclusions : {0}", _excludedSubnets);
+ _logger.LogInformation("Using LAN addresses: {0}", NetCollection.AsNetworks(_lanSubnets.Exclude(_excludedSubnets)));
+ }
+ }
+
+ ///
+ /// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
+ /// Generate a list of all active mac addresses that aren't loopback addreses.
+ ///
+ private void InitialiseInterfaces()
+ {
+ lock (_intLock)
+ {
+ _logger.LogDebug("Refreshing interfaces.");
+
+ _interfaceNames.Clear();
+ _interfaceAddresses.Clear();
+
+ try
+ {
+ IEnumerable nics = NetworkInterface.GetAllNetworkInterfaces()
+ .Where(i => i.SupportsMulticast && i.OperationalStatus == OperationalStatus.Up);
+
+ foreach (NetworkInterface adapter in nics)
+ {
+ try
+ {
+ IPInterfaceProperties ipProperties = adapter.GetIPProperties();
+ PhysicalAddress mac = adapter.GetPhysicalAddress();
+
+ // populate mac list
+ if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && mac != null && mac != PhysicalAddress.None)
+ {
+ _macAddresses.Add(mac);
+ }
+
+ // populate interface address list
+ foreach (UnicastIPAddressInformation info in ipProperties.UnicastAddresses)
+ {
+ if (IsIP4Enabled && info.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ IPNetAddress nw = new IPNetAddress(info.Address, info.IPv4Mask)
+ {
+ // Keep the number of gateways on this interface, along with its index.
+ Tag = ipProperties.GetIPv4Properties().Index
+ };
+
+ 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())
+ {
+ // -ve Tags signify the interface has a gateway.
+ nw.Tag *= -1;
+ }
+
+ _interfaceAddresses.Add(nw);
+
+ // Store interface name so we can use the name in Collections.
+ _interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
+ _interfaceNames["eth" + tag.ToString(CultureInfo.InvariantCulture)] = tag;
+ }
+ else if (IsIP6Enabled && info.Address.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ IPNetAddress nw = new IPNetAddress(info.Address, (byte)info.PrefixLength)
+ {
+ // Keep the number of gateways on this interface, along with its index.
+ Tag = ipProperties.GetIPv6Properties().Index
+ };
+
+ 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())
+ {
+ // -ve Tags signify the interface has a gateway.
+ nw.Tag *= -1;
+ }
+
+ _interfaceAddresses.Add(nw);
+
+ // Store interface name so we can use the name in Collections.
+ _interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
+ _interfaceNames["eth" + tag.ToString(CultureInfo.InvariantCulture)] = tag;
+ }
+ }
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch
+ {
+ // Ignore error, and attempt to continue.
+ }
+#pragma warning restore CA1031 // Do not catch general exception types
+ }
+
+ _logger.LogDebug("Discovered {0} interfaces.", _interfaceAddresses.Count);
+ _logger.LogDebug("Interfaces addresses : {0}", _interfaceAddresses);
+
+ // 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.");
+
+ IPHost host = new IPHost(Dns.GetHostName());
+ foreach (var a in host.GetAddresses())
+ {
+ _interfaceAddresses.Add(a);
+ }
+
+ if (_interfaceAddresses.Count == 0)
+ {
+ _logger.LogError("No interfaces information available. Resolving DNS name.");
+ // Last ditch attempt - use loopback address.
+ _interfaceAddresses.Add(IPNetAddress.IP4Loopback);
+ if (IsIP6Enabled)
+ {
+ _interfaceAddresses.Add(IPNetAddress.IP6Loopback);
+ }
+ }
+ }
+ }
+ catch (NetworkInformationException ex)
+ {
+ _logger.LogError(ex, "Error in InitialiseInterfaces.");
+ }
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 30ed3e6af3..335a1a9156 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -36,6 +36,7 @@
+
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 8f04baa089..8c19665cc1 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -1,4 +1,4 @@
-#nullable enable
+#nullable enable
#pragma warning disable CA1307
using System;
@@ -12,10 +12,10 @@ 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 8d569a779a..566ba0ad85 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -5,6 +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;
@@ -34,21 +35,18 @@ namespace Jellyfin.Server
/// The to be used by the .
/// The to be used by the .
/// The to be used by the .
- /// The to be used by the .
/// The to be used by the .
public CoreAppHost(
IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
IStartupOptions options,
IFileSystem fileSystem,
- INetworkManager networkManager,
IServiceCollection collection)
: base(
applicationPaths,
loggerFactory,
options,
fileSystem,
- networkManager,
collection)
{
}
diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs
index 4bda8f2737..110290027b 100644
--- a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs
+++ b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs
@@ -1,9 +1,11 @@
using System.Linq;
using System.Threading.Tasks;
+using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using Microsoft.AspNetCore.Http;
+using NetworkCollection;
namespace Jellyfin.Server.Middleware
{
@@ -38,36 +40,35 @@ namespace Jellyfin.Server.Middleware
return;
}
- var remoteIp = httpContext.GetNormalizedRemoteIp();
+ var remoteIp = httpContext.Connection.RemoteIpAddress;
if (serverConfigurationManager.Configuration.EnableRemoteAccess)
{
- var addressFilter = serverConfigurationManager.Configuration.RemoteIPFilter.Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
+ // 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;
- if (addressFilter.Length > 0 && !networkManager.IsInLocalNetwork(remoteIp))
+ if (remoteAddressFilter.Count > 0 && !networkManager.IsInLocalNetwork(remoteIp))
{
- if (serverConfigurationManager.Configuration.IsRemoteIPFilterBlacklist)
+ // remoteAddressFilter is a whitelist or blacklist.
+ bool isListed = remoteAddressFilter.Contains(remoteIp);
+ if (!serverConfigurationManager.Configuration.IsRemoteIPFilterBlacklist)
{
- if (networkManager.IsAddressInSubnets(remoteIp, addressFilter))
- {
- return;
- }
+ // Black list, so flip over.
+ isListed = !isListed;
}
- else
+
+ if (!isListed)
{
- if (!networkManager.IsAddressInSubnets(remoteIp, addressFilter))
- {
- return;
- }
+ // If your name isn't on the list, you arn't coming in.
+ return;
}
}
}
- else
+ else if (!networkManager.IsInLocalNetwork(remoteIp))
{
- if (!networkManager.IsInLocalNetwork(remoteIp))
- {
- return;
- }
+ // Remote not enabled. So everyone should be LAN.
+ return;
}
await _next(httpContext).ConfigureAwait(false);
diff --git a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs
index 9d795145aa..2ff6f8a765 100644
--- a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs
+++ b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
+using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using Microsoft.AspNetCore.Http;
@@ -32,32 +33,13 @@ namespace Jellyfin.Server.Middleware
/// The async task.
public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
{
- var currentHost = httpContext.Request.Host.ToString();
- var hosts = serverConfigurationManager
- .Configuration
- .LocalNetworkAddresses
- .Select(NormalizeConfiguredLocalAddress)
- .ToList();
+ var host = httpContext.Connection.RemoteIpAddress;
- if (hosts.Count == 0)
+ if (!networkManager.IsInLocalNetwork(host) && !serverConfigurationManager.Configuration.EnableRemoteAccess)
{
- await _next(httpContext).ConfigureAwait(false);
return;
}
- currentHost ??= string.Empty;
-
- if (networkManager.IsInPrivateAddressSpace(currentHost))
- {
- hosts.Add("localhost");
- hosts.Add("127.0.0.1");
-
- if (hosts.All(i => currentHost.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1))
- {
- return;
- }
- }
-
await _next(httpContext).ConfigureAwait(false);
}
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index c933d679f4..8549e39dbc 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -12,8 +12,8 @@ using System.Threading.Tasks;
using CommandLine;
using Emby.Server.Implementations;
using Emby.Server.Implementations.IO;
-using Emby.Server.Implementations.Networking;
using Jellyfin.Api.Controllers;
+using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Extensions;
using Microsoft.AspNetCore.Hosting;
@@ -24,6 +24,7 @@ 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;
@@ -161,7 +162,6 @@ namespace Jellyfin.Server
_loggerFactory,
options,
new ManagedFileSystem(_loggerFactory.CreateLogger(), appPaths),
- new NetworkManager(_loggerFactory.CreateLogger()),
serviceCollection);
try
@@ -272,57 +272,16 @@ namespace Jellyfin.Server
return builder
.UseKestrel((builderContext, options) =>
{
- var addresses = appHost.ServerConfigurationManager
- .Configuration
- .LocalNetworkAddresses
- .Select(x => appHost.NormalizeConfiguredLocalAddress(x))
- .Where(i => i != null)
- .ToHashSet();
- if (addresses.Count > 0 && !addresses.Contains(IPAddress.Any))
- {
- if (!addresses.Contains(IPAddress.Loopback))
- {
- // we must listen on loopback for LiveTV to function regardless of the settings
- addresses.Add(IPAddress.Loopback);
- }
+ NetCollection addresses = NetworkManager.Instance.GetAllBindInterfaces();
- foreach (var address in addresses)
- {
- _logger.LogInformation("Kestrel listening on {IpAddress}", address);
- options.Listen(address, appHost.HttpPort);
- if (appHost.ListenWithHttps)
- {
- options.Listen(address, appHost.HttpsPort, listenOptions =>
- {
- listenOptions.UseHttps(appHost.Certificate);
- listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
- });
- }
- else if (builderContext.HostingEnvironment.IsDevelopment())
- {
- try
- {
- options.Listen(address, appHost.HttpsPort, listenOptions =>
- {
- listenOptions.UseHttps();
- listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
- });
- }
- catch (InvalidOperationException ex)
- {
- _logger.LogError(ex, "Failed to listen to HTTPS using the ASP.NET Core HTTPS development certificate. Please ensure it has been installed and set as trusted.");
- }
- }
- }
- }
- else
+ bool flagged = false;
+ foreach (IPObject netAdd in addresses)
{
- _logger.LogInformation("Kestrel listening on all interfaces");
- options.ListenAnyIP(appHost.HttpPort);
-
+ _logger.LogInformation("Kestrel listening on {0}", netAdd);
+ options.Listen(netAdd.Address, appHost.HttpPort);
if (appHost.ListenWithHttps)
{
- options.ListenAnyIP(appHost.HttpsPort, listenOptions =>
+ options.Listen(netAdd.Address, appHost.HttpsPort, listenOptions =>
{
listenOptions.UseHttps(appHost.Certificate);
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
@@ -332,15 +291,19 @@ namespace Jellyfin.Server
{
try
{
- options.ListenAnyIP(appHost.HttpsPort, listenOptions =>
+ options.Listen(netAdd.Address, appHost.HttpsPort, listenOptions =>
{
listenOptions.UseHttps();
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
});
}
- catch (InvalidOperationException ex)
+ catch (InvalidOperationException)
{
- _logger.LogError(ex, "Failed to listen to HTTPS using the ASP.NET Core HTTPS development certificate. Please ensure it has been installed and set as trusted.");
+ if (!flagged)
+ {
+ _logger.LogWarning("Failed to listen to HTTPS using the ASP.NET Core HTTPS development certificate. Please ensure it has been installed and set as trusted.");
+ flagged = true;
+ }
}
}
}
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);
- }
-}
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index cfad17fb72..f147e6a86c 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -56,41 +56,27 @@ namespace MediaBrowser.Controller
///
/// Gets the system info.
///
+ /// The originator of the request.
/// SystemInfo.
- Task GetSystemInfo(CancellationToken cancellationToken);
+ SystemInfo GetSystemInfo(IPAddress source);
- Task GetPublicSystemInfo(CancellationToken cancellationToken);
-
- ///
- /// Gets all the local IP addresses of this API instance. Each address is validated by sending a 'ping' request
- /// to the API that should exist at the address.
- ///
- /// A cancellation token that can be used to cancel the task.
- /// A list containing all the local IP addresses of the server.
- Task> GetLocalIpAddresses(CancellationToken cancellationToken);
+ 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
- /// IP address that can be found via . HTTPS will be preferred when available.
+ /// HTTPS will be preferred when available.
///
- /// A cancellation token that can be used to cancel the task.
+ /// The source of the request.
/// The server URL.
- Task GetLocalApiUrl(CancellationToken cancellationToken);
+ string GetSmartApiUrl(object source);
///
- /// Gets a localhost URL that can be used to access the API using the loop-back IP address (127.0.0.1)
+ /// Gets a localhost URL that can be used to access the API using the loop-back IP address.
/// over HTTP (not HTTPS).
///
/// The API URL.
string GetLoopbackHttpApiUrl();
- ///
- /// Gets a local (LAN) URL that can be used to access the API. HTTPS will be preferred when available.
- ///
- /// The IP address to use as the hostname in the URL.
- /// The API URL.
- string GetLocalApiUrl(IPAddress address);
-
///
/// Gets a local (LAN) URL that can be used to access the API.
/// Note: if passing non-null scheme or port it is up to the caller to ensure they form the correct pair.
@@ -105,7 +91,7 @@ namespace MediaBrowser.Controller
/// preferring the HTTPS port, if available.
///
/// The API URL.
- string GetLocalApiUrl(ReadOnlySpan hostname, string scheme = null, int? port = null);
+ string GetLocalApiUrl(string hostname, string scheme = null, int? port = null);
///
/// Open a URL in an external browser window.
diff --git a/MediaBrowser.Model/Configuration/PathSubstitution.cs b/MediaBrowser.Model/Configuration/PathSubstitution.cs
new file mode 100644
index 0000000000..40eb36c2ea
--- /dev/null
+++ b/MediaBrowser.Model/Configuration/PathSubstitution.cs
@@ -0,0 +1,19 @@
+#nullable enable
+#pragma warning disable CS1591
+#pragma warning disable CA1819
+
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Updates;
+
+namespace MediaBrowser.Model.Configuration
+{
+
+ public class PathSubstitution
+ {
+ public string From { get; set; } = string.Empty;
+
+ public string To { get; set; } = string.Empty;
+ }
+}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 48d1a7346a..073a629829 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -1,5 +1,6 @@
#nullable disable
#pragma warning disable CS1591
+#pragma warning disable CA1819
using System;
using System.Collections.Generic;
@@ -15,41 +16,174 @@ namespace MediaBrowser.Model.Configuration
{
public const int DefaultHttpPort = 8096;
public const int DefaultHttpsPort = 8920;
- private string _baseUrl;
+ private string _baseUrl = string.Empty;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ServerConfiguration()
+ {
+ MetadataOptions = new[]
+ {
+ new MetadataOptions()
+ {
+ ItemType = "Book"
+ },
+ new MetadataOptions()
+ {
+ ItemType = "Movie"
+ },
+ new MetadataOptions
+ {
+ ItemType = "MusicVideo",
+ DisabledMetadataFetchers = new[] { "The Open Movie Database" },
+ DisabledImageFetchers = new[] { "The Open Movie Database" }
+ },
+ new MetadataOptions
+ {
+ ItemType = "Series",
+ DisabledMetadataFetchers = new[] { "TheMovieDb" },
+ DisabledImageFetchers = new[] { "TheMovieDb" }
+ },
+ new MetadataOptions
+ {
+ ItemType = "MusicAlbum",
+ DisabledMetadataFetchers = new[] { "TheAudioDB" }
+ },
+ new MetadataOptions
+ {
+ ItemType = "MusicArtist",
+ DisabledMetadataFetchers = new[] { "TheAudioDB" }
+ },
+ new MetadataOptions
+ {
+ ItemType = "BoxSet"
+ },
+ new MetadataOptions
+ {
+ ItemType = "Season",
+ DisabledMetadataFetchers = new[] { "TheMovieDb" },
+ },
+ new MetadataOptions
+ {
+ ItemType = "Episode",
+ DisabledMetadataFetchers = new[] { "The Open Movie Database", "TheMovieDb" },
+ DisabledImageFetchers = new[] { "The Open Movie Database", "TheMovieDb" }
+ }
+ };
+ }
///
/// Gets or sets a value indicating whether to enable automatic port forwarding.
///
- public bool EnableUPnP { get; set; }
+ public bool EnableUPnP { get; set; } = false;
///
/// Gets or sets a value indicating whether to enable prometheus metrics exporting.
///
- public bool EnableMetrics { get; set; }
+ public bool EnableMetrics { get; set; } = false;
///
/// Gets or sets the public mapped port.
///
/// The public mapped port.
- public int PublicPort { get; set; }
+ 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 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.
+ /// If the setting "Emby.Dlna": "Debug" msut be set in logging.default.json for this property to work.
+ ///
+ public bool EnableSSDPTracing { get; set; } = false;
+
+ ///
+ /// 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 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.
+ ///
+ 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 PublishedServerUri to advertise for specific subnets.
+ ///
+ public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty();
+
+ ///
+ /// Gets or sets a value indicating whether gets or sets Autodiscovery tracing.
+ ///
+ 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 public HTTPS port.
///
/// The public HTTPS port.
- public int PublicHttpsPort { get; set; }
+ public int PublicHttpsPort { get; set; } = DefaultHttpsPort;
///
/// Gets or sets the HTTP server port number.
///
/// The HTTP server port number.
- public int HttpServerPortNumber { get; set; }
+ public int HttpServerPortNumber { get; set; } = DefaultHttpPort;
///
/// Gets or sets the HTTPS server port number.
///
/// The HTTPS server port number.
- public int HttpsPortNumber { get; set; }
+ public int HttpsPortNumber { get; set; } = DefaultHttpsPort;
///
/// Gets or sets a value indicating whether to use HTTPS.
@@ -58,19 +192,19 @@ namespace MediaBrowser.Model.Configuration
/// 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; }
+ public bool EnableHttps { get; set; } = false;
- public bool EnableNormalizedItemByNameIds { get; set; }
+ public bool EnableNormalizedItemByNameIds { get; set; } = true;
///
/// Gets or sets the filesystem path of an X.509 certificate to use for SSL.
///
- public string CertificatePath { get; set; }
+ public string CertificatePath { get; set; } = string.Empty;
///
/// Gets or sets the password required to access the X.509 certificate data in the file specified by .
///
- public string CertificatePassword { get; set; }
+ public string CertificatePassword { get; set; } = string.Empty;
///
/// Gets or sets a value indicating whether this instance is port authorized.
@@ -79,92 +213,95 @@ namespace MediaBrowser.Model.Configuration
public bool IsPortAuthorized { get; set; }
///
- /// Gets or sets if quick connect is available for use on this server.
+ /// Gets or sets a value indicating whether quick connect is available for use on this server.
///
- public bool QuickConnectAvailable { get; set; }
+ public bool QuickConnectAvailable { get; set; } = false;
- public bool AutoRunWebApp { get; set; }
+ public bool AutoRunWebApp { get; set; } = true;
- public bool EnableRemoteAccess { get; set; }
+ ///
+ /// Gets or sets a value indicating whether access outside of the LAN is permitted.
+ ///
+ public bool EnableRemoteAccess { get; set; } = true;
///
/// Gets or sets a value indicating whether [enable case sensitive item ids].
///
/// true if [enable case sensitive item ids]; otherwise, false.
- public bool EnableCaseSensitiveItemIds { get; set; }
+ public bool EnableCaseSensitiveItemIds { get; set; } = true;
- public bool DisableLiveTvChannelUserDataName { get; set; }
+ public bool DisableLiveTvChannelUserDataName { get; set; } = true;
///
/// Gets or sets the metadata path.
///
/// The metadata path.
- public string MetadataPath { get; set; }
+ public string MetadataPath { get; set; } = string.Empty;
- public string MetadataNetworkPath { get; set; }
+ public string MetadataNetworkPath { get; set; } = string.Empty;
///
/// Gets or sets the preferred metadata language.
///
/// The preferred metadata language.
- public string PreferredMetadataLanguage { get; set; }
+ public string PreferredMetadataLanguage { get; set; } = string.Empty;
///
/// Gets or sets the metadata country code.
///
/// The metadata country code.
- public string MetadataCountryCode { get; set; }
+ public string MetadataCountryCode { get; set; } = "US";
///
- /// Characters to be replaced with a ' ' in strings to create a sort name.
+ /// Gets or sets characters to be replaced with a ' ' in strings to create a sort name.
///
/// The sort replace characters.
- public string[] SortReplaceCharacters { get; set; }
+ public string[] SortReplaceCharacters { get; set; } = new[] { ".", "+", "%" };
///
- /// Characters to be removed from strings to create a sort name.
+ /// Gets or sets characters to be removed from strings to create a sort name.
///
/// The sort remove characters.
- public string[] SortRemoveCharacters { get; set; }
+ public string[] SortRemoveCharacters { get; set; } = new[] { ",", "&", "-", "{", "}", "'" };
///
- /// Words to be removed from strings to create a sort name.
+ /// Gets or sets words to be removed from strings to create a sort name.
///
/// The sort remove words.
- public string[] SortRemoveWords { get; set; }
+ public string[] SortRemoveWords { get; set; } = new[] { "the", "a", "an" };
///
/// Gets or sets the minimum percentage of an item that must be played in order for playstate to be updated.
///
/// The min resume PCT.
- public int MinResumePct { get; set; }
+ public int MinResumePct { get; set; } = 5;
///
/// Gets or sets the maximum percentage of an item that can be played while still saving playstate. If this percentage is crossed playstate will be reset to the beginning and the item will be marked watched.
///
/// The max resume PCT.
- public int MaxResumePct { get; set; }
+ public int MaxResumePct { get; set; } = 90;
///
/// Gets or sets the minimum duration that an item must have in order to be eligible for playstate updates..
///
/// The min resume duration seconds.
- public int MinResumeDurationSeconds { get; set; }
+ public int MinResumeDurationSeconds { get; set; } = 300;
///
- /// The delay in seconds that we will wait after a file system change to try and discover what has been added/removed
+ /// Gets or sets the delay in seconds that we will wait after a file system change to try and discover what has been added/removed
/// Some delay is necessary with some items because their creation is not atomic. It involves the creation of several
/// different directories and files.
///
/// The file watcher delay.
- public int LibraryMonitorDelay { get; set; }
+ public int LibraryMonitorDelay { get; set; } = 60;
///
/// Gets or sets a value indicating whether [enable dashboard response caching].
/// Allows potential contributors without visual studio to modify production dashboard code and test changes.
///
/// true if [enable dashboard response caching]; otherwise, false.
- public bool EnableDashboardResponseCaching { get; set; }
+ public bool EnableDashboardResponseCaching { get; set; } = true;
///
/// Gets or sets the image saving convention.
@@ -174,9 +311,9 @@ namespace MediaBrowser.Model.Configuration
public MetadataOptions[] MetadataOptions { get; set; }
- public bool SkipDeserializationForBasicTypes { get; set; }
+ public bool SkipDeserializationForBasicTypes { get; set; } = true;
- public string ServerName { get; set; }
+ public string ServerName { get; set; } = string.Empty;
public string BaseUrl
{
@@ -208,189 +345,80 @@ namespace MediaBrowser.Model.Configuration
}
}
- public string UICulture { get; set; }
-
- public bool SaveMetadataHidden { get; set; }
+ public string UICulture { get; set; } = "en-US";
- public NameValuePair[] ContentTypes { get; set; }
+ public bool SaveMetadataHidden { get; set; } = false;
- public int RemoteClientBitrateLimit { get; set; }
+ public NameValuePair[] ContentTypes { get; set; } = Array.Empty();
- public bool EnableFolderView { get; set; }
+ public int RemoteClientBitrateLimit { get; set; } = 0;
- public bool EnableGroupingIntoCollections { get; set; }
+ public bool EnableFolderView { get; set; } = false;
- public bool DisplaySpecialsWithinSeasons { get; set; }
+ public bool EnableGroupingIntoCollections { get; set; } = false;
- public string[] LocalNetworkSubnets { get; set; }
+ public bool DisplaySpecialsWithinSeasons { get; set; } = true;
- public string[] LocalNetworkAddresses { get; set; }
+ ///
+ /// Gets or sets the subnets that are deemed to make up the LAN.
+ ///
+ public string[] LocalNetworkSubnets { get; set; } = Array.Empty();
- public string[] CodecsUsed { get; set; }
+ ///
+ /// 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();
- public List PluginRepositories { get; set; }
+ public string[] CodecsUsed { get; set; } = Array.Empty();
- public bool IgnoreVirtualInterfaces { get; set; }
+ public List PluginRepositories { get; set; } = new List();
- public bool EnableExternalContentInSuggestions { get; set; }
+ public bool EnableExternalContentInSuggestions { get; set; } = true;
///
/// Gets or sets a value indicating whether the server should force connections over HTTPS.
///
- public bool RequireHttps { get; set; }
+ public bool RequireHttps { get; set; } = false;
- public bool EnableNewOmdbSupport { get; set; }
+ public bool EnableNewOmdbSupport { get; set; } = true;
- public string[] RemoteIPFilter { get; set; }
+ ///
+ /// Gets or sets the filter for remote IP connectivity. Used in conjuntion with .
+ ///
+ public string[] RemoteIPFilter { get; set; } = Array.Empty();
- public bool IsRemoteIPFilterBlacklist { get; set; }
+ ///
+ /// Gets or sets a value indicating whether contains a blacklist or a whitelist. Default is a whitelist.
+ ///
+ public bool IsRemoteIPFilterBlacklist { get; set; } = false;
- public int ImageExtractionTimeoutMs { get; set; }
+ public int ImageExtractionTimeoutMs { get; set; } = 0;
- public PathSubstitution[] PathSubstitutions { get; set; }
+ public PathSubstitution[] PathSubstitutions { get; set; } = Array.Empty();
- public bool EnableSimpleArtistDetection { get; set; }
+ public bool EnableSimpleArtistDetection { get; set; } = false;
- public string[] UninstalledPlugins { get; set; }
+ public string[] UninstalledPlugins { get; set; } = Array.Empty();
///
/// Gets or sets a value indicating whether slow server responses should be logged as a warning.
///
- public bool EnableSlowResponseWarning { get; set; }
+ public bool EnableSlowResponseWarning { get; set; } = true;
///
/// Gets or sets the threshold for the slow response time warning in ms.
///
- public long SlowResponseThresholdMs { get; set; }
+ public long SlowResponseThresholdMs { get; set; } = 500;
///
/// Gets or sets the cors hosts.
///
- public string[] CorsHosts { get; set; }
+ public string[] CorsHosts { get; set; } = new[] { "*" };
///
/// Gets or sets the known proxies.
///
- public string[] KnownProxies { get; set; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public ServerConfiguration()
- {
- UninstalledPlugins = Array.Empty();
- RemoteIPFilter = Array.Empty();
- LocalNetworkSubnets = Array.Empty();
- LocalNetworkAddresses = Array.Empty();
- CodecsUsed = Array.Empty();
- PathSubstitutions = Array.Empty();
- IgnoreVirtualInterfaces = false;
- EnableSimpleArtistDetection = false;
- SkipDeserializationForBasicTypes = true;
-
- PluginRepositories = new List();
-
- DisplaySpecialsWithinSeasons = true;
- EnableExternalContentInSuggestions = true;
-
- ImageSavingConvention = ImageSavingConvention.Compatible;
- PublicPort = DefaultHttpPort;
- PublicHttpsPort = DefaultHttpsPort;
- HttpServerPortNumber = DefaultHttpPort;
- HttpsPortNumber = DefaultHttpsPort;
- EnableMetrics = false;
- EnableHttps = false;
- EnableDashboardResponseCaching = true;
- EnableCaseSensitiveItemIds = true;
- EnableNormalizedItemByNameIds = true;
- DisableLiveTvChannelUserDataName = true;
- EnableNewOmdbSupport = true;
-
- AutoRunWebApp = true;
- EnableRemoteAccess = true;
- QuickConnectAvailable = false;
-
- EnableUPnP = false;
- MinResumePct = 5;
- MaxResumePct = 90;
-
- // 5 minutes
- MinResumeDurationSeconds = 300;
-
- LibraryMonitorDelay = 60;
-
- ContentTypes = Array.Empty();
-
- PreferredMetadataLanguage = "en";
- MetadataCountryCode = "US";
-
- SortReplaceCharacters = new[] { ".", "+", "%" };
- SortRemoveCharacters = new[] { ",", "&", "-", "{", "}", "'" };
- SortRemoveWords = new[] { "the", "a", "an" };
-
- BaseUrl = string.Empty;
- UICulture = "en-US";
-
- MetadataOptions = new[]
- {
- new MetadataOptions()
- {
- ItemType = "Book"
- },
- new MetadataOptions()
- {
- ItemType = "Movie"
- },
- new MetadataOptions
- {
- ItemType = "MusicVideo",
- DisabledMetadataFetchers = new[] { "The Open Movie Database" },
- DisabledImageFetchers = new[] { "The Open Movie Database" }
- },
- new MetadataOptions
- {
- ItemType = "Series",
- DisabledMetadataFetchers = new[] { "TheMovieDb" },
- DisabledImageFetchers = new[] { "TheMovieDb" }
- },
- new MetadataOptions
- {
- ItemType = "MusicAlbum",
- DisabledMetadataFetchers = new[] { "TheAudioDB" }
- },
- new MetadataOptions
- {
- ItemType = "MusicArtist",
- DisabledMetadataFetchers = new[] { "TheAudioDB" }
- },
- new MetadataOptions
- {
- ItemType = "BoxSet"
- },
- new MetadataOptions
- {
- ItemType = "Season",
- DisabledMetadataFetchers = new[] { "TheMovieDb" },
- },
- new MetadataOptions
- {
- ItemType = "Episode",
- DisabledMetadataFetchers = new[] { "The Open Movie Database", "TheMovieDb" },
- DisabledImageFetchers = new[] { "The Open Movie Database", "TheMovieDb" }
- }
- };
-
- EnableSlowResponseWarning = true;
- SlowResponseThresholdMs = 500;
- CorsHosts = new[] { "*" };
- KnownProxies = Array.Empty();
- }
- }
-
- public class PathSubstitution
- {
- public string From { get; set; }
+ public string[] KnownProxies { get; set; } = Array.Empty();
- public string To { get; set; }
}
}
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index 25402aee13..d460c0ab0c 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26730.3
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30503.244
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}"
EndProject
@@ -66,12 +66,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Data", "Jellyfin.D
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations", "Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj", "{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jellyfin.Networking\Jellyfin.Networking.csproj", "{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -132,10 +138,6 @@ Global
{960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU
- {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU
{154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -176,10 +178,22 @@ Global
{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {DF194677-DFD3-42AF-9F75-D44D5A416478} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+ {28464062-0939-4AA7-9F7B-24DDDA61A7C0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+ {3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+ {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}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}
EndGlobalSection
@@ -201,12 +215,4 @@ Global
$0.DotNetNamingPolicy = $2
$2.DirectoryNamespaceAssociation = PrefixedHierarchical
EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {DF194677-DFD3-42AF-9F75-D44D5A416478} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
- {28464062-0939-4AA7-9F7B-24DDDA61A7C0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
- {3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
- {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}
- EndGlobalSection
EndGlobal
diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj
index 664663bd76..3266a05cbf 100644
--- a/RSSDP/RSSDP.csproj
+++ b/RSSDP/RSSDP.csproj
@@ -6,6 +6,7 @@
+
diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs
index a4be32e7d2..e28a2f48d4 100644
--- a/RSSDP/SsdpCommunicationsServer.cs
+++ b/RSSDP/SsdpCommunicationsServer.cs
@@ -7,6 +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;
@@ -352,7 +353,7 @@ namespace Rssdp.Infrastructure
if (_enableMultiSocketBinding)
{
- foreach (var address in _networkManager.GetLocalIpAddresses())
+ foreach (var address in _networkManager.GetAllBindInterfaces())
{
if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
@@ -362,7 +363,7 @@ namespace Rssdp.Infrastructure
try
{
- sockets.Add(_SocketFactory.CreateSsdpUdpSocket(address, _LocalPort));
+ sockets.Add(_SocketFactory.CreateSsdpUdpSocket(address.Address, _LocalPort));
}
catch (Exception ex)
{
diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs
index 1a8577d8da..43fccdad42 100644
--- a/RSSDP/SsdpDevicePublisher.cs
+++ b/RSSDP/SsdpDevicePublisher.cs
@@ -5,7 +5,9 @@ using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Net;
+using NetworkCollection;
namespace Rssdp.Infrastructure
{
@@ -300,17 +302,14 @@ namespace Rssdp.Infrastructure
foreach (var device in deviceList)
{
- if (!_sendOnlyMatchedHost ||
- _networkManager.IsInSameSubnet(device.ToRootDevice().Address, remoteEndPoint.Address, device.ToRootDevice().SubnetMask))
+ var ip1 = new IPNetAddress(device.ToRootDevice().Address, device.ToRootDevice().SubnetMask);
+ var ip2 = new IPNetAddress(remoteEndPoint.Address, device.ToRootDevice().SubnetMask);
+ if (!_sendOnlyMatchedHost || ip1.NetworkAddress.Equals(ip2.NetworkAddress))
{
SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken);
}
}
}
- else
- {
- // WriteTrace(String.Format("Sending 0 search responses."));
- }
});
}
diff --git a/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs
index 09ffa84689..2c7f0c4f9b 100644
--- a/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs
@@ -1,9 +1,10 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Threading.Tasks;
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;
diff --git a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
index 77f1640fa3..939023a95b 100644
--- a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
+++ b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
@@ -3,8 +3,6 @@ using System.Collections.Concurrent;
using System.IO;
using Emby.Server.Implementations;
using Emby.Server.Implementations.IO;
-using Emby.Server.Implementations.Networking;
-using Jellyfin.Drawing.Skia;
using Jellyfin.Server;
using MediaBrowser.Common;
using Microsoft.AspNetCore.Hosting;
@@ -81,7 +79,6 @@ namespace Jellyfin.Api.Tests
loggerFactory,
commandLineOpts,
new ManagedFileSystem(loggerFactory.CreateLogger(), appPaths),
- new NetworkManager(loggerFactory.CreateLogger()),
serviceCollection);
_disposableComponents.Add(appHost);
appHost.Init();