Refactor URI overrides (#10051)

This commit is contained in:
Tim Eisele
2023-10-11 00:02:37 +02:00
committed by GitHub
parent a88e13a677
commit dc27d8f9cd
18 changed files with 433 additions and 278 deletions

View File

@@ -15,7 +15,9 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Jellyfin.Networking.Manager
{
@@ -33,12 +35,14 @@ namespace Jellyfin.Networking.Manager
private readonly IConfigurationManager _configurationManager;
private readonly IConfiguration _startupConfig;
private readonly object _networkEventLock;
/// <summary>
/// Holds the published server URLs and the IPs to use them on.
/// </summary>
private IReadOnlyDictionary<IPData, string> _publishedServerUrls;
private IReadOnlyList<PublishedServerUriOverride> _publishedServerUrls;
private IReadOnlyList<IPNetwork> _remoteAddressFilter;
@@ -76,20 +80,22 @@ namespace Jellyfin.Networking.Manager
/// <summary>
/// Initializes a new instance of the <see cref="NetworkManager"/> class.
/// </summary>
/// <param name="configurationManager">IServerConfigurationManager instance.</param>
/// <param name="configurationManager">The <see cref="IConfigurationManager"/> instance.</param>
/// <param name="startupConfig">The <see cref="IConfiguration"/> instance holding startup parameters.</param>
/// <param name="logger">Logger to use for messages.</param>
#pragma warning disable CS8618 // Non-nullable field is uninitialized. : Values are set in UpdateSettings function. Compiler doesn't yet recognise this.
public NetworkManager(IConfigurationManager configurationManager, ILogger<NetworkManager> logger)
public NetworkManager(IConfigurationManager configurationManager, IConfiguration startupConfig, ILogger<NetworkManager> logger)
{
ArgumentNullException.ThrowIfNull(logger);
ArgumentNullException.ThrowIfNull(configurationManager);
_logger = logger;
_configurationManager = configurationManager;
_startupConfig = startupConfig;
_initLock = new();
_interfaces = new List<IPData>();
_macAddresses = new List<PhysicalAddress>();
_publishedServerUrls = new Dictionary<IPData, string>();
_publishedServerUrls = new List<PublishedServerUriOverride>();
_networkEventLock = new object();
_remoteAddressFilter = new List<IPNetwork>();
@@ -130,7 +136,7 @@ namespace Jellyfin.Networking.Manager
/// <summary>
/// Gets the Published server override list.
/// </summary>
public IReadOnlyDictionary<IPData, string> PublishedServerUrls => _publishedServerUrls;
public IReadOnlyList<PublishedServerUriOverride> PublishedServerUrls => _publishedServerUrls;
/// <inheritdoc/>
public void Dispose()
@@ -170,7 +176,6 @@ namespace Jellyfin.Networking.Manager
{
if (!_eventfire)
{
_logger.LogDebug("Network Address Change Event.");
// As network events tend to fire one after the other only fire once every second.
_eventfire = true;
OnNetworkChange();
@@ -193,11 +198,12 @@ namespace Jellyfin.Networking.Manager
}
else
{
InitialiseInterfaces();
InitialiseLan(networkConfig);
InitializeInterfaces();
InitializeLan(networkConfig);
EnforceBindSettings(networkConfig);
}
PrintNetworkInformation(networkConfig);
NetworkChanged?.Invoke(this, EventArgs.Empty);
}
finally
@@ -210,7 +216,7 @@ namespace Jellyfin.Networking.Manager
/// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
/// Generate a list of all active mac addresses that aren't loopback addresses.
/// </summary>
private void InitialiseInterfaces()
private void InitializeInterfaces()
{
lock (_initLock)
{
@@ -222,7 +228,7 @@ namespace Jellyfin.Networking.Manager
try
{
var nics = NetworkInterface.GetAllNetworkInterfaces()
.Where(i => i.SupportsMulticast && i.OperationalStatus == OperationalStatus.Up);
.Where(i => i.OperationalStatus == OperationalStatus.Up);
foreach (NetworkInterface adapter in nics)
{
@@ -242,34 +248,36 @@ namespace Jellyfin.Networking.Manager
{
if (IsIPv4Enabled && info.Address.AddressFamily == AddressFamily.InterNetwork)
{
var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name);
interfaceObject.Index = ipProperties.GetIPv4Properties().Index;
interfaceObject.Name = adapter.Name;
var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name)
{
Index = ipProperties.GetIPv4Properties().Index,
Name = adapter.Name,
SupportsMulticast = adapter.SupportsMulticast
};
interfaces.Add(interfaceObject);
}
else if (IsIPv6Enabled && info.Address.AddressFamily == AddressFamily.InterNetworkV6)
{
var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name);
interfaceObject.Index = ipProperties.GetIPv6Properties().Index;
interfaceObject.Name = adapter.Name;
var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name)
{
Index = ipProperties.GetIPv6Properties().Index,
Name = adapter.Name,
SupportsMulticast = adapter.SupportsMulticast
};
interfaces.Add(interfaceObject);
}
}
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
// Ignore error, and attempt to continue.
_logger.LogError(ex, "Error encountered parsing interfaces.");
}
}
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
_logger.LogError(ex, "Error obtaining interfaces.");
}
@@ -279,14 +287,14 @@ namespace Jellyfin.Networking.Manager
{
_logger.LogWarning("No interface information available. Using loopback interface(s).");
if (IsIPv4Enabled && !IsIPv6Enabled)
if (IsIPv4Enabled)
{
interfaces.Add(new IPData(IPAddress.Loopback, new IPNetwork(IPAddress.Loopback, 8), "lo"));
interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
}
if (!IsIPv4Enabled && IsIPv6Enabled)
if (IsIPv6Enabled)
{
interfaces.Add(new IPData(IPAddress.IPv6Loopback, new IPNetwork(IPAddress.IPv6Loopback, 128), "lo"));
interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
}
}
@@ -299,9 +307,9 @@ namespace Jellyfin.Networking.Manager
}
/// <summary>
/// Initialises internal LAN cache.
/// Initializes internal LAN cache.
/// </summary>
private void InitialiseLan(NetworkConfiguration config)
private void InitializeLan(NetworkConfiguration config)
{
lock (_initLock)
{
@@ -341,10 +349,6 @@ namespace Jellyfin.Networking.Manager
_excludedSubnets = NetworkExtensions.TryParseToSubnets(subnets, out var excludedSubnets, true)
? excludedSubnets
: new List<IPNetwork>();
_logger.LogInformation("Defined LAN addresses: {0}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
_logger.LogInformation("Defined LAN exclusions: {0}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
_logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength));
}
}
@@ -369,12 +373,12 @@ namespace Jellyfin.Networking.Manager
.ToHashSet();
interfaces = interfaces.Where(x => bindAddresses.Contains(x.Address)).ToList();
if (bindAddresses.Contains(IPAddress.Loopback))
if (bindAddresses.Contains(IPAddress.Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.Loopback)))
{
interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
}
if (bindAddresses.Contains(IPAddress.IPv6Loopback))
if (bindAddresses.Contains(IPAddress.IPv6Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.IPv6Loopback)))
{
interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
}
@@ -409,15 +413,14 @@ namespace Jellyfin.Networking.Manager
interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
}
_logger.LogInformation("Using bind addresses: {0}", interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
_interfaces = interfaces;
}
}
/// <summary>
/// Initialises the remote address values.
/// Initializes the remote address values.
/// </summary>
private void InitialiseRemote(NetworkConfiguration config)
private void InitializeRemote(NetworkConfiguration config)
{
lock (_initLock)
{
@@ -455,13 +458,33 @@ namespace Jellyfin.Networking.Manager
/// format is subnet=ipaddress|host|uri
/// when subnet = 0.0.0.0, any external address matches.
/// </summary>
private void InitialiseOverrides(NetworkConfiguration config)
private void InitializeOverrides(NetworkConfiguration config)
{
lock (_initLock)
{
var publishedServerUrls = new Dictionary<IPData, string>();
var overrides = config.PublishedServerUriBySubnet;
var publishedServerUrls = new List<PublishedServerUriOverride>();
// Prefer startup configuration.
var startupOverrideKey = _startupConfig[AddressOverrideKey];
if (!string.IsNullOrEmpty(startupOverrideKey))
{
publishedServerUrls.Add(
new PublishedServerUriOverride(
new IPData(IPAddress.Any, Network.IPv4Any),
startupOverrideKey,
true,
true));
publishedServerUrls.Add(
new PublishedServerUriOverride(
new IPData(IPAddress.IPv6Any, Network.IPv6Any),
startupOverrideKey,
true,
true));
_publishedServerUrls = publishedServerUrls;
return;
}
var overrides = config.PublishedServerUriBySubnet;
foreach (var entry in overrides)
{
var parts = entry.Split('=');
@@ -475,31 +498,70 @@ namespace Jellyfin.Networking.Manager
var identifier = parts[0];
if (string.Equals(identifier, "all", StringComparison.OrdinalIgnoreCase))
{
publishedServerUrls[new IPData(IPAddress.Broadcast, null)] = replacement;
// Drop any other overrides in case an "all" override exists
publishedServerUrls.Clear();
publishedServerUrls.Add(
new PublishedServerUriOverride(
new IPData(IPAddress.Any, Network.IPv4Any),
replacement,
true,
true));
publishedServerUrls.Add(
new PublishedServerUriOverride(
new IPData(IPAddress.IPv6Any, Network.IPv6Any),
replacement,
true,
true));
break;
}
else if (string.Equals(identifier, "external", StringComparison.OrdinalIgnoreCase))
{
publishedServerUrls[new IPData(IPAddress.Any, Network.IPv4Any)] = replacement;
publishedServerUrls[new IPData(IPAddress.IPv6Any, Network.IPv6Any)] = replacement;
publishedServerUrls.Add(
new PublishedServerUriOverride(
new IPData(IPAddress.Any, Network.IPv4Any),
replacement,
false,
true));
publishedServerUrls.Add(
new PublishedServerUriOverride(
new IPData(IPAddress.IPv6Any, Network.IPv6Any),
replacement,
false,
true));
}
else if (string.Equals(identifier, "internal", StringComparison.OrdinalIgnoreCase))
{
foreach (var lan in _lanSubnets)
{
var lanPrefix = lan.Prefix;
publishedServerUrls[new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength))] = replacement;
publishedServerUrls.Add(
new PublishedServerUriOverride(
new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength)),
replacement,
true,
false));
}
}
else if (NetworkExtensions.TryParseToSubnet(identifier, out var result) && result is not null)
{
var data = new IPData(result.Prefix, result);
publishedServerUrls[data] = replacement;
publishedServerUrls.Add(
new PublishedServerUriOverride(
data,
replacement,
true,
true));
}
else if (TryParseInterface(identifier, out var ifaces))
{
foreach (var iface in ifaces)
{
publishedServerUrls[iface] = replacement;
publishedServerUrls.Add(
new PublishedServerUriOverride(
iface,
replacement,
true,
true));
}
}
else
@@ -521,7 +583,7 @@ namespace Jellyfin.Networking.Manager
}
/// <summary>
/// Reloads all settings and re-initialises the instance.
/// Reloads all settings and re-Initializes the instance.
/// </summary>
/// <param name="configuration">The <see cref="NetworkConfiguration"/> to use.</param>
public void UpdateSettings(object configuration)
@@ -531,12 +593,12 @@ namespace Jellyfin.Networking.Manager
var config = (NetworkConfiguration)configuration;
HappyEyeballs.HttpClientExtension.UseIPv6 = config.EnableIPv6;
InitialiseLan(config);
InitialiseRemote(config);
InitializeLan(config);
InitializeRemote(config);
if (string.IsNullOrEmpty(MockNetworkSettings))
{
InitialiseInterfaces();
InitializeInterfaces();
}
else // Used in testing only.
{
@@ -552,8 +614,10 @@ namespace Jellyfin.Networking.Manager
var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
{
var data = new IPData(address, subnet, parts[2]);
data.Index = index;
var data = new IPData(address, subnet, parts[2])
{
Index = index
};
interfaces.Add(data);
}
}
@@ -567,7 +631,9 @@ namespace Jellyfin.Networking.Manager
}
EnforceBindSettings(config);
InitialiseOverrides(config);
InitializeOverrides(config);
PrintNetworkInformation(config, false);
}
/// <summary>
@@ -672,20 +738,13 @@ namespace Jellyfin.Networking.Manager
/// <inheritdoc/>
public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false)
{
if (_interfaces.Count != 0)
if (_interfaces.Count > 0 || individualInterfaces)
{
return _interfaces;
}
// No bind address and no exclusions, so listen on all interfaces.
var result = new List<IPData>();
if (individualInterfaces)
{
result.AddRange(_interfaces);
return result;
}
if (IsIPv4Enabled && IsIPv6Enabled)
{
// Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any by default
@@ -892,31 +951,34 @@ namespace Jellyfin.Networking.Manager
bindPreference = string.Empty;
int? port = null;
var validPublishedServerUrls = _publishedServerUrls.Where(x => x.Key.Address.Equals(IPAddress.Any)
|| x.Key.Address.Equals(IPAddress.IPv6Any)
|| x.Key.Subnet.Contains(source))
.DistinctBy(x => x.Key)
.OrderBy(x => x.Key.Address.Equals(IPAddress.Any)
|| x.Key.Address.Equals(IPAddress.IPv6Any))
// Only consider subnets including the source IP, prefering specific overrides
List<PublishedServerUriOverride> validPublishedServerUrls;
if (!isInExternalSubnet)
{
// Only use matching internal subnets
// Prefer more specific (bigger subnet prefix) overrides
validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsInternalOverride && x.Data.Subnet.Contains(source))
.OrderByDescending(x => x.Data.Subnet.PrefixLength)
.ToList();
}
else
{
// Only use matching external subnets
// Prefer more specific (bigger subnet prefix) overrides
validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsExternalOverride && x.Data.Subnet.Contains(source))
.OrderByDescending(x => x.Data.Subnet.PrefixLength)
.ToList();
}
// Check for user override.
foreach (var data in validPublishedServerUrls)
{
if (isInExternalSubnet && (data.Key.Address.Equals(IPAddress.Any) || data.Key.Address.Equals(IPAddress.IPv6Any)))
{
// External.
bindPreference = data.Value;
break;
}
// Get address interface.
var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Key.Subnet.Contains(x.Address));
// Get interface matching override subnet
var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Data.Subnet.Contains(x.Address));
if (intf?.Address is not null)
{
// Match IP address.
bindPreference = data.Value;
// If matching interface is found, use override
bindPreference = data.OverrideUri;
break;
}
}
@@ -927,7 +989,7 @@ namespace Jellyfin.Networking.Manager
return false;
}
// Has it got a port defined?
// Handle override specifying port
var parts = bindPreference.Split(':');
if (parts.Length > 1)
{
@@ -935,18 +997,12 @@ namespace Jellyfin.Networking.Manager
{
bindPreference = parts[0];
port = p;
_logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port);
return true;
}
}
if (port is not null)
{
_logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port);
}
else
{
_logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference);
}
_logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference);
return true;
}
@@ -1053,5 +1109,19 @@ namespace Jellyfin.Networking.Manager
_logger.LogDebug("{Source}: Using first external interface as bind address: {Result}", source, result);
return true;
}
private void PrintNetworkInformation(NetworkConfiguration config, bool debug = true)
{
var logLevel = debug ? LogLevel.Debug : LogLevel.Information;
if (_logger.IsEnabled(logLevel))
{
_logger.Log(logLevel, "Defined LAN addresses: {0}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
_logger.Log(logLevel, "Defined LAN exclusions: {0}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
_logger.Log(logLevel, "Using LAN addresses: {0}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength));
_logger.Log(logLevel, "Using bind addresses: {0}", _interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
_logger.Log(logLevel, "Remote IP filter is {0}", config.IsRemoteIPFilterBlacklist ? "Blocklist" : "Allowlist");
_logger.Log(logLevel, "Filter list: {0}", _remoteAddressFilter.Select(s => s.Prefix + "/" + s.PrefixLength));
}
}
}
}