mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-24 02:02:29 -04:00
Refactor URI overrides (#10051)
This commit is contained in:
parent
a88e13a677
commit
dc27d8f9cd
@ -201,9 +201,10 @@ namespace Emby.Dlna.Main
|
||||
{
|
||||
if (_communicationsServer is null)
|
||||
{
|
||||
var enableMultiSocketBinding = OperatingSystem.IsWindows() || OperatingSystem.IsLinux();
|
||||
|
||||
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
||||
_communicationsServer = new SsdpCommunicationsServer(
|
||||
_socketFactory,
|
||||
_networkManager,
|
||||
_logger)
|
||||
{
|
||||
IsShared = true
|
||||
};
|
||||
@ -213,7 +214,7 @@ namespace Emby.Dlna.Main
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error starting ssdp handlers");
|
||||
_logger.LogError(ex, "Error starting SSDP handlers");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -450,7 +450,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
||||
|
||||
NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
|
||||
NetManager = new NetworkManager(ConfigurationManager, _startupConfig, LoggerFactory.CreateLogger<NetworkManager>());
|
||||
|
||||
// Initialize runtime stat collection
|
||||
if (ConfigurationManager.Configuration.EnableMetrics)
|
||||
@ -913,7 +913,7 @@ namespace Emby.Server.Implementations
|
||||
/// <inheritdoc/>
|
||||
public string GetSmartApiUrl(HttpRequest request)
|
||||
{
|
||||
// Return the host in the HTTP request as the API url
|
||||
// Return the host in the HTTP request as the API URL if not configured otherwise
|
||||
if (ConfigurationManager.GetNetworkConfiguration().EnablePublishedServerUriByRequest)
|
||||
{
|
||||
int? requestPort = request.Host.Port;
|
||||
@ -948,7 +948,7 @@ namespace Emby.Server.Implementations
|
||||
public string GetApiUrlForLocalAccess(IPAddress ipAddress = null, bool allowHttps = true)
|
||||
{
|
||||
// With an empty source, the port will be null
|
||||
var smart = NetManager.GetBindAddress(ipAddress, out _, true);
|
||||
var smart = NetManager.GetBindAddress(ipAddress, out _, false);
|
||||
var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
|
||||
int? port = !allowHttps ? HttpPort : null;
|
||||
return GetLocalApiUrl(smart, scheme, port);
|
||||
|
@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Class UdpServerEntryPoint.
|
||||
/// Class responsible for registering all UDP broadcast endpoints and their handlers.
|
||||
/// </summary>
|
||||
public sealed class UdpServerEntryPoint : IServerEntryPoint
|
||||
{
|
||||
@ -35,7 +35,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
private readonly IConfiguration _config;
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly bool _enableMultiSocketBinding;
|
||||
|
||||
/// <summary>
|
||||
/// The UDP server.
|
||||
@ -65,7 +64,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_configurationManager = configurationManager;
|
||||
_networkManager = networkManager;
|
||||
_udpServers = new List<UdpServer>();
|
||||
_enableMultiSocketBinding = OperatingSystem.IsWindows() || OperatingSystem.IsLinux();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -80,14 +78,16 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
try
|
||||
{
|
||||
if (_enableMultiSocketBinding)
|
||||
// Linux needs to bind to the broadcast addresses to get broadcast traffic
|
||||
// Windows receives broadcast fine when binding to just the interface, it is unable to bind to broadcast addresses
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
// Add global broadcast socket
|
||||
// Add global broadcast listener
|
||||
var server = new UdpServer(_logger, _appHost, _config, IPAddress.Broadcast, PortNumber);
|
||||
server.Start(_cancellationTokenSource.Token);
|
||||
_udpServers.Add(server);
|
||||
|
||||
// Add bind address specific broadcast sockets
|
||||
// Add bind address specific broadcast listeners
|
||||
// IPv6 is currently unsupported
|
||||
var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
|
||||
foreach (var intf in validInterfaces)
|
||||
@ -102,9 +102,18 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
}
|
||||
else
|
||||
{
|
||||
var server = new UdpServer(_logger, _appHost, _config, IPAddress.Any, PortNumber);
|
||||
server.Start(_cancellationTokenSource.Token);
|
||||
_udpServers.Add(server);
|
||||
// Add bind address specific broadcast listeners
|
||||
// IPv6 is currently unsupported
|
||||
var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
|
||||
foreach (var intf in validInterfaces)
|
||||
{
|
||||
var intfAddress = intf.Address;
|
||||
_logger.LogDebug("Binding UDP server to {Address} on port {PortNumber}", intfAddress, PortNumber);
|
||||
|
||||
var server = new UdpServer(_logger, _appHost, _config, intfAddress, PortNumber);
|
||||
server.Start(_cancellationTokenSource.Token);
|
||||
_udpServers.Add(server);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SocketException ex)
|
||||
@ -119,7 +128,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(this.GetType().Name);
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
||||
namespace Emby.Server.Implementations.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory class to create different kinds of sockets.
|
||||
/// </summary>
|
||||
public class SocketFactory : ISocketFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@ -38,7 +41,8 @@ namespace Emby.Server.Implementations.Net
|
||||
/// <inheritdoc />
|
||||
public Socket CreateSsdpUdpSocket(IPData bindInterface, int localPort)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(bindInterface.Address);
|
||||
var interfaceAddress = bindInterface.Address;
|
||||
ArgumentNullException.ThrowIfNull(interfaceAddress);
|
||||
|
||||
if (localPort < 0)
|
||||
{
|
||||
@ -49,7 +53,7 @@ namespace Emby.Server.Implementations.Net
|
||||
try
|
||||
{
|
||||
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
socket.Bind(new IPEndPoint(bindInterface.Address, localPort));
|
||||
socket.Bind(new IPEndPoint(interfaceAddress, localPort));
|
||||
|
||||
return socket;
|
||||
}
|
||||
@ -82,16 +86,25 @@ namespace Emby.Server.Implementations.Net
|
||||
|
||||
try
|
||||
{
|
||||
var interfaceIndex = bindInterface.Index;
|
||||
var interfaceIndexSwapped = (int)IPAddress.HostToNetworkOrder(interfaceIndex);
|
||||
|
||||
socket.MulticastLoopback = false;
|
||||
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true);
|
||||
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
|
||||
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, interfaceIndexSwapped);
|
||||
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress, interfaceIndex));
|
||||
socket.Bind(new IPEndPoint(multicastAddress, localPort));
|
||||
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress));
|
||||
socket.Bind(new IPEndPoint(multicastAddress, localPort));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only create socket if interface supports multicast
|
||||
var interfaceIndex = bindInterface.Index;
|
||||
var interfaceIndexSwapped = IPAddress.HostToNetworkOrder(interfaceIndex);
|
||||
|
||||
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress, interfaceIndex));
|
||||
socket.Bind(new IPEndPoint(bindIPAddress, localPort));
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
@ -52,7 +52,10 @@ namespace Emby.Server.Implementations.Udp
|
||||
|
||||
_endpoint = new IPEndPoint(bindAddress, port);
|
||||
|
||||
_udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
_udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
|
||||
{
|
||||
MulticastLoopback = false,
|
||||
};
|
||||
_udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
}
|
||||
|
||||
@ -74,6 +77,7 @@ namespace Emby.Server.Implementations.Udp
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Sending AutoDiscovery response");
|
||||
await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), SocketFlags.None, endpoint, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
@ -99,7 +103,8 @@ namespace Emby.Server.Implementations.Udp
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _udpSocket.ReceiveFromAsync(_receiveBuffer, SocketFlags.None, _endpoint, cancellationToken).ConfigureAwait(false);
|
||||
var endpoint = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
|
||||
var result = await _udpSocket.ReceiveFromAsync(_receiveBuffer, endpoint, cancellationToken).ConfigureAwait(false);
|
||||
var text = Encoding.UTF8.GetString(_receiveBuffer, 0, result.ReceivedBytes);
|
||||
if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@ -112,7 +117,7 @@ namespace Emby.Server.Implementations.Udp
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Don't throw
|
||||
_logger.LogDebug("Broadcast socket operation cancelled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,12 +231,12 @@ public static partial class NetworkExtensions
|
||||
}
|
||||
else if (address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
result = new IPNetwork(address, Network.MinimumIPv4PrefixSize);
|
||||
result = address.Equals(IPAddress.Any) ? Network.IPv4Any : new IPNetwork(address, Network.MinimumIPv4PrefixSize);
|
||||
return true;
|
||||
}
|
||||
else if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
result = new IPNetwork(address, Network.MinimumIPv6PrefixSize);
|
||||
result = address.Equals(IPAddress.IPv6Any) ? Network.IPv6Any : new IPNetwork(address, Network.MinimumIPv6PrefixSize);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -284,12 +284,15 @@ public static partial class NetworkExtensions
|
||||
|
||||
if (hosts.Count <= 2)
|
||||
{
|
||||
var firstPart = hosts[0];
|
||||
|
||||
// Is hostname or hostname:port
|
||||
if (FqdnGeneratedRegex().IsMatch(hosts[0]))
|
||||
if (FqdnGeneratedRegex().IsMatch(firstPart))
|
||||
{
|
||||
try
|
||||
{
|
||||
addresses = Dns.GetHostAddresses(hosts[0]);
|
||||
// .NET automatically filters only supported returned addresses based on OS support.
|
||||
addresses = Dns.GetHostAddresses(firstPart);
|
||||
return true;
|
||||
}
|
||||
catch (SocketException)
|
||||
@ -299,7 +302,7 @@ public static partial class NetworkExtensions
|
||||
}
|
||||
|
||||
// Is an IPv4 or IPv4:port
|
||||
if (IPAddress.TryParse(hosts[0].AsSpan().LeftPart('/'), out var address))
|
||||
if (IPAddress.TryParse(firstPart.AsSpan().LeftPart('/'), out var address))
|
||||
{
|
||||
if (((address.AddressFamily == AddressFamily.InterNetwork) && (!isIPv4Enabled && isIPv6Enabled))
|
||||
|| ((address.AddressFamily == AddressFamily.InterNetworkV6) && (isIPv4Enabled && !isIPv6Enabled)))
|
||||
|
@ -15,7 +15,9 @@ 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;
|
||||
|
||||
namespace Jellyfin.Networking.Manager
|
||||
{
|
||||
@ -33,12 +35,14 @@ namespace Jellyfin.Networking.Manager
|
||||
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
|
||||
private readonly IConfiguration _startupConfig;
|
||||
|
||||
private readonly object _networkEventLock;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the published server URLs and the IPs to use them on.
|
||||
/// </summary>
|
||||
private IReadOnlyDictionary<IPData, string> _publishedServerUrls;
|
||||
private IReadOnlyList<PublishedServerUriOverride> _publishedServerUrls;
|
||||
|
||||
private IReadOnlyList<IPNetwork> _remoteAddressFilter;
|
||||
|
||||
@ -76,20 +80,22 @@ namespace Jellyfin.Networking.Manager
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NetworkManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="configurationManager">IServerConfigurationManager instance.</param>
|
||||
/// <param name="configurationManager">The <see cref="IConfigurationManager"/> instance.</param>
|
||||
/// <param name="startupConfig">The <see cref="IConfiguration"/> instance holding startup parameters.</param>
|
||||
/// <param name="logger">Logger to use for messages.</param>
|
||||
#pragma warning disable CS8618 // Non-nullable field is uninitialized. : Values are set in UpdateSettings function. Compiler doesn't yet recognise this.
|
||||
public NetworkManager(IConfigurationManager configurationManager, ILogger<NetworkManager> logger)
|
||||
public NetworkManager(IConfigurationManager configurationManager, IConfiguration startupConfig, ILogger<NetworkManager> logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
ArgumentNullException.ThrowIfNull(configurationManager);
|
||||
|
||||
_logger = logger;
|
||||
_configurationManager = configurationManager;
|
||||
_startupConfig = startupConfig;
|
||||
_initLock = new();
|
||||
_interfaces = new List<IPData>();
|
||||
_macAddresses = new List<PhysicalAddress>();
|
||||
_publishedServerUrls = new Dictionary<IPData, string>();
|
||||
_publishedServerUrls = new List<PublishedServerUriOverride>();
|
||||
_networkEventLock = new object();
|
||||
_remoteAddressFilter = new List<IPNetwork>();
|
||||
|
||||
@ -130,7 +136,7 @@ namespace Jellyfin.Networking.Manager
|
||||
/// <summary>
|
||||
/// Gets the Published server override list.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<IPData, string> PublishedServerUrls => _publishedServerUrls;
|
||||
public IReadOnlyList<PublishedServerUriOverride> PublishedServerUrls => _publishedServerUrls;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
@ -170,7 +176,6 @@ namespace Jellyfin.Networking.Manager
|
||||
{
|
||||
if (!_eventfire)
|
||||
{
|
||||
_logger.LogDebug("Network Address Change Event.");
|
||||
// As network events tend to fire one after the other only fire once every second.
|
||||
_eventfire = true;
|
||||
OnNetworkChange();
|
||||
@ -193,11 +198,12 @@ namespace Jellyfin.Networking.Manager
|
||||
}
|
||||
else
|
||||
{
|
||||
InitialiseInterfaces();
|
||||
InitialiseLan(networkConfig);
|
||||
InitializeInterfaces();
|
||||
InitializeLan(networkConfig);
|
||||
EnforceBindSettings(networkConfig);
|
||||
}
|
||||
|
||||
PrintNetworkInformation(networkConfig);
|
||||
NetworkChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
finally
|
||||
@ -210,7 +216,7 @@ namespace Jellyfin.Networking.Manager
|
||||
/// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
|
||||
/// Generate a list of all active mac addresses that aren't loopback addresses.
|
||||
/// </summary>
|
||||
private void InitialiseInterfaces()
|
||||
private void InitializeInterfaces()
|
||||
{
|
||||
lock (_initLock)
|
||||
{
|
||||
@ -222,7 +228,7 @@ namespace Jellyfin.Networking.Manager
|
||||
try
|
||||
{
|
||||
var nics = NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Where(i => i.SupportsMulticast && i.OperationalStatus == OperationalStatus.Up);
|
||||
.Where(i => i.OperationalStatus == OperationalStatus.Up);
|
||||
|
||||
foreach (NetworkInterface adapter in nics)
|
||||
{
|
||||
@ -242,34 +248,36 @@ namespace Jellyfin.Networking.Manager
|
||||
{
|
||||
if (IsIPv4Enabled && info.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name);
|
||||
interfaceObject.Index = ipProperties.GetIPv4Properties().Index;
|
||||
interfaceObject.Name = adapter.Name;
|
||||
var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name)
|
||||
{
|
||||
Index = ipProperties.GetIPv4Properties().Index,
|
||||
Name = adapter.Name,
|
||||
SupportsMulticast = adapter.SupportsMulticast
|
||||
};
|
||||
|
||||
interfaces.Add(interfaceObject);
|
||||
}
|
||||
else if (IsIPv6Enabled && info.Address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name);
|
||||
interfaceObject.Index = ipProperties.GetIPv6Properties().Index;
|
||||
interfaceObject.Name = adapter.Name;
|
||||
var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name)
|
||||
{
|
||||
Index = ipProperties.GetIPv6Properties().Index,
|
||||
Name = adapter.Name,
|
||||
SupportsMulticast = adapter.SupportsMulticast
|
||||
};
|
||||
|
||||
interfaces.Add(interfaceObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
catch (Exception ex)
|
||||
#pragma warning restore CA1031 // Do not catch general exception types
|
||||
{
|
||||
// Ignore error, and attempt to continue.
|
||||
_logger.LogError(ex, "Error encountered parsing interfaces.");
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
catch (Exception ex)
|
||||
#pragma warning restore CA1031 // Do not catch general exception types
|
||||
{
|
||||
_logger.LogError(ex, "Error obtaining interfaces.");
|
||||
}
|
||||
@ -279,14 +287,14 @@ namespace Jellyfin.Networking.Manager
|
||||
{
|
||||
_logger.LogWarning("No interface information available. Using loopback interface(s).");
|
||||
|
||||
if (IsIPv4Enabled && !IsIPv6Enabled)
|
||||
if (IsIPv4Enabled)
|
||||
{
|
||||
interfaces.Add(new IPData(IPAddress.Loopback, new IPNetwork(IPAddress.Loopback, 8), "lo"));
|
||||
interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
|
||||
}
|
||||
|
||||
if (!IsIPv4Enabled && IsIPv6Enabled)
|
||||
if (IsIPv6Enabled)
|
||||
{
|
||||
interfaces.Add(new IPData(IPAddress.IPv6Loopback, new IPNetwork(IPAddress.IPv6Loopback, 128), "lo"));
|
||||
interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,9 +307,9 @@ namespace Jellyfin.Networking.Manager
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialises internal LAN cache.
|
||||
/// Initializes internal LAN cache.
|
||||
/// </summary>
|
||||
private void InitialiseLan(NetworkConfiguration config)
|
||||
private void InitializeLan(NetworkConfiguration config)
|
||||
{
|
||||
lock (_initLock)
|
||||
{
|
||||
@ -341,10 +349,6 @@ namespace Jellyfin.Networking.Manager
|
||||
_excludedSubnets = NetworkExtensions.TryParseToSubnets(subnets, out var excludedSubnets, true)
|
||||
? excludedSubnets
|
||||
: new List<IPNetwork>();
|
||||
|
||||
_logger.LogInformation("Defined LAN addresses: {0}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
|
||||
_logger.LogInformation("Defined LAN exclusions: {0}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
|
||||
_logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength));
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,12 +373,12 @@ namespace Jellyfin.Networking.Manager
|
||||
.ToHashSet();
|
||||
interfaces = interfaces.Where(x => bindAddresses.Contains(x.Address)).ToList();
|
||||
|
||||
if (bindAddresses.Contains(IPAddress.Loopback))
|
||||
if (bindAddresses.Contains(IPAddress.Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.Loopback)))
|
||||
{
|
||||
interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
|
||||
}
|
||||
|
||||
if (bindAddresses.Contains(IPAddress.IPv6Loopback))
|
||||
if (bindAddresses.Contains(IPAddress.IPv6Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.IPv6Loopback)))
|
||||
{
|
||||
interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
|
||||
}
|
||||
@ -409,15 +413,14 @@ namespace Jellyfin.Networking.Manager
|
||||
interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Using bind addresses: {0}", interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
|
||||
_interfaces = interfaces;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialises the remote address values.
|
||||
/// Initializes the remote address values.
|
||||
/// </summary>
|
||||
private void InitialiseRemote(NetworkConfiguration config)
|
||||
private void InitializeRemote(NetworkConfiguration config)
|
||||
{
|
||||
lock (_initLock)
|
||||
{
|
||||
@ -455,13 +458,33 @@ namespace Jellyfin.Networking.Manager
|
||||
/// format is subnet=ipaddress|host|uri
|
||||
/// when subnet = 0.0.0.0, any external address matches.
|
||||
/// </summary>
|
||||
private void InitialiseOverrides(NetworkConfiguration config)
|
||||
private void InitializeOverrides(NetworkConfiguration config)
|
||||
{
|
||||
lock (_initLock)
|
||||
{
|
||||
var publishedServerUrls = new Dictionary<IPData, string>();
|
||||
var overrides = config.PublishedServerUriBySubnet;
|
||||
var publishedServerUrls = new List<PublishedServerUriOverride>();
|
||||
|
||||
// Prefer startup configuration.
|
||||
var startupOverrideKey = _startupConfig[AddressOverrideKey];
|
||||
if (!string.IsNullOrEmpty(startupOverrideKey))
|
||||
{
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.Any, Network.IPv4Any),
|
||||
startupOverrideKey,
|
||||
true,
|
||||
true));
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.IPv6Any, Network.IPv6Any),
|
||||
startupOverrideKey,
|
||||
true,
|
||||
true));
|
||||
_publishedServerUrls = publishedServerUrls;
|
||||
return;
|
||||
}
|
||||
|
||||
var overrides = config.PublishedServerUriBySubnet;
|
||||
foreach (var entry in overrides)
|
||||
{
|
||||
var parts = entry.Split('=');
|
||||
@ -475,31 +498,70 @@ namespace Jellyfin.Networking.Manager
|
||||
var identifier = parts[0];
|
||||
if (string.Equals(identifier, "all", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
publishedServerUrls[new IPData(IPAddress.Broadcast, null)] = replacement;
|
||||
// Drop any other overrides in case an "all" override exists
|
||||
publishedServerUrls.Clear();
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.Any, Network.IPv4Any),
|
||||
replacement,
|
||||
true,
|
||||
true));
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.IPv6Any, Network.IPv6Any),
|
||||
replacement,
|
||||
true,
|
||||
true));
|
||||
break;
|
||||
}
|
||||
else if (string.Equals(identifier, "external", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
publishedServerUrls[new IPData(IPAddress.Any, Network.IPv4Any)] = replacement;
|
||||
publishedServerUrls[new IPData(IPAddress.IPv6Any, Network.IPv6Any)] = replacement;
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.Any, Network.IPv4Any),
|
||||
replacement,
|
||||
false,
|
||||
true));
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.IPv6Any, Network.IPv6Any),
|
||||
replacement,
|
||||
false,
|
||||
true));
|
||||
}
|
||||
else if (string.Equals(identifier, "internal", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (var lan in _lanSubnets)
|
||||
{
|
||||
var lanPrefix = lan.Prefix;
|
||||
publishedServerUrls[new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength))] = replacement;
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength)),
|
||||
replacement,
|
||||
true,
|
||||
false));
|
||||
}
|
||||
}
|
||||
else if (NetworkExtensions.TryParseToSubnet(identifier, out var result) && result is not null)
|
||||
{
|
||||
var data = new IPData(result.Prefix, result);
|
||||
publishedServerUrls[data] = replacement;
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
data,
|
||||
replacement,
|
||||
true,
|
||||
true));
|
||||
}
|
||||
else if (TryParseInterface(identifier, out var ifaces))
|
||||
{
|
||||
foreach (var iface in ifaces)
|
||||
{
|
||||
publishedServerUrls[iface] = replacement;
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
iface,
|
||||
replacement,
|
||||
true,
|
||||
true));
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -521,7 +583,7 @@ namespace Jellyfin.Networking.Manager
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads all settings and re-initialises the instance.
|
||||
/// Reloads all settings and re-Initializes the instance.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The <see cref="NetworkConfiguration"/> to use.</param>
|
||||
public void UpdateSettings(object configuration)
|
||||
@ -531,12 +593,12 @@ namespace Jellyfin.Networking.Manager
|
||||
var config = (NetworkConfiguration)configuration;
|
||||
HappyEyeballs.HttpClientExtension.UseIPv6 = config.EnableIPv6;
|
||||
|
||||
InitialiseLan(config);
|
||||
InitialiseRemote(config);
|
||||
InitializeLan(config);
|
||||
InitializeRemote(config);
|
||||
|
||||
if (string.IsNullOrEmpty(MockNetworkSettings))
|
||||
{
|
||||
InitialiseInterfaces();
|
||||
InitializeInterfaces();
|
||||
}
|
||||
else // Used in testing only.
|
||||
{
|
||||
@ -552,8 +614,10 @@ namespace Jellyfin.Networking.Manager
|
||||
var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
|
||||
if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
var data = new IPData(address, subnet, parts[2]);
|
||||
data.Index = index;
|
||||
var data = new IPData(address, subnet, parts[2])
|
||||
{
|
||||
Index = index
|
||||
};
|
||||
interfaces.Add(data);
|
||||
}
|
||||
}
|
||||
@ -567,7 +631,9 @@ namespace Jellyfin.Networking.Manager
|
||||
}
|
||||
|
||||
EnforceBindSettings(config);
|
||||
InitialiseOverrides(config);
|
||||
InitializeOverrides(config);
|
||||
|
||||
PrintNetworkInformation(config, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -672,20 +738,13 @@ namespace Jellyfin.Networking.Manager
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false)
|
||||
{
|
||||
if (_interfaces.Count != 0)
|
||||
if (_interfaces.Count > 0 || individualInterfaces)
|
||||
{
|
||||
return _interfaces;
|
||||
}
|
||||
|
||||
// No bind address and no exclusions, so listen on all interfaces.
|
||||
var result = new List<IPData>();
|
||||
|
||||
if (individualInterfaces)
|
||||
{
|
||||
result.AddRange(_interfaces);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (IsIPv4Enabled && IsIPv6Enabled)
|
||||
{
|
||||
// Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any by default
|
||||
@ -892,31 +951,34 @@ namespace Jellyfin.Networking.Manager
|
||||
bindPreference = string.Empty;
|
||||
int? port = null;
|
||||
|
||||
var validPublishedServerUrls = _publishedServerUrls.Where(x => x.Key.Address.Equals(IPAddress.Any)
|
||||
|| x.Key.Address.Equals(IPAddress.IPv6Any)
|
||||
|| x.Key.Subnet.Contains(source))
|
||||
.DistinctBy(x => x.Key)
|
||||
.OrderBy(x => x.Key.Address.Equals(IPAddress.Any)
|
||||
|| x.Key.Address.Equals(IPAddress.IPv6Any))
|
||||
// Only consider subnets including the source IP, prefering specific overrides
|
||||
List<PublishedServerUriOverride> validPublishedServerUrls;
|
||||
if (!isInExternalSubnet)
|
||||
{
|
||||
// Only use matching internal subnets
|
||||
// Prefer more specific (bigger subnet prefix) overrides
|
||||
validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsInternalOverride && x.Data.Subnet.Contains(source))
|
||||
.OrderByDescending(x => x.Data.Subnet.PrefixLength)
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only use matching external subnets
|
||||
// Prefer more specific (bigger subnet prefix) overrides
|
||||
validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsExternalOverride && x.Data.Subnet.Contains(source))
|
||||
.OrderByDescending(x => x.Data.Subnet.PrefixLength)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Check for user override.
|
||||
foreach (var data in validPublishedServerUrls)
|
||||
{
|
||||
if (isInExternalSubnet && (data.Key.Address.Equals(IPAddress.Any) || data.Key.Address.Equals(IPAddress.IPv6Any)))
|
||||
{
|
||||
// External.
|
||||
bindPreference = data.Value;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get address interface.
|
||||
var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Key.Subnet.Contains(x.Address));
|
||||
// Get interface matching override subnet
|
||||
var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Data.Subnet.Contains(x.Address));
|
||||
|
||||
if (intf?.Address is not null)
|
||||
{
|
||||
// Match IP address.
|
||||
bindPreference = data.Value;
|
||||
// If matching interface is found, use override
|
||||
bindPreference = data.OverrideUri;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -927,7 +989,7 @@ namespace Jellyfin.Networking.Manager
|
||||
return false;
|
||||
}
|
||||
|
||||
// Has it got a port defined?
|
||||
// Handle override specifying port
|
||||
var parts = bindPreference.Split(':');
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
@ -935,18 +997,12 @@ namespace Jellyfin.Networking.Manager
|
||||
{
|
||||
bindPreference = parts[0];
|
||||
port = p;
|
||||
_logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (port is not null)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
|
||||
@ -1053,5 +1109,19 @@ namespace Jellyfin.Networking.Manager
|
||||
_logger.LogDebug("{Source}: Using first external interface as bind address: {Result}", source, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void PrintNetworkInformation(NetworkConfiguration config, bool debug = true)
|
||||
{
|
||||
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, "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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ namespace Jellyfin.Server.Extensions
|
||||
AddIPAddress(config, options, subnet.Prefix, subnet.PrefixLength);
|
||||
}
|
||||
}
|
||||
else if (NetworkExtensions.TryParseHost(allowedProxies[i], out var addresses))
|
||||
else if (NetworkExtensions.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6))
|
||||
{
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
|
@ -3,7 +3,6 @@ using System.IO;
|
||||
using System.Net;
|
||||
using Jellyfin.Server.Helpers;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@ -36,7 +35,7 @@ public static class WebHostBuilderExtensions
|
||||
return builder
|
||||
.UseKestrel((builderContext, options) =>
|
||||
{
|
||||
var addresses = appHost.NetManager.GetAllBindInterfaces();
|
||||
var addresses = appHost.NetManager.GetAllBindInterfaces(true);
|
||||
|
||||
bool flagged = false;
|
||||
foreach (var netAdd in addresses)
|
||||
|
@ -47,6 +47,11 @@ public class IPData
|
||||
/// </summary>
|
||||
public int Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the network supports multicast.
|
||||
/// </summary>
|
||||
public bool SupportsMulticast { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the interface name.
|
||||
/// </summary>
|
||||
|
42
MediaBrowser.Model/Net/PublishedServerUriOverride.cs
Normal file
42
MediaBrowser.Model/Net/PublishedServerUriOverride.cs
Normal file
@ -0,0 +1,42 @@
|
||||
namespace MediaBrowser.Model.Net;
|
||||
|
||||
/// <summary>
|
||||
/// Class holding information for a published server URI override.
|
||||
/// </summary>
|
||||
public class PublishedServerUriOverride
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PublishedServerUriOverride"/> class.
|
||||
/// </summary>
|
||||
/// <param name="data">The <see cref="IPData"/>.</param>
|
||||
/// <param name="overrideUri">The override.</param>
|
||||
/// <param name="internalOverride">A value indicating whether the override is for internal requests.</param>
|
||||
/// <param name="externalOverride">A value indicating whether the override is for external requests.</param>
|
||||
public PublishedServerUriOverride(IPData data, string overrideUri, bool internalOverride, bool externalOverride)
|
||||
{
|
||||
Data = data;
|
||||
OverrideUri = overrideUri;
|
||||
IsInternalOverride = internalOverride;
|
||||
IsExternalOverride = externalOverride;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the object's IP address.
|
||||
/// </summary>
|
||||
public IPData Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the override URI.
|
||||
/// </summary>
|
||||
public string OverrideUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the override should be applied to internal requests.
|
||||
/// </summary>
|
||||
public bool IsInternalOverride { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the override should be applied to external requests.
|
||||
/// </summary>
|
||||
public bool IsExternalOverride { get; set; }
|
||||
}
|
@ -32,10 +32,10 @@ namespace Rssdp.Infrastructure
|
||||
* port to use, we will default to 0 which allows the underlying system to auto-assign a free port.
|
||||
*/
|
||||
|
||||
private object _BroadcastListenSocketSynchroniser = new object();
|
||||
private object _BroadcastListenSocketSynchroniser = new();
|
||||
private List<Socket> _MulticastListenSockets;
|
||||
|
||||
private object _SendSocketSynchroniser = new object();
|
||||
private object _SendSocketSynchroniser = new();
|
||||
private List<Socket> _sendSockets;
|
||||
|
||||
private HttpRequestParser _RequestParser;
|
||||
@ -48,7 +48,6 @@ namespace Rssdp.Infrastructure
|
||||
private int _MulticastTtl;
|
||||
|
||||
private bool _IsShared;
|
||||
private readonly bool _enableMultiSocketBinding;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a HTTPU request message is received by a socket (unicast or multicast).
|
||||
@ -64,9 +63,11 @@ namespace Rssdp.Infrastructure
|
||||
/// Minimum constructor.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception>
|
||||
public SsdpCommunicationsServer(ISocketFactory socketFactory,
|
||||
INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
|
||||
: this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding)
|
||||
public SsdpCommunicationsServer(
|
||||
ISocketFactory socketFactory,
|
||||
INetworkManager networkManager,
|
||||
ILogger logger)
|
||||
: this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger)
|
||||
{
|
||||
|
||||
}
|
||||
@ -76,7 +77,12 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="multicastTimeToLive"/> argument is less than or equal to zero.</exception>
|
||||
public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive, INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
|
||||
public SsdpCommunicationsServer(
|
||||
ISocketFactory socketFactory,
|
||||
int localPort,
|
||||
int multicastTimeToLive,
|
||||
INetworkManager networkManager,
|
||||
ILogger logger)
|
||||
{
|
||||
if (socketFactory is null)
|
||||
{
|
||||
@ -88,19 +94,18 @@ namespace Rssdp.Infrastructure
|
||||
throw new ArgumentOutOfRangeException(nameof(multicastTimeToLive), "multicastTimeToLive must be greater than zero.");
|
||||
}
|
||||
|
||||
_BroadcastListenSocketSynchroniser = new object();
|
||||
_SendSocketSynchroniser = new object();
|
||||
_BroadcastListenSocketSynchroniser = new();
|
||||
_SendSocketSynchroniser = new();
|
||||
|
||||
_LocalPort = localPort;
|
||||
_SocketFactory = socketFactory;
|
||||
|
||||
_RequestParser = new HttpRequestParser();
|
||||
_ResponseParser = new HttpResponseParser();
|
||||
_RequestParser = new();
|
||||
_ResponseParser = new();
|
||||
|
||||
_MulticastTtl = multicastTimeToLive;
|
||||
_networkManager = networkManager;
|
||||
_logger = logger;
|
||||
_enableMultiSocketBinding = enableMultiSocketBinding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -335,7 +340,7 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
sockets = sockets.ToList();
|
||||
|
||||
var tasks = sockets.Where(s => (fromlocalIPAddress is null || fromlocalIPAddress.Equals(((IPEndPoint)s.LocalEndPoint).Address)))
|
||||
var tasks = sockets.Where(s => fromlocalIPAddress is null || fromlocalIPAddress.Equals(((IPEndPoint)s.LocalEndPoint).Address))
|
||||
.Select(s => SendFromSocket(s, messageData, destination, cancellationToken));
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
@ -347,33 +352,26 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
var sockets = new List<Socket>();
|
||||
var multicastGroupAddress = IPAddress.Parse(SsdpConstants.MulticastLocalAdminAddress);
|
||||
if (_enableMultiSocketBinding)
|
||||
{
|
||||
// IPv6 is currently unsupported
|
||||
var validInterfaces = _networkManager.GetInternalBindAddresses()
|
||||
.Where(x => x.Address is not null)
|
||||
.Where(x => x.AddressFamily == AddressFamily.InterNetwork)
|
||||
.DistinctBy(x => x.Index);
|
||||
|
||||
foreach (var intf in validInterfaces)
|
||||
{
|
||||
try
|
||||
{
|
||||
var socket = _SocketFactory.CreateUdpMulticastSocket(multicastGroupAddress, intf, _MulticastTtl, SsdpConstants.MulticastPort);
|
||||
_ = ListenToSocketInternal(socket);
|
||||
sockets.Add(socket);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in CreateMulticastSocketsAndListen. IP address: {0}", intf.Address);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
// IPv6 is currently unsupported
|
||||
var validInterfaces = _networkManager.GetInternalBindAddresses()
|
||||
.Where(x => x.Address is not null)
|
||||
.Where(x => x.SupportsMulticast)
|
||||
.Where(x => x.AddressFamily == AddressFamily.InterNetwork)
|
||||
.DistinctBy(x => x.Index);
|
||||
|
||||
foreach (var intf in validInterfaces)
|
||||
{
|
||||
var socket = _SocketFactory.CreateUdpMulticastSocket(multicastGroupAddress, new IPData(IPAddress.Any, null), _MulticastTtl, SsdpConstants.MulticastPort);
|
||||
_ = ListenToSocketInternal(socket);
|
||||
sockets.Add(socket);
|
||||
try
|
||||
{
|
||||
var socket = _SocketFactory.CreateUdpMulticastSocket(multicastGroupAddress, intf, _MulticastTtl, SsdpConstants.MulticastPort);
|
||||
_ = ListenToSocketInternal(socket);
|
||||
sockets.Add(socket);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to create SSDP UDP multicast socket for {0} on interface {1} (index {2})", intf.Address, intf.Name, intf.Index);
|
||||
}
|
||||
}
|
||||
|
||||
return sockets;
|
||||
@ -382,34 +380,32 @@ namespace Rssdp.Infrastructure
|
||||
private List<Socket> CreateSendSockets()
|
||||
{
|
||||
var sockets = new List<Socket>();
|
||||
if (_enableMultiSocketBinding)
|
||||
{
|
||||
// IPv6 is currently unsupported
|
||||
var validInterfaces = _networkManager.GetInternalBindAddresses()
|
||||
.Where(x => x.Address is not null)
|
||||
.Where(x => x.AddressFamily == AddressFamily.InterNetwork);
|
||||
|
||||
foreach (var intf in validInterfaces)
|
||||
// IPv6 is currently unsupported
|
||||
var validInterfaces = _networkManager.GetInternalBindAddresses()
|
||||
.Where(x => x.Address is not null)
|
||||
.Where(x => x.SupportsMulticast)
|
||||
.Where(x => x.AddressFamily == AddressFamily.InterNetwork);
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
// Manually remove loopback on macOS due to https://github.com/dotnet/runtime/issues/24340
|
||||
validInterfaces = validInterfaces.Where(x => !x.Address.Equals(IPAddress.Loopback));
|
||||
}
|
||||
|
||||
foreach (var intf in validInterfaces)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var socket = _SocketFactory.CreateSsdpUdpSocket(intf, _LocalPort);
|
||||
_ = ListenToSocketInternal(socket);
|
||||
sockets.Add(socket);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in CreateSsdpUdpSocket. IPAddress: {0}", intf.Address);
|
||||
}
|
||||
var socket = _SocketFactory.CreateSsdpUdpSocket(intf, _LocalPort);
|
||||
_ = ListenToSocketInternal(socket);
|
||||
sockets.Add(socket);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to create SSDP UDP sender socket for {0} on interface {1} (index {2})", intf.Address, intf.Name, intf.Index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var socket = _SocketFactory.CreateSsdpUdpSocket(new IPData(IPAddress.Any, null), _LocalPort);
|
||||
_ = ListenToSocketInternal(socket);
|
||||
sockets.Add(socket);
|
||||
}
|
||||
|
||||
|
||||
return sockets;
|
||||
}
|
||||
@ -423,7 +419,7 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await socket.ReceiveMessageFromAsync(receiveBuffer, SocketFlags.None, new IPEndPoint(IPAddress.Any, 0), CancellationToken.None).ConfigureAwait(false);;
|
||||
var result = await socket.ReceiveMessageFromAsync(receiveBuffer, new IPEndPoint(IPAddress.Any, _LocalPort), CancellationToken.None).ConfigureAwait(false);;
|
||||
|
||||
if (result.ReceivedBytes > 0)
|
||||
{
|
||||
@ -431,7 +427,7 @@ namespace Rssdp.Infrastructure
|
||||
var localEndpointAdapter = _networkManager.GetAllBindInterfaces().First(a => a.Index == result.PacketInformation.Interface);
|
||||
|
||||
ProcessMessage(
|
||||
UTF8Encoding.UTF8.GetString(receiveBuffer, 0, result.ReceivedBytes),
|
||||
Encoding.UTF8.GetString(receiveBuffer, 0, result.ReceivedBytes),
|
||||
remoteEndpoint,
|
||||
localEndpointAdapter.Address);
|
||||
}
|
||||
@ -511,7 +507,7 @@ namespace Rssdp.Infrastructure
|
||||
return;
|
||||
}
|
||||
|
||||
var handlers = this.RequestReceived;
|
||||
var handlers = RequestReceived;
|
||||
if (handlers is not null)
|
||||
{
|
||||
handlers(this, new RequestReceivedEventArgs(data, remoteEndPoint, receivedOnlocalIPAddress));
|
||||
@ -520,7 +516,7 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
private void OnResponseReceived(HttpResponseMessage data, IPEndPoint endPoint, IPAddress localIPAddress)
|
||||
{
|
||||
var handlers = this.ResponseReceived;
|
||||
var handlers = ResponseReceived;
|
||||
if (handlers is not null)
|
||||
{
|
||||
handlers(this, new ResponseReceivedEventArgs(data, endPoint)
|
||||
|
@ -17,7 +17,7 @@ namespace Rssdp.Infrastructure
|
||||
private ISsdpCommunicationsServer _CommunicationsServer;
|
||||
|
||||
private Timer _BroadcastTimer;
|
||||
private object _timerLock = new object();
|
||||
private object _timerLock = new();
|
||||
|
||||
private string _OSName;
|
||||
|
||||
@ -221,12 +221,12 @@ namespace Rssdp.Infrastructure
|
||||
/// <seealso cref="DeviceAvailable"/>
|
||||
protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IPAddress)
|
||||
{
|
||||
if (this.IsDisposed)
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var handlers = this.DeviceAvailable;
|
||||
var handlers = DeviceAvailable;
|
||||
if (handlers is not null)
|
||||
{
|
||||
handlers(this, new DeviceAvailableEventArgs(device, isNewDevice)
|
||||
@ -244,12 +244,12 @@ namespace Rssdp.Infrastructure
|
||||
/// <seealso cref="DeviceUnavailable"/>
|
||||
protected virtual void OnDeviceUnavailable(DiscoveredSsdpDevice device, bool expired)
|
||||
{
|
||||
if (this.IsDisposed)
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var handlers = this.DeviceUnavailable;
|
||||
var handlers = DeviceUnavailable;
|
||||
if (handlers is not null)
|
||||
{
|
||||
handlers(this, new DeviceUnavailableEventArgs(device, expired));
|
||||
@ -291,8 +291,8 @@ namespace Rssdp.Infrastructure
|
||||
_CommunicationsServer = null;
|
||||
if (commsServer is not null)
|
||||
{
|
||||
commsServer.ResponseReceived -= this.CommsServer_ResponseReceived;
|
||||
commsServer.RequestReceived -= this.CommsServer_RequestReceived;
|
||||
commsServer.ResponseReceived -= CommsServer_ResponseReceived;
|
||||
commsServer.RequestReceived -= CommsServer_RequestReceived;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -341,7 +341,7 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
values["HOST"] = "239.255.255.250:1900";
|
||||
values["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort);
|
||||
values["USER-AGENT"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
|
||||
values["MAN"] = "\"ssdp:discover\"";
|
||||
|
||||
@ -382,17 +382,17 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress IPAddress)
|
||||
{
|
||||
if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0)
|
||||
if (string.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var notificationType = GetFirstHeaderStringValue("NTS", message);
|
||||
if (String.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
if (string.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
ProcessAliveNotification(message, IPAddress);
|
||||
}
|
||||
else if (String.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
else if (string.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
ProcessByeByeNotification(message);
|
||||
}
|
||||
@ -420,7 +420,7 @@ namespace Rssdp.Infrastructure
|
||||
private void ProcessByeByeNotification(HttpRequestMessage message)
|
||||
{
|
||||
var notficationType = GetFirstHeaderStringValue("NT", message);
|
||||
if (!String.IsNullOrEmpty(notficationType))
|
||||
if (!string.IsNullOrEmpty(notficationType))
|
||||
{
|
||||
var usn = GetFirstHeaderStringValue("USN", message);
|
||||
|
||||
@ -447,10 +447,9 @@ namespace Rssdp.Infrastructure
|
||||
private string GetFirstHeaderStringValue(string headerName, HttpResponseMessage message)
|
||||
{
|
||||
string retVal = null;
|
||||
IEnumerable<string> values;
|
||||
if (message.Headers.Contains(headerName))
|
||||
{
|
||||
message.Headers.TryGetValues(headerName, out values);
|
||||
message.Headers.TryGetValues(headerName, out var values);
|
||||
if (values is not null)
|
||||
{
|
||||
retVal = values.FirstOrDefault();
|
||||
@ -463,10 +462,9 @@ namespace Rssdp.Infrastructure
|
||||
private string GetFirstHeaderStringValue(string headerName, HttpRequestMessage message)
|
||||
{
|
||||
string retVal = null;
|
||||
IEnumerable<string> values;
|
||||
if (message.Headers.Contains(headerName))
|
||||
{
|
||||
message.Headers.TryGetValues(headerName, out values);
|
||||
message.Headers.TryGetValues(headerName, out var values);
|
||||
if (values is not null)
|
||||
{
|
||||
retVal = values.FirstOrDefault();
|
||||
@ -479,10 +477,9 @@ namespace Rssdp.Infrastructure
|
||||
private Uri GetFirstHeaderUriValue(string headerName, HttpRequestMessage request)
|
||||
{
|
||||
string value = null;
|
||||
IEnumerable<string> values;
|
||||
if (request.Headers.Contains(headerName))
|
||||
{
|
||||
request.Headers.TryGetValues(headerName, out values);
|
||||
request.Headers.TryGetValues(headerName, out var values);
|
||||
if (values is not null)
|
||||
{
|
||||
value = values.FirstOrDefault();
|
||||
@ -496,10 +493,9 @@ namespace Rssdp.Infrastructure
|
||||
private Uri GetFirstHeaderUriValue(string headerName, HttpResponseMessage response)
|
||||
{
|
||||
string value = null;
|
||||
IEnumerable<string> values;
|
||||
if (response.Headers.Contains(headerName))
|
||||
{
|
||||
response.Headers.TryGetValues(headerName, out values);
|
||||
response.Headers.TryGetValues(headerName, out var values);
|
||||
if (values is not null)
|
||||
{
|
||||
value = values.FirstOrDefault();
|
||||
@ -529,7 +525,7 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
foreach (var device in expiredDevices)
|
||||
{
|
||||
if (this.IsDisposed)
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -543,7 +539,7 @@ namespace Rssdp.Infrastructure
|
||||
// problems.
|
||||
foreach (var expiredUsn in (from expiredDevice in expiredDevices select expiredDevice.Usn).Distinct())
|
||||
{
|
||||
if (this.IsDisposed)
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -560,7 +556,7 @@ namespace Rssdp.Infrastructure
|
||||
existingDevices = FindExistingDeviceNotifications(_Devices, deviceUsn);
|
||||
foreach (var existingDevice in existingDevices)
|
||||
{
|
||||
if (this.IsDisposed)
|
||||
if (IsDisposed)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -206,9 +206,9 @@ namespace Rssdp.Infrastructure
|
||||
IPAddress receivedOnlocalIPAddress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (String.IsNullOrEmpty(searchTarget))
|
||||
if (string.IsNullOrEmpty(searchTarget))
|
||||
{
|
||||
WriteTrace(String.Format(CultureInfo.InvariantCulture, "Invalid search request received From {0}, Target is null/empty.", remoteEndPoint.ToString()));
|
||||
WriteTrace(string.Format(CultureInfo.InvariantCulture, "Invalid search request received From {0}, Target is null/empty.", remoteEndPoint.ToString()));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -232,7 +232,7 @@ namespace Rssdp.Infrastructure
|
||||
// return;
|
||||
}
|
||||
|
||||
if (!Int32.TryParse(mx, out var maxWaitInterval) || maxWaitInterval <= 0)
|
||||
if (!int.TryParse(mx, out var maxWaitInterval) || maxWaitInterval <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -243,27 +243,27 @@ namespace Rssdp.Infrastructure
|
||||
}
|
||||
|
||||
// Do not block synchronously as that may tie up a threadpool thread for several seconds.
|
||||
Task.Delay(_Random.Next(16, (maxWaitInterval * 1000))).ContinueWith((parentTask) =>
|
||||
Task.Delay(_Random.Next(16, maxWaitInterval * 1000)).ContinueWith((parentTask) =>
|
||||
{
|
||||
// Copying devices to local array here to avoid threading issues/enumerator exceptions.
|
||||
IEnumerable<SsdpDevice> devices = null;
|
||||
lock (_Devices)
|
||||
{
|
||||
if (String.Compare(SsdpConstants.SsdpDiscoverAllSTHeader, searchTarget, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
if (string.Compare(SsdpConstants.SsdpDiscoverAllSTHeader, searchTarget, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
devices = GetAllDevicesAsFlatEnumerable().ToArray();
|
||||
}
|
||||
else if (String.Compare(SsdpConstants.UpnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 || (this.SupportPnpRootDevice && String.Compare(SsdpConstants.PnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0))
|
||||
else if (string.Compare(SsdpConstants.UpnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 || (SupportPnpRootDevice && String.Compare(SsdpConstants.PnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0))
|
||||
{
|
||||
devices = _Devices.ToArray();
|
||||
}
|
||||
else if (searchTarget.Trim().StartsWith("uuid:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
devices = GetAllDevicesAsFlatEnumerable().Where(d => String.Compare(d.Uuid, searchTarget.Substring(5), StringComparison.OrdinalIgnoreCase) == 0).ToArray();
|
||||
devices = GetAllDevicesAsFlatEnumerable().Where(d => string.Compare(d.Uuid, searchTarget.Substring(5), StringComparison.OrdinalIgnoreCase) == 0).ToArray();
|
||||
}
|
||||
else if (searchTarget.StartsWith("urn:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
devices = GetAllDevicesAsFlatEnumerable().Where(d => String.Compare(d.FullDeviceType, searchTarget, StringComparison.OrdinalIgnoreCase) == 0).ToArray();
|
||||
devices = GetAllDevicesAsFlatEnumerable().Where(d => string.Compare(d.FullDeviceType, searchTarget, StringComparison.OrdinalIgnoreCase) == 0).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,7 +299,7 @@ namespace Rssdp.Infrastructure
|
||||
if (isRootDevice)
|
||||
{
|
||||
SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIPAddress, cancellationToken);
|
||||
if (this.SupportPnpRootDevice)
|
||||
if (SupportPnpRootDevice)
|
||||
{
|
||||
SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIPAddress, cancellationToken);
|
||||
}
|
||||
@ -312,7 +312,7 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
private string GetUsn(string udn, string fullDeviceType)
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, "{0}::{1}", udn, fullDeviceType);
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0}::{1}", udn, fullDeviceType);
|
||||
}
|
||||
|
||||
private async void SendSearchResponse(
|
||||
@ -326,16 +326,17 @@ namespace Rssdp.Infrastructure
|
||||
const string header = "HTTP/1.1 200 OK";
|
||||
|
||||
var rootDevice = device.ToRootDevice();
|
||||
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
values["EXT"] = "";
|
||||
values["DATE"] = DateTime.UtcNow.ToString("r");
|
||||
values["HOST"] = "239.255.255.250:1900";
|
||||
values["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds;
|
||||
values["ST"] = searchTarget;
|
||||
values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
|
||||
values["USN"] = uniqueServiceName;
|
||||
values["LOCATION"] = rootDevice.Location.ToString();
|
||||
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["EXT"] = "",
|
||||
["DATE"] = DateTime.UtcNow.ToString("r"),
|
||||
["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort),
|
||||
["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds,
|
||||
["ST"] = searchTarget,
|
||||
["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion),
|
||||
["USN"] = uniqueServiceName,
|
||||
["LOCATION"] = rootDevice.Location.ToString()
|
||||
};
|
||||
|
||||
var message = BuildMessage(header, values);
|
||||
|
||||
@ -439,7 +440,7 @@ namespace Rssdp.Infrastructure
|
||||
if (isRoot)
|
||||
{
|
||||
SendAliveNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken);
|
||||
if (this.SupportPnpRootDevice)
|
||||
if (SupportPnpRootDevice)
|
||||
{
|
||||
SendAliveNotification(device, SsdpConstants.PnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), cancellationToken);
|
||||
}
|
||||
@ -460,17 +461,18 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
const string header = "NOTIFY * HTTP/1.1";
|
||||
|
||||
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// If needed later for non-server devices, these headers will need to be dynamic
|
||||
values["HOST"] = "239.255.255.250:1900";
|
||||
values["DATE"] = DateTime.UtcNow.ToString("r");
|
||||
values["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds;
|
||||
values["LOCATION"] = rootDevice.Location.ToString();
|
||||
values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
|
||||
values["NTS"] = "ssdp:alive";
|
||||
values["NT"] = notificationType;
|
||||
values["USN"] = uniqueServiceName;
|
||||
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// If needed later for non-server devices, these headers will need to be dynamic
|
||||
["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort),
|
||||
["DATE"] = DateTime.UtcNow.ToString("r"),
|
||||
["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds,
|
||||
["LOCATION"] = rootDevice.Location.ToString(),
|
||||
["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion),
|
||||
["NTS"] = "ssdp:alive",
|
||||
["NT"] = notificationType,
|
||||
["USN"] = uniqueServiceName
|
||||
};
|
||||
|
||||
var message = BuildMessage(header, values);
|
||||
|
||||
@ -485,7 +487,7 @@ namespace Rssdp.Infrastructure
|
||||
if (isRoot)
|
||||
{
|
||||
tasks.Add(SendByeByeNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken));
|
||||
if (this.SupportPnpRootDevice)
|
||||
if (SupportPnpRootDevice)
|
||||
{
|
||||
tasks.Add(SendByeByeNotification(device, "pnp:rootdevice", GetUsn(device.Udn, "pnp:rootdevice"), cancellationToken));
|
||||
}
|
||||
@ -506,20 +508,21 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
const string header = "NOTIFY * HTTP/1.1";
|
||||
|
||||
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// If needed later for non-server devices, these headers will need to be dynamic
|
||||
values["HOST"] = "239.255.255.250:1900";
|
||||
values["DATE"] = DateTime.UtcNow.ToString("r");
|
||||
values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
|
||||
values["NTS"] = "ssdp:byebye";
|
||||
values["NT"] = notificationType;
|
||||
values["USN"] = uniqueServiceName;
|
||||
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// If needed later for non-server devices, these headers will need to be dynamic
|
||||
["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort),
|
||||
["DATE"] = DateTime.UtcNow.ToString("r"),
|
||||
["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion),
|
||||
["NTS"] = "ssdp:byebye",
|
||||
["NT"] = notificationType,
|
||||
["USN"] = uniqueServiceName
|
||||
};
|
||||
|
||||
var message = BuildMessage(header, values);
|
||||
|
||||
var sendCount = IsDisposed ? 1 : 3;
|
||||
WriteTrace(String.Format(CultureInfo.InvariantCulture, "Sent byebye notification"), device);
|
||||
WriteTrace(string.Format(CultureInfo.InvariantCulture, "Sent byebye notification"), device);
|
||||
return _CommsServer.SendMulticastMessage(message, sendCount, _sendOnlyMatchedHost ? device.ToRootDevice().Address : null, cancellationToken);
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ namespace Jellyfin.Networking.Tests
|
||||
[InlineData("127.0.0.1:123")]
|
||||
[InlineData("localhost")]
|
||||
[InlineData("localhost:1345")]
|
||||
[InlineData("www.google.co.uk")]
|
||||
[InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")]
|
||||
[InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")]
|
||||
[InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]:124")]
|
||||
|
@ -1,7 +1,9 @@
|
||||
using System.Net;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Networking.Manager;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Networking.Tests
|
||||
@ -28,7 +30,8 @@ namespace Jellyfin.Networking.Tests
|
||||
LocalNetworkSubnets = network.Split(',')
|
||||
};
|
||||
|
||||
using var networkManager = new NetworkManager(NetworkParseTests.GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||
var startupConf = new Mock<IConfiguration>();
|
||||
using var networkManager = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
|
||||
|
||||
Assert.True(networkManager.IsInLocalNetwork(ip));
|
||||
}
|
||||
@ -56,9 +59,10 @@ namespace Jellyfin.Networking.Tests
|
||||
LocalNetworkSubnets = network.Split(',')
|
||||
};
|
||||
|
||||
using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||
var startupConf = new Mock<IConfiguration>();
|
||||
using var networkManager = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
|
||||
|
||||
Assert.False(nm.IsInLocalNetwork(ip));
|
||||
Assert.False(networkManager.IsInLocalNetwork(ip));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using Jellyfin.Networking.Extensions;
|
||||
using Jellyfin.Networking.Manager;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
@ -54,7 +55,8 @@ namespace Jellyfin.Networking.Tests
|
||||
};
|
||||
|
||||
NetworkManager.MockNetworkSettings = interfaces;
|
||||
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||
var startupConf = new Mock<IConfiguration>();
|
||||
using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
|
||||
NetworkManager.MockNetworkSettings = string.Empty;
|
||||
|
||||
Assert.Equal(value, "[" + string.Join(",", nm.GetInternalBindAddresses().Select(x => x.Address + "/" + x.Subnet.PrefixLength)) + "]");
|
||||
@ -200,7 +202,8 @@ namespace Jellyfin.Networking.Tests
|
||||
};
|
||||
|
||||
NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11";
|
||||
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||
var startupConf = new Mock<IConfiguration>();
|
||||
using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
|
||||
NetworkManager.MockNetworkSettings = string.Empty;
|
||||
|
||||
// Check to see if DNS resolution is working. If not, skip test.
|
||||
@ -229,24 +232,24 @@ namespace Jellyfin.Networking.Tests
|
||||
[InlineData("192.168.1.1", "192.168.1.0/24", "eth16,eth11", false, "192.168.1.0/24=internal.jellyfin", "internal.jellyfin")]
|
||||
|
||||
// User on external network, we're bound internal and external - so result is override.
|
||||
[InlineData("8.8.8.8", "192.168.1.0/24", "eth16,eth11", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
|
||||
[InlineData("8.8.8.8", "192.168.1.0/24", "eth16,eth11", false, "all=http://helloworld.com", "http://helloworld.com")]
|
||||
|
||||
// User on internal network, we're bound internal only, but the address isn't in the LAN - so return the override.
|
||||
[InlineData("10.10.10.10", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://internalButNotDefinedAsLan.com", "http://internalButNotDefinedAsLan.com")]
|
||||
[InlineData("10.10.10.10", "192.168.1.0/24", "eth16", false, "external=http://internalButNotDefinedAsLan.com", "http://internalButNotDefinedAsLan.com")]
|
||||
|
||||
// User on internal network, no binding specified - so result is the 1st internal.
|
||||
[InlineData("192.168.1.1", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")]
|
||||
[InlineData("192.168.1.1", "192.168.1.0/24", "", false, "external=http://helloworld.com", "eth16")]
|
||||
|
||||
// User on external network, internal binding only - so assumption is a proxy forward, return external override.
|
||||
[InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
|
||||
[InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "external=http://helloworld.com", "http://helloworld.com")]
|
||||
|
||||
// User on external network, no binding - so result is the 1st external which is overriden.
|
||||
[InlineData("jellyfin.org", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
|
||||
[InlineData("jellyfin.org", "192.168.1.0/24", "", false, "external=http://helloworld.com", "http://helloworld.com")]
|
||||
|
||||
// User assumed to be internal, no binding - so result is the 1st internal.
|
||||
[InlineData("", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")]
|
||||
// User assumed to be internal, no binding - so result is the 1st matching interface.
|
||||
[InlineData("", "192.168.1.0/24", "", false, "all=http://helloworld.com", "eth16")]
|
||||
|
||||
// User is internal, no binding - so result is the 1st internal, which is then overridden.
|
||||
// User is internal, no binding - so result is the 1st internal interface, which is then overridden.
|
||||
[InlineData("192.168.1.1", "192.168.1.0/24", "", false, "eth16=http://helloworld.com", "http://helloworld.com")]
|
||||
public void TestBindInterfaceOverrides(string source, string lan, string bindAddresses, bool ipv6enabled, string publishedServers, string result)
|
||||
{
|
||||
@ -264,7 +267,8 @@ namespace Jellyfin.Networking.Tests
|
||||
};
|
||||
|
||||
NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11";
|
||||
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||
var startupConf = new Mock<IConfiguration>();
|
||||
using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
|
||||
NetworkManager.MockNetworkSettings = string.Empty;
|
||||
|
||||
if (nm.TryParseInterface(result, out IReadOnlyList<IPData>? resultObj) && resultObj is not null)
|
||||
@ -293,7 +297,9 @@ namespace Jellyfin.Networking.Tests
|
||||
RemoteIPFilter = addresses.Split(','),
|
||||
IsRemoteIPFilterBlacklist = false
|
||||
};
|
||||
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||
|
||||
var startupConf = new Mock<IConfiguration>();
|
||||
using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
|
||||
|
||||
Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIP)), denied);
|
||||
}
|
||||
@ -314,7 +320,8 @@ namespace Jellyfin.Networking.Tests
|
||||
IsRemoteIPFilterBlacklist = true
|
||||
};
|
||||
|
||||
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||
var startupConf = new Mock<IConfiguration>();
|
||||
using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
|
||||
|
||||
Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIP)), denied);
|
||||
}
|
||||
@ -334,7 +341,8 @@ namespace Jellyfin.Networking.Tests
|
||||
};
|
||||
|
||||
NetworkManager.MockNetworkSettings = interfaces;
|
||||
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||
var startupConf = new Mock<IConfiguration>();
|
||||
using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
|
||||
|
||||
var interfaceToUse = nm.GetBindAddress(string.Empty, out _);
|
||||
|
||||
@ -358,7 +366,8 @@ namespace Jellyfin.Networking.Tests
|
||||
};
|
||||
|
||||
NetworkManager.MockNetworkSettings = interfaces;
|
||||
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||
var startupConf = new Mock<IConfiguration>();
|
||||
using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
|
||||
|
||||
var interfaceToUse = nm.GetBindAddress(source, out _);
|
||||
|
||||
|
@ -7,6 +7,7 @@ using Jellyfin.Server.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
@ -119,8 +120,8 @@ namespace Jellyfin.Server.Tests
|
||||
EnableIPv6 = true,
|
||||
EnableIPv4 = true,
|
||||
};
|
||||
|
||||
return new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||
var startupConf = new Mock<IConfiguration>();
|
||||
return new NetworkManager(GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user