Refactor URI overrides (#10051)

pull/10387/head
Tim Eisele 7 months ago committed by GitHub
parent a88e13a677
commit dc27d8f9cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -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)
// 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)
{
try
{
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);
}
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);
}
}
else
{
var socket = _SocketFactory.CreateUdpMulticastSocket(multicastGroupAddress, new IPData(IPAddress.Any, null), _MulticastTtl, SsdpConstants.MulticastPort);
_ = ListenToSocketInternal(socket);
sockets.Add(socket);
}
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.SupportsMulticast)
.Where(x => x.AddressFamily == AddressFamily.InterNetwork);
if (OperatingSystem.IsMacOS())
{
// IPv6 is currently unsupported
var validInterfaces = _networkManager.GetInternalBindAddresses()
.Where(x => x.Address is not null)
.Where(x => x.AddressFamily == AddressFamily.InterNetwork);
// 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)
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…
Cancel
Save