Update based on PR1 changes.

This commit is contained in:
Jim Cartlidge 2020-09-14 15:46:38 +01:00
parent 288d89493e
commit b44455ad0d
31 changed files with 388 additions and 263 deletions

View File

@ -9,7 +9,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.PlayTo; using Emby.Dlna.PlayTo;
using Emby.Dlna.Ssdp; using Emby.Dlna.Ssdp;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;

View File

@ -160,6 +160,11 @@ namespace Emby.Server.Implementations
} }
} }
/// <summary>
/// Gets the <see cref="INetworkManager"/> singleton instance.
/// </summary>
public INetworkManager NetManager { get; internal set; }
/// <summary> /// <summary>
/// Occurs when [has pending restart changed]. /// Occurs when [has pending restart changed].
/// </summary> /// </summary>
@ -189,11 +194,6 @@ namespace Emby.Server.Implementations
/// <value>The plugins.</value> /// <value>The plugins.</value>
public IReadOnlyList<IPlugin> Plugins => _plugins; public IReadOnlyList<IPlugin> Plugins => _plugins;
/// <summary>
/// Gets the NetworkManager object.
/// </summary>
private readonly INetworkManager _networkManager;
/// <summary> /// <summary>
/// Gets the logger factory. /// Gets the logger factory.
/// </summary> /// </summary>
@ -267,7 +267,7 @@ namespace Emby.Server.Implementations
ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager); 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>(); Logger = LoggerFactory.CreateLogger<ApplicationHost>();
@ -524,7 +524,7 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton(_fileSystemManager); ServiceCollection.AddSingleton(_fileSystemManager);
ServiceCollection.AddSingleton<TvdbClientManager>(); ServiceCollection.AddSingleton<TvdbClientManager>();
ServiceCollection.AddSingleton(_networkManager); ServiceCollection.AddSingleton(NetManager);
ServiceCollection.AddSingleton<IIsoManager, IsoManager>(); ServiceCollection.AddSingleton<IIsoManager, IsoManager>();
@ -1116,7 +1116,7 @@ namespace Emby.Server.Implementations
} }
public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo() public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
=> _networkManager.GetMacAddresses() => NetManager.GetMacAddresses()
.Select(i => new WakeOnLanInfo(i)) .Select(i => new WakeOnLanInfo(i))
.ToList(); .ToList();
@ -1138,7 +1138,7 @@ namespace Emby.Server.Implementations
public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps; public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps;
/// <inheritdoc/> /// <inheritdoc/>
public string GetSmartApiUrl(object source) public string GetSmartApiUrl(IPAddress ipAddress, int? port = null)
{ {
// Published server ends with a / // Published server ends with a /
if (_startupOptions.PublishedServerUrl != null) if (_startupOptions.PublishedServerUrl != null)
@ -1147,7 +1147,47 @@ namespace Emby.Server.Implementations
return _startupOptions.PublishedServerUrl.ToString().Trim('/'); 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 the smartAPI doesn't start with http then treat it as a host or ip.
if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase)) if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
@ -1155,7 +1195,7 @@ namespace Emby.Server.Implementations
return smart.Trim('/'); return smart.Trim('/');
} }
return GetLocalApiUrl(smart.Trim('/'), source is HttpRequest request ? request.Scheme : null, port); return GetLocalApiUrl(smart.Trim('/'), null, port);
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@ -10,7 +10,7 @@ using System.Net.Http;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;

View File

@ -7,7 +7,7 @@ using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller; using MediaBrowser.Controller;

View File

@ -8,7 +8,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller; using MediaBrowser.Controller;

View File

@ -1,7 +1,7 @@
using System.Security.Claims; using System.Security.Claims;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;

View File

@ -1,5 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@ -1,5 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@ -1,5 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;

View File

@ -1,5 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;

View File

@ -1,6 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;

View File

@ -1,5 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@ -1,6 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@ -1,5 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@ -1,6 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@ -8,7 +8,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;

View File

@ -7,7 +7,7 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.UserDtos; using Jellyfin.Api.Models.UserDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;

View File

@ -8,7 +8,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;

View File

@ -6,7 +6,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;

View File

@ -7,6 +7,7 @@ using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -19,8 +20,6 @@ namespace Jellyfin.Networking.Manager
/// </summary> /// </summary>
public class NetworkManager : INetworkManager, IDisposable public class NetworkManager : INetworkManager, IDisposable
{ {
private static NetworkManager? _instance;
/// <summary> /// <summary>
/// Contains the description of the interface along with its index. /// Contains the description of the interface along with its index.
/// </summary> /// </summary>
@ -48,7 +47,7 @@ namespace Jellyfin.Networking.Manager
/// <summary> /// <summary>
/// Holds the bind address overrides. /// Holds the bind address overrides.
/// </summary> /// </summary>
private readonly Dictionary<IPNetAddress, string> _overrideUrls; private readonly Dictionary<IPNetAddress, string> _publishedServerUrls;
/// <summary> /// <summary>
/// Used to stop "event-racing conditions". /// Used to stop "event-racing conditions".
@ -105,7 +104,7 @@ namespace Jellyfin.Networking.Manager
_interfaceAddresses = new NetCollection(unique: false); _interfaceAddresses = new NetCollection(unique: false);
_macAddresses = new List<PhysicalAddress>(); _macAddresses = new List<PhysicalAddress>();
_interfaceNames = new SortedList<string, int>(); _interfaceNames = new SortedList<string, int>();
_overrideUrls = new Dictionary<IPNetAddress, string>(); _publishedServerUrls = new Dictionary<IPNetAddress, string>();
UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration); UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration);
if (!IsIP6Enabled && !IsIP4Enabled) if (!IsIP6Enabled && !IsIP4Enabled)
@ -117,8 +116,6 @@ namespace Jellyfin.Networking.Manager
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged; NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
_configurationManager.ConfigurationUpdated += ConfigurationUpdated; _configurationManager.ConfigurationUpdated += ConfigurationUpdated;
Instance = this;
} }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. #pragma warning restore CS8618 // Non-nullable field is uninitialized.
@ -127,19 +124,6 @@ namespace Jellyfin.Networking.Manager
/// </summary> /// </summary>
public event EventHandler? NetworkChanged; public event EventHandler? NetworkChanged;
/// <summary>
/// Gets the singleton of this object.
/// </summary>
public static NetworkManager Instance
{
get => GetInstance();
internal set
{
_instance = value;
}
}
/// <summary> /// <summary>
/// Gets the unique network location signature, which is updated on every network change. /// Gets the unique network location signature, which is updated on every network change.
/// </summary> /// </summary>
@ -176,7 +160,7 @@ namespace Jellyfin.Networking.Manager
/// <summary> /// <summary>
/// Gets the Published server override list. /// Gets the Published server override list.
/// </summary> /// </summary>
public Dictionary<IPNetAddress, string> PublishedServerOverrides => _overrideUrls; public Dictionary<IPNetAddress, string> PublishedServerUrls => _publishedServerUrls;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
@ -198,9 +182,12 @@ namespace Jellyfin.Networking.Manager
/// <inheritdoc/> /// <inheritdoc/>
public bool IsGatewayInterface(object? addressObj) public bool IsGatewayInterface(object? addressObj)
{ {
var address = (addressObj is IPAddress addressIP) ? var address = addressObj switch
addressIP : (addressObj is IPObject addressIPObj) ? {
addressIPObj.Address : IPAddress.None; IPAddress addressIp => addressIp,
IPObject addressIpObj => addressIpObj.Address,
_ => IPAddress.None
};
lock (_intLock) lock (_intLock)
{ {
@ -320,243 +307,127 @@ namespace Jellyfin.Networking.Manager
} }
/// <inheritdoc/> /// <inheritdoc/>
public string GetBindInterface(object? source, out int? port) public string GetBindInterface(string source, out int? port)
{ {
bool chromeCast = false; if (!string.IsNullOrEmpty(source))
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 (string.Equals(source, "chromecast", StringComparison.OrdinalIgnoreCase))
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; // 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; return GetBindInterface(host, out port);
}
else
{
// Assume it's external, as we cannot resolve the host.
sourceAddr = IPHost.None;
} }
} }
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 else
{ {
// If we have no idea, then assume it came from an external address. result = GetBindInterface(IPNetAddress.None, out port);
sourceAddr = IPHost.None; 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? // 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 (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."); _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."); _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 (MatchesPublishedServerUrl(source, isExternal, isChromeCast, out string result, out port))
if (haveSource)
{
// Check for user override.
foreach (var addr in _overrideUrls)
{ {
// Remaining. Match anything. _logger.LogInformation("{0}: Using BindAddress {1}:{2}", source, result, port);
if (addr.Key.Equals(IPAddress.Broadcast)) return result;
{
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); _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. // No preference given, so move on to bind addresses.
lock (_intLock) lock (_intLock)
{ {
var nc = _bindAddresses.Exclude(_bindExclusions).Where(p => !p.IsLoopback()); if (MatchesBindInterface(source, isExternal, out string result))
int count = nc.Count();
if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses.Equals(IPAddress.IPv6Any)))
{ {
// Ignore IPAny addresses. return result;
count = 0;
} }
if (count != 0) if (isExternal && MatchesExternalInterface(source, out result))
{ {
// Check to see if any of the bind interfaces are in the same subnet. return result;
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);
} }
// Get the first LAN interface address that isn't a loopback. // Get the first LAN interface address that isn't a loopback.
var result = _interfaceAddresses var interfaces = new NetCollection(_interfaceAddresses
.Exclude(_bindExclusions) .Exclude(_bindExclusions)
.Where(p => IsInLocalNetwork(p)) .Where(p => IsInLocalNetwork(p))
.OrderBy(p => p.Tag); .OrderBy(p => p.Tag));
if (result.Any()) if (interfaces.Count > 0)
{ {
if (haveSource) if (haveSource)
{ {
// Does the request originate in one of the interface subnets? // Does the request originate in one of the interface subnets?
// (For systems with multiple internal network cards, and multiple 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); result = FormatIP6String(intf.Address);
_logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", sourceAddr, ipresult); _logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", source, result);
return ipresult; return result;
} }
} }
} }
ipresult = FormatIP6String(result.First().Address); result = FormatIP6String(interfaces.First().Address);
_logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", sourceAddr, ipresult); _logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", source, result);
return ipresult; return result;
} }
// There isn't any others, so we'll use the loopback. // There isn't any others, so we'll use the loopback.
ipresult = IsIP6Enabled ? "::" : "127.0.0.1"; result = IsIP6Enabled ? "::" : "127.0.0.1";
_logger.LogWarning("{0}: GetBindInterface: Loopback return.", sourceAddr, ipresult); _logger.LogWarning("{0}: GetBindInterface: Loopback return.", source, result);
return ipresult; 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) private void ConfigurationUpdated(object? sender, EventArgs args)
{ {
UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration); UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration);
@ -944,7 +805,7 @@ namespace Jellyfin.Networking.Manager
{ {
lock (_intLock) lock (_intLock)
{ {
_overrideUrls.Clear(); _publishedServerUrls.Clear();
} }
return; return;
@ -952,7 +813,7 @@ namespace Jellyfin.Networking.Manager
lock (_intLock) lock (_intLock)
{ {
_overrideUrls.Clear(); _publishedServerUrls.Clear();
foreach (var entry in overrides) foreach (var entry in overrides)
{ {
@ -966,15 +827,15 @@ namespace Jellyfin.Networking.Manager
var replacement = parts[1].Trim(); var replacement = parts[1].Trim();
if (string.Equals(parts[0], "remaining", StringComparison.OrdinalIgnoreCase)) 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)) 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)) else if (TryParseInterface(parts[0], out IPNetAddress address))
{ {
_overrideUrls[address] = replacement; _publishedServerUrls[address] = replacement;
} }
else 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;
}
} }
} }

View File

@ -12,10 +12,11 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
using Jellyfin.Data.Events.Users; using Jellyfin.Data.Events.Users;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Cryptography; using MediaBrowser.Common.Cryptography;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events;

View File

@ -5,7 +5,7 @@ using System.Reflection;
using Emby.Drawing; using Emby.Drawing;
using Emby.Server.Implementations; using Emby.Server.Implementations;
using Jellyfin.Drawing.Skia; using Jellyfin.Drawing.Skia;
using Jellyfin.Networking.Manager;
using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Activity;
using Jellyfin.Server.Implementations.Events; using Jellyfin.Server.Implementations.Events;

View File

@ -1,6 +1,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;

View File

@ -2,7 +2,7 @@ using System;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;

View File

@ -13,7 +13,7 @@ using CommandLine;
using Emby.Server.Implementations; using Emby.Server.Implementations;
using Emby.Server.Implementations.IO; using Emby.Server.Implementations.IO;
using Jellyfin.Api.Controllers; using Jellyfin.Api.Controllers;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Extensions;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@ -272,7 +272,7 @@ namespace Jellyfin.Server
return builder return builder
.UseKestrel((builderContext, options) => .UseKestrel((builderContext, options) =>
{ {
NetCollection addresses = NetworkManager.Instance.GetAllBindInterfaces(); NetCollection addresses = appHost.NetManager.GetAllBindInterfaces();
bool flagged = false; bool flagged = false;
foreach (IPObject netAdd in addresses) foreach (IPObject netAdd in addresses)

View File

@ -20,8 +20,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.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="Microsoft.Net.Http.Headers" Version="2.2.8" />
<PackageReference Include="NetworkCollection" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,11 +1,13 @@
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using Microsoft.AspNetCore.Http;
using NetworkCollection; using NetworkCollection;
namespace Jellyfin.Networking.Manager namespace MediaBrowser.Common.Net
{ {
/// <summary> /// <summary>
/// Interface for the NetworkManager class. /// Interface for the NetworkManager class.
@ -18,9 +20,9 @@ namespace Jellyfin.Networking.Manager
event EventHandler NetworkChanged; event EventHandler NetworkChanged;
/// <summary> /// <summary>
/// Gets the Published server override list. /// Gets the published server urls list.
/// </summary> /// </summary>
Dictionary<IPNetAddress, string> PublishedServerOverrides { get; } Dictionary<IPNetAddress, string> PublishedServerUrls { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether is all IPv6 interfaces are trusted as internal. /// 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; } public bool TrustAllIP6Interfaces { get; }
/// <summary> /// <summary>
/// Gets returns the remote address filter. /// Gets the remote address filter.
/// </summary> /// </summary>
NetCollection RemoteAddressFilter { get; } NetCollection RemoteAddressFilter { get; }
@ -75,7 +77,37 @@ namespace Jellyfin.Networking.Manager
/// <param name="source">Source of the request.</param> /// <param name="source">Source of the request.</param>
/// <param name="port">Optional port returned, if it's part of an override.</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> /// <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> /// <summary>
/// Checks to see if the ip address is specifically excluded in LocalNetworkAddresses. /// Checks to see if the ip address is specifically excluded in LocalNetworkAddresses.

View File

@ -63,12 +63,28 @@ namespace MediaBrowser.Controller
PublicSystemInfo GetPublicSystemInfo(IPAddress address); PublicSystemInfo GetPublicSystemInfo(IPAddress address);
/// <summary> /// <summary>
/// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured /// Gets a URL specific for the request.
/// HTTPS will be preferred when available.
/// </summary> /// </summary>
/// <param name="source">The source of the request.</param> /// <param name="request">The <see cref="HttpRequest"/> instance.</param>
/// <returns>The server URL.</returns> /// <param name="port">Optional port number.</param>
string GetSmartApiUrl(object source); /// <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> /// <summary>
/// Gets a localhost URL that can be used to access the API using the loop-back IP address. /// Gets a localhost URL that can be used to access the API using the loop-back IP address.

View File

@ -7,7 +7,7 @@ using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -5,7 +5,7 @@ using System.Linq;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using NetworkCollection; using NetworkCollection;

View File

@ -4,7 +4,7 @@ using AutoFixture;
using AutoFixture.AutoMoq; using AutoFixture.AutoMoq;
using Jellyfin.Api.Auth.LocalAccessPolicy; using Jellyfin.Api.Auth.LocalAccessPolicy;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;