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:
Xu Fasheng 2019-02-22 12:06:49 +08:00
parent 2db1826ed8
commit cbd0e71c07
10 changed files with 116 additions and 15 deletions

View File

@ -7,6 +7,7 @@ namespace Emby.Dlna.Configuration
public bool EnableServer { get; set; }
public bool EnableDebugLog { get; set; }
public bool BlastAliveMessages { get; set; }
public bool SendOnlyMatchedHost { get; set; }
public int ClientDiscoveryIntervalSeconds { get; set; }
public int BlastAliveMessageIntervalSeconds { get; set; }
public string DefaultUserId { get; set; }
@ -16,6 +17,7 @@ namespace Emby.Dlna.Configuration
EnablePlayTo = true;
EnableServer = true;
BlastAliveMessages = true;
SendOnlyMatchedHost = true;
ClientDiscoveryIntervalSeconds = 60;
BlastAliveMessageIntervalSeconds = 1800;
}

View File

@ -230,7 +230,7 @@ namespace Emby.Dlna.Main
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.SupportPnpRootDevice = false;
@ -269,6 +269,8 @@ namespace Emby.Dlna.Main
{
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.
Address = address,
SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
FriendlyName = "Jellyfin",
Manufacturer = "Jellyfin",
ModelName = "Jellyfin Server",

View File

@ -636,6 +636,66 @@ namespace Emby.Server.Implementations.Networking
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)
{
if (endpoint == null)

View File

@ -62,5 +62,8 @@ namespace MediaBrowser.Common.Net
Task<IpAddressInfo[]> GetHostAddressesAsync(string host);
bool IsAddressInSubnets(string addressString, string[] subnets);
bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask);
IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address);
}
}

View File

@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Net
public static IpAddressInfo IPv6Loopback = new IpAddressInfo("::1", IpAddressFamily.InterNetworkV6);
public string Address { get; set; }
public IpAddressInfo SubnetMask { get; set; }
public IpAddressFamily AddressFamily { get; set; }
public IpAddressInfo(string address, IpAddressFamily addressFamily)

View File

@ -45,8 +45,8 @@ namespace Rssdp.Infrastructure
/// <summary>
/// Sends a message to the SSDP multicast address and port.
/// </summary>
Task SendMulticastMessage(string message, CancellationToken cancellationToken);
Task SendMulticastMessage(string message, int sendCount, CancellationToken cancellationToken);
Task SendMulticastMessage(string message, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken);
Task SendMulticastMessage(string message, int sendCount, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken);
#endregion
@ -63,4 +63,4 @@ namespace Rssdp.Infrastructure
#endregion
}
}
}

View File

@ -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>
/// Sends a message to the SSDP multicast address and port.
/// </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));
@ -268,7 +268,7 @@ namespace Rssdp.Infrastructure
IpAddress = new IpAddressInfo(SsdpConstants.MulticastLocalAdminAddress, IpAddressFamily.InterNetwork),
Port = SsdpConstants.MulticastPort
}, cancellationToken).ConfigureAwait(false);
}, fromLocalIpAddress, cancellationToken).ConfigureAwait(false);
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
@ -336,14 +336,15 @@ namespace Rssdp.Infrastructure
#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;
if (sockets != null)
{
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);
}

View File

@ -354,7 +354,7 @@ namespace Rssdp.Infrastructure
var message = BuildMessage(header, values);
return _CommunicationsServer.SendMulticastMessage(message, cancellationToken);
return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken);
}
private void ProcessSearchResponseMessage(HttpResponseMessage message, IpAddressInfo localIpAddress)

View File

@ -7,6 +7,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
using MediaBrowser.Common.Net;
using Rssdp;
namespace Rssdp.Infrastructure
@ -16,10 +17,12 @@ namespace Rssdp.Infrastructure
/// </summary>
public class SsdpDevicePublisher : DisposableManagedObjectBase, ISsdpDevicePublisher
{
private readonly INetworkManager _networkManager;
private ISsdpCommunicationsServer _CommsServer;
private string _OSName;
private string _OSVersion;
private bool _sendOnlyMatchedHost;
private bool _SupportPnpRootDevice;
@ -37,9 +40,11 @@ namespace Rssdp.Infrastructure
/// <summary>
/// Default constructor.
/// </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 (networkManager == null) throw new ArgumentNullException(nameof(networkManager));
if (osName == null) throw new ArgumentNullException(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));
@ -51,10 +56,12 @@ namespace Rssdp.Infrastructure
_RecentSearchRequests = new Dictionary<string, SearchRequest>(StringComparer.OrdinalIgnoreCase);
_Random = new Random();
_networkManager = networkManager;
_CommsServer = communicationsServer;
_CommsServer.RequestReceived += CommsServer_RequestReceived;
_OSName = osName;
_OSVersion = osVersion;
_sendOnlyMatchedHost = sendOnlyMatchedHost;
_CommsServer.BeginListeningForBroadcasts();
}
@ -250,7 +257,11 @@ namespace Rssdp.Infrastructure
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
@ -427,7 +438,12 @@ namespace Rssdp.Infrastructure
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);
}
@ -472,7 +488,13 @@ namespace Rssdp.Infrastructure
var sendCount = IsDisposed ? 1 : 3;
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()

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Text;
using System.Xml;
using Rssdp.Infrastructure;
using MediaBrowser.Model.Net;
namespace Rssdp
{
@ -52,6 +53,15 @@ namespace Rssdp
/// </summary>
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>
/// The base URL to use for all relative url's provided in other propertise (and those of child devices). Optional.