aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXu Fasheng <fasheng.xu@gmail.com>2019-02-22 12:06:49 +0800
committerXu Fasheng <fasheng.xu@gmail.com>2019-02-22 20:18:34 +0800
commitcbd0e71c077e6233bcbc751f9a2f1ee742000ba3 (patch)
treee79824216db472de61243098c03012903902603d
parent2db1826ed887bb84dc5065ff38ae67fe92cc6c51 (diff)
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.
-rw-r--r--Emby.Dlna/Configuration/DlnaOptions.cs2
-rw-r--r--Emby.Dlna/Main/DlnaEntryPoint.cs4
-rw-r--r--Emby.Server.Implementations/Networking/NetworkManager.cs60
-rw-r--r--MediaBrowser.Common/Net/INetworkManager.cs3
-rw-r--r--MediaBrowser.Model/Net/IpAddressInfo.cs1
-rw-r--r--RSSDP/ISsdpCommunicationsServer.cs6
-rw-r--r--RSSDP/SsdpCommunicationsServer.cs13
-rw-r--r--RSSDP/SsdpDeviceLocator.cs2
-rw-r--r--RSSDP/SsdpDevicePublisher.cs30
-rw-r--r--RSSDP/SsdpRootDevice.cs10
10 files changed, 116 insertions, 15 deletions
diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs
index 0ebb490a1..c7cb364a8 100644
--- a/Emby.Dlna/Configuration/DlnaOptions.cs
+++ b/Emby.Dlna/Configuration/DlnaOptions.cs
@@ -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;
}
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index 8eff7f773..427b3a5c0 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -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",
diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs
index b7a125f20..8696d1896 100644
--- a/Emby.Server.Implementations/Networking/NetworkManager.cs
+++ b/Emby.Server.Implementations/Networking/NetworkManager.cs
@@ -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)
diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs
index 3364230d1..34c6f5866 100644
--- a/MediaBrowser.Common/Net/INetworkManager.cs
+++ b/MediaBrowser.Common/Net/INetworkManager.cs
@@ -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);
}
}
diff --git a/MediaBrowser.Model/Net/IpAddressInfo.cs b/MediaBrowser.Model/Net/IpAddressInfo.cs
index 7a278d4d4..87fa55bca 100644
--- a/MediaBrowser.Model/Net/IpAddressInfo.cs
+++ b/MediaBrowser.Model/Net/IpAddressInfo.cs
@@ -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)
diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs
index ef75f997f..c99d684a1 100644
--- a/RSSDP/ISsdpCommunicationsServer.cs
+++ b/RSSDP/ISsdpCommunicationsServer.cs
@@ -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
}
-} \ No newline at end of file
+}
diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs
index 9da906b49..ea4d79c99 100644
--- a/RSSDP/SsdpCommunicationsServer.cs
+++ b/RSSDP/SsdpCommunicationsServer.cs
@@ -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);
}
diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs
index 128bdfcbb..e17e14c1a 100644
--- a/RSSDP/SsdpDeviceLocator.cs
+++ b/RSSDP/SsdpDeviceLocator.cs
@@ -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)
diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs
index ce64ba117..076246b24 100644
--- a/RSSDP/SsdpDevicePublisher.cs
+++ b/RSSDP/SsdpDevicePublisher.cs
@@ -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()
diff --git a/RSSDP/SsdpRootDevice.cs b/RSSDP/SsdpRootDevice.cs
index a2b0f60f5..d918b9040 100644
--- a/RSSDP/SsdpRootDevice.cs
+++ b/RSSDP/SsdpRootDevice.cs
@@ -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.