Explorar el Código

Use System.Net.IPNetwork

Bond_009 hace 1 año
padre
commit
99e0d46ad9

+ 363 - 0
Emby.Dlna/Main/DlnaEntryPoint.cs

@@ -0,0 +1,363 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using Emby.Dlna.PlayTo;
+using Emby.Dlna.Ssdp;
+using Jellyfin.Networking.Configuration;
+using Jellyfin.Networking.Extensions;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Globalization;
+using Microsoft.Extensions.Logging;
+using Rssdp;
+using Rssdp.Infrastructure;
+
+namespace Emby.Dlna.Main
+{
+    public sealed class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
+    {
+        private readonly IServerConfigurationManager _config;
+        private readonly ILogger<DlnaEntryPoint> _logger;
+        private readonly IServerApplicationHost _appHost;
+        private readonly ISessionManager _sessionManager;
+        private readonly IHttpClientFactory _httpClientFactory;
+        private readonly ILibraryManager _libraryManager;
+        private readonly IUserManager _userManager;
+        private readonly IDlnaManager _dlnaManager;
+        private readonly IImageProcessor _imageProcessor;
+        private readonly IUserDataManager _userDataManager;
+        private readonly ILocalizationManager _localization;
+        private readonly IMediaSourceManager _mediaSourceManager;
+        private readonly IMediaEncoder _mediaEncoder;
+        private readonly IDeviceDiscovery _deviceDiscovery;
+        private readonly ISsdpCommunicationsServer _communicationsServer;
+        private readonly INetworkManager _networkManager;
+        private readonly object _syncLock = new();
+        private readonly bool _disabled;
+
+        private PlayToManager _manager;
+        private SsdpDevicePublisher _publisher;
+
+        private bool _disposed;
+
+        public DlnaEntryPoint(
+            IServerConfigurationManager config,
+            ILoggerFactory loggerFactory,
+            IServerApplicationHost appHost,
+            ISessionManager sessionManager,
+            IHttpClientFactory httpClientFactory,
+            ILibraryManager libraryManager,
+            IUserManager userManager,
+            IDlnaManager dlnaManager,
+            IImageProcessor imageProcessor,
+            IUserDataManager userDataManager,
+            ILocalizationManager localizationManager,
+            IMediaSourceManager mediaSourceManager,
+            IDeviceDiscovery deviceDiscovery,
+            IMediaEncoder mediaEncoder,
+            ISsdpCommunicationsServer communicationsServer,
+            INetworkManager networkManager)
+        {
+            _config = config;
+            _appHost = appHost;
+            _sessionManager = sessionManager;
+            _httpClientFactory = httpClientFactory;
+            _libraryManager = libraryManager;
+            _userManager = userManager;
+            _dlnaManager = dlnaManager;
+            _imageProcessor = imageProcessor;
+            _userDataManager = userDataManager;
+            _localization = localizationManager;
+            _mediaSourceManager = mediaSourceManager;
+            _deviceDiscovery = deviceDiscovery;
+            _mediaEncoder = mediaEncoder;
+            _communicationsServer = communicationsServer;
+            _networkManager = networkManager;
+            _logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
+
+            var netConfig = config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
+            _disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
+
+            if (_disabled && _config.GetDlnaConfiguration().EnableServer)
+            {
+                _logger.LogError("The DLNA specification does not support HTTPS.");
+            }
+        }
+
+        public async Task RunAsync()
+        {
+            await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
+
+            if (_disabled)
+            {
+                // No use starting as dlna won't work, as we're running purely on HTTPS.
+                return;
+            }
+
+            ReloadComponents();
+
+            _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
+        }
+
+        private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
+        {
+            if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
+            {
+                ReloadComponents();
+            }
+        }
+
+        private void ReloadComponents()
+        {
+            var options = _config.GetDlnaConfiguration();
+            StartDeviceDiscovery();
+
+            if (options.EnableServer)
+            {
+                StartDevicePublisher(options);
+            }
+            else
+            {
+                DisposeDevicePublisher();
+            }
+
+            if (options.EnablePlayTo)
+            {
+                StartPlayToManager();
+            }
+            else
+            {
+                DisposePlayToManager();
+            }
+        }
+
+        private void StartDeviceDiscovery()
+        {
+            try
+            {
+                ((DeviceDiscovery)_deviceDiscovery).Start(_communicationsServer);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Error starting device discovery");
+            }
+        }
+
+        public void StartDevicePublisher(Configuration.DlnaOptions options)
+        {
+            if (_publisher is not null)
+            {
+                return;
+            }
+
+            try
+            {
+                _publisher = new SsdpDevicePublisher(
+                    _communicationsServer,
+                    Environment.OSVersion.Platform.ToString(),
+                    // Can not use VersionString here since that includes OS and version
+                    Environment.OSVersion.Version.ToString(),
+                    _config.GetDlnaConfiguration().SendOnlyMatchedHost)
+                {
+                    LogFunction = (msg) => _logger.LogDebug("{Msg}", msg),
+                    SupportPnpRootDevice = false
+                };
+
+                RegisterServerEndpoints();
+
+                if (options.BlastAliveMessages)
+                {
+                    _publisher.StartSendingAliveNotifications(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
+                }
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Error registering endpoint");
+            }
+        }
+
+        private void RegisterServerEndpoints()
+        {
+            var udn = CreateUuid(_appHost.SystemId);
+            var descriptorUri = "/dlna/" + udn + "/description.xml";
+
+            // Only get bind addresses in LAN
+            // IPv6 is currently unsupported
+            var validInterfaces = _networkManager.GetInternalBindAddresses()
+                .Where(x => x.Address is not null)
+                .Where(x => x.AddressFamily != AddressFamily.InterNetworkV6)
+                .ToList();
+
+            if (validInterfaces.Count == 0)
+            {
+                // No interfaces returned, fall back to loopback
+                validInterfaces = _networkManager.GetLoopbacks().ToList();
+            }
+
+            foreach (var intf in validInterfaces)
+            {
+                var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
+
+                _logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, intf.Address);
+
+                var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(intf.Address, false) + descriptorUri);
+
+                var device = new SsdpRootDevice
+                {
+                    CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
+                    Location = uri.Uri, // Must point to the URL that serves your devices UPnP description document.
+                    Address = intf.Address,
+                    PrefixLength = NetworkExtensions.MaskToCidr(intf.Subnet.BaseAddress),
+                    FriendlyName = "Jellyfin",
+                    Manufacturer = "Jellyfin",
+                    ModelName = "Jellyfin Server",
+                    Uuid = udn
+                    // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
+                };
+
+                SetProperties(device, fullService);
+                _publisher.AddDevice(device);
+
+                var embeddedDevices = new[]
+                {
+                    "urn:schemas-upnp-org:service:ContentDirectory:1",
+                    "urn:schemas-upnp-org:service:ConnectionManager:1",
+                    // "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
+                };
+
+                foreach (var subDevice in embeddedDevices)
+                {
+                    var embeddedDevice = new SsdpEmbeddedDevice
+                    {
+                        FriendlyName = device.FriendlyName,
+                        Manufacturer = device.Manufacturer,
+                        ModelName = device.ModelName,
+                        Uuid = udn
+                        // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
+                    };
+
+                    SetProperties(embeddedDevice, subDevice);
+                    device.AddDevice(embeddedDevice);
+                }
+            }
+        }
+
+        private static string CreateUuid(string text)
+        {
+            if (!Guid.TryParse(text, out var guid))
+            {
+                guid = text.GetMD5();
+            }
+
+            return guid.ToString("D", CultureInfo.InvariantCulture);
+        }
+
+        private static void SetProperties(SsdpDevice device, string fullDeviceType)
+        {
+            var serviceParts = fullDeviceType
+                .Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase)
+                .Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase)
+                .Split(':');
+
+            device.DeviceTypeNamespace = serviceParts[0].Replace('.', '-');
+            device.DeviceClass = serviceParts[1];
+            device.DeviceType = serviceParts[2];
+        }
+
+        private void StartPlayToManager()
+        {
+            lock (_syncLock)
+            {
+                if (_manager is not null)
+                {
+                    return;
+                }
+
+                try
+                {
+                    _manager = new PlayToManager(
+                        _logger,
+                        _sessionManager,
+                        _libraryManager,
+                        _userManager,
+                        _dlnaManager,
+                        _appHost,
+                        _imageProcessor,
+                        _deviceDiscovery,
+                        _httpClientFactory,
+                        _userDataManager,
+                        _localization,
+                        _mediaSourceManager,
+                        _mediaEncoder);
+
+                    _manager.Start();
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError(ex, "Error starting PlayTo manager");
+                }
+            }
+        }
+
+        private void DisposePlayToManager()
+        {
+            lock (_syncLock)
+            {
+                if (_manager is not null)
+                {
+                    try
+                    {
+                        _logger.LogInformation("Disposing PlayToManager");
+                        _manager.Dispose();
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogError(ex, "Error disposing PlayTo manager");
+                    }
+
+                    _manager = null;
+                }
+            }
+        }
+
+        public void DisposeDevicePublisher()
+        {
+            if (_publisher is not null)
+            {
+                _logger.LogInformation("Disposing SsdpDevicePublisher");
+                _publisher.Dispose();
+                _publisher = null;
+            }
+        }
+
+        /// <inheritdoc />
+        public void Dispose()
+        {
+            if (_disposed)
+            {
+                return;
+            }
+
+            DisposeDevicePublisher();
+            DisposePlayToManager();
+            _disposed = true;
+        }
+    }
+}

+ 8 - 9
Jellyfin.Networking/Manager/NetworkManager.cs

@@ -11,7 +11,6 @@ using MediaBrowser.Common.Configuration;
 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;
@@ -530,7 +529,7 @@ namespace Jellyfin.Networking.Manager
                     {
                         foreach (var lan in _lanSubnets)
                         {
-                            var lanPrefix = lan.Prefix;
+                            var lanPrefix = lan.BaseAddress;
                             publishedServerUrls.Add(
                                 new PublishedServerUriOverride(
                                     new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength)),
@@ -541,7 +540,7 @@ namespace Jellyfin.Networking.Manager
                     }
                     else if (NetworkUtils.TryParseToSubnet(identifier, out var result) && result is not null)
                     {
-                        var data = new IPData(result.Prefix, result);
+                        var data = new IPData(result.BaseAddress, result);
                         publishedServerUrls.Add(
                             new PublishedServerUriOverride(
                                 data,
@@ -607,7 +606,7 @@ namespace Jellyfin.Networking.Manager
                     var parts = details.Split(',');
                     if (NetworkUtils.TryParseToSubnet(parts[0], out var subnet))
                     {
-                        var address = subnet.Prefix;
+                        var address = subnet.BaseAddress;
                         var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
                         if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
                         {
@@ -881,7 +880,7 @@ namespace Jellyfin.Networking.Manager
         {
             if (NetworkUtils.TryParseToSubnet(address, out var subnet))
             {
-                return IPAddress.IsLoopback(subnet.Prefix) || (_lanSubnets.Any(x => x.Contains(subnet.Prefix)) && !_excludedSubnets.Any(x => x.Contains(subnet.Prefix)));
+                return IPAddress.IsLoopback(subnet.BaseAddress) || (_lanSubnets.Any(x => x.Contains(subnet.BaseAddress)) && !_excludedSubnets.Any(x => x.Contains(subnet.BaseAddress)));
             }
 
             if (NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
@@ -1112,12 +1111,12 @@ namespace Jellyfin.Networking.Manager
             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, "Defined LAN addresses: {0}", _lanSubnets.Select(s => s.BaseAddress + "/" + s.PrefixLength));
+                _logger.Log(logLevel, "Defined LAN exclusions: {0}", _excludedSubnets.Select(s => s.BaseAddress + "/" + s.PrefixLength));
+                _logger.Log(logLevel, "Using LAN addresses: {0}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.BaseAddress + "/" + 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));
+                _logger.Log(logLevel, "Filter list: {0}", _remoteAddressFilter.Select(s => s.BaseAddress + "/" + s.PrefixLength));
             }
         }
     }

+ 1 - 2
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -30,7 +30,6 @@ using Microsoft.AspNetCore.Authentication;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Cors.Infrastructure;
-using Microsoft.AspNetCore.HttpOverrides;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.OpenApi.Any;
 using Microsoft.OpenApi.Interfaces;
@@ -280,7 +279,7 @@ namespace Jellyfin.Server.Extensions
                 {
                     if (subnet is not null)
                     {
-                        AddIPAddress(config, options, subnet.Prefix, subnet.PrefixLength);
+                        AddIPAddress(config, options, subnet.BaseAddress, subnet.PrefixLength);
                     }
                 }
                 else if (NetworkUtils.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6))

+ 0 - 1
MediaBrowser.Common/Net/NetworkConstants.cs

@@ -1,5 +1,4 @@
 using System.Net;
-using Microsoft.AspNetCore.HttpOverrides;
 
 namespace MediaBrowser.Common.Net;
 

+ 2 - 1
MediaBrowser.Common/Net/NetworkUtils.cs

@@ -5,6 +5,7 @@ using System.Net;
 using System.Net.Sockets;
 using System.Text.RegularExpressions;
 using Jellyfin.Extensions;
+using Jellyfin.Networking.Constants;
 using Microsoft.AspNetCore.HttpOverrides;
 
 namespace MediaBrowser.Common.Net;
@@ -335,7 +336,7 @@ public static partial class NetworkUtils
     /// <returns>The broadcast address.</returns>
     public static IPAddress GetBroadcastAddress(IPNetwork network)
     {
-        var addressBytes = network.Prefix.GetAddressBytes();
+        var addressBytes = network.BaseAddress.GetAddressBytes();
         uint ipAddress = BitConverter.ToUInt32(addressBytes, 0);
         uint ipMaskV4 = BitConverter.ToUInt32(CidrToMask(network.PrefixLength, AddressFamily.InterNetwork).GetAddressBytes(), 0);
         uint broadCastIPAddress = ipAddress | ~ipMaskV4;

+ 0 - 1
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -33,7 +33,6 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.HttpOverrides" />
     <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
     <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
     <PackageReference Include="MimeTypes">

+ 2 - 3
MediaBrowser.Model/Net/IPData.cs

@@ -1,6 +1,5 @@
 using System.Net;
 using System.Net.Sockets;
-using Microsoft.AspNetCore.HttpOverrides;
 
 namespace MediaBrowser.Model.Net;
 
@@ -66,9 +65,9 @@ public class IPData
         {
             if (Address.Equals(IPAddress.None))
             {
-                return Subnet.Prefix.AddressFamily.Equals(IPAddress.None)
+                return Subnet.BaseAddress.AddressFamily.Equals(IPAddress.None)
                     ? AddressFamily.Unspecified
-                    : Subnet.Prefix.AddressFamily;
+                    : Subnet.BaseAddress.AddressFamily;
             }
             else
             {

+ 2 - 0
src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj

@@ -9,6 +9,8 @@
     <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <!-- TODO: Remove once we update SkiaSharp > 2.88.5 -->
+    <NoWarn>NU1903</NoWarn>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 2
tests/Jellyfin.Server.Tests/ParseNetworkTests.cs

@@ -6,7 +6,6 @@ using Jellyfin.Server.Extensions;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.HttpOverrides;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging.Abstractions;
 using Moq;
@@ -99,7 +98,7 @@ namespace Jellyfin.Server.Tests
             Assert.Equal(knownNetworks.Length, options.KnownNetworks.Count);
             foreach (var item in knownNetworks)
             {
-                Assert.NotNull(options.KnownNetworks.FirstOrDefault(x => x.Prefix.Equals(item.Prefix) && x.PrefixLength == item.PrefixLength));
+                Assert.NotNull(options.KnownNetworks.FirstOrDefault(x => x.BaseAddress.Equals(item.BaseAddress) && x.PrefixLength == item.PrefixLength));
             }
         }