Apply review suggestions

This commit is contained in:
Shadowghost 2023-02-15 11:31:47 +01:00
parent 414eb45899
commit 1cc7572445
2 changed files with 212 additions and 186 deletions

View File

@ -263,6 +263,7 @@ namespace Jellyfin.Networking.Manager
_logger.LogError(ex, "Error obtaining interfaces.");
}
// If no interfaces are found, fallback to loopback interfaces.
if (_interfaces.Count == 0)
{
_logger.LogWarning("No interface information available. Using loopback interface(s).");
@ -278,8 +279,8 @@ namespace Jellyfin.Networking.Manager
}
}
_logger.LogDebug("Discovered {0} interfaces.", _interfaces.Count);
_logger.LogDebug("Interfaces addresses: {0}", _interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString()));
_logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", _interfaces.Count);
_logger.LogDebug("Interfaces addresses: {Addresses}", _interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString()));
}
}
@ -293,14 +294,21 @@ namespace Jellyfin.Networking.Manager
_logger.LogDebug("Refreshing LAN information.");
// Get configuration options
string[] subnets = config.LocalNetworkSubnets;
var subnets = config.LocalNetworkSubnets;
_ = NetworkExtensions.TryParseToSubnets(subnets, out _lanSubnets, false);
_ = NetworkExtensions.TryParseToSubnets(subnets, out _excludedSubnets, true);
if (!NetworkExtensions.TryParseToSubnets(subnets, out _lanSubnets, false))
{
_lanSubnets.Clear();
}
if (!NetworkExtensions.TryParseToSubnets(subnets, out _excludedSubnets, true))
{
_excludedSubnets.Clear();
}
// If no LAN addresses are specified, all private subnets and Loopback are deemed to be the LAN
if (_lanSubnets.Count == 0)
{
// If no LAN addresses are specified, all private subnets and Loopback are deemed to be the LAN
_logger.LogDebug("Using LAN interface addresses as user provided no LAN details.");
if (IsIpv6Enabled)
@ -334,15 +342,15 @@ namespace Jellyfin.Networking.Manager
{
// Respect explicit bind addresses
var localNetworkAddresses = config.LocalNetworkAddresses;
if (localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses.First()))
if (localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0]))
{
var bindAddresses = localNetworkAddresses.Select(p => NetworkExtensions.TryParseToSubnet(p, out var network)
? network.Prefix
: (_interfaces.Where(x => x.Name.Equals(p, StringComparison.OrdinalIgnoreCase))
.Select(x => x.Address)
.FirstOrDefault() ?? IPAddress.None))
.ToList();
bindAddresses.RemoveAll(x => x == IPAddress.None);
.Where(x => x != IPAddress.None)
.ToHashSet();
_interfaces = _interfaces.Where(x => bindAddresses.Contains(x.Address)).ToList();
if (bindAddresses.Contains(IPAddress.Loopback))
@ -401,11 +409,15 @@ namespace Jellyfin.Networking.Manager
if (remoteIPFilter.Any() && !string.IsNullOrWhiteSpace(remoteIPFilter.First()))
{
// Parse all IPs with netmask to a subnet
_ = NetworkExtensions.TryParseToSubnets(remoteIPFilter.Where(x => x.Contains('/', StringComparison.OrdinalIgnoreCase)).ToArray(), out _remoteAddressFilter, false);
var remoteFilteredSubnets = remoteIPFilter.Where(x => x.Contains('/', StringComparison.OrdinalIgnoreCase)).ToArray();
if (!NetworkExtensions.TryParseToSubnets(remoteFilteredSubnets, out _remoteAddressFilter, false))
{
_remoteAddressFilter.Clear();
}
// Parse everything else as an IP and construct subnet with a single IP
var ips = remoteIPFilter.Where(x => !x.Contains('/', StringComparison.OrdinalIgnoreCase));
foreach (var ip in ips)
var remoteFilteredIPs = remoteIPFilter.Where(x => !x.Contains('/', StringComparison.OrdinalIgnoreCase));
foreach (var ip in remoteFilteredIPs)
{
if (IPAddress.TryParse(ip, out var ipp))
{
@ -436,44 +448,43 @@ namespace Jellyfin.Networking.Manager
if (parts.Length != 2)
{
_logger.LogError("Unable to parse bind override: {Entry}", entry);
return;
}
var replacement = parts[1].Trim();
var identifier = parts[0];
if (string.Equals(identifier, "all", StringComparison.OrdinalIgnoreCase))
{
_publishedServerUrls[new IPData(IPAddress.Broadcast, null)] = replacement;
}
else if (string.Equals(identifier, "external", StringComparison.OrdinalIgnoreCase))
{
_publishedServerUrls[new IPData(IPAddress.Any, new IPNetwork(IPAddress.Any, 0))] = replacement;
_publishedServerUrls[new IPData(IPAddress.IPv6Any, new IPNetwork(IPAddress.IPv6Any, 0))] = replacement;
}
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;
}
}
else if (NetworkExtensions.TryParseToSubnet(identifier, out var result) && result != null)
{
var data = new IPData(result.Prefix, result);
_publishedServerUrls[data] = replacement;
}
else if (TryParseInterface(identifier, out var ifaces))
{
foreach (var iface in ifaces)
{
_publishedServerUrls[iface] = replacement;
}
}
else
{
var replacement = parts[1].Trim();
var identifier = parts[0];
if (string.Equals(identifier, "all", StringComparison.OrdinalIgnoreCase))
{
_publishedServerUrls[new IPData(IPAddress.Broadcast, null)] = replacement;
}
else if (string.Equals(identifier, "external", StringComparison.OrdinalIgnoreCase))
{
_publishedServerUrls[new IPData(IPAddress.Any, new IPNetwork(IPAddress.Any, 0))] = replacement;
_publishedServerUrls[new IPData(IPAddress.IPv6Any, new IPNetwork(IPAddress.IPv6Any, 0))] = replacement;
}
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;
}
}
else if (NetworkExtensions.TryParseToSubnet(identifier, out var result) && result != null)
{
var data = new IPData(result.Prefix, result);
_publishedServerUrls[data] = replacement;
}
else if (TryParseInterface(identifier, out var ifaces))
{
foreach (var iface in ifaces)
{
_publishedServerUrls[iface] = replacement;
}
}
else
{
_logger.LogError("Unable to parse bind override: {Entry}", entry);
}
_logger.LogError("Unable to parse bind override: {Entry}", entry);
}
}
}
@ -556,31 +567,28 @@ namespace Jellyfin.Networking.Manager
public bool TryParseInterface(string intf, out List<IPData> result)
{
result = new List<IPData>();
if (string.IsNullOrEmpty(intf))
if (string.IsNullOrEmpty(intf) || _interfaces is null)
{
return false;
}
if (_interfaces != null)
// Match all interfaces starting with names starting with token
var matchedInterfaces = _interfaces.Where(s => s.Name.Equals(intf, StringComparison.OrdinalIgnoreCase)).OrderBy(x => x.Index).ToList();
if (matchedInterfaces.Count > 0)
{
// Match all interfaces starting with names starting with token
var matchedInterfaces = _interfaces.Where(s => s.Name.Equals(intf, StringComparison.OrdinalIgnoreCase)).OrderBy(x => x.Index).ToList();
if (matchedInterfaces.Any())
_logger.LogInformation("Interface {Token} used in settings. Using its interface addresses.", intf);
// Use interface IP instead of name
foreach (var iface in matchedInterfaces)
{
_logger.LogInformation("Interface {Token} used in settings. Using its interface addresses.", intf);
// Use interface IP instead of name
foreach (var iface in matchedInterfaces)
if ((IsIpv4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork)
|| (IsIpv6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6))
{
if ((IsIpv4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork)
|| (IsIpv6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6))
{
result.Add(iface);
}
result.Add(iface);
}
return true;
}
return true;
}
return false;
@ -626,6 +634,11 @@ namespace Jellyfin.Networking.Manager
/// <inheritdoc/>
public IReadOnlyList<IPData> GetLoopbacks()
{
if (!(IsIpv4Enabled && IsIpv4Enabled))
{
return Array.Empty<IPData>();
}
var loopbackNetworks = new List<IPData>();
if (IsIpv4Enabled)
{
@ -643,62 +656,64 @@ namespace Jellyfin.Networking.Manager
/// <inheritdoc/>
public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false)
{
if (_interfaces.Count == 0)
if (_interfaces.Count != 0)
{
// No bind address and no exclusions, so listen on all interfaces.
var result = new List<IPData>();
return _interfaces;
}
if (individualInterfaces)
{
foreach (var iface in _interfaces)
{
result.Add(iface);
}
// No bind address and no exclusions, so listen on all interfaces.
var result = new List<IPData>();
return result;
}
if (IsIpv4Enabled && IsIpv6Enabled)
if (individualInterfaces)
{
foreach (var iface in _interfaces)
{
// Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any by default
result.Add(new IPData(IPAddress.IPv6Any, new IPNetwork(IPAddress.IPv6Any, 0)));
}
else if (IsIpv4Enabled)
{
result.Add(new IPData(IPAddress.Any, new IPNetwork(IPAddress.Any, 0)));
}
else if (IsIpv6Enabled)
{
// Cannot use IPv6Any as Kestrel will bind to IPv4 addresses too.
foreach (var iface in _interfaces)
{
if (iface.AddressFamily == AddressFamily.InterNetworkV6)
{
result.Add(iface);
}
}
result.Add(iface);
}
return result;
}
return _interfaces;
if (IsIpv4Enabled && IsIpv6Enabled)
{
// Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any by default
result.Add(new IPData(IPAddress.IPv6Any, new IPNetwork(IPAddress.IPv6Any, 0)));
}
else if (IsIpv4Enabled)
{
result.Add(new IPData(IPAddress.Any, new IPNetwork(IPAddress.Any, 0)));
}
else if (IsIpv6Enabled)
{
// Cannot use IPv6Any as Kestrel will bind to IPv4 addresses too.
foreach (var iface in _interfaces)
{
if (iface.AddressFamily == AddressFamily.InterNetworkV6)
{
result.Add(iface);
}
}
}
return result;
}
/// <inheritdoc/>
public string GetBindInterface(string source, out int? port)
{
_ = NetworkExtensions.TryParseHost(source, out var address, IsIpv4Enabled, IsIpv6Enabled);
var result = GetBindAddress(address.FirstOrDefault(), out port);
if (!NetworkExtensions.TryParseHost(source, out var addresses, IsIpv4Enabled, IsIpv6Enabled))
{
addresses = Array.Empty<IPAddress>();
}
var result = GetBindAddress(addresses.FirstOrDefault(), out port);
return result;
}
/// <inheritdoc/>
public string GetBindInterface(HttpRequest source, out int? port)
{
string result;
_ = NetworkExtensions.TryParseHost(source.Host.Host, out var addresses, IsIpv4Enabled, IsIpv6Enabled);
result = GetBindAddress(addresses.FirstOrDefault(), out port);
var result = GetBindInterface(source.Host.Host, out port);
port ??= source.Host.Port;
return result;
@ -749,36 +764,36 @@ namespace Jellyfin.Networking.Manager
.ThenBy(x => x.Index)
.ToList();
if (availableInterfaces.Any())
if (availableInterfaces.Count > 0)
{
if (source != null)
if (source is null)
{
foreach (var intf in availableInterfaces)
{
if (intf.Address.Equals(source))
{
result = NetworkExtensions.FormatIpString(intf.Address);
_logger.LogDebug("{Source}: Found matching interface to use as bind address: {Result}", source, result);
return result;
}
}
result = NetworkExtensions.FormatIpString(availableInterfaces.First().Address);
_logger.LogDebug("{Source}: Using first internal interface as bind address: {Result}", source, result);
return result;
}
// Does the request originate in one of the interface subnets?
// (For systems with multiple internal network cards, and multiple subnets)
foreach (var intf in availableInterfaces)
foreach (var intf in availableInterfaces)
{
if (intf.Address.Equals(source))
{
if (intf.Subnet.Contains(source))
{
result = NetworkExtensions.FormatIpString(intf.Address);
_logger.LogDebug("{Source}: Found internal interface with matching subnet, using it as bind address: {Result}", source, result);
return result;
}
result = NetworkExtensions.FormatIpString(intf.Address);
_logger.LogDebug("{Source}: Found matching interface to use as bind address: {Result}", source, result);
return result;
}
}
result = NetworkExtensions.FormatIpString(availableInterfaces.First().Address);
_logger.LogDebug("{Source}: Using first internal interface as bind address: {Result}", source, result);
return result;
// Does the request originate in one of the interface subnets?
// (For systems with multiple internal network cards, and multiple subnets)
foreach (var intf in availableInterfaces)
{
if (intf.Subnet.Contains(source))
{
result = NetworkExtensions.FormatIpString(intf.Address);
_logger.LogDebug("{Source}: Found internal interface with matching subnet, using it as bind address: {Result}", source, result);
return result;
}
}
}
// There isn't any others, so we'll use the loopback.
@ -810,6 +825,11 @@ namespace Jellyfin.Networking.Manager
foreach (var ept in addresses)
{
match |= IPAddress.IsLoopback(ept) || (_lanSubnets.Any(x => x.Contains(ept)) && !_excludedSubnets.Any(x => x.Contains(ept)));
if (match)
{
break;
}
}
return match;
@ -824,15 +844,15 @@ namespace Jellyfin.Networking.Manager
ArgumentNullException.ThrowIfNull(address);
// See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
if (TrustAllIpv6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
if ((TrustAllIpv6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
|| address.Equals(IPAddress.Loopback)
|| address.Equals(IPAddress.IPv6Loopback))
{
return true;
}
// As private addresses can be redefined by Configuration.LocalNetworkAddresses
var match = CheckIfLanAndNotExcluded(address);
return address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback) || match;
return CheckIfLanAndNotExcluded(address);
}
private bool CheckIfLanAndNotExcluded(IPAddress address)
@ -865,20 +885,16 @@ namespace Jellyfin.Networking.Manager
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))
.GroupBy(x => x.Key)
.Select(x => x.First())
.OrderBy(x => x.Key.Address.Equals(IPAddress.Any)
|| x.Key.Address.Equals(IPAddress.IPv6Any))
.ToList();
|| 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))
.ToList();
// Check for user override.
foreach (var data in validPublishedServerUrls)
{
// Get address interface.
var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Key.Subnet.Contains(x.Address));
if (isInExternalSubnet && (data.Key.Address.Equals(IPAddress.Any) || data.Key.Address.Equals(IPAddress.IPv6Any)))
{
// External.
@ -886,6 +902,9 @@ namespace Jellyfin.Networking.Manager
break;
}
// Get address interface.
var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Key.Subnet.Contains(x.Address));
if (intf?.Address != null)
{
// Match IP address.
@ -896,7 +915,7 @@ namespace Jellyfin.Networking.Manager
if (string.IsNullOrEmpty(bindPreference))
{
_logger.LogDebug("{Source}: No matching bind address override found.", source);
_logger.LogDebug("{Source}: No matching bind address override found", source);
return false;
}
@ -941,40 +960,22 @@ namespace Jellyfin.Networking.Manager
count = 0;
}
if (count > 0)
if (count == 0)
{
return false;
}
IPAddress? bindAddress = null;
if (isInExternalSubnet)
{
IPAddress? bindAddress = null;
var externalInterfaces = _interfaces.Where(x => !IsInLocalNetwork(x.Address))
.OrderBy(x => x.Index)
.ToList();
if (isInExternalSubnet)
if (externalInterfaces.Count > 0)
{
if (externalInterfaces.Any())
{
// Check to see if any of the external bind interfaces are in the same subnet as the source.
// If none exists, this will select the first external interface if there is one.
bindAddress = externalInterfaces
.OrderByDescending(x => x.Subnet.Contains(source))
.ThenBy(x => x.Index)
.Select(x => x.Address)
.FirstOrDefault();
if (bindAddress != null)
{
result = NetworkExtensions.FormatIpString(bindAddress);
_logger.LogDebug("{Source}: External request received, matching external bind address found: {Result}", source, result);
return true;
}
}
_logger.LogWarning("{Source}: External request received, no matching external bind address found, trying internal addresses.", source);
}
else
{
// Check to see if any of the internal bind interfaces are in the same subnet as the source.
// If none exists, this will select the first internal interface if there is one.
bindAddress = _interfaces.Where(x => IsInLocalNetwork(x.Address))
// Check to see if any of the external bind interfaces are in the same subnet as the source.
// If none exists, this will select the first external interface if there is one.
bindAddress = externalInterfaces
.OrderByDescending(x => x.Subnet.Contains(source))
.ThenBy(x => x.Index)
.Select(x => x.Address)
@ -983,10 +984,29 @@ namespace Jellyfin.Networking.Manager
if (bindAddress != null)
{
result = NetworkExtensions.FormatIpString(bindAddress);
_logger.LogDebug("{Source}: Internal request received, matching internal bind address found: {Result}", source, result);
_logger.LogDebug("{Source}: External request received, matching external bind address found: {Result}", source, result);
return true;
}
}
_logger.LogWarning("{Source}: External request received, no matching external bind address found, trying internal addresses.", source);
}
else
{
// Check to see if any of the internal bind interfaces are in the same subnet as the source.
// If none exists, this will select the first internal interface if there is one.
bindAddress = _interfaces.Where(x => IsInLocalNetwork(x.Address))
.OrderByDescending(x => x.Subnet.Contains(source))
.ThenBy(x => x.Index)
.Select(x => x.Address)
.FirstOrDefault();
if (bindAddress != null)
{
result = NetworkExtensions.FormatIpString(bindAddress);
_logger.LogDebug("{Source}: Internal request received, matching internal bind address found: {Result}", source, result);
return true;
}
}
return false;
@ -1000,16 +1020,21 @@ namespace Jellyfin.Networking.Manager
/// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
private bool MatchesExternalInterface(IPAddress source, out string result)
{
result = string.Empty;
// Get the first WAN interface address that isn't a loopback.
var extResult = _interfaces.Where(p => !IsInLocalNetwork(p.Address)).OrderBy(x => x.Index);
// Get the first external interface address that isn't a loopback.
var extResult = _interfaces.Where(p => !IsInLocalNetwork(p.Address)).OrderBy(x => x.Index).ToArray();
// No external interface found
if (extResult.Length == 0)
{
result = string.Empty;
_logger.LogWarning("{Source}: External request received, but no external interface found. Need to route through internal network.", source);
return false;
}
IPAddress? hasResult = null;
// Does the request originate in one of the interface subnets?
// (For systems with multiple internal network cards, and multiple subnets)
// (For systems with multiple network cards and/or multiple subnets)
foreach (var intf in extResult)
{
hasResult ??= intf.Address;
if (!IsInLocalNetwork(intf.Address) && intf.Subnet.Contains(source))
{
result = NetworkExtensions.FormatIpString(intf.Address);
@ -1018,15 +1043,10 @@ namespace Jellyfin.Networking.Manager
}
}
if (hasResult != null)
{
result = NetworkExtensions.FormatIpString(hasResult);
_logger.LogDebug("{Source}: Using first external interface as bind address: {Result}", source, result);
return true;
}
_logger.LogWarning("{Source}: External request received, but no external interface found. Need to route through internal network.", source);
return false;
// Fallback to first external interface.
result = NetworkExtensions.FormatIpString(extResult.First().Address);
_logger.LogDebug("{Source}: Using first external interface as bind address: {Result}", source, result);
return true;
}
}
}

View File

@ -59,10 +59,16 @@ namespace MediaBrowser.Common.Net
{
get
{
return Address.Equals(IPAddress.None)
? (Subnet.Prefix.AddressFamily.Equals(IPAddress.None)
? AddressFamily.Unspecified : Subnet.Prefix.AddressFamily)
: Address.AddressFamily;
if (Address.Equals(IPAddress.None))
{
return Subnet.Prefix.AddressFamily.Equals(IPAddress.None)
? AddressFamily.Unspecified
: Subnet.Prefix.AddressFamily;
}
else
{
return Address.AddressFamily;
}
}
}
}