Use System.Net.IPNetwork

This commit is contained in:
Bond_009 2023-10-23 23:45:01 +02:00
parent eb022c49cc
commit 99e0d46ad9
9 changed files with 379 additions and 19 deletions

View File

@ -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;
}
}
}

View File

@ -11,7 +11,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
@ -530,7 +529,7 @@ namespace Jellyfin.Networking.Manager
{ {
foreach (var lan in _lanSubnets) foreach (var lan in _lanSubnets)
{ {
var lanPrefix = lan.Prefix; var lanPrefix = lan.BaseAddress;
publishedServerUrls.Add( publishedServerUrls.Add(
new PublishedServerUriOverride( new PublishedServerUriOverride(
new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength)), 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) 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( publishedServerUrls.Add(
new PublishedServerUriOverride( new PublishedServerUriOverride(
data, data,
@ -607,7 +606,7 @@ namespace Jellyfin.Networking.Manager
var parts = details.Split(','); var parts = details.Split(',');
if (NetworkUtils.TryParseToSubnet(parts[0], out var subnet)) if (NetworkUtils.TryParseToSubnet(parts[0], out var subnet))
{ {
var address = subnet.Prefix; var address = subnet.BaseAddress;
var index = int.Parse(parts[1], CultureInfo.InvariantCulture); var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6) if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
{ {
@ -881,7 +880,7 @@ namespace Jellyfin.Networking.Manager
{ {
if (NetworkUtils.TryParseToSubnet(address, out var subnet)) 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)) if (NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
@ -1112,12 +1111,12 @@ namespace Jellyfin.Networking.Manager
var logLevel = debug ? LogLevel.Debug : LogLevel.Information; var logLevel = debug ? LogLevel.Debug : LogLevel.Information;
if (_logger.IsEnabled(logLevel)) if (_logger.IsEnabled(logLevel))
{ {
_logger.Log(logLevel, "Defined LAN addresses: {0}", _lanSubnets.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.Prefix + "/" + 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.Prefix + "/" + 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, "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, "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));
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,6 @@ using Jellyfin.Server.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Moq; using Moq;
@ -99,7 +98,7 @@ namespace Jellyfin.Server.Tests
Assert.Equal(knownNetworks.Length, options.KnownNetworks.Count); Assert.Equal(knownNetworks.Length, options.KnownNetworks.Count);
foreach (var item in knownNetworks) 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));
} }
} }