Merge pull request #978 from fasheng/fix-dlna-multiple-interfaces
Fix DLNA for multiple interfaces on linux
This commit is contained in:
commit
3769453541
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue
Block a user