Fix host and port handling for published server URI overrides

This commit is contained in:
WizardOfYendor1
2026-07-10 14:19:08 -04:00
parent 53aafcd38e
commit ce43df6f43
3 changed files with 71 additions and 17 deletions
@@ -965,8 +965,9 @@ namespace Emby.Server.Implementations
/// <inheritdoc/>
public string GetLocalApiUrl(string hostname, string scheme = null, int? port = null)
{
// If the smartAPI doesn't start with http then treat it as a host or ip.
if (hostname.StartsWith("http", StringComparison.OrdinalIgnoreCase))
// If the smartAPI isn't already a complete URL then treat it as a host or ip.
if (hostname.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
|| hostname.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
return hostname.TrimEnd('/');
}
@@ -851,7 +851,7 @@ public class NetworkManager : INetworkManager, IDisposable
bool isExternal = !IsInLocalNetwork(source);
_logger.LogDebug("Trying to get bind address for source {Source} - External: {IsExternal}", source, isExternal);
if (!skipOverrides && MatchesPublishedServerUrl(source, isExternal, out result))
if (!skipOverrides && MatchesPublishedServerUrl(source, isExternal, out result, out port))
{
return result;
}
@@ -1017,11 +1017,12 @@ public class NetworkManager : INetworkManager, IDisposable
/// <param name="source">IP source address to use.</param>
/// <param name="isInExternalSubnet">True if the source is in an external subnet.</param>
/// <param name="bindPreference">The published server URL that matches the source address.</param>
/// <param name="port">The explicit port parsed from the override, if any.</param>
/// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
private bool MatchesPublishedServerUrl(IPAddress source, bool isInExternalSubnet, out string bindPreference)
private bool MatchesPublishedServerUrl(IPAddress source, bool isInExternalSubnet, out string bindPreference, out int? port)
{
bindPreference = string.Empty;
int? port = null;
port = null;
// Only consider subnets including the source IP, preferring specific overrides
List<PublishedServerUriOverride> validPublishedServerUrls;
@@ -1063,24 +1064,42 @@ public class NetworkManager : INetworkManager, IDisposable
return false;
}
// Handle override specifying port
var parts = bindPreference.Split(':');
if (parts.Length > 1)
// Handle override specifying an explicit port.
(bindPreference, port) = ParseHostAndPort(bindPreference);
if (port.HasValue)
{
if (int.TryParse(parts[1], out int p))
{
bindPreference = parts[0];
port = p;
_logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port);
return true;
}
_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;
}
/// <summary>
/// Splits a published server URL override into its host and explicit port, if any.
/// Full URLs (containing "://") are returned whole, with any port left embedded.
/// </summary>
/// <param name="value">The override value, e.g. "host:port", "[::1]:port", or a full URL.</param>
/// <returns>The parsed host (or the original value if not split) and the explicit port, if any.</returns>
private static (string Host, int? Port) ParseHostAndPort(string value)
{
if (value.Contains("://", StringComparison.Ordinal))
{
return (value, null);
}
if (Uri.TryCreate("any://" + value, UriKind.Absolute, out var parsed) && parsed.Port != -1)
{
return (parsed.DnsSafeHost, parsed.Port);
}
return (value, null);
}
/// <summary>
/// Attempts to match the source against the user defined bind interfaces.
/// </summary>
@@ -493,5 +493,39 @@ namespace Jellyfin.Networking.Tests
Assert.Equal(result, interfaceToUse);
}
[Theory]
// Internal override with an explicit port.
[InlineData("192.168.1.1", "192.168.1.0/24=internal.jellyfin:8097", "internal.jellyfin", 8097)]
// External/all override with an explicit port.
[InlineData("8.8.8.8", "all=external.jellyfin:8097", "external.jellyfin", 8097)]
// Bracketed IPv6 override with an explicit port.
[InlineData("8.8.8.8", "all=[fd00:1234::1]:8097", "fd00:1234::1", 8097)]
// Bare IPv6 override without a port - must remain whole, not mangled by the extra colons.
[InlineData("8.8.8.8", "all=fd00:1234::1", "fd00:1234::1", null)]
// Full HTTPS URL override with an explicit port - the URL stays whole, port stays embedded.
[InlineData("8.8.8.8", "all=https://secure.jellyfin.org:8920", "https://secure.jellyfin.org:8920", null)]
// Hostname beginning with "http" is a hostname, not a URL scheme.
[InlineData("8.8.8.8", "all=http-proxy.lan:8097", "http-proxy.lan", 8097)]
public void GetBindAddress_PublishedServerOverride_ParsesHostAndPort(string source, string publishedServers, string expectedHost, int? expectedPort)
{
var conf = new NetworkConfiguration
{
LocalNetworkSubnets = new[] { "192.168.1.0/24" },
LocalNetworkAddresses = new[] { "eth16", "eth11" },
EnableIPv4 = true,
PublishedServerUriBySubnet = new[] { publishedServers }
};
NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11";
var startupConf = new Mock<IConfiguration>();
using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
NetworkManager.MockNetworkSettings = string.Empty;
var intf = nm.GetBindAddress(IPAddress.Parse(source), out int? port);
Assert.Equal(expectedHost, intf);
Assert.Equal(expectedPort, port);
}
}
}