Procházet zdrojové kódy

Merge pull request #978 from fasheng/fix-dlna-multiple-interfaces

Fix DLNA for multiple interfaces on linux
Vasily před 6 roky
rodič
revize
3769453541

+ 2 - 0
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;
         }

+ 11 - 8
Emby.Dlna/Main/DlnaEntryPoint.cs

@@ -169,9 +169,10 @@ namespace Emby.Dlna.Main
             {
                 if (_communicationsServer == null)
                 {
-                    var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
+                    var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows ||
+                                                   _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Linux;
 
-                    _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
+                    _communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
                     {
                         IsShared = true
                     };
@@ -229,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;
 
@@ -251,11 +252,11 @@ namespace Emby.Dlna.Main
 
             foreach (var address in addresses)
             {
-                // TODO: Remove this condition on platforms that support it
-                //if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
-                //{
-                //    continue;
-                //}
+                if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
+                {
+                   // Not support IPv6 right now
+                   continue;
+                }
 
                 var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
 
@@ -268,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",

+ 1 - 1
Emby.Server.Implementations/ApplicationHost.cs

@@ -1579,7 +1579,7 @@ namespace Emby.Server.Implementations
 
             if (addresses.Count == 0)
             {
-                addresses.AddRange(NetworkManager.GetLocalIpAddresses());
+                addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
             }
 
             var resultList = new List<IpAddressInfo>();

+ 66 - 6
Emby.Server.Implementations/Networking/NetworkManager.cs

@@ -79,13 +79,13 @@ namespace Emby.Server.Implementations.Networking
         private IpAddressInfo[] _localIpAddresses;
         private readonly object _localIpAddressSyncLock = new object();
 
-        public IpAddressInfo[] GetLocalIpAddresses()
+        public IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
         {
             lock (_localIpAddressSyncLock)
             {
                 if (_localIpAddresses == null)
                 {
-                    var addresses = GetLocalIpAddressesInternal().Result.Select(ToIpAddressInfo).ToArray();
+                    var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).Result.Select(ToIpAddressInfo).ToArray();
 
                     _localIpAddresses = addresses;
 
@@ -95,9 +95,9 @@ namespace Emby.Server.Implementations.Networking
             }
         }
 
-        private async Task<List<IPAddress>> GetLocalIpAddressesInternal()
+        private async Task<List<IPAddress>> GetLocalIpAddressesInternal(bool ignoreVirtualInterface)
         {
-            var list = GetIPsDefault()
+            var list = GetIPsDefault(ignoreVirtualInterface)
                 .ToList();
 
             if (list.Count == 0)
@@ -383,7 +383,7 @@ namespace Emby.Server.Implementations.Networking
             return Dns.GetHostAddressesAsync(hostName);
         }
 
-        private List<IPAddress> GetIPsDefault()
+        private List<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
         {
             NetworkInterface[] interfaces;
 
@@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.Networking
                     // Try to exclude virtual adapters
                     // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms
                     var addr = ipProperties.GatewayAddresses.FirstOrDefault();
-                    if (addr == null || string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))
+                    if (addr == null || ignoreVirtualInterface && string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))
                     {
                         return new List<IPAddress>();
                     }
@@ -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)

+ 4 - 1
MediaBrowser.Common/Net/INetworkManager.cs

@@ -53,7 +53,7 @@ namespace MediaBrowser.Common.Net
         /// <returns><c>true</c> if [is in local network] [the specified endpoint]; otherwise, <c>false</c>.</returns>
         bool IsInLocalNetwork(string endpoint);
 
-        IpAddressInfo[] GetLocalIpAddresses();
+        IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface);
 
         IpAddressInfo ParseIpAddress(string ipAddress);
 
@@ -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);
     }
 }

+ 2 - 0
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -178,6 +178,7 @@ namespace MediaBrowser.Model.Configuration
         public string[] LocalNetworkSubnets { get; set; }
         public string[] LocalNetworkAddresses { get; set; }
         public string[] CodecsUsed { get; set; }
+        public bool IgnoreVirtualInterfaces { get; set; }
         public bool EnableExternalContentInSuggestions { get; set; }
         public bool RequireHttps { get; set; }
         public bool IsBehindProxy { get; set; }
@@ -205,6 +206,7 @@ namespace MediaBrowser.Model.Configuration
             CodecsUsed = Array.Empty<string>();
             ImageExtractionTimeoutMs = 0;
             PathSubstitutions = Array.Empty<PathSubstitution>();
+            IgnoreVirtualInterfaces = false;
             EnableSimpleArtistDetection = true;
 
             DisplaySpecialsWithinSeasons = true;

+ 1 - 0
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)

+ 3 - 3
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
 
     }
-}
+}

+ 1 - 0
RSSDP/RSSDP.csproj

@@ -3,6 +3,7 @@
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
+    <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
   </ItemGroup>
 
   <PropertyGroup>

+ 14 - 9
RSSDP/SsdpCommunicationsServer.cs

@@ -9,6 +9,7 @@ using System.Threading.Tasks;
 using MediaBrowser.Common.Net;
 using Microsoft.Extensions.Logging;
 using MediaBrowser.Model.Net;
+using MediaBrowser.Controller.Configuration;
 
 namespace Rssdp.Infrastructure
 {
@@ -45,6 +46,7 @@ namespace Rssdp.Infrastructure
         private readonly ILogger _logger;
         private ISocketFactory _SocketFactory;
         private readonly INetworkManager _networkManager;
+        private readonly IServerConfigurationManager _config;
 
         private int _LocalPort;
         private int _MulticastTtl;
@@ -74,9 +76,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)
+        public SsdpCommunicationsServer(IServerConfigurationManager config, ISocketFactory socketFactory,
+            INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
             : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding)
         {
+            _config = config;
         }
 
         /// <summary>
@@ -236,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));
 
@@ -264,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);
             }
@@ -332,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);
             }
 
@@ -363,11 +368,11 @@ namespace Rssdp.Infrastructure
 
             if (_enableMultiSocketBinding)
             {
-                foreach (var address in _networkManager.GetLocalIpAddresses())
+                foreach (var address in _networkManager.GetLocalIpAddresses(_config.Configuration.IgnoreVirtualInterfaces))
                 {
                     if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
                     {
-                        // Not supported ?
+                        // Not support IPv6 right now
                         continue;
                     }
 

+ 1 - 1
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)

+ 15 - 4
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,7 @@ namespace Rssdp.Infrastructure
 
             var message = BuildMessage(header, values);
 
-            _CommsServer.SendMulticastMessage(message, cancellationToken);
+            _CommsServer.SendMulticastMessage(message, _sendOnlyMatchedHost ? rootDevice.Address : null, cancellationToken);
 
             //WriteTrace(String.Format("Sent alive notification"), device);
         }
@@ -472,7 +483,7 @@ namespace Rssdp.Infrastructure
 
             var sendCount = IsDisposed ? 1 : 3;
             WriteTrace(String.Format("Sent byebye notification"), device);
-            return _CommsServer.SendMulticastMessage(message, sendCount, cancellationToken);
+            return _CommsServer.SendMulticastMessage(message, sendCount, _sendOnlyMatchedHost ? device.ToRootDevice().Address : null, cancellationToken);
         }
 
         private void DisposeRebroadcastTimer()

+ 10 - 0
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.