aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Common
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Common')
-rw-r--r--MediaBrowser.Common/Extensions/HttpContextExtensions.cs2
-rw-r--r--MediaBrowser.Common/Net/INetworkManager.cs177
-rw-r--r--MediaBrowser.Common/Net/IPHost.cs441
-rw-r--r--MediaBrowser.Common/Net/IPNetAddress.cs278
-rw-r--r--MediaBrowser.Common/Net/IPObject.cs355
-rw-r--r--MediaBrowser.Common/Net/NetworkExtensions.cs368
6 files changed, 275 insertions, 1346 deletions
diff --git a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs
index 6608704c0..a1056b7c8 100644
--- a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs
+++ b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs
@@ -25,7 +25,7 @@ namespace MediaBrowser.Common.Extensions
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns>The remote caller IP address.</returns>
- public static IPAddress GetNormalizedRemoteIp(this HttpContext context)
+ public static IPAddress GetNormalizedRemoteIP(this HttpContext context)
{
// Default to the loopback address if no RemoteIpAddress is specified (i.e. during integration tests)
var ip = context.Connection.RemoteIpAddress ?? IPAddress.Loopback;
diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs
index b93939730..c51090e38 100644
--- a/MediaBrowser.Common/Net/INetworkManager.cs
+++ b/MediaBrowser.Common/Net/INetworkManager.cs
@@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.NetworkInformation;
+using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Common.Net
@@ -18,47 +19,32 @@ namespace MediaBrowser.Common.Net
event EventHandler NetworkChanged;
/// <summary>
- /// Gets the published server urls list.
+ /// Gets a value indicating whether IPv4 is enabled.
/// </summary>
- Dictionary<IPNetAddress, string> PublishedServerUrls { get; }
+ bool IsIPv4Enabled { get; }
/// <summary>
- /// Gets a value indicating whether is all IPv6 interfaces are trusted as internal.
+ /// Gets a value indicating whether IPv6 is enabled.
/// </summary>
- bool TrustAllIP6Interfaces { get; }
-
- /// <summary>
- /// Gets the remote address filter.
- /// </summary>
- Collection<IPObject> RemoteAddressFilter { get; }
-
- /// <summary>
- /// Gets or sets a value indicating whether iP6 is enabled.
- /// </summary>
- bool IsIP6Enabled { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether iP4 is enabled.
- /// </summary>
- bool IsIP4Enabled { get; set; }
+ bool IsIPv6Enabled { get; }
/// <summary>
/// Calculates the list of interfaces to use for Kestrel.
/// </summary>
- /// <returns>A Collection{IPObject} object containing all the interfaces to bind.
+ /// <returns>A IReadOnlyList{IPData} object containing all the interfaces to bind.
/// If all the interfaces are specified, and none are excluded, it returns zero items
/// to represent any address.</returns>
/// <param name="individualInterfaces">When false, return <see cref="IPAddress.Any"/> or <see cref="IPAddress.IPv6Any"/> for all interfaces.</param>
- Collection<IPObject> GetAllBindInterfaces(bool individualInterfaces = false);
+ IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false);
/// <summary>
- /// Returns a collection containing the loopback interfaces.
+ /// Returns a list containing the loopback interfaces.
/// </summary>
- /// <returns>Collection{IPObject}.</returns>
- Collection<IPObject> GetLoopbacks();
+ /// <returns>IReadOnlyList{IPData}.</returns>
+ IReadOnlyList<IPData> GetLoopbacks();
/// <summary>
- /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo)
+ /// Retrieves the bind address to use in system URLs. (Server Discovery, PlayTo, LiveTV, SystemInfo)
/// If no bind addresses are specified, an internal interface address is selected.
/// The priority of selection is as follows:-
///
@@ -72,90 +58,50 @@ namespace MediaBrowser.Common.Net
///
/// If the source is from a public subnet address range and the user hasn't specified any bind addresses:-
/// The first public interface that isn't a loopback and contains the source subnet.
- /// The first public interface that isn't a loopback. Priority is given to interfaces with gateways.
- /// An internal interface if there are no public ip addresses.
+ /// The first public interface that isn't a loopback.
+ /// The first internal interface that isn't a loopback.
///
/// If the source is from a private subnet address range and the user hasn't specified any bind addresses:-
/// The first private interface that contains the source subnet.
- /// The first private interface that isn't a loopback. Priority is given to interfaces with gateways.
+ /// The first private interface that isn't a loopback.
///
/// If no interfaces meet any of these criteria, then a loopback address is returned.
///
- /// Interface that have been specifically excluded from binding are not used in any of the calculations.
- /// </summary>
- /// <param name="source">Source of the request.</param>
- /// <param name="port">Optional port returned, if it's part of an override.</param>
- /// <returns>IP Address to use, or loopback address if all else fails.</returns>
- string GetBindInterface(IPObject source, out int? port);
-
- /// <summary>
- /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo)
- /// If no bind addresses are specified, an internal interface address is selected.
- /// (See <see cref="GetBindInterface(IPObject, out int?)"/>.
+ /// Interfaces that have been specifically excluded from binding are not used in any of the calculations.
/// </summary>
/// <param name="source">Source of the request.</param>
/// <param name="port">Optional port returned, if it's part of an override.</param>
- /// <returns>IP Address to use, or loopback address if all else fails.</returns>
- string GetBindInterface(HttpRequest source, out int? port);
+ /// <returns>IP address to use, or loopback address if all else fails.</returns>
+ string GetBindAddress(HttpRequest source, out int? port);
/// <summary>
- /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo)
+ /// Retrieves the bind address to use in system URLs. (Server Discovery, PlayTo, LiveTV, SystemInfo)
/// If no bind addresses are specified, an internal interface address is selected.
- /// (See <see cref="GetBindInterface(IPObject, out int?)"/>.
/// </summary>
/// <param name="source">IP address of the request.</param>
/// <param name="port">Optional port returned, if it's part of an override.</param>
- /// <returns>IP Address to use, or loopback address if all else fails.</returns>
- string GetBindInterface(IPAddress source, out int? port);
+ /// <param name="skipOverrides">Optional boolean denoting if published server overrides should be ignored. Defaults to false.</param>
+ /// <returns>IP address to use, or loopback address if all else fails.</returns>
+ string GetBindAddress(IPAddress source, out int? port, bool skipOverrides = false);
/// <summary>
- /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo)
+ /// Retrieves the bind address to use in system URLs. (Server Discovery, PlayTo, LiveTV, SystemInfo)
/// If no bind addresses are specified, an internal interface address is selected.
- /// (See <see cref="GetBindInterface(IPObject, out int?)"/>.
+ /// (See <see cref="GetBindAddress(IPAddress, out int?, bool)"/>.
/// </summary>
/// <param name="source">Source of the request.</param>
/// <param name="port">Optional port returned, if it's part of an override.</param>
- /// <returns>IP Address to use, or loopback address if all else fails.</returns>
- string GetBindInterface(string source, out int? port);
-
- /// <summary>
- /// Checks to see if the ip address is specifically excluded in LocalNetworkAddresses.
- /// </summary>
- /// <param name="address">IP address to check.</param>
- /// <returns>True if it is.</returns>
- bool IsExcludedInterface(IPAddress address);
+ /// <returns>IP address to use, or loopback address if all else fails.</returns>
+ string GetBindAddress(string source, out int? port);
/// <summary>
/// Get a list of all the MAC addresses associated with active interfaces.
/// </summary>
/// <returns>List of MAC addresses.</returns>
- IReadOnlyCollection<PhysicalAddress> GetMacAddresses();
-
- /// <summary>
- /// Checks to see if the IP Address provided matches an interface that has a gateway.
- /// </summary>
- /// <param name="addressObj">IP to check. Can be an IPAddress or an IPObject.</param>
- /// <returns>Result of the check.</returns>
- bool IsGatewayInterface(IPObject? addressObj);
-
- /// <summary>
- /// Checks to see if the IP Address provided matches an interface that has a gateway.
- /// </summary>
- /// <param name="addressObj">IP to check. Can be an IPAddress or an IPObject.</param>
- /// <returns>Result of the check.</returns>
- bool IsGatewayInterface(IPAddress? addressObj);
-
- /// <summary>
- /// Returns true if the address is a private address.
- /// The configuration option TrustIP6Interfaces overrides this functions behaviour.
- /// </summary>
- /// <param name="address">Address to check.</param>
- /// <returns>True or False.</returns>
- bool IsPrivateAddressRange(IPObject address);
+ IReadOnlyList<PhysicalAddress> GetMacAddresses();
/// <summary>
/// Returns true if the address is part of the user defined LAN.
- /// The configuration option TrustIP6Interfaces overrides this functions behaviour.
/// </summary>
/// <param name="address">IP to check.</param>
/// <returns>True if endpoint is within the LAN range.</returns>
@@ -163,76 +109,31 @@ namespace MediaBrowser.Common.Net
/// <summary>
/// Returns true if the address is part of the user defined LAN.
- /// The configuration option TrustIP6Interfaces overrides this functions behaviour.
- /// </summary>
- /// <param name="address">IP to check.</param>
- /// <returns>True if endpoint is within the LAN range.</returns>
- bool IsInLocalNetwork(IPObject address);
-
- /// <summary>
- /// Returns true if the address is part of the user defined LAN.
- /// The configuration option TrustIP6Interfaces overrides this functions behaviour.
/// </summary>
/// <param name="address">IP to check.</param>
/// <returns>True if endpoint is within the LAN range.</returns>
bool IsInLocalNetwork(IPAddress address);
/// <summary>
- /// Attempts to convert the token to an IP address, permitting for interface descriptions and indexes.
- /// eg. "eth1", or "TP-LINK Wireless USB Adapter".
+ /// Attempts to convert the interface name to an IP address.
+ /// eg. "eth1", or "enp3s5".
/// </summary>
- /// <param name="token">Token to parse.</param>
- /// <param name="result">Resultant object's ip addresses, if successful.</param>
+ /// <param name="intf">Interface name.</param>
+ /// <param name="result">Resulting object's IP addresses, if successful.</param>
/// <returns>Success of the operation.</returns>
- bool TryParseInterface(string token, out Collection<IPObject>? result);
-
- /// <summary>
- /// Parses an array of strings into a Collection{IPObject}.
- /// </summary>
- /// <param name="values">Values to parse.</param>
- /// <param name="negated">When true, only include values beginning with !. When false, ignore ! values.</param>
- /// <returns>IPCollection object containing the value strings.</returns>
- Collection<IPObject> CreateIPCollection(string[] values, bool negated = false);
-
- /// <summary>
- /// Returns all the internal Bind interface addresses.
- /// </summary>
- /// <returns>An internal list of interfaces addresses.</returns>
- Collection<IPObject> GetInternalBindAddresses();
-
- /// <summary>
- /// Checks to see if an IP address is still a valid interface address.
- /// </summary>
- /// <param name="address">IP address to check.</param>
- /// <returns>True if it is.</returns>
- bool IsValidInterfaceAddress(IPAddress address);
-
- /// <summary>
- /// Returns true if the IP address is in the excluded list.
- /// </summary>
- /// <param name="ip">IP to check.</param>
- /// <returns>True if excluded.</returns>
- bool IsExcluded(IPAddress ip);
-
- /// <summary>
- /// Returns true if the IP address is in the excluded list.
- /// </summary>
- /// <param name="ip">IP to check.</param>
- /// <returns>True if excluded.</returns>
- bool IsExcluded(EndPoint ip);
+ bool TryParseInterface(string intf, [NotNullWhen(true)] out IReadOnlyList<IPData>? result);
/// <summary>
- /// Gets the filtered LAN ip addresses.
+ /// Returns all internal (LAN) bind interface addresses.
/// </summary>
- /// <param name="filter">Optional filter for the list.</param>
- /// <returns>Returns a filtered list of LAN addresses.</returns>
- Collection<IPObject> GetFilteredLANSubnets(Collection<IPObject>? filter = null);
+ /// <returns>An list of internal (LAN) interfaces addresses.</returns>
+ IReadOnlyList<IPData> GetInternalBindAddresses();
/// <summary>
- /// Checks to see if <paramref name="remoteIp"/> has access.
+ /// Checks if <paramref name="remoteIP"/> has access to the server.
/// </summary>
- /// <param name="remoteIp">IP Address of client.</param>
- /// <returns><b>True</b> if has access, otherwise <b>false</b>.</returns>
- bool HasRemoteAccess(IPAddress remoteIp);
+ /// <param name="remoteIP">IP address of the client.</param>
+ /// <returns><b>True</b> if it has access, otherwise <b>false</b>.</returns>
+ bool HasRemoteAccess(IPAddress remoteIP);
}
}
diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs
deleted file mode 100644
index ec76a43b6..000000000
--- a/MediaBrowser.Common/Net/IPHost.cs
+++ /dev/null
@@ -1,441 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-using System.Text.RegularExpressions;
-
-namespace MediaBrowser.Common.Net
-{
- /// <summary>
- /// Object that holds a host name.
- /// </summary>
- public class IPHost : IPObject
- {
- /// <summary>
- /// Gets or sets timeout value before resolve required, in minutes.
- /// </summary>
- public const int Timeout = 30;
-
- /// <summary>
- /// Represents an IPHost that has no value.
- /// </summary>
- public static readonly IPHost None = new IPHost(string.Empty, IPAddress.None);
-
- /// <summary>
- /// Time when last resolved in ticks.
- /// </summary>
- private DateTime? _lastResolved = null;
-
- /// <summary>
- /// Gets the IP Addresses, attempting to resolve the name, if there are none.
- /// </summary>
- private IPAddress[] _addresses;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="IPHost"/> class.
- /// </summary>
- /// <param name="name">Host name to assign.</param>
- public IPHost(string name)
- {
- HostName = name ?? throw new ArgumentNullException(nameof(name));
- _addresses = Array.Empty<IPAddress>();
- Resolved = false;
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="IPHost"/> class.
- /// </summary>
- /// <param name="name">Host name to assign.</param>
- /// <param name="address">Address to assign.</param>
- private IPHost(string name, IPAddress address)
- {
- HostName = name ?? throw new ArgumentNullException(nameof(name));
- _addresses = new IPAddress[] { address ?? throw new ArgumentNullException(nameof(address)) };
- Resolved = !address.Equals(IPAddress.None);
- }
-
- /// <summary>
- /// Gets or sets the object's first IP address.
- /// </summary>
- public override IPAddress Address
- {
- get
- {
- return ResolveHost() ? this[0] : IPAddress.None;
- }
-
- set
- {
- // Not implemented, as a host's address is determined by DNS.
- throw new NotImplementedException("The address of a host is determined by DNS.");
- }
- }
-
- /// <summary>
- /// Gets or sets the object's first IP's subnet prefix.
- /// The setter does nothing, but shouldn't raise an exception.
- /// </summary>
- public override byte PrefixLength
- {
- get => (byte)(ResolveHost() ? 128 : 32);
-
- // Not implemented, as a host object can only have a prefix length of 128 (IPv6) or 32 (IPv4) prefix length,
- // which is automatically determined by it's IP type. Anything else is meaningless.
- set => throw new NotImplementedException();
- }
-
- /// <summary>
- /// Gets a value indicating whether the address has a value.
- /// </summary>
- public bool HasAddress => _addresses.Length != 0;
-
- /// <summary>
- /// Gets the host name of this object.
- /// </summary>
- public string HostName { get; }
-
- /// <summary>
- /// Gets a value indicating whether this host has attempted to be resolved.
- /// </summary>
- public bool Resolved { get; private set; }
-
- /// <summary>
- /// Gets or sets the IP Addresses associated with this object.
- /// </summary>
- /// <param name="index">Index of address.</param>
- public IPAddress this[int index]
- {
- get
- {
- ResolveHost();
- return index >= 0 && index < _addresses.Length ? _addresses[index] : IPAddress.None;
- }
- }
-
- /// <summary>
- /// Attempts to parse the host string.
- /// </summary>
- /// <param name="host">Host name to parse.</param>
- /// <param name="hostObj">Object representing the string, if it has successfully been parsed.</param>
- /// <returns><c>true</c> if the parsing is successful, <c>false</c> if not.</returns>
- public static bool TryParse(string host, out IPHost hostObj)
- {
- if (string.IsNullOrWhiteSpace(host))
- {
- hostObj = IPHost.None;
- return false;
- }
-
- // See if it's an IPv6 with port address e.g. [::1] or [::1]:120.
- int i = host.IndexOf(']', StringComparison.Ordinal);
- if (i != -1)
- {
- return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj);
- }
-
- if (IPNetAddress.TryParse(host, out var netAddress))
- {
- // Host name is an ip address, so fake resolve.
- hostObj = new IPHost(host, netAddress.Address);
- return true;
- }
-
- // Is it a host, IPv4/6 with/out port?
- string[] hosts = host.Split(':');
-
- if (hosts.Length <= 2)
- {
- // This is either a hostname: port, or an IP4:port.
- host = hosts[0];
-
- if (string.Equals("localhost", host, StringComparison.OrdinalIgnoreCase))
- {
- hostObj = new IPHost(host);
- return true;
- }
-
- if (IPAddress.TryParse(host, out var netIP))
- {
- // Host name is an ip address, so fake resolve.
- hostObj = new IPHost(host, netIP);
- return true;
- }
- }
- else
- {
- // Invalid host name, as it cannot contain :
- hostObj = new IPHost(string.Empty, IPAddress.None);
- return false;
- }
-
- // Use regular expression as CheckHostName isn't RFC5892 compliant.
- // Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation
- string pattern = @"(?im)^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)$";
-
- if (Regex.IsMatch(host, pattern))
- {
- hostObj = new IPHost(host);
- return true;
- }
-
- hostObj = IPHost.None;
- return false;
- }
-
- /// <summary>
- /// Attempts to parse the host string.
- /// </summary>
- /// <param name="host">Host name to parse.</param>
- /// <returns>Object representing the string, if it has successfully been parsed.</returns>
- public static IPHost Parse(string host)
- {
- if (IPHost.TryParse(host, out IPHost res))
- {
- return res;
- }
-
- throw new InvalidCastException($"Host does not contain a valid value. {host}");
- }
-
- /// <summary>
- /// Attempts to parse the host string, ensuring that it resolves only to a specific IP type.
- /// </summary>
- /// <param name="host">Host name to parse.</param>
- /// <param name="family">Addressfamily filter.</param>
- /// <returns>Object representing the string, if it has successfully been parsed.</returns>
- public static IPHost Parse(string host, AddressFamily family)
- {
- if (IPHost.TryParse(host, out IPHost res))
- {
- if (family == AddressFamily.InterNetwork)
- {
- res.Remove(AddressFamily.InterNetworkV6);
- }
- else
- {
- res.Remove(AddressFamily.InterNetwork);
- }
-
- return res;
- }
-
- throw new InvalidCastException($"Host does not contain a valid value. {host}");
- }
-
- /// <summary>
- /// Returns the Addresses that this item resolved to.
- /// </summary>
- /// <returns>IPAddress Array.</returns>
- public IPAddress[] GetAddresses()
- {
- ResolveHost();
- return _addresses;
- }
-
- /// <inheritdoc/>
- public override bool Contains(IPAddress address)
- {
- if (address is not null && !Address.Equals(IPAddress.None))
- {
- if (address.IsIPv4MappedToIPv6)
- {
- address = address.MapToIPv4();
- }
-
- foreach (var addr in GetAddresses())
- {
- if (address.Equals(addr))
- {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /// <inheritdoc/>
- public override bool Equals(IPObject? other)
- {
- if (other is IPHost otherObj)
- {
- // Do we have the name Hostname?
- if (string.Equals(otherObj.HostName, HostName, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- if (!ResolveHost() || !otherObj.ResolveHost())
- {
- return false;
- }
-
- // Do any of our IP addresses match?
- foreach (IPAddress addr in _addresses)
- {
- foreach (IPAddress otherAddress in otherObj._addresses)
- {
- if (addr.Equals(otherAddress))
- {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- /// <inheritdoc/>
- public override bool IsIP6()
- {
- // Returns true if interfaces are only IP6.
- if (ResolveHost())
- {
- foreach (IPAddress i in _addresses)
- {
- if (i.AddressFamily != AddressFamily.InterNetworkV6)
- {
- return false;
- }
- }
-
- return true;
- }
-
- return false;
- }
-
- /// <inheritdoc/>
- public override string ToString()
- {
- // StringBuilder not optimum here.
- string output = string.Empty;
- if (_addresses.Length > 0)
- {
- bool moreThanOne = _addresses.Length > 1;
- if (moreThanOne)
- {
- output = "[";
- }
-
- foreach (var i in _addresses)
- {
- if (Address.Equals(IPAddress.None) && Address.AddressFamily == AddressFamily.Unspecified)
- {
- output += HostName + ",";
- }
- else if (i.Equals(IPAddress.Any))
- {
- output += "Any IP4 Address,";
- }
- else if (Address.Equals(IPAddress.IPv6Any))
- {
- output += "Any IP6 Address,";
- }
- else if (i.Equals(IPAddress.Broadcast))
- {
- output += "Any Address,";
- }
- else if (i.AddressFamily == AddressFamily.InterNetwork)
- {
- output += $"{i}/32,";
- }
- else
- {
- output += $"{i}/128,";
- }
- }
-
- output = output[..^1];
-
- if (moreThanOne)
- {
- output += "]";
- }
- }
- else
- {
- output = HostName;
- }
-
- return output;
- }
-
- /// <inheritdoc/>
- public override void Remove(AddressFamily family)
- {
- if (ResolveHost())
- {
- _addresses = _addresses.Where(p => p.AddressFamily != family).ToArray();
- }
- }
-
- /// <inheritdoc/>
- public override bool Contains(IPObject address)
- {
- // An IPHost cannot contain another IPObject, it can only be equal.
- return Equals(address);
- }
-
- /// <inheritdoc/>
- protected override IPObject CalculateNetworkAddress()
- {
- var (address, prefixLength) = NetworkAddressOf(this[0], PrefixLength);
- return new IPNetAddress(address, prefixLength);
- }
-
- /// <summary>
- /// Attempt to resolve the ip address of a host.
- /// </summary>
- /// <returns><c>true</c> if any addresses have been resolved, otherwise <c>false</c>.</returns>
- private bool ResolveHost()
- {
- // When was the last time we resolved?
- _lastResolved ??= DateTime.UtcNow;
-
- // If we haven't resolved before, or our timer has run out...
- if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout)))
- {
- _lastResolved = DateTime.UtcNow;
- ResolveHostInternal();
- Resolved = true;
- }
-
- return _addresses.Length > 0;
- }
-
- /// <summary>
- /// Task that looks up a Host name and returns its IP addresses.
- /// </summary>
- private void ResolveHostInternal()
- {
- var hostName = HostName;
- if (string.IsNullOrEmpty(hostName))
- {
- return;
- }
-
- // Resolves the host name - so save a DNS lookup.
- if (string.Equals(hostName, "localhost", StringComparison.OrdinalIgnoreCase))
- {
- _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback };
- return;
- }
-
- if (Uri.CheckHostName(hostName) == UriHostNameType.Dns)
- {
- try
- {
- _addresses = Dns.GetHostEntry(hostName).AddressList;
- }
- catch (SocketException ex)
- {
- // Log and then ignore socket errors, as the result value will just be an empty array.
- Debug.WriteLine("GetHostAddresses failed with {Message}.", ex.Message);
- }
- }
- }
- }
-}
diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs
deleted file mode 100644
index de72d978e..000000000
--- a/MediaBrowser.Common/Net/IPNetAddress.cs
+++ /dev/null
@@ -1,278 +0,0 @@
-using System;
-using System.Net;
-using System.Net.Sockets;
-
-namespace MediaBrowser.Common.Net
-{
- /// <summary>
- /// An object that holds and IP address and subnet mask.
- /// </summary>
- public class IPNetAddress : IPObject
- {
- /// <summary>
- /// Represents an IPNetAddress that has no value.
- /// </summary>
- public static readonly IPNetAddress None = new IPNetAddress(IPAddress.None);
-
- /// <summary>
- /// IPv4 multicast address.
- /// </summary>
- public static readonly IPAddress SSDPMulticastIPv4 = IPAddress.Parse("239.255.255.250");
-
- /// <summary>
- /// IPv6 local link multicast address.
- /// </summary>
- public static readonly IPAddress SSDPMulticastIPv6LinkLocal = IPAddress.Parse("ff02::C");
-
- /// <summary>
- /// IPv6 site local multicast address.
- /// </summary>
- public static readonly IPAddress SSDPMulticastIPv6SiteLocal = IPAddress.Parse("ff05::C");
-
- /// <summary>
- /// IP4Loopback address host.
- /// </summary>
- public static readonly IPNetAddress IP4Loopback = IPNetAddress.Parse("127.0.0.1/8");
-
- /// <summary>
- /// IP6Loopback address host.
- /// </summary>
- public static readonly IPNetAddress IP6Loopback = new IPNetAddress(IPAddress.IPv6Loopback);
-
- /// <summary>
- /// Object's IP address.
- /// </summary>
- private IPAddress _address;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="IPNetAddress"/> class.
- /// </summary>
- /// <param name="address">Address to assign.</param>
- public IPNetAddress(IPAddress address)
- {
- _address = address ?? throw new ArgumentNullException(nameof(address));
- PrefixLength = (byte)(address.AddressFamily == AddressFamily.InterNetwork ? 32 : 128);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="IPNetAddress"/> class.
- /// </summary>
- /// <param name="address">IP Address.</param>
- /// <param name="prefixLength">Mask as a CIDR.</param>
- public IPNetAddress(IPAddress address, byte prefixLength)
- {
- if (address?.IsIPv4MappedToIPv6 ?? throw new ArgumentNullException(nameof(address)))
- {
- _address = address.MapToIPv4();
- }
- else
- {
- _address = address;
- }
-
- PrefixLength = prefixLength;
- }
-
- /// <summary>
- /// Gets or sets the object's IP address.
- /// </summary>
- public override IPAddress Address
- {
- get
- {
- return _address;
- }
-
- set
- {
- _address = value ?? IPAddress.None;
- }
- }
-
- /// <inheritdoc/>
- public override byte PrefixLength { get; set; }
-
- /// <summary>
- /// Try to parse the address and subnet strings into an IPNetAddress object.
- /// </summary>
- /// <param name="addr">IP address to parse. Can be CIDR or X.X.X.X notation.</param>
- /// <param name="ip">Resultant object.</param>
- /// <returns>True if the values parsed successfully. False if not, resulting in the IP being null.</returns>
- public static bool TryParse(string addr, out IPNetAddress ip)
- {
- if (!string.IsNullOrEmpty(addr))
- {
- addr = addr.Trim();
-
- // Try to parse it as is.
- if (IPAddress.TryParse(addr, out IPAddress? res))
- {
- ip = new IPNetAddress(res);
- return true;
- }
-
- // Is it a network?
- string[] tokens = addr.Split('/');
-
- if (tokens.Length == 2)
- {
- tokens[0] = tokens[0].TrimEnd();
- tokens[1] = tokens[1].TrimStart();
-
- if (IPAddress.TryParse(tokens[0], out res))
- {
- // Is the subnet part a cidr?
- if (byte.TryParse(tokens[1], out byte cidr))
- {
- ip = new IPNetAddress(res, cidr);
- return true;
- }
-
- // Is the subnet in x.y.a.b form?
- if (IPAddress.TryParse(tokens[1], out IPAddress? mask))
- {
- ip = new IPNetAddress(res, MaskToCidr(mask));
- return true;
- }
- }
- }
- }
-
- ip = None;
- return false;
- }
-
- /// <summary>
- /// Parses the string provided, throwing an exception if it is badly formed.
- /// </summary>
- /// <param name="addr">String to parse.</param>
- /// <returns>IPNetAddress object.</returns>
- public static IPNetAddress Parse(string addr)
- {
- if (TryParse(addr, out IPNetAddress o))
- {
- return o;
- }
-
- throw new ArgumentException("Unable to recognise object :" + addr);
- }
-
- /// <inheritdoc/>
- public override bool Contains(IPAddress address)
- {
- ArgumentNullException.ThrowIfNull(address);
-
- if (address.IsIPv4MappedToIPv6)
- {
- address = address.MapToIPv4();
- }
-
- if (address.AddressFamily != AddressFamily)
- {
- return false;
- }
-
- var (altAddress, altPrefix) = NetworkAddressOf(address, PrefixLength);
- return NetworkAddress.Address.Equals(altAddress) && NetworkAddress.PrefixLength >= altPrefix;
- }
-
- /// <inheritdoc/>
- public override bool Contains(IPObject address)
- {
- if (address is IPHost addressObj && addressObj.HasAddress)
- {
- foreach (IPAddress addr in addressObj.GetAddresses())
- {
- if (Contains(addr))
- {
- return true;
- }
- }
- }
- else if (address is IPNetAddress netaddrObj)
- {
- // Have the same network address, but different subnets?
- if (NetworkAddress.Address.Equals(netaddrObj.NetworkAddress.Address))
- {
- return NetworkAddress.PrefixLength <= netaddrObj.PrefixLength;
- }
-
- var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength).Address;
- return NetworkAddress.Address.Equals(altAddress);
- }
-
- return false;
- }
-
- /// <inheritdoc/>
- public override bool Equals(IPObject? other)
- {
- if (other is IPNetAddress otherObj && !Address.Equals(IPAddress.None) && !otherObj.Address.Equals(IPAddress.None))
- {
- return Address.Equals(otherObj.Address) &&
- PrefixLength == otherObj.PrefixLength;
- }
-
- return false;
- }
-
- /// <inheritdoc/>
- public override bool Equals(IPAddress ip)
- {
- if (ip is not null && !ip.Equals(IPAddress.None) && !Address.Equals(IPAddress.None))
- {
- return ip.Equals(Address);
- }
-
- return false;
- }
-
- /// <inheritdoc/>
- public override string ToString()
- {
- return ToString(false);
- }
-
- /// <summary>
- /// Returns a textual representation of this object.
- /// </summary>
- /// <param name="shortVersion">Set to true, if the subnet is to be excluded as part of the address.</param>
- /// <returns>String representation of this object.</returns>
- public string ToString(bool shortVersion)
- {
- if (!Address.Equals(IPAddress.None))
- {
- if (Address.Equals(IPAddress.Any))
- {
- return "Any IP4 Address";
- }
-
- if (Address.Equals(IPAddress.IPv6Any))
- {
- return "Any IP6 Address";
- }
-
- if (Address.Equals(IPAddress.Broadcast))
- {
- return "Any Address";
- }
-
- if (shortVersion)
- {
- return Address.ToString();
- }
-
- return $"{Address}/{PrefixLength}";
- }
-
- return string.Empty;
- }
-
- /// <inheritdoc/>
- protected override IPObject CalculateNetworkAddress()
- {
- var (address, prefixLength) = NetworkAddressOf(_address, PrefixLength);
- return new IPNetAddress(address, prefixLength);
- }
- }
-}
diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs
deleted file mode 100644
index 93655234b..000000000
--- a/MediaBrowser.Common/Net/IPObject.cs
+++ /dev/null
@@ -1,355 +0,0 @@
-using System;
-using System.Net;
-using System.Net.Sockets;
-
-namespace MediaBrowser.Common.Net
-{
- /// <summary>
- /// Base network object class.
- /// </summary>
- public abstract class IPObject : IEquatable<IPObject>
- {
- /// <summary>
- /// The network address of this object.
- /// </summary>
- private IPObject? _networkAddress;
-
- /// <summary>
- /// Gets or sets a user defined value that is associated with this object.
- /// </summary>
- public int Tag { get; set; }
-
- /// <summary>
- /// Gets or sets the object's IP address.
- /// </summary>
- public abstract IPAddress Address { get; set; }
-
- /// <summary>
- /// Gets the object's network address.
- /// </summary>
- public IPObject NetworkAddress => _networkAddress ??= CalculateNetworkAddress();
-
- /// <summary>
- /// Gets or sets the object's IP address.
- /// </summary>
- public abstract byte PrefixLength { get; set; }
-
- /// <summary>
- /// Gets the AddressFamily of this object.
- /// </summary>
- public AddressFamily AddressFamily
- {
- get
- {
- // Keep terms separate as Address performs other functions in inherited objects.
- IPAddress address = Address;
- return address.Equals(IPAddress.None) ? AddressFamily.Unspecified : address.AddressFamily;
- }
- }
-
- /// <summary>
- /// Returns the network address of an object.
- /// </summary>
- /// <param name="address">IP Address to convert.</param>
- /// <param name="prefixLength">Subnet prefix.</param>
- /// <returns>IPAddress.</returns>
- public static (IPAddress Address, byte PrefixLength) NetworkAddressOf(IPAddress address, byte prefixLength)
- {
- ArgumentNullException.ThrowIfNull(address);
-
- if (address.IsIPv4MappedToIPv6)
- {
- address = address.MapToIPv4();
- }
-
- if (IPAddress.IsLoopback(address))
- {
- return (address, prefixLength);
- }
-
- // An ip address is just a list of bytes, each one representing a segment on the network.
- // This separates the IP address into octets and calculates how many octets will need to be altered or set to zero dependant upon the
- // prefix length value. eg. /16 on a 4 octet ip4 address (192.168.2.240) will result in the 2 and the 240 being zeroed out.
- // Where there is not an exact boundary (eg /23), mod is used to calculate how many bits of this value are to be kept.
-
- // GetAddressBytes
- Span<byte> addressBytes = stackalloc byte[address.AddressFamily == AddressFamily.InterNetwork ? 4 : 16];
- address.TryWriteBytes(addressBytes, out _);
-
- int div = prefixLength / 8;
- int mod = prefixLength % 8;
- if (mod != 0)
- {
- // Prefix length is counted right to left, so subtract 8 so we know how many bits to clear.
- mod = 8 - mod;
-
- // Shift out the bits from the octet that we don't want, by moving right then back left.
- addressBytes[div] = (byte)((int)addressBytes[div] >> mod << mod);
- // Move on the next byte.
- div++;
- }
-
- // Blank out the remaining octets from mod + 1 to the end of the byte array. (192.168.2.240/16 becomes 192.168.0.0)
- for (int octet = div; octet < addressBytes.Length; octet++)
- {
- addressBytes[octet] = 0;
- }
-
- // Return the network address for the prefix.
- return (new IPAddress(addressBytes), prefixLength);
- }
-
- /// <summary>
- /// Tests to see if the ip address is an IP6 address.
- /// </summary>
- /// <param name="address">Value to test.</param>
- /// <returns>True if it is.</returns>
- public static bool IsIP6(IPAddress address)
- {
- ArgumentNullException.ThrowIfNull(address);
-
- if (address.IsIPv4MappedToIPv6)
- {
- address = address.MapToIPv4();
- }
-
- return !address.Equals(IPAddress.None) && (address.AddressFamily == AddressFamily.InterNetworkV6);
- }
-
- /// <summary>
- /// Tests to see if the address in the private address range.
- /// </summary>
- /// <param name="address">Object to test.</param>
- /// <returns>True if it contains a private address.</returns>
- public static bool IsPrivateAddressRange(IPAddress address)
- {
- ArgumentNullException.ThrowIfNull(address);
-
- if (!address.Equals(IPAddress.None))
- {
- if (address.IsIPv4MappedToIPv6)
- {
- address = address.MapToIPv4();
- }
-
- if (address.AddressFamily == AddressFamily.InterNetwork)
- {
- // GetAddressBytes
- Span<byte> octet = stackalloc byte[4];
- address.TryWriteBytes(octet, out _);
-
- return (octet[0] == 10)
- || (octet[0] == 172 && octet[1] >= 16 && octet[1] <= 31) // RFC1918
- || (octet[0] == 192 && octet[1] == 168) // RFC1918
- || (octet[0] == 127); // RFC1122
- }
- else
- {
- // GetAddressBytes
- Span<byte> octet = stackalloc byte[16];
- address.TryWriteBytes(octet, out _);
-
- uint word = (uint)(octet[0] << 8) + octet[1];
-
- return (word >= 0xfe80 && word <= 0xfebf) // fe80::/10 :Local link.
- || (word >= 0xfc00 && word <= 0xfdff); // fc00::/7 :Unique local address.
- }
- }
-
- return false;
- }
-
- /// <summary>
- /// Returns true if the IPAddress contains an IP6 Local link address.
- /// </summary>
- /// <param name="address">IPAddress object to check.</param>
- /// <returns>True if it is a local link address.</returns>
- /// <remarks>
- /// See https://stackoverflow.com/questions/6459928/explain-the-instance-properties-of-system-net-ipaddress
- /// it appears that the IPAddress.IsIPv6LinkLocal is out of date.
- /// </remarks>
- public static bool IsIPv6LinkLocal(IPAddress address)
- {
- ArgumentNullException.ThrowIfNull(address);
-
- if (address.IsIPv4MappedToIPv6)
- {
- address = address.MapToIPv4();
- }
-
- if (address.AddressFamily != AddressFamily.InterNetworkV6)
- {
- return false;
- }
-
- // GetAddressBytes
- Span<byte> octet = stackalloc byte[16];
- address.TryWriteBytes(octet, out _);
- uint word = (uint)(octet[0] << 8) + octet[1];
-
- return word >= 0xfe80 && word <= 0xfebf; // fe80::/10 :Local link.
- }
-
- /// <summary>
- /// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only.
- /// </summary>
- /// <param name="cidr">Subnet mask in CIDR notation.</param>
- /// <param name="family">IPv4 or IPv6 family.</param>
- /// <returns>String value of the subnet mask in dotted decimal notation.</returns>
- public static IPAddress CidrToMask(byte cidr, AddressFamily family)
- {
- uint addr = 0xFFFFFFFF << (family == AddressFamily.InterNetwork ? 32 : 128 - cidr);
- addr = ((addr & 0xff000000) >> 24)
- | ((addr & 0x00ff0000) >> 8)
- | ((addr & 0x0000ff00) << 8)
- | ((addr & 0x000000ff) << 24);
- return new IPAddress(addr);
- }
-
- /// <summary>
- /// Convert a mask to a CIDR. IPv4 only.
- /// https://stackoverflow.com/questions/36954345/get-cidr-from-netmask.
- /// </summary>
- /// <param name="mask">Subnet mask.</param>
- /// <returns>Byte CIDR representing the mask.</returns>
- public static byte MaskToCidr(IPAddress mask)
- {
- ArgumentNullException.ThrowIfNull(mask);
-
- byte cidrnet = 0;
- if (!mask.Equals(IPAddress.Any))
- {
- // GetAddressBytes
- Span<byte> bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? 4 : 16];
- mask.TryWriteBytes(bytes, out _);
-
- var zeroed = false;
- for (var i = 0; i < bytes.Length; i++)
- {
- for (int v = bytes[i]; (v & 0xFF) != 0; v <<= 1)
- {
- if (zeroed)
- {
- // Invalid netmask.
- return (byte)~cidrnet;
- }
-
- if ((v & 0x80) == 0)
- {
- zeroed = true;
- }
- else
- {
- cidrnet++;
- }
- }
- }
- }
-
- return cidrnet;
- }
-
- /// <summary>
- /// Tests to see if this object is a Loopback address.
- /// </summary>
- /// <returns>True if it is.</returns>
- public virtual bool IsLoopback()
- {
- return IPAddress.IsLoopback(Address);
- }
-
- /// <summary>
- /// Removes all addresses of a specific type from this object.
- /// </summary>
- /// <param name="family">Type of address to remove.</param>
- public virtual void Remove(AddressFamily family)
- {
- // This method only performs a function in the IPHost implementation of IPObject.
- }
-
- /// <summary>
- /// Tests to see if this object is an IPv6 address.
- /// </summary>
- /// <returns>True if it is.</returns>
- public virtual bool IsIP6()
- {
- return IsIP6(Address);
- }
-
- /// <summary>
- /// Returns true if this IP address is in the RFC private address range.
- /// </summary>
- /// <returns>True this object has a private address.</returns>
- public virtual bool IsPrivateAddressRange()
- {
- return IsPrivateAddressRange(Address);
- }
-
- /// <summary>
- /// Compares this to the object passed as a parameter.
- /// </summary>
- /// <param name="ip">Object to compare to.</param>
- /// <returns>Equality result.</returns>
- public virtual bool Equals(IPAddress ip)
- {
- if (ip is not null)
- {
- if (ip.IsIPv4MappedToIPv6)
- {
- ip = ip.MapToIPv4();
- }
-
- return !Address.Equals(IPAddress.None) && Address.Equals(ip);
- }
-
- return false;
- }
-
- /// <summary>
- /// Compares this to the object passed as a parameter.
- /// </summary>
- /// <param name="other">Object to compare to.</param>
- /// <returns>Equality result.</returns>
- public virtual bool Equals(IPObject? other)
- {
- if (other is not null)
- {
- return !Address.Equals(IPAddress.None) && Address.Equals(other.Address);
- }
-
- return false;
- }
-
- /// <summary>
- /// Compares the address in this object and the address in the object passed as a parameter.
- /// </summary>
- /// <param name="address">Object's IP address to compare to.</param>
- /// <returns>Comparison result.</returns>
- public abstract bool Contains(IPObject address);
-
- /// <summary>
- /// Compares the address in this object and the address in the object passed as a parameter.
- /// </summary>
- /// <param name="address">Object's IP address to compare to.</param>
- /// <returns>Comparison result.</returns>
- public abstract bool Contains(IPAddress address);
-
- /// <inheritdoc/>
- public override int GetHashCode()
- {
- return Address.GetHashCode();
- }
-
- /// <inheritdoc/>
- public override bool Equals(object? obj)
- {
- return Equals(obj as IPObject);
- }
-
- /// <summary>
- /// Calculates the network address of this object.
- /// </summary>
- /// <returns>Returns the network address of this object.</returns>
- protected abstract IPObject CalculateNetworkAddress();
- }
-}
diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs
index 5e5e5b81b..47475b3da 100644
--- a/MediaBrowser.Common/Net/NetworkExtensions.cs
+++ b/MediaBrowser.Common/Net/NetworkExtensions.cs
@@ -1,6 +1,12 @@
using System;
-using System.Collections.ObjectModel;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using System.Net;
+using System.Net.Sockets;
+using System.Text.RegularExpressions;
+using Jellyfin.Extensions;
+using Microsoft.AspNetCore.HttpOverrides;
namespace MediaBrowser.Common.Net
{
@@ -9,240 +15,336 @@ namespace MediaBrowser.Common.Net
/// </summary>
public static class NetworkExtensions
{
+ // Use regular expression as CheckHostName isn't RFC5892 compliant.
+ // Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation
+ private static readonly Regex _fqdnRegex = new Regex(@"(?im)^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)(:(\d){1,5}){0,1}$");
+
/// <summary>
- /// Add an address to the collection.
+ /// Returns true if the IPAddress contains an IP6 Local link address.
/// </summary>
- /// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
- /// <param name="ip">Item to add.</param>
- public static void AddItem(this Collection<IPObject> source, IPAddress ip)
+ /// <param name="address">IPAddress object to check.</param>
+ /// <returns>True if it is a local link address.</returns>
+ /// <remarks>
+ /// See https://stackoverflow.com/questions/6459928/explain-the-instance-properties-of-system-net-ipaddress
+ /// it appears that the IPAddress.IsIPv6LinkLocal is out of date.
+ /// </remarks>
+ public static bool IsIPv6LinkLocal(IPAddress address)
{
- if (!source.ContainsAddress(ip))
+ ArgumentNullException.ThrowIfNull(address);
+
+ if (address.IsIPv4MappedToIPv6)
+ {
+ address = address.MapToIPv4();
+ }
+
+ if (address.AddressFamily != AddressFamily.InterNetworkV6)
{
- source.Add(new IPNetAddress(ip, 32));
+ return false;
}
+
+ // GetAddressBytes
+ Span<byte> octet = stackalloc byte[16];
+ address.TryWriteBytes(octet, out _);
+ uint word = (uint)(octet[0] << 8) + octet[1];
+
+ return word >= 0xfe80 && word <= 0xfebf; // fe80::/10 :Local link.
}
/// <summary>
- /// Adds a network to the collection.
+ /// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only.
/// </summary>
- /// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
- /// <param name="item">Item to add.</param>
- /// <param name="itemsAreNetworks">If <c>true</c> the values are treated as subnets.
- /// If <b>false</b> items are addresses.</param>
- public static void AddItem(this Collection<IPObject> source, IPObject item, bool itemsAreNetworks = true)
+ /// <param name="cidr">Subnet mask in CIDR notation.</param>
+ /// <param name="family">IPv4 or IPv6 family.</param>
+ /// <returns>String value of the subnet mask in dotted decimal notation.</returns>
+ public static IPAddress CidrToMask(byte cidr, AddressFamily family)
{
- if (!source.ContainsAddress(item) || !itemsAreNetworks)
- {
- source.Add(item);
- }
+ uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? 32 : 128) - cidr);
+ addr = ((addr & 0xff000000) >> 24)
+ | ((addr & 0x00ff0000) >> 8)
+ | ((addr & 0x0000ff00) << 8)
+ | ((addr & 0x000000ff) << 24);
+ return new IPAddress(addr);
}
/// <summary>
- /// Converts this object to a string.
+ /// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only.
/// </summary>
- /// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
- /// <returns>Returns a string representation of this object.</returns>
- public static string AsString(this Collection<IPObject> source)
+ /// <param name="cidr">Subnet mask in CIDR notation.</param>
+ /// <param name="family">IPv4 or IPv6 family.</param>
+ /// <returns>String value of the subnet mask in dotted decimal notation.</returns>
+ public static IPAddress CidrToMask(int cidr, AddressFamily family)
{
- return $"[{string.Join(',', source)}]";
+ uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? 32 : 128) - cidr);
+ addr = ((addr & 0xff000000) >> 24)
+ | ((addr & 0x00ff0000) >> 8)
+ | ((addr & 0x0000ff00) << 8)
+ | ((addr & 0x000000ff) << 24);
+ return new IPAddress(addr);
}
/// <summary>
- /// Returns true if the collection contains an item with the ip address,
- /// or the ip address falls within any of the collection's network ranges.
+ /// Convert a subnet mask to a CIDR. IPv4 only.
+ /// https://stackoverflow.com/questions/36954345/get-cidr-from-netmask.
/// </summary>
- /// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
- /// <param name="item">The item to look for.</param>
- /// <returns>True if the collection contains the item.</returns>
- public static bool ContainsAddress(this Collection<IPObject> source, IPAddress item)
+ /// <param name="mask">Subnet mask.</param>
+ /// <returns>Byte CIDR representing the mask.</returns>
+ public static byte MaskToCidr(IPAddress mask)
{
- if (source.Count == 0)
+ ArgumentNullException.ThrowIfNull(mask);
+
+ byte cidrnet = 0;
+ if (mask.Equals(IPAddress.Any))
{
- return false;
+ return cidrnet;
}
- ArgumentNullException.ThrowIfNull(item);
-
- if (item.IsIPv4MappedToIPv6)
+ // GetAddressBytes
+ Span<byte> bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? 4 : 16];
+ if (!mask.TryWriteBytes(bytes, out var bytesWritten))
{
- item = item.MapToIPv4();
+ Console.WriteLine("Unable to write address bytes, only {Bytes} bytes written.", bytesWritten);
}
- foreach (var i in source)
+ var zeroed = false;
+ for (var i = 0; i < bytes.Length; i++)
{
- if (i.Contains(item))
+ for (int v = bytes[i]; (v & 0xFF) != 0; v <<= 1)
{
- return true;
+ if (zeroed)
+ {
+ // Invalid netmask.
+ return (byte)~cidrnet;
+ }
+
+ if ((v & 0x80) == 0)
+ {
+ zeroed = true;
+ }
+ else
+ {
+ cidrnet++;
+ }
}
}
- return false;
+ return cidrnet;
}
/// <summary>
- /// Returns true if the collection contains an item with the ip address,
- /// or the ip address falls within any of the collection's network ranges.
+ /// Converts an IPAddress into a string.
+ /// IPv6 addresses are returned in [ ], with their scope removed.
/// </summary>
- /// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
- /// <param name="item">The item to look for.</param>
- /// <returns>True if the collection contains the item.</returns>
- public static bool ContainsAddress(this Collection<IPObject> source, IPObject item)
+ /// <param name="address">Address to convert.</param>
+ /// <returns>URI safe conversion of the address.</returns>
+ public static string FormatIPString(IPAddress? address)
{
- if (source.Count == 0)
+ if (address is null)
{
- return false;
+ return string.Empty;
}
- ArgumentNullException.ThrowIfNull(item);
-
- foreach (var i in source)
+ var str = address.ToString();
+ if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
- if (i.Contains(item))
+ int i = str.IndexOf('%', StringComparison.Ordinal);
+ if (i != -1)
{
- return true;
+ str = str.Substring(0, i);
}
+
+ return $"[{str}]";
}
- return false;
+ return str;
}
/// <summary>
- /// Compares two Collection{IPObject} objects. The order is ignored.
+ /// Try parsing an array of strings into <see cref="IPNetwork"/> objects, respecting exclusions.
+ /// Elements without a subnet mask will be represented as <see cref="IPNetwork"/> with a single IP.
/// </summary>
- /// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
- /// <param name="dest">Item to compare to.</param>
- /// <returns>True if both are equal.</returns>
- public static bool Compare(this Collection<IPObject> source, Collection<IPObject> dest)
+ /// <param name="values">Input string array to be parsed.</param>
+ /// <param name="result">Collection of <see cref="IPNetwork"/>.</param>
+ /// <param name="negated">Boolean signaling if negated or not negated values should be parsed.</param>
+ /// <returns><c>True</c> if parsing was successful.</returns>
+ public static bool TryParseToSubnets(string[] values, [NotNullWhen(true)] out IReadOnlyList<IPNetwork>? result, bool negated = false)
{
- if (dest is null || source.Count != dest.Count)
+ if (values is null || values.Length == 0)
{
+ result = null;
return false;
}
- foreach (var sourceItem in source)
+ var tmpResult = new List<IPNetwork>();
+ for (int a = 0; a < values.Length; a++)
{
- bool found = false;
- foreach (var destItem in dest)
- {
- if (sourceItem.Equals(destItem))
- {
- found = true;
- break;
- }
- }
-
- if (!found)
+ if (TryParseToSubnet(values[a], out var innerResult, negated))
{
- return false;
+ tmpResult.Add(innerResult);
}
}
- return true;
+ result = tmpResult;
+ return tmpResult.Count > 0;
}
/// <summary>
- /// Returns a collection containing the subnets of this collection given.
+ /// Try parsing a string into an <see cref="IPNetwork"/>, respecting exclusions.
+ /// Inputs without a subnet mask will be represented as <see cref="IPNetwork"/> with a single IP.
/// </summary>
- /// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
- /// <returns>Collection{IPObject} object containing the subnets.</returns>
- public static Collection<IPObject> AsNetworks(this Collection<IPObject> source)
+ /// <param name="value">Input string to be parsed.</param>
+ /// <param name="result">An <see cref="IPNetwork"/>.</param>
+ /// <param name="negated">Boolean signaling if negated or not negated values should be parsed.</param>
+ /// <returns><c>True</c> if parsing was successful.</returns>
+ public static bool TryParseToSubnet(ReadOnlySpan<char> value, [NotNullWhen(true)] out IPNetwork? result, bool negated = false)
{
- ArgumentNullException.ThrowIfNull(source);
-
- Collection<IPObject> res = new Collection<IPObject>();
-
- foreach (IPObject i in source)
+ var splitString = value.Trim().Split('/');
+ if (splitString.MoveNext())
{
- if (i is IPNetAddress nw)
+ var ipBlock = splitString.Current;
+ var address = IPAddress.None;
+ if (negated && ipBlock.StartsWith("!") && IPAddress.TryParse(ipBlock[1..], out var tmpAddress))
+ {
+ address = tmpAddress;
+ }
+ else if (!negated && IPAddress.TryParse(ipBlock, out tmpAddress))
{
- // Add the subnet calculated from the interface address/mask.
- var na = nw.NetworkAddress;
- na.Tag = i.Tag;
- res.AddItem(na);
+ address = tmpAddress;
}
- else if (i is IPHost ipHost)
+
+ if (address != IPAddress.None)
{
- // Flatten out IPHost and add all its ip addresses.
- foreach (var addr in ipHost.GetAddresses())
+ if (splitString.MoveNext())
{
- IPNetAddress host = new IPNetAddress(addr)
+ var subnetBlock = splitString.Current;
+ if (int.TryParse(subnetBlock, out var netmask))
{
- Tag = i.Tag
- };
-
- res.AddItem(host);
+ result = new IPNetwork(address, netmask);
+ return true;
+ }
+ else if (IPAddress.TryParse(subnetBlock, out var netmaskAddress))
+ {
+ result = new IPNetwork(address, NetworkExtensions.MaskToCidr(netmaskAddress));
+ return true;
+ }
+ }
+ else if (address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ result = new IPNetwork(address, 32);
+ return true;
+ }
+ else if (address.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ result = new IPNetwork(address, 128);
+ return true;
}
}
}
- return res;
+ result = null;
+ return false;
}
/// <summary>
- /// Excludes all the items from this list that are found in excludeList.
+ /// Attempts to parse a host span.
/// </summary>
- /// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
- /// <param name="excludeList">Items to exclude.</param>
- /// <param name="isNetwork">Collection is a network collection.</param>
- /// <returns>A new collection, with the items excluded.</returns>
- public static Collection<IPObject> Exclude(this Collection<IPObject> source, Collection<IPObject> excludeList, bool isNetwork)
+ /// <param name="host">Host name to parse.</param>
+ /// <param name="addresses">Object representing the span, if it has successfully been parsed.</param>
+ /// <param name="isIPv4Enabled"><c>true</c> if IPv4 is enabled.</param>
+ /// <param name="isIPv6Enabled"><c>true</c> if IPv6 is enabled.</param>
+ /// <returns><c>true</c> if the parsing is successful, <c>false</c> if not.</returns>
+ public static bool TryParseHost(ReadOnlySpan<char> host, [NotNullWhen(true)] out IPAddress[]? addresses, bool isIPv4Enabled = true, bool isIPv6Enabled = false)
{
- if (source.Count == 0 || excludeList is null)
+ if (host.IsEmpty)
{
- return new Collection<IPObject>(source);
+ addresses = null;
+ return false;
}
- Collection<IPObject> results = new Collection<IPObject>();
+ host = host.Trim();
+
+ // See if it's an IPv6 with port address e.g. [::1] or [::1]:120.
+ if (host[0] == '[')
+ {
+ int i = host.IndexOf("]", StringComparison.Ordinal);
+ if (i != -1)
+ {
+ return TryParseHost(host[1..(i - 1)], out addresses);
+ }
- bool found;
- foreach (var outer in source)
+ addresses = Array.Empty<IPAddress>();
+ return false;
+ }
+
+ var hosts = new List<string>();
+ foreach (var splitSpan in host.Split(':'))
{
- found = false;
+ hosts.Add(splitSpan.ToString());
+ }
- foreach (var inner in excludeList)
+ if (hosts.Count <= 2)
+ {
+ // Is hostname or hostname:port
+ if (_fqdnRegex.IsMatch(hosts[0]))
{
- if (outer.Equals(inner))
+ try
+ {
+ addresses = Dns.GetHostAddresses(hosts[0]);
+ return true;
+ }
+ catch (SocketException)
{
- found = true;
- break;
+ // Log and then ignore socket errors, as the result value will just be an empty array.
+ Console.WriteLine("GetHostAddresses failed.");
}
}
- if (!found)
+ // Is an IP4 or IP4:port
+ if (IPAddress.TryParse(hosts[0].AsSpan().LeftPart('/'), out var address))
+ {
+ if (((address.AddressFamily == AddressFamily.InterNetwork) && (!isIPv4Enabled && isIPv6Enabled))
+ || ((address.AddressFamily == AddressFamily.InterNetworkV6) && (isIPv4Enabled && !isIPv6Enabled)))
+ {
+ addresses = Array.Empty<IPAddress>();
+ return false;
+ }
+
+ addresses = new[] { address };
+
+ // Host name is an ip4 address, so fake resolve.
+ return true;
+ }
+ }
+ else if (hosts.Count > 0 && hosts.Count <= 9) // 8 octets + port
+ {
+ if (IPAddress.TryParse(host.LeftPart('/'), out var address))
{
- results.AddItem(outer, isNetwork);
+ addresses = new[] { address };
+ return true;
}
}
- return results;
+ addresses = Array.Empty<IPAddress>();
+ return false;
}
/// <summary>
- /// Returns all items that co-exist in this object and target.
+ /// Gets the broadcast address for a <see cref="IPNetwork"/>.
/// </summary>
- /// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
- /// <param name="target">Collection to compare with.</param>
- /// <returns>A collection containing all the matches.</returns>
- public static Collection<IPObject> ThatAreContainedInNetworks(this Collection<IPObject> source, Collection<IPObject> target)
+ /// <param name="network">The <see cref="IPNetwork"/>.</param>
+ /// <returns>The broadcast address.</returns>
+ public static IPAddress GetBroadcastAddress(IPNetwork network)
{
- if (source.Count == 0)
+ var addressBytes = network.Prefix.GetAddressBytes();
+ if (BitConverter.IsLittleEndian)
{
- return new Collection<IPObject>();
+ addressBytes.Reverse();
}
- ArgumentNullException.ThrowIfNull(target);
-
- Collection<IPObject> nc = new Collection<IPObject>();
-
- foreach (IPObject i in source)
- {
- if (target.ContainsAddress(i))
- {
- nc.AddItem(i);
- }
- }
+ uint iPAddress = BitConverter.ToUInt32(addressBytes, 0);
+ uint ipMaskV4 = BitConverter.ToUInt32(CidrToMask(network.PrefixLength, AddressFamily.InterNetwork).GetAddressBytes(), 0);
+ uint broadCastIPAddress = iPAddress | ~ipMaskV4;
- return nc;
+ return new IPAddress(BitConverter.GetBytes(broadCastIPAddress));
}
}
}