mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-15 09:59:06 -07:00
Update based on PR1 changes.
This commit is contained in:
parent
288d89493e
commit
b44455ad0d
@ -9,7 +9,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.PlayTo;
|
||||
using Emby.Dlna.Ssdp;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
@ -160,6 +160,11 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="INetworkManager"/> singleton instance.
|
||||
/// </summary>
|
||||
public INetworkManager NetManager { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [has pending restart changed].
|
||||
/// </summary>
|
||||
@ -189,11 +194,6 @@ namespace Emby.Server.Implementations
|
||||
/// <value>The plugins.</value>
|
||||
public IReadOnlyList<IPlugin> Plugins => _plugins;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkManager object.
|
||||
/// </summary>
|
||||
private readonly INetworkManager _networkManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger factory.
|
||||
/// </summary>
|
||||
@ -267,7 +267,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
|
||||
|
||||
_networkManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
|
||||
NetManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
|
||||
|
||||
Logger = LoggerFactory.CreateLogger<ApplicationHost>();
|
||||
|
||||
@ -524,7 +524,7 @@ namespace Emby.Server.Implementations
|
||||
ServiceCollection.AddSingleton(_fileSystemManager);
|
||||
ServiceCollection.AddSingleton<TvdbClientManager>();
|
||||
|
||||
ServiceCollection.AddSingleton(_networkManager);
|
||||
ServiceCollection.AddSingleton(NetManager);
|
||||
|
||||
ServiceCollection.AddSingleton<IIsoManager, IsoManager>();
|
||||
|
||||
@ -1116,7 +1116,7 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
|
||||
public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
|
||||
=> _networkManager.GetMacAddresses()
|
||||
=> NetManager.GetMacAddresses()
|
||||
.Select(i => new WakeOnLanInfo(i))
|
||||
.ToList();
|
||||
|
||||
@ -1138,7 +1138,7 @@ namespace Emby.Server.Implementations
|
||||
public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetSmartApiUrl(object source)
|
||||
public string GetSmartApiUrl(IPAddress ipAddress, int? port = null)
|
||||
{
|
||||
// Published server ends with a /
|
||||
if (_startupOptions.PublishedServerUrl != null)
|
||||
@ -1147,7 +1147,47 @@ namespace Emby.Server.Implementations
|
||||
return _startupOptions.PublishedServerUrl.ToString().Trim('/');
|
||||
}
|
||||
|
||||
string smart = _networkManager.GetBindInterface(source, out int? port);
|
||||
string smart = NetManager.GetBindInterface(ipAddress, out port);
|
||||
// If the smartAPI doesn't start with http then treat it as a host or ip.
|
||||
if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return smart.Trim('/');
|
||||
}
|
||||
|
||||
return GetLocalApiUrl(smart.Trim('/'), null, port);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetSmartApiUrl(HttpRequest request, int? port = null)
|
||||
{
|
||||
// Published server ends with a /
|
||||
if (_startupOptions.PublishedServerUrl != null)
|
||||
{
|
||||
// Published server ends with a '/', so we need to remove it.
|
||||
return _startupOptions.PublishedServerUrl.ToString().Trim('/');
|
||||
}
|
||||
|
||||
string smart = NetManager.GetBindInterface(request, out port);
|
||||
// If the smartAPI doesn't start with http then treat it as a host or ip.
|
||||
if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return smart.Trim('/');
|
||||
}
|
||||
|
||||
return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetSmartApiUrl(string hostname, int? port = null)
|
||||
{
|
||||
// Published server ends with a /
|
||||
if (_startupOptions.PublishedServerUrl != null)
|
||||
{
|
||||
// Published server ends with a '/', so we need to remove it.
|
||||
return _startupOptions.PublishedServerUrl.ToString().Trim('/');
|
||||
}
|
||||
|
||||
string smart = NetManager.GetBindInterface(hostname, out port);
|
||||
|
||||
// If the smartAPI doesn't start with http then treat it as a host or ip.
|
||||
if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
@ -1155,7 +1195,7 @@ namespace Emby.Server.Implementations
|
||||
return smart.Trim('/');
|
||||
}
|
||||
|
||||
return GetLocalApiUrl(smart.Trim('/'), source is HttpRequest request ? request.Scheme : null, port);
|
||||
return GetLocalApiUrl(smart.Trim('/'), null, port);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -10,7 +10,7 @@ using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
@ -7,7 +7,7 @@ using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
|
@ -8,7 +8,7 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System.Security.Claims;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -8,7 +8,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
@ -7,7 +7,7 @@ using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.Models.UserDtos;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
|
@ -8,7 +8,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Models.StreamingDtos;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -6,7 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -7,6 +7,7 @@ using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -19,8 +20,6 @@ namespace Jellyfin.Networking.Manager
|
||||
/// </summary>
|
||||
public class NetworkManager : INetworkManager, IDisposable
|
||||
{
|
||||
private static NetworkManager? _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the description of the interface along with its index.
|
||||
/// </summary>
|
||||
@ -48,7 +47,7 @@ namespace Jellyfin.Networking.Manager
|
||||
/// <summary>
|
||||
/// Holds the bind address overrides.
|
||||
/// </summary>
|
||||
private readonly Dictionary<IPNetAddress, string> _overrideUrls;
|
||||
private readonly Dictionary<IPNetAddress, string> _publishedServerUrls;
|
||||
|
||||
/// <summary>
|
||||
/// Used to stop "event-racing conditions".
|
||||
@ -105,7 +104,7 @@ namespace Jellyfin.Networking.Manager
|
||||
_interfaceAddresses = new NetCollection(unique: false);
|
||||
_macAddresses = new List<PhysicalAddress>();
|
||||
_interfaceNames = new SortedList<string, int>();
|
||||
_overrideUrls = new Dictionary<IPNetAddress, string>();
|
||||
_publishedServerUrls = new Dictionary<IPNetAddress, string>();
|
||||
|
||||
UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration);
|
||||
if (!IsIP6Enabled && !IsIP4Enabled)
|
||||
@ -117,8 +116,6 @@ namespace Jellyfin.Networking.Manager
|
||||
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
|
||||
|
||||
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
#pragma warning restore CS8618 // Non-nullable field is uninitialized.
|
||||
|
||||
@ -127,19 +124,6 @@ namespace Jellyfin.Networking.Manager
|
||||
/// </summary>
|
||||
public event EventHandler? NetworkChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton of this object.
|
||||
/// </summary>
|
||||
public static NetworkManager Instance
|
||||
{
|
||||
get => GetInstance();
|
||||
|
||||
internal set
|
||||
{
|
||||
_instance = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique network location signature, which is updated on every network change.
|
||||
/// </summary>
|
||||
@ -176,7 +160,7 @@ namespace Jellyfin.Networking.Manager
|
||||
/// <summary>
|
||||
/// Gets the Published server override list.
|
||||
/// </summary>
|
||||
public Dictionary<IPNetAddress, string> PublishedServerOverrides => _overrideUrls;
|
||||
public Dictionary<IPNetAddress, string> PublishedServerUrls => _publishedServerUrls;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
@ -198,9 +182,12 @@ namespace Jellyfin.Networking.Manager
|
||||
/// <inheritdoc/>
|
||||
public bool IsGatewayInterface(object? addressObj)
|
||||
{
|
||||
var address = (addressObj is IPAddress addressIP) ?
|
||||
addressIP : (addressObj is IPObject addressIPObj) ?
|
||||
addressIPObj.Address : IPAddress.None;
|
||||
var address = addressObj switch
|
||||
{
|
||||
IPAddress addressIp => addressIp,
|
||||
IPObject addressIpObj => addressIpObj.Address,
|
||||
_ => IPAddress.None
|
||||
};
|
||||
|
||||
lock (_intLock)
|
||||
{
|
||||
@ -320,243 +307,127 @@ namespace Jellyfin.Networking.Manager
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetBindInterface(object? source, out int? port)
|
||||
public string GetBindInterface(string source, out int? port)
|
||||
{
|
||||
bool chromeCast = false;
|
||||
port = null;
|
||||
// Parse the source object in an attempt to discover where the request originated.
|
||||
IPObject sourceAddr;
|
||||
if (source is HttpRequest sourceReq)
|
||||
if (!string.IsNullOrEmpty(source))
|
||||
{
|
||||
port = sourceReq.Host.Port;
|
||||
if (IPHost.TryParse(sourceReq.Host.Host, out IPHost host))
|
||||
if (string.Equals(source, "chromecast", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sourceAddr = host;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume it's external, as we cannot resolve the host.
|
||||
sourceAddr = IPHost.None;
|
||||
}
|
||||
}
|
||||
else if (source is string sourceStr && !string.IsNullOrEmpty(sourceStr))
|
||||
{
|
||||
if (string.Equals(sourceStr, "chromecast", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
chromeCast = true;
|
||||
// Just assign a variable so has source = true;
|
||||
sourceAddr = IPNetAddress.IP4Loopback;
|
||||
return GetBindInterface(IPNetAddress.IP4Loopback, out port);
|
||||
}
|
||||
|
||||
if (IPHost.TryParse(sourceStr, out IPHost host))
|
||||
if (IPHost.TryParse(source, out IPHost host))
|
||||
{
|
||||
sourceAddr = host;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume it's external, as we cannot resolve the host.
|
||||
sourceAddr = IPHost.None;
|
||||
return GetBindInterface(host, out port);
|
||||
}
|
||||
}
|
||||
else if (source is IPAddress sourceIP)
|
||||
|
||||
return GetBindInterface(IPHost.None, out port);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetBindInterface(IPAddress source, out int? port)
|
||||
{
|
||||
return GetBindInterface(new IPNetAddress(source), out port);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetBindInterface(HttpRequest source, out int? port)
|
||||
{
|
||||
string result;
|
||||
|
||||
if (source != null && IPHost.TryParse(source.Host.Host, out IPHost host))
|
||||
{
|
||||
sourceAddr = new IPNetAddress(sourceIP);
|
||||
result = GetBindInterface(host, out port);
|
||||
port ??= source.Host.Port;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have no idea, then assume it came from an external address.
|
||||
sourceAddr = IPHost.None;
|
||||
result = GetBindInterface(IPNetAddress.None, out port);
|
||||
port ??= source?.Host.Port;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetBindInterface(IPObject source, out int? port)
|
||||
{
|
||||
port = null;
|
||||
bool isChromeCast = source == IPNetAddress.IP4Loopback;
|
||||
// Do we have a source?
|
||||
bool haveSource = !sourceAddr.Address.Equals(IPAddress.None);
|
||||
bool haveSource = !source.Address.Equals(IPAddress.None);
|
||||
bool isExternal = false;
|
||||
|
||||
if (haveSource)
|
||||
{
|
||||
if (!IsIP6Enabled && sourceAddr.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
if (!IsIP6Enabled && source.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
_logger.LogWarning("IPv6 is disabled in JellyFin, but enabled in the OS. This may affect how the interface is selected.");
|
||||
}
|
||||
|
||||
if (!IsIP4Enabled && sourceAddr.AddressFamily == AddressFamily.InterNetwork)
|
||||
if (!IsIP4Enabled && source.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
_logger.LogWarning("IPv4 is disabled in JellyFin, but enabled in the OS. This may affect how the interface is selected.");
|
||||
}
|
||||
}
|
||||
|
||||
bool isExternal = haveSource && !IsInLocalNetwork(sourceAddr);
|
||||
isExternal = !IsInLocalNetwork(source);
|
||||
|
||||
string bindPreference = string.Empty;
|
||||
if (haveSource)
|
||||
{
|
||||
// Check for user override.
|
||||
foreach (var addr in _overrideUrls)
|
||||
if (MatchesPublishedServerUrl(source, isExternal, isChromeCast, out string result, out port))
|
||||
{
|
||||
// Remaining. Match anything.
|
||||
if (addr.Key.Equals(IPAddress.Broadcast))
|
||||
{
|
||||
bindPreference = addr.Value;
|
||||
break;
|
||||
}
|
||||
else if ((addr.Key.Equals(IPAddress.Any) || addr.Key.Equals(IPAddress.IPv6Any)) && (isExternal || chromeCast))
|
||||
{
|
||||
// External.
|
||||
bindPreference = addr.Value;
|
||||
break;
|
||||
}
|
||||
else if (addr.Key.Contains(sourceAddr))
|
||||
{
|
||||
// Match ip address.
|
||||
bindPreference = addr.Value;
|
||||
break;
|
||||
}
|
||||
_logger.LogInformation("{0}: Using BindAddress {1}:{2}", source, result, port);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogDebug("GetBindInterface: Souce: {0}, External: {1}:", haveSource, isExternal);
|
||||
|
||||
if (!string.IsNullOrEmpty(bindPreference))
|
||||
{
|
||||
// Has it got a port defined?
|
||||
var parts = bindPreference.Split(':');
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
if (int.TryParse(parts[1], out int p))
|
||||
{
|
||||
bindPreference = parts[0];
|
||||
port = p;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("{0}: Using BindAddress {1}:{2}", sourceAddr, bindPreference, port);
|
||||
return bindPreference;
|
||||
}
|
||||
|
||||
string ipresult;
|
||||
|
||||
// No preference given, so move on to bind addresses.
|
||||
lock (_intLock)
|
||||
{
|
||||
var nc = _bindAddresses.Exclude(_bindExclusions).Where(p => !p.IsLoopback());
|
||||
|
||||
int count = nc.Count();
|
||||
if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses.Equals(IPAddress.IPv6Any)))
|
||||
if (MatchesBindInterface(source, isExternal, out string result))
|
||||
{
|
||||
// Ignore IPAny addresses.
|
||||
count = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (count != 0)
|
||||
if (isExternal && MatchesExternalInterface(source, out result))
|
||||
{
|
||||
// Check to see if any of the bind interfaces are in the same subnet.
|
||||
|
||||
IEnumerable<IPObject> 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);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get the first LAN interface address that isn't a loopback.
|
||||
var result = _interfaceAddresses
|
||||
var interfaces = new NetCollection(_interfaceAddresses
|
||||
.Exclude(_bindExclusions)
|
||||
.Where(p => IsInLocalNetwork(p))
|
||||
.OrderBy(p => p.Tag);
|
||||
.OrderBy(p => p.Tag));
|
||||
|
||||
if (result.Any())
|
||||
if (interfaces.Count > 0)
|
||||
{
|
||||
if (haveSource)
|
||||
{
|
||||
// Does the request originate in one of the interface subnets?
|
||||
// (For systems with multiple internal network cards, and multiple subnets)
|
||||
foreach (var intf in result)
|
||||
foreach (var intf in interfaces)
|
||||
{
|
||||
if (intf.Contains(sourceAddr))
|
||||
if (intf.Contains(source))
|
||||
{
|
||||
ipresult = FormatIP6String(intf.Address);
|
||||
_logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", sourceAddr, ipresult);
|
||||
return ipresult;
|
||||
result = FormatIP6String(intf.Address);
|
||||
_logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", source, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipresult = FormatIP6String(result.First().Address);
|
||||
_logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", sourceAddr, ipresult);
|
||||
return ipresult;
|
||||
result = FormatIP6String(interfaces.First().Address);
|
||||
_logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", source, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// There isn't any others, so we'll use the loopback.
|
||||
ipresult = IsIP6Enabled ? "::" : "127.0.0.1";
|
||||
_logger.LogWarning("{0}: GetBindInterface: Loopback return.", sourceAddr, ipresult);
|
||||
return ipresult;
|
||||
result = IsIP6Enabled ? "::" : "127.0.0.1";
|
||||
_logger.LogWarning("{0}: GetBindInterface: Loopback return.", source, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -771,16 +642,6 @@ namespace Jellyfin.Networking.Manager
|
||||
}
|
||||
}
|
||||
|
||||
private static NetworkManager GetInstance()
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
throw new ApplicationException("NetworkManager is not initialised.");
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
|
||||
private void ConfigurationUpdated(object? sender, EventArgs args)
|
||||
{
|
||||
UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration);
|
||||
@ -944,7 +805,7 @@ namespace Jellyfin.Networking.Manager
|
||||
{
|
||||
lock (_intLock)
|
||||
{
|
||||
_overrideUrls.Clear();
|
||||
_publishedServerUrls.Clear();
|
||||
}
|
||||
|
||||
return;
|
||||
@ -952,7 +813,7 @@ namespace Jellyfin.Networking.Manager
|
||||
|
||||
lock (_intLock)
|
||||
{
|
||||
_overrideUrls.Clear();
|
||||
_publishedServerUrls.Clear();
|
||||
|
||||
foreach (var entry in overrides)
|
||||
{
|
||||
@ -966,15 +827,15 @@ namespace Jellyfin.Networking.Manager
|
||||
var replacement = parts[1].Trim();
|
||||
if (string.Equals(parts[0], "remaining", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_overrideUrls[new IPNetAddress(IPAddress.Broadcast)] = replacement;
|
||||
_publishedServerUrls[new IPNetAddress(IPAddress.Broadcast)] = replacement;
|
||||
}
|
||||
else if (string.Equals(parts[0], "external", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_overrideUrls[new IPNetAddress(IPAddress.Any)] = replacement;
|
||||
_publishedServerUrls[new IPNetAddress(IPAddress.Any)] = replacement;
|
||||
}
|
||||
else if (TryParseInterface(parts[0], out IPNetAddress address))
|
||||
{
|
||||
_overrideUrls[address] = replacement;
|
||||
_publishedServerUrls[address] = replacement;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1199,5 +1060,179 @@ namespace Jellyfin.Networking.Manager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to match the source against a user defined bind interface.
|
||||
/// </summary>
|
||||
/// <param name="source">IP source address to use.</param>
|
||||
/// <param name="isExternal">True if the source is in the external subnet.</param>
|
||||
/// <param name="isChromeCast">True if the request is for a chromecast device.</param>
|
||||
/// <param name="bindPreference">The published server url that matches the source address.</param>
|
||||
/// <param name="port">The resultant port, if one exists.</param>
|
||||
/// <returns>True if a match is found.</returns>
|
||||
private bool MatchesPublishedServerUrl(IPObject source, bool isExternal, bool isChromeCast, out string bindPreference, out int? port)
|
||||
{
|
||||
bindPreference = string.Empty;
|
||||
port = null;
|
||||
|
||||
// Check for user override.
|
||||
foreach (var addr in _publishedServerUrls)
|
||||
{
|
||||
// Remaining. Match anything.
|
||||
if (addr.Key.Equals(IPAddress.Broadcast))
|
||||
{
|
||||
bindPreference = addr.Value;
|
||||
break;
|
||||
}
|
||||
else if ((addr.Key.Equals(IPAddress.Any) || addr.Key.Equals(IPAddress.IPv6Any)) && (isExternal || isChromeCast))
|
||||
{
|
||||
// External.
|
||||
bindPreference = addr.Value;
|
||||
break;
|
||||
}
|
||||
else if (addr.Key.Contains(source))
|
||||
{
|
||||
// Match ip address.
|
||||
bindPreference = addr.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(bindPreference))
|
||||
{
|
||||
// Has it got a port defined?
|
||||
var parts = bindPreference.Split(':');
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
if (int.TryParse(parts[1], out int p))
|
||||
{
|
||||
bindPreference = parts[0];
|
||||
port = p;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to match the source against a user defined bind interface.
|
||||
/// </summary>
|
||||
/// <param name="source">IP source address to use.</param>
|
||||
/// <param name="isExternal">True if the source is in the external subnet.</param>
|
||||
/// <param name="result">The result, if a match is found.</param>
|
||||
/// <returns>True if a match is found.</returns>
|
||||
private bool MatchesBindInterface(IPObject source, bool isExternal, out string result)
|
||||
{
|
||||
result = string.Empty;
|
||||
var nc = new NetCollection(_bindAddresses.Exclude(_bindExclusions).Where(p => !p.IsLoopback()));
|
||||
|
||||
int count = nc.Count;
|
||||
if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses[0].Equals(IPAddress.IPv6Any)))
|
||||
{
|
||||
// Ignore IPAny addresses.
|
||||
count = 0;
|
||||
}
|
||||
|
||||
if (count != 0)
|
||||
{
|
||||
// Check to see if any of the bind interfaces are in the same subnet.
|
||||
|
||||
NetCollection bindResult;
|
||||
IPAddress? defaultGateway = null;
|
||||
IPAddress? bindAddress;
|
||||
|
||||
if (isExternal)
|
||||
{
|
||||
// Find all external bind addresses. Store the default gateway, but check to see if there is a better match first.
|
||||
bindResult = new NetCollection(nc
|
||||
.Where(p => !IsInLocalNetwork(p))
|
||||
.OrderBy(p => p.Tag));
|
||||
defaultGateway = bindResult.FirstOrDefault()?.Address;
|
||||
bindAddress = bindResult
|
||||
.Where(p => p.Contains(source))
|
||||
.OrderBy(p => p.Tag)
|
||||
.FirstOrDefault()?.Address;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Look for the best internal address.
|
||||
bindAddress = nc
|
||||
.Where(p => IsInLocalNetwork(p) && (p.Contains(source) || p.Equals(IPAddress.None)))
|
||||
.OrderBy(p => p.Tag)
|
||||
.FirstOrDefault()?.Address;
|
||||
}
|
||||
|
||||
if (bindAddress != null)
|
||||
{
|
||||
result = FormatIP6String(bindAddress);
|
||||
_logger.LogDebug("{0}: GetBindInterface: Has source, found a match bind interface subnets. {1}", source, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isExternal && defaultGateway != null)
|
||||
{
|
||||
result = FormatIP6String(defaultGateway);
|
||||
_logger.LogDebug("{0}: GetBindInterface: Using first user defined external interface. {1}", source, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = FormatIP6String(nc.First().Address);
|
||||
_logger.LogDebug("{0}: GetBindInterface: Selected first user defined interface. {1}", source, result);
|
||||
|
||||
if (isExternal)
|
||||
{
|
||||
// TODO: remove this after testing.
|
||||
_logger.LogWarning("{0}: External request received, however, only an internal interface bind found.", source);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to match the source against am external interface.
|
||||
/// </summary>
|
||||
/// <param name="source">IP source address to use.</param>
|
||||
/// <param name="result">The result, if a match is found.</param>
|
||||
/// <returns>True if a match is found.</returns>
|
||||
private bool MatchesExternalInterface(IPObject source, out string result)
|
||||
{
|
||||
result = string.Empty;
|
||||
// Get the first WAN interface address that isn't a loopback.
|
||||
var extResult = new NetCollection(_interfaceAddresses
|
||||
.Exclude(_bindExclusions)
|
||||
.Where(p => !IsInLocalNetwork(p))
|
||||
.OrderBy(p => p.Tag));
|
||||
|
||||
if (extResult.Count > 0)
|
||||
{
|
||||
// Does the request originate in one of the interface subnets?
|
||||
// (For systems with multiple internal network cards, and multiple subnets)
|
||||
foreach (var intf in extResult)
|
||||
{
|
||||
if (!IsInLocalNetwork(intf) && intf.Contains(source))
|
||||
{
|
||||
result = FormatIP6String(intf.Address);
|
||||
_logger.LogDebug("{0}: GetBindInterface: Selected best external on interface on range. {1}", source, result);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
result = FormatIP6String(extResult.First().Address);
|
||||
_logger.LogDebug("{0}: GetBindInterface: Selected first external interface. {0}", source, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Have to return something, so return an internal address
|
||||
|
||||
// TODO: remove this after testing.
|
||||
_logger.LogWarning("{0}: External request received, however, no WAN interface found.", source);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,10 +12,11 @@ using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Data.Events.Users;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Cryptography;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Events;
|
||||
|
@ -5,7 +5,7 @@ using System.Reflection;
|
||||
using Emby.Drawing;
|
||||
using Emby.Server.Implementations;
|
||||
using Jellyfin.Drawing.Skia;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using Jellyfin.Server.Implementations;
|
||||
using Jellyfin.Server.Implementations.Activity;
|
||||
using Jellyfin.Server.Implementations.Events;
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -2,7 +2,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -13,7 +13,7 @@ using CommandLine;
|
||||
using Emby.Server.Implementations;
|
||||
using Emby.Server.Implementations.IO;
|
||||
using Jellyfin.Api.Controllers;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
@ -272,7 +272,7 @@ namespace Jellyfin.Server
|
||||
return builder
|
||||
.UseKestrel((builderContext, options) =>
|
||||
{
|
||||
NetCollection addresses = NetworkManager.Instance.GetAllBindInterfaces();
|
||||
NetCollection addresses = appHost.NetManager.GetAllBindInterfaces();
|
||||
|
||||
bool flagged = false;
|
||||
foreach (IPObject netAdd in addresses)
|
||||
|
@ -20,8 +20,9 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
|
||||
<PackageReference Include="NetworkCollection" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,11 +1,13 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using NetworkCollection;
|
||||
|
||||
namespace Jellyfin.Networking.Manager
|
||||
namespace MediaBrowser.Common.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for the NetworkManager class.
|
||||
@ -18,9 +20,9 @@ namespace Jellyfin.Networking.Manager
|
||||
event EventHandler NetworkChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Published server override list.
|
||||
/// Gets the published server urls list.
|
||||
/// </summary>
|
||||
Dictionary<IPNetAddress, string> PublishedServerOverrides { get; }
|
||||
Dictionary<IPNetAddress, string> PublishedServerUrls { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether is all IPv6 interfaces are trusted as internal.
|
||||
@ -28,7 +30,7 @@ namespace Jellyfin.Networking.Manager
|
||||
public bool TrustAllIP6Interfaces { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets returns the remote address filter.
|
||||
/// Gets the remote address filter.
|
||||
/// </summary>
|
||||
NetCollection RemoteAddressFilter { get; }
|
||||
|
||||
@ -75,7 +77,37 @@ namespace Jellyfin.Networking.Manager
|
||||
/// <param name="source">Source of the request.</param>
|
||||
/// <param name="port">Optional port returned, if it's part of an override.</param>
|
||||
/// <returns>IP Address to use, or loopback address if all else fails.</returns>
|
||||
string GetBindInterface(object? source, out int? port);
|
||||
string GetBindInterface(IPObject source, out int? port);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo)
|
||||
/// If no bind addresses are specified, an internal interface address is selected.
|
||||
/// (See above).
|
||||
/// </summary>
|
||||
/// <param name="source">Source of the request.</param>
|
||||
/// <param name="port">Optional port returned, if it's part of an override.</param>
|
||||
/// <returns>IP Address to use, or loopback address if all else fails.</returns>
|
||||
string GetBindInterface(HttpRequest source, out int? port);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo)
|
||||
/// If no bind addresses are specified, an internal interface address is selected.
|
||||
/// (See above).
|
||||
/// </summary>
|
||||
/// <param name="source">IP address of the request.</param>
|
||||
/// <param name="port">Optional port returned, if it's part of an override.</param>
|
||||
/// <returns>IP Address to use, or loopback address if all else fails.</returns>
|
||||
string GetBindInterface(IPAddress source, out int? port);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo)
|
||||
/// If no bind addresses are specified, an internal interface address is selected.
|
||||
/// (See above).
|
||||
/// </summary>
|
||||
/// <param name="source">Source of the request.</param>
|
||||
/// <param name="port">Optional port returned, if it's part of an override.</param>
|
||||
/// <returns>IP Address to use, or loopback address if all else fails.</returns>
|
||||
string GetBindInterface(string source, out int? port);
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the ip address is specifically excluded in LocalNetworkAddresses.
|
@ -63,12 +63,28 @@ namespace MediaBrowser.Controller
|
||||
PublicSystemInfo GetPublicSystemInfo(IPAddress address);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured
|
||||
/// HTTPS will be preferred when available.
|
||||
/// Gets a URL specific for the request.
|
||||
/// </summary>
|
||||
/// <param name="source">The source of the request.</param>
|
||||
/// <returns>The server URL.</returns>
|
||||
string GetSmartApiUrl(object source);
|
||||
/// <param name="request">The <see cref="HttpRequest"/> instance.</param>
|
||||
/// <param name="port">Optional port number.</param>
|
||||
/// <returns>An accessible URL.</returns>
|
||||
string GetSmartApiUrl(HttpRequest request, int? port = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a URL specific for the request.
|
||||
/// </summary>
|
||||
/// <param name="remoteAddr">The remote <see cref="IPAddress"/> of the connection.</param>
|
||||
/// <param name="port">Optional port number.</param>
|
||||
/// <returns>An accessible URL.</returns>
|
||||
string GetSmartApiUrl(IPAddress remoteAddr, int? port = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a URL specific for the request.
|
||||
/// </summary>
|
||||
/// <param name="hostname">The hostname used in the connection.</param>
|
||||
/// <param name="port">Optional port number.</param>
|
||||
/// <returns>An accessible URL.</returns>
|
||||
string GetSmartApiUrl(string hostname, int? port = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localhost URL that can be used to access the API using the loop-back IP address.
|
||||
|
@ -7,7 +7,7 @@ using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -5,7 +5,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using NetworkCollection;
|
||||
|
||||
|
@ -4,7 +4,7 @@ using AutoFixture;
|
||||
using AutoFixture.AutoMoq;
|
||||
using Jellyfin.Api.Auth.LocalAccessPolicy;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Networking.Manager;
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
Loading…
Reference in New Issue
Block a user