diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 6add7e0b39..06f0ea09c8 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -878,7 +878,14 @@ namespace Emby.Server.Implementations return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort); } - return GetSmartApiUrl(request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback); + // Gets the server URL that can also be used by applications not on the server, for instance + // a cast receiver device. In case the request has a localhost url, LocalAddress will be set to a + // server Private network address (IPv4) or Unique Local Address (ULA, IPv6) which is valid for all + // devices on the LAN. + IPAddress localAddress = request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback; + return NetworkConstants.IPv4RFC5735Loopback.Contains(localAddress) || NetworkConstants.IPv6RFC4291Loopback.Contains(localAddress) + ? GetSmartApiUrl(NetworkManager.ServerLanAddress) + : GetSmartApiUrl(localAddress); } /// diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs index 1da44b0484..a62dc27456 100644 --- a/src/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs @@ -132,6 +132,11 @@ public class NetworkManager : INetworkManager, IDisposable /// public bool TrustAllIPv6Interfaces { get; private set; } + /// + /// Gets the IP LAN address of Jellyfin server usable for a Google cast receiver and such. + /// + public static string ServerLanAddress { get; private set; } = string.Empty; + /// /// Gets the Published server override list. /// @@ -633,6 +638,8 @@ public class NetworkManager : INetworkManager, IDisposable InitializeOverrides(config); PrintNetworkInformation(config, false); + + InitializeServerLanAddress(config); } /// @@ -1122,4 +1129,80 @@ public class NetworkManager : INetworkManager, IDisposable _logger.Log(logLevel, "Filter list: {0}", _remoteAddressFilter.Select(s => s.Prefix + "/" + s.PrefixLength)); } } + + /// + /// Initializes the LAN IP address that the server advertises for cast receivers. + /// A LAN device like a Chromecast receiver needs an unique LAN address of the Jellyfin server. + /// For IPv4, a Private network address (like 192.168.0.0/16 )serves this purpose, and for IPv6 + /// the Unique Local Address (ULA, fd00::/8) does. An IPv6 global address (2000::/3) also works + /// as unique server address but is ranked as less desirable than a local address. + /// See e.g. https://en.wikipedia.org/wiki/Private_network + /// + /// If multiple addresses are available: + /// - prefer Unique Local Addresses (ULA) or Site Local Addresses over other (global) addresses. + /// - all things equal, prefer IPv4 over IPv6 as the former in practice appears to be more stable. + /// + private void InitializeServerLanAddress(NetworkConfiguration config) + { + lock (_initLock) + { + int rank = 0; // ranks most preferable ip address + IPAddress ipSrv = IPAddress.None; + NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces(); + + foreach (var adapter in adapters) + { + if (adapter.NetworkInterfaceType == NetworkInterfaceType.Loopback) + { + continue; + } + + // check if the network interface serves as IPv4 gateway + bool haveIP4GateWay = false; + foreach (var address in adapter.GetIPProperties().GatewayAddresses) + { + haveIP4GateWay = haveIP4GateWay || address.Address.AddressFamily == AddressFamily.InterNetwork; + } + + // if the network interface is used as gateway, and has an acceptable IP address for this server, + // then this address is assumed to be accessible by other devices on the LAN. + foreach (var inf in _interfaces) + { + IPAddress ip = inf.Address; + + if (!inf.Name.Equals(adapter.Name, StringComparison.OrdinalIgnoreCase) || + ip.Equals(IPAddress.Loopback) || ip.Equals(IPAddress.IPv6Loopback)) + { + continue; + } + + if (ip.AddressFamily == AddressFamily.InterNetworkV6) + { + if (IsIPv6Enabled) + { + if (rank < 2 && (ip.IsIPv6SiteLocal || ip.IsIPv6UniqueLocal)) + { + rank = 2; + ipSrv = ip; + } + else if (rank < 1) + { + rank = 1; + ipSrv = ip; + } + } + } + else if (haveIP4GateWay && IsIPv4Enabled && rank < 3) + { + rank = 3; + ipSrv = ip; + } + } + + ServerLanAddress = ipSrv.ToString(); + } + } + + _logger.LogInformation("ServerLanAddress: {0} ", ServerLanAddress); + } }