|
|
|
@ -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)))
|
|
|
|
|
{
|
|
|
|
|
// Ignore IPAny addresses.
|
|
|
|
|
count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (count != 0)
|
|
|
|
|
if (MatchesBindInterface(source, isExternal, out string result))
|
|
|
|
|
{
|
|
|
|
|
// Check to see if any of the bind interfaces are in the same subnet.
|
|
|
|
|
|
|
|
|
|
IEnumerable<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;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isExternal)
|
|
|
|
|
if (isExternal && MatchesExternalInterface(source, out result))
|
|
|
|
|
{
|
|
|
|
|
// Get the first WAN interface address that isn't a loopback.
|
|
|
|
|
var extResult = _interfaceAddresses
|
|
|
|
|
.Exclude(_bindExclusions)
|
|
|
|
|
.Where(p => !IsInLocalNetwork(p))
|
|
|
|
|
.OrderBy(p => p.Tag);
|
|
|
|
|
|
|
|
|
|
if (extResult.Any())
|
|
|
|
|
{
|
|
|
|
|
// Does the request originate in one of the interface subnets?
|
|
|
|
|
// (For systems with multiple internal network cards, and multiple subnets)
|
|
|
|
|
foreach (var intf in extResult)
|
|
|
|
|
{
|
|
|
|
|
if (!IsInLocalNetwork(intf) && intf.Contains(sourceAddr))
|
|
|
|
|
{
|
|
|
|
|
ipresult = FormatIP6String(intf.Address);
|
|
|
|
|
_logger.LogDebug("{0}: GetBindInterface: Selected best external on interface on range. {1}", sourceAddr, ipresult);
|
|
|
|
|
return ipresult;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ipresult = FormatIP6String(extResult.First().Address);
|
|
|
|
|
_logger.LogDebug("{0}: GetBindInterface: Selected first external interface. {0}", sourceAddr, ipresult);
|
|
|
|
|
return ipresult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Have to return something, so return an internal address
|
|
|
|
|
|
|
|
|
|
// TODO: remove this after testing.
|
|
|
|
|
_logger.LogWarning("{0}: External request received, however, no WAN interface found.", sourceAddr);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the first LAN interface address that isn't a loopback.
|
|
|
|
|
var result = _interfaceAddresses
|
|
|
|
|
var interfaces = new NetCollection(_interfaceAddresses
|
|
|
|
|
.Exclude(_bindExclusions)
|
|
|
|
|
.Where(p => IsInLocalNetwork(p))
|
|
|
|
|
.OrderBy(p => p.Tag);
|
|
|
|
|
.OrderBy(p => p.Tag));
|
|
|
|
|
|
|
|
|
|
if (result.Any())
|
|
|
|
|
if (interfaces.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
if (haveSource)
|
|
|
|
|
{
|
|
|
|
|
// Does the request originate in one of the interface subnets?
|
|
|
|
|
// (For systems with multiple internal network cards, and multiple subnets)
|
|
|
|
|
foreach (var intf in result)
|
|
|
|
|
foreach (var intf in interfaces)
|
|
|
|
|
{
|
|
|
|
|
if (intf.Contains(sourceAddr))
|
|
|
|
|
if (intf.Contains(source))
|
|
|
|
|
{
|
|
|
|
|
ipresult = FormatIP6String(intf.Address);
|
|
|
|
|
_logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", sourceAddr, ipresult);
|
|
|
|
|
return ipresult;
|
|
|
|
|
result = FormatIP6String(intf.Address);
|
|
|
|
|
_logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", source, result);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ipresult = FormatIP6String(result.First().Address);
|
|
|
|
|
_logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", sourceAddr, ipresult);
|
|
|
|
|
return ipresult;
|
|
|
|
|
result = FormatIP6String(interfaces.First().Address);
|
|
|
|
|
_logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", source, result);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// There isn't any others, so we'll use the loopback.
|
|
|
|
|
ipresult = IsIP6Enabled ? "::" : "127.0.0.1";
|
|
|
|
|
_logger.LogWarning("{0}: GetBindInterface: Loopback return.", sourceAddr, ipresult);
|
|
|
|
|
return ipresult;
|
|
|
|
|
result = IsIP6Enabled ? "::" : "127.0.0.1";
|
|
|
|
|
_logger.LogWarning("{0}: GetBindInterface: Loopback return.", source, result);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -771,16 +642,6 @@ namespace Jellyfin.Networking.Manager
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static NetworkManager GetInstance()
|
|
|
|
|
{
|
|
|
|
|
if (_instance == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ApplicationException("NetworkManager is not initialised.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ConfigurationUpdated(object? sender, EventArgs args)
|
|
|
|
|
{
|
|
|
|
|
UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration);
|
|
|
|
@ -944,7 +805,7 @@ namespace Jellyfin.Networking.Manager
|
|
|
|
|
{
|
|
|
|
|
lock (_intLock)
|
|
|
|
|
{
|
|
|
|
|
_overrideUrls.Clear();
|
|
|
|
|
_publishedServerUrls.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
@ -952,7 +813,7 @@ namespace Jellyfin.Networking.Manager
|
|
|
|
|
|
|
|
|
|
lock (_intLock)
|
|
|
|
|
{
|
|
|
|
|
_overrideUrls.Clear();
|
|
|
|
|
_publishedServerUrls.Clear();
|
|
|
|
|
|
|
|
|
|
foreach (var entry in overrides)
|
|
|
|
|
{
|
|
|
|
@ -966,15 +827,15 @@ namespace Jellyfin.Networking.Manager
|
|
|
|
|
var replacement = parts[1].Trim();
|
|
|
|
|
if (string.Equals(parts[0], "remaining", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
_overrideUrls[new IPNetAddress(IPAddress.Broadcast)] = replacement;
|
|
|
|
|
_publishedServerUrls[new IPNetAddress(IPAddress.Broadcast)] = replacement;
|
|
|
|
|
}
|
|
|
|
|
else if (string.Equals(parts[0], "external", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
_overrideUrls[new IPNetAddress(IPAddress.Any)] = replacement;
|
|
|
|
|
_publishedServerUrls[new IPNetAddress(IPAddress.Any)] = replacement;
|
|
|
|
|
}
|
|
|
|
|
else if (TryParseInterface(parts[0], out IPNetAddress address))
|
|
|
|
|
{
|
|
|
|
|
_overrideUrls[address] = replacement;
|
|
|
|
|
_publishedServerUrls[address] = replacement;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
@ -1199,5 +1060,179 @@ namespace Jellyfin.Networking.Manager
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|