Send DLNA devices message to only the matched interface
This will be the right way for multiple interfaces, or the client will receive all devices message with different IP addresses and could not detect which one could access. And provide one option DlnaOptions.SendOnlyMatchedHost to fallback to old behaviour if this commit missed something.
This commit is contained in:
parent
2db1826ed8
commit
cbd0e71c07
|
@ -7,6 +7,7 @@ namespace Emby.Dlna.Configuration
|
||||||
public bool EnableServer { get; set; }
|
public bool EnableServer { get; set; }
|
||||||
public bool EnableDebugLog { get; set; }
|
public bool EnableDebugLog { get; set; }
|
||||||
public bool BlastAliveMessages { get; set; }
|
public bool BlastAliveMessages { get; set; }
|
||||||
|
public bool SendOnlyMatchedHost { get; set; }
|
||||||
public int ClientDiscoveryIntervalSeconds { get; set; }
|
public int ClientDiscoveryIntervalSeconds { get; set; }
|
||||||
public int BlastAliveMessageIntervalSeconds { get; set; }
|
public int BlastAliveMessageIntervalSeconds { get; set; }
|
||||||
public string DefaultUserId { get; set; }
|
public string DefaultUserId { get; set; }
|
||||||
|
@ -16,6 +17,7 @@ namespace Emby.Dlna.Configuration
|
||||||
EnablePlayTo = true;
|
EnablePlayTo = true;
|
||||||
EnableServer = true;
|
EnableServer = true;
|
||||||
BlastAliveMessages = true;
|
BlastAliveMessages = true;
|
||||||
|
SendOnlyMatchedHost = true;
|
||||||
ClientDiscoveryIntervalSeconds = 60;
|
ClientDiscoveryIntervalSeconds = 60;
|
||||||
BlastAliveMessageIntervalSeconds = 1800;
|
BlastAliveMessageIntervalSeconds = 1800;
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,7 +230,7 @@ namespace Emby.Dlna.Main
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_Publisher = new SsdpDevicePublisher(_communicationsServer, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion);
|
_Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion, _config.GetDlnaConfiguration().SendOnlyMatchedHost);
|
||||||
_Publisher.LogFunction = LogMessage;
|
_Publisher.LogFunction = LogMessage;
|
||||||
_Publisher.SupportPnpRootDevice = false;
|
_Publisher.SupportPnpRootDevice = false;
|
||||||
|
|
||||||
|
@ -269,6 +269,8 @@ namespace Emby.Dlna.Main
|
||||||
{
|
{
|
||||||
CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
|
CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
|
||||||
Location = uri, // Must point to the URL that serves your devices UPnP description document.
|
Location = uri, // Must point to the URL that serves your devices UPnP description document.
|
||||||
|
Address = address,
|
||||||
|
SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
|
||||||
FriendlyName = "Jellyfin",
|
FriendlyName = "Jellyfin",
|
||||||
Manufacturer = "Jellyfin",
|
Manufacturer = "Jellyfin",
|
||||||
ModelName = "Jellyfin Server",
|
ModelName = "Jellyfin Server",
|
||||||
|
|
|
@ -636,6 +636,66 @@ namespace Emby.Server.Implementations.Networking
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask)
|
||||||
|
{
|
||||||
|
IPAddress network1 = GetNetworkAddress(ToIPAddress(address1), ToIPAddress(subnetMask));
|
||||||
|
IPAddress network2 = GetNetworkAddress(ToIPAddress(address2), ToIPAddress(subnetMask));
|
||||||
|
return network1.Equals(network2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
|
||||||
|
{
|
||||||
|
byte[] ipAdressBytes = address.GetAddressBytes();
|
||||||
|
byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
|
||||||
|
|
||||||
|
if (ipAdressBytes.Length != subnetMaskBytes.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
|
||||||
|
for (int i = 0; i < broadcastAddress.Length; i++)
|
||||||
|
{
|
||||||
|
broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
|
||||||
|
}
|
||||||
|
return new IPAddress(broadcastAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address)
|
||||||
|
{
|
||||||
|
NetworkInterface[] interfaces;
|
||||||
|
IPAddress ipaddress = ToIPAddress(address);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown };
|
||||||
|
|
||||||
|
interfaces = NetworkInterface.GetAllNetworkInterfaces()
|
||||||
|
.Where(i => validStatuses.Contains(i.OperationalStatus))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Error in GetAllNetworkInterfaces");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (NetworkInterface ni in interfaces)
|
||||||
|
{
|
||||||
|
if (ni.GetIPProperties().GatewayAddresses.FirstOrDefault() != null)
|
||||||
|
{
|
||||||
|
foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
|
||||||
|
{
|
||||||
|
if (ip.Address.Equals(ipaddress) && ip.IPv4Mask != null)
|
||||||
|
{
|
||||||
|
return ToIpAddressInfo(ip.IPv4Mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint)
|
public static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint)
|
||||||
{
|
{
|
||||||
if (endpoint == null)
|
if (endpoint == null)
|
||||||
|
|
|
@ -62,5 +62,8 @@ namespace MediaBrowser.Common.Net
|
||||||
Task<IpAddressInfo[]> GetHostAddressesAsync(string host);
|
Task<IpAddressInfo[]> GetHostAddressesAsync(string host);
|
||||||
|
|
||||||
bool IsAddressInSubnets(string addressString, string[] subnets);
|
bool IsAddressInSubnets(string addressString, string[] subnets);
|
||||||
|
|
||||||
|
bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask);
|
||||||
|
IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Net
|
||||||
public static IpAddressInfo IPv6Loopback = new IpAddressInfo("::1", IpAddressFamily.InterNetworkV6);
|
public static IpAddressInfo IPv6Loopback = new IpAddressInfo("::1", IpAddressFamily.InterNetworkV6);
|
||||||
|
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
|
public IpAddressInfo SubnetMask { get; set; }
|
||||||
public IpAddressFamily AddressFamily { get; set; }
|
public IpAddressFamily AddressFamily { get; set; }
|
||||||
|
|
||||||
public IpAddressInfo(string address, IpAddressFamily addressFamily)
|
public IpAddressInfo(string address, IpAddressFamily addressFamily)
|
||||||
|
|
|
@ -45,8 +45,8 @@ namespace Rssdp.Infrastructure
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a message to the SSDP multicast address and port.
|
/// Sends a message to the SSDP multicast address and port.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task SendMulticastMessage(string message, CancellationToken cancellationToken);
|
Task SendMulticastMessage(string message, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken);
|
||||||
Task SendMulticastMessage(string message, int sendCount, CancellationToken cancellationToken);
|
Task SendMulticastMessage(string message, int sendCount, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -63,4 +63,4 @@ namespace Rssdp.Infrastructure
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,15 +240,15 @@ namespace Rssdp.Infrastructure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendMulticastMessage(string message, CancellationToken cancellationToken)
|
public Task SendMulticastMessage(string message, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return SendMulticastMessage(message, SsdpConstants.UdpResendCount, cancellationToken);
|
return SendMulticastMessage(message, SsdpConstants.UdpResendCount, fromLocalIpAddress, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a message to the SSDP multicast address and port.
|
/// Sends a message to the SSDP multicast address and port.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task SendMulticastMessage(string message, int sendCount, CancellationToken cancellationToken)
|
public async Task SendMulticastMessage(string message, int sendCount, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||||
|
|
||||||
|
@ -268,7 +268,7 @@ namespace Rssdp.Infrastructure
|
||||||
IpAddress = new IpAddressInfo(SsdpConstants.MulticastLocalAdminAddress, IpAddressFamily.InterNetwork),
|
IpAddress = new IpAddressInfo(SsdpConstants.MulticastLocalAdminAddress, IpAddressFamily.InterNetwork),
|
||||||
Port = SsdpConstants.MulticastPort
|
Port = SsdpConstants.MulticastPort
|
||||||
|
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}, fromLocalIpAddress, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -336,14 +336,15 @@ namespace Rssdp.Infrastructure
|
||||||
|
|
||||||
#region Private Methods
|
#region Private Methods
|
||||||
|
|
||||||
private Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination, CancellationToken cancellationToken)
|
private Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var sockets = _sendSockets;
|
var sockets = _sendSockets;
|
||||||
if (sockets != null)
|
if (sockets != null)
|
||||||
{
|
{
|
||||||
sockets = sockets.ToList();
|
sockets = sockets.ToList();
|
||||||
|
|
||||||
var tasks = sockets.Select(s => SendFromSocket(s, messageData, destination, cancellationToken));
|
var tasks = sockets.Where(s => (fromLocalIpAddress == null || fromLocalIpAddress.Equals(s.LocalIPAddress)))
|
||||||
|
.Select(s => SendFromSocket(s, messageData, destination, cancellationToken));
|
||||||
return Task.WhenAll(tasks);
|
return Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -354,7 +354,7 @@ namespace Rssdp.Infrastructure
|
||||||
|
|
||||||
var message = BuildMessage(header, values);
|
var message = BuildMessage(header, values);
|
||||||
|
|
||||||
return _CommunicationsServer.SendMulticastMessage(message, cancellationToken);
|
return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessSearchResponseMessage(HttpResponseMessage message, IpAddressInfo localIpAddress)
|
private void ProcessSearchResponseMessage(HttpResponseMessage message, IpAddressInfo localIpAddress)
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
using Rssdp;
|
using Rssdp;
|
||||||
|
|
||||||
namespace Rssdp.Infrastructure
|
namespace Rssdp.Infrastructure
|
||||||
|
@ -16,10 +17,12 @@ namespace Rssdp.Infrastructure
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SsdpDevicePublisher : DisposableManagedObjectBase, ISsdpDevicePublisher
|
public class SsdpDevicePublisher : DisposableManagedObjectBase, ISsdpDevicePublisher
|
||||||
{
|
{
|
||||||
|
private readonly INetworkManager _networkManager;
|
||||||
|
|
||||||
private ISsdpCommunicationsServer _CommsServer;
|
private ISsdpCommunicationsServer _CommsServer;
|
||||||
private string _OSName;
|
private string _OSName;
|
||||||
private string _OSVersion;
|
private string _OSVersion;
|
||||||
|
private bool _sendOnlyMatchedHost;
|
||||||
|
|
||||||
private bool _SupportPnpRootDevice;
|
private bool _SupportPnpRootDevice;
|
||||||
|
|
||||||
|
@ -37,9 +40,11 @@ namespace Rssdp.Infrastructure
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default constructor.
|
/// Default constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, string osName, string osVersion)
|
public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, INetworkManager networkManager,
|
||||||
|
string osName, string osVersion, bool sendOnlyMatchedHost)
|
||||||
{
|
{
|
||||||
if (communicationsServer == null) throw new ArgumentNullException(nameof(communicationsServer));
|
if (communicationsServer == null) throw new ArgumentNullException(nameof(communicationsServer));
|
||||||
|
if (networkManager == null) throw new ArgumentNullException(nameof(networkManager));
|
||||||
if (osName == null) throw new ArgumentNullException(nameof(osName));
|
if (osName == null) throw new ArgumentNullException(nameof(osName));
|
||||||
if (osName.Length == 0) throw new ArgumentException("osName cannot be an empty string.", nameof(osName));
|
if (osName.Length == 0) throw new ArgumentException("osName cannot be an empty string.", nameof(osName));
|
||||||
if (osVersion == null) throw new ArgumentNullException(nameof(osVersion));
|
if (osVersion == null) throw new ArgumentNullException(nameof(osVersion));
|
||||||
|
@ -51,10 +56,12 @@ namespace Rssdp.Infrastructure
|
||||||
_RecentSearchRequests = new Dictionary<string, SearchRequest>(StringComparer.OrdinalIgnoreCase);
|
_RecentSearchRequests = new Dictionary<string, SearchRequest>(StringComparer.OrdinalIgnoreCase);
|
||||||
_Random = new Random();
|
_Random = new Random();
|
||||||
|
|
||||||
|
_networkManager = networkManager;
|
||||||
_CommsServer = communicationsServer;
|
_CommsServer = communicationsServer;
|
||||||
_CommsServer.RequestReceived += CommsServer_RequestReceived;
|
_CommsServer.RequestReceived += CommsServer_RequestReceived;
|
||||||
_OSName = osName;
|
_OSName = osName;
|
||||||
_OSVersion = osVersion;
|
_OSVersion = osVersion;
|
||||||
|
_sendOnlyMatchedHost = sendOnlyMatchedHost;
|
||||||
|
|
||||||
_CommsServer.BeginListeningForBroadcasts();
|
_CommsServer.BeginListeningForBroadcasts();
|
||||||
}
|
}
|
||||||
|
@ -250,7 +257,11 @@ namespace Rssdp.Infrastructure
|
||||||
|
|
||||||
foreach (var device in deviceList)
|
foreach (var device in deviceList)
|
||||||
{
|
{
|
||||||
SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken);
|
if (!_sendOnlyMatchedHost ||
|
||||||
|
_networkManager.IsInSameSubnet(device.ToRootDevice().Address, remoteEndPoint.IpAddress, device.ToRootDevice().SubnetMask))
|
||||||
|
{
|
||||||
|
SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -427,7 +438,12 @@ namespace Rssdp.Infrastructure
|
||||||
|
|
||||||
var message = BuildMessage(header, values);
|
var message = BuildMessage(header, values);
|
||||||
|
|
||||||
_CommsServer.SendMulticastMessage(message, cancellationToken);
|
if (_sendOnlyMatchedHost)
|
||||||
|
{
|
||||||
|
_CommsServer.SendMulticastMessage(message, _sendOnlyMatchedHost ? rootDevice.Address : null, cancellationToken);
|
||||||
|
} else {
|
||||||
|
_CommsServer.SendMulticastMessage(message, null, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
//WriteTrace(String.Format("Sent alive notification"), device);
|
//WriteTrace(String.Format("Sent alive notification"), device);
|
||||||
}
|
}
|
||||||
|
@ -472,7 +488,13 @@ namespace Rssdp.Infrastructure
|
||||||
|
|
||||||
var sendCount = IsDisposed ? 1 : 3;
|
var sendCount = IsDisposed ? 1 : 3;
|
||||||
WriteTrace(String.Format("Sent byebye notification"), device);
|
WriteTrace(String.Format("Sent byebye notification"), device);
|
||||||
return _CommsServer.SendMulticastMessage(message, sendCount, cancellationToken);
|
if (_sendOnlyMatchedHost)
|
||||||
|
{
|
||||||
|
return _CommsServer.SendMulticastMessage(message, sendCount,
|
||||||
|
_sendOnlyMatchedHost ? device.ToRootDevice().Address : null, cancellationToken);
|
||||||
|
} else {
|
||||||
|
return _CommsServer.SendMulticastMessage(message, sendCount, null, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeRebroadcastTimer()
|
private void DisposeRebroadcastTimer()
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using Rssdp.Infrastructure;
|
using Rssdp.Infrastructure;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
|
||||||
namespace Rssdp
|
namespace Rssdp
|
||||||
{
|
{
|
||||||
|
@ -52,6 +53,15 @@ namespace Rssdp
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Uri Location { get; set; }
|
public Uri Location { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Address used to check if the received message from same interface with this device/tree. Required.
|
||||||
|
/// </summary>
|
||||||
|
public IpAddressInfo Address { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the SubnetMask used to check if the received message from same interface with this device/tree. Required.
|
||||||
|
/// </summary>
|
||||||
|
public IpAddressInfo SubnetMask { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The base URL to use for all relative url's provided in other propertise (and those of child devices). Optional.
|
/// The base URL to use for all relative url's provided in other propertise (and those of child devices). Optional.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user