diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 5d252d8dc4..8e89d9ae6a 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -52,7 +52,6 @@ namespace Emby.Dlna.Main private readonly ISocketFactory _socketFactory; private readonly INetworkManager _networkManager; private readonly object _syncLock = new object(); - private readonly NetworkConfiguration _netConfig; private readonly bool _disabled; private PlayToManager _manager; @@ -125,8 +124,8 @@ namespace Emby.Dlna.Main config); Current = this; - _netConfig = config.GetConfiguration("network"); - _disabled = appHost.ListenWithHttps && _netConfig.RequireHttps; + var netConfig = config.GetConfiguration("network"); + _disabled = appHost.ListenWithHttps && netConfig.RequireHttps; if (_disabled && _config.GetDlnaConfiguration().EnableServer) { @@ -318,15 +317,9 @@ namespace Emby.Dlna.Main var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; - _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); + _logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, address); - var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri); - if (!string.IsNullOrEmpty(_appHost.PublishedServerUrl)) - { - // DLNA will only work over http, so we must reset to http:// : {port}. - uri.Scheme = "http"; - uri.Port = _netConfig.HttpServerPortNumber; - } + var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(false) + descriptorUri); var device = new SsdpRootDevice { diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f359ee44c3..73919f306f 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1123,12 +1123,6 @@ namespace Emby.Server.Implementations } string smart = NetManager.GetBindInterface(remoteAddr, out port); - // If the smartAPI doesn't start with http then treat it as a host or ip. - if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - return smart.Trim('/'); - } - return GetLocalApiUrl(smart.Trim('/'), null, port); } @@ -1155,12 +1149,6 @@ namespace Emby.Server.Implementations } string smart = NetManager.GetBindInterface(request, out port); - // If the smartAPI doesn't start with http then treat it as a host or ip. - if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - return smart.Trim('/'); - } - return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port); } @@ -1175,30 +1163,28 @@ namespace Emby.Server.Implementations } string smart = NetManager.GetBindInterface(hostname, out port); - - // If the smartAPI doesn't start with http then treat it as a host or ip. - if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - return smart.Trim('/'); - } - return GetLocalApiUrl(smart.Trim('/'), null, port); } /// - public string GetLoopbackHttpApiUrl() + public string GetApiUrlForLocalAccess(bool allowHttps) { - if (NetManager.IsIP6Enabled) - { - return GetLocalApiUrl("::1", Uri.UriSchemeHttp, HttpPort); - } - - return GetLocalApiUrl("127.0.0.1", Uri.UriSchemeHttp, HttpPort); + // With an empty source, the port will be null + string smart = NetManager.GetBindInterface(string.Empty, out _); + var scheme = allowHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; + var port = allowHttps ? HttpsPort : HttpPort; + return GetLocalApiUrl(smart.Trim('/'), scheme, port); } /// 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)) + { + return hostname.TrimEnd('/'); + } + // NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does // not. For consistency, always trim the trailing slash. return new UriBuilder diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index e5abb523c5..367f3cb9ed 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1027,7 +1027,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var stream = new MediaSourceInfo { - EncoderPath = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveRecordings/" + info.Id + "/stream", + EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream", EncoderProtocol = MediaProtocol.Http, Path = info.Path, Protocol = MediaProtocol.File, diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index ecd28097d3..4b7584af34 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -104,7 +104,7 @@ namespace Emby.Server.Implementations.LiveTv // Dummy this up so that direct play checks can still run if (string.IsNullOrEmpty(source.Path) && source.Protocol == MediaProtocol.Http) { - source.Path = _appHost.GetSmartApiUrl(string.Empty); + source.Path = _appHost.GetApiUrlForLocalAccess(); } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index b621055d8e..9fba17ca3b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -147,7 +147,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun // OpenedMediaSource.Path = tempFile; // OpenedMediaSource.ReadAtNativeFramerate = true; - MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; + MediaSource.Path = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; MediaSource.Protocol = MediaProtocol.Http; // OpenedMediaSource.SupportsDirectPlay = false; // OpenedMediaSource.SupportsDirectStream = true; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 3b69e55b01..b1ce7b2b3c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts // OpenedMediaSource.Path = tempFile; // OpenedMediaSource.ReadAtNativeFramerate = true; - MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; + MediaSource.Path = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; MediaSource.Protocol = MediaProtocol.Http; // OpenedMediaSource.Path = TempFilePath; diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 4078fd1269..cf002dc738 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -455,10 +455,10 @@ namespace Jellyfin.Networking.Manager } // No bind address, so return all internal interfaces. - return CreateCollection(_internalInterfaces.Where(p => !p.IsLoopback())); + return CreateCollection(_internalInterfaces); } - return new Collection(_bindAddresses); + return new Collection(_bindAddresses.Where(a => IsInLocalNetwork(a)).ToArray()); } /// @@ -481,7 +481,7 @@ namespace Jellyfin.Networking.Manager } // As private addresses can be redefined by Configuration.LocalNetworkAddresses - return _lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address); + return address.IsLoopback() || (_lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address)); } /// @@ -647,16 +647,6 @@ namespace Jellyfin.Networking.Manager _interfaceAddresses.AddItem(address, false); _interfaceNames[parts[2]] = Math.Abs(index); } - - if (IsIP4Enabled) - { - _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback); - } - - if (IsIP6Enabled) - { - _interfaceAddresses.AddItem(IPNetAddress.IP6Loopback); - } } InitialiseLAN(config); @@ -1037,17 +1027,14 @@ namespace Jellyfin.Networking.Manager // Subnets are the same as the calculated internal interface. _lanSubnets = new Collection(); - // We must listen on loopback for LiveTV to function regardless of the settings. if (IsIP6Enabled) { - _lanSubnets.AddItem(IPNetAddress.IP6Loopback); _lanSubnets.AddItem(IPNetAddress.Parse("fc00::/7")); // ULA _lanSubnets.AddItem(IPNetAddress.Parse("fe80::/10")); // Site local } if (IsIP4Enabled) { - _lanSubnets.AddItem(IPNetAddress.IP4Loopback); _lanSubnets.AddItem(IPNetAddress.Parse("10.0.0.0/8")); _lanSubnets.AddItem(IPNetAddress.Parse("172.16.0.0/12")); _lanSubnets.AddItem(IPNetAddress.Parse("192.168.0.0/16")); @@ -1055,17 +1042,6 @@ namespace Jellyfin.Networking.Manager } else { - // We must listen on loopback for LiveTV to function regardless of the settings. - if (IsIP6Enabled) - { - _lanSubnets.AddItem(IPNetAddress.IP6Loopback); - } - - if (IsIP4Enabled) - { - _lanSubnets.AddItem(IPNetAddress.IP4Loopback); - } - // Internal interfaces must be private, not excluded and part of the LocalNetworkSubnet. _internalInterfaces = CreateCollection(_interfaceAddresses.Where(IsInLocalNetwork)); } diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 7e5e742f5d..7da492af3c 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -81,11 +81,11 @@ namespace MediaBrowser.Controller string GetSmartApiUrl(string hostname, int? port = null); /// - /// Gets a localhost URL that can be used to access the API using the loop-back IP address. - /// over HTTP (not HTTPS). + /// Gets an URL that can be used to access the API over LAN. /// + /// A value indicating whether to allow HTTPS. /// The API URL. - string GetLoopbackHttpApiUrl(); + string GetApiUrlForLocalAccess(bool allowHttps = true); /// /// Gets a local (LAN) URL that can be used to access the API. diff --git a/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs b/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs index 1cad625b73..61f9132528 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs @@ -34,7 +34,7 @@ namespace Jellyfin.Networking.Tests } /// - /// Checks that thge given IP address is not in the network provided. + /// Checks that the given IP address is not in the network provided. /// /// Network address(es). /// The IP to check. diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index a24eee6934..6b93974373 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -35,9 +35,9 @@ namespace Jellyfin.Networking.Tests // eth16 only [InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")] // All interfaces excluded. (including loopbacks) - [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[127.0.0.1/8,::1/128]")] + [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[]")] // vEthernet1 and vEthernet212 should be excluded. - [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24,127.0.0.1/8,::1/128]")] + [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24]")] // Overlapping interface, [InlineData("192.168.1.110/24,-20,br0|192.168.1.10/24,-16,br0|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.110/24,192.168.1.10/24]")] public void IgnoreVirtualInterfaces(string interfaces, string lan, string value) @@ -476,5 +476,51 @@ namespace Jellyfin.Networking.Tests Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIp)), denied); } + + [Theory] + [InlineData("192.168.1.209/24,-16,eth16", "192.168.1.0/24", "", "192.168.1.209")] // Only 1 address so use it. + [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "", "192.168.1.208")] // LAN address is specified by default. + [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "10.0.0.1", "10.0.0.1")] // return bind address + + public void GetBindInterface_NoSourceGiven_Success(string interfaces, string lan, string bind, string result) + { + var conf = new NetworkConfiguration + { + EnableIPV4 = true, + LocalNetworkSubnets = lan.Split(','), + LocalNetworkAddresses = bind.Split(',') + }; + + NetworkManager.MockNetworkSettings = interfaces; + using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + + var interfaceToUse = nm.GetBindInterface(string.Empty, out _); + + Assert.Equal(result, interfaceToUse); + } + + [Theory] + [InlineData("192.168.1.209/24,-16,eth16", "192.168.1.0/24", "", "192.168.1.210", "192.168.1.209")] // Source on LAN + [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "", "192.168.1.209", "192.168.1.208")] // Source on LAN + [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "", "8.8.8.8", "10.0.0.1")] // Source external. + [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "10.0.0.1", "192.168.1.209", "10.0.0.1")] // LAN not bound, so return external. + [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "192.168.1.208,10.0.0.1", "8.8.8.8", "10.0.0.1")] // return external bind address + [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "192.168.1.208,10.0.0.1", "192.168.1.210", "192.168.1.208")] // return LAN bind address + public void GetBindInterface_ValidSourceGiven_Success(string interfaces, string lan, string bind, string source, string result) + { + var conf = new NetworkConfiguration + { + EnableIPV4 = true, + LocalNetworkSubnets = lan.Split(','), + LocalNetworkAddresses = bind.Split(',') + }; + + NetworkManager.MockNetworkSettings = interfaces; + using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + + var interfaceToUse = nm.GetBindInterface(source, out _); + + Assert.Equal(result, interfaceToUse); + } } }