#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(); } }