#nullable enable
using System;
using System.Net;
using System.Net.Sockets;
namespace MediaBrowser.Common.Net
{
///
/// Base network object class.
///
public abstract class IPObject : IEquatable
{
///
/// IPv6 Loopback address.
///
protected static readonly byte[] Ipv6Loopback = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
///
/// IPv4 Loopback address.
///
protected static readonly byte[] Ipv4Loopback = { 127, 0, 0, 1 };
///
/// The network address of this object.
///
private IPObject? _networkAddress;
///
/// Gets or sets the user defined functions that need storage in this object.
///
public int Tag { get; set; }
///
/// Gets or sets the object's IP address.
///
public abstract IPAddress Address { get; set; }
///
/// Gets the object's network address.
///
public IPObject NetworkAddress
{
get
{
if (_networkAddress == null)
{
_networkAddress = CalculateNetworkAddress();
}
return _networkAddress;
}
}
///
/// Gets or sets the object's IP address.
///
public abstract byte PrefixLength { get; set; }
///
/// Gets the AddressFamily of this object.
///
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;
}
}
///
/// Returns the network address of an object.
///
/// IP Address to convert.
/// Subnet prefix.
/// IPAddress.
public static (IPAddress Address, byte PrefixLength) NetworkAddressOf(IPAddress address, byte prefixLength)
{
if (address == null)
{
throw new ArgumentNullException(nameof(address));
}
if (address.IsIPv4MappedToIPv6)
{
address = address.MapToIPv4();
}
if (IsLoopback(address))
{
return (Address: address, PrefixLength: prefixLength);
}
byte[] addressBytes = address.GetAddressBytes();
int div = prefixLength / 8;
int mod = prefixLength % 8;
if (mod != 0)
{
mod = 8 - mod;
addressBytes[div] = (byte)((int)addressBytes[div] >> mod << mod);
div++;
}
for (int octet = div; octet < addressBytes.Length; octet++)
{
addressBytes[octet] = 0;
}
return (Address: new IPAddress(addressBytes), PrefixLength: prefixLength);
}
///
/// Tests to see if the ip address is a Loopback address.
///
/// Value to test.
/// True if it is.
public static bool IsLoopback(IPAddress address)
{
if (address == null)
{
throw new ArgumentNullException(nameof(address));
}
if (!address.Equals(IPAddress.None))
{
if (address.IsIPv4MappedToIPv6)
{
address = address.MapToIPv4();
}
return address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback);
}
return false;
}
///
/// Tests to see if the ip address is an IP6 address.
///
/// Value to test.
/// True if it is.
public static bool IsIP6(IPAddress address)
{
if (address == null)
{
throw new ArgumentNullException(nameof(address));
}
if (address.IsIPv4MappedToIPv6)
{
address = address.MapToIPv4();
}
return !address.Equals(IPAddress.None) && (address.AddressFamily == AddressFamily.InterNetworkV6);
}
///
/// Tests to see if the address in the private address range.
///
/// Object to test.
/// True if it contains a private address.
public static bool IsPrivateAddressRange(IPAddress address)
{
if (address == null)
{
throw new ArgumentNullException(nameof(address));
}
if (!address.Equals(IPAddress.None))
{
if (address.AddressFamily == AddressFamily.InterNetwork)
{
if (address.IsIPv4MappedToIPv6)
{
address = address.MapToIPv4();
}
byte[] octet = address.GetAddressBytes();
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
{
byte[] octet = address.GetAddressBytes();
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;
}
///
/// Returns true if the IPAddress contains an IP6 Local link address.
///
/// IPAddress object to check.
/// True if it is a local link address.
/// See https://stackoverflow.com/questions/6459928/explain-the-instance-properties-of-system-net-ipaddress
/// it appears that the IPAddress.IsIPv6LinkLocal is out of date.
///
public static bool IsIPv6LinkLocal(IPAddress address)
{
if (address == null)
{
throw new ArgumentNullException(nameof(address));
}
if (address.IsIPv4MappedToIPv6)
{
address = address.MapToIPv4();
}
if (address.AddressFamily != AddressFamily.InterNetworkV6)
{
return false;
}
byte[] octet = address.GetAddressBytes();
uint word = (uint)(octet[0] << 8) + octet[1];
return word >= 0xfe80 && word <= 0xfebf; // fe80::/10 :Local link.
}
///
/// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only.
///
/// Subnet mask in CIDR notation.
/// IPv4 or IPv6 family.
/// String value of the subnet mask in dotted decimal notation.
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);
}
///
/// Convert a mask to a CIDR. IPv4 only.
/// https://stackoverflow.com/questions/36954345/get-cidr-from-netmask.
///
/// Subnet mask.
/// Byte CIDR representing the mask.
public static byte MaskToCidr(IPAddress mask)
{
if (mask == null)
{
throw new ArgumentNullException(nameof(mask));
}
byte cidrnet = 0;
if (!mask.Equals(IPAddress.Any))
{
byte[] bytes = mask.GetAddressBytes();
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;
}
///
/// Tests to see if this object is a Loopback address.
///
/// True if it is.
public virtual bool IsLoopback()
{
return IsLoopback(Address);
}
///
/// Removes all addresses of a specific type from this object.
///
/// Type of address to remove.
public virtual void Remove(AddressFamily family)
{
// This method only peforms a function in the IPHost implementation of IPObject.
}
///
/// Tests to see if this object is an IPv6 address.
///
/// True if it is.
public virtual bool IsIP6()
{
return IsIP6(Address);
}
///
/// Returns true if this IP address is in the RFC private address range.
///
/// True this object has a private address.
public virtual bool IsPrivateAddressRange()
{
return IsPrivateAddressRange(Address);
}
///
/// Compares this to the object passed as a parameter.
///
/// Object to compare to.
/// Equality result.
public virtual bool Equals(IPAddress ip)
{
if (ip != null)
{
if (ip.IsIPv4MappedToIPv6)
{
ip = ip.MapToIPv4();
}
return !Address.Equals(IPAddress.None) && Address.Equals(ip);
}
return false;
}
///
/// Compares this to the object passed as a parameter.
///
/// Object to compare to.
/// Equality result.
public virtual bool Equals(IPObject? other)
{
if (other != null && other is IPObject otherObj)
{
return !Address.Equals(IPAddress.None) && Address.Equals(otherObj.Address);
}
return false;
}
///
/// Compares the address in this object and the address in the object passed as a parameter.
///
/// Object's IP address to compare to.
/// Comparison result.
public abstract bool Contains(IPObject address);
///
/// Compares the address in this object and the address in the object passed as a parameter.
///
/// Object's IP address to compare to.
/// Comparison result.
public abstract bool Contains(IPAddress address);
///
public override int GetHashCode()
{
return Address.GetHashCode();
}
///
public override bool Equals(object obj)
{
return Equals(obj as IPObject);
}
///
/// Calculates the network address of this object.
///
/// Returns the network address of this object.
protected abstract IPObject CalculateNetworkAddress();
}
}