using System; using System.Net; using System.Net.Sockets; namespace MediaBrowser.Common.Net { /// /// Base network object class. /// public abstract class IPObject : IEquatable { /// /// The network address of this object. /// private IPObject? _networkAddress; /// /// Gets or sets a user defined value that is associated with 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 => _networkAddress ??= CalculateNetworkAddress(); /// /// 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) { 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 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); } /// /// Tests to see if the ip address is an IP6 address. /// /// Value to test. /// True if it is. public static bool IsIP6(IPAddress address) { ArgumentNullException.ThrowIfNull(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) { ArgumentNullException.ThrowIfNull(address); if (!address.Equals(IPAddress.None)) { if (address.IsIPv4MappedToIPv6) { address = address.MapToIPv4(); } if (address.AddressFamily == AddressFamily.InterNetwork) { // GetAddressBytes Span 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 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; } /// /// 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) { ArgumentNullException.ThrowIfNull(address); if (address.IsIPv4MappedToIPv6) { address = address.MapToIPv4(); } if (address.AddressFamily != AddressFamily.InterNetworkV6) { return false; } // GetAddressBytes Span 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. } /// /// 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) { ArgumentNullException.ThrowIfNull(mask); byte cidrnet = 0; if (!mask.Equals(IPAddress.Any)) { // GetAddressBytes Span 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; } /// /// Tests to see if this object is a Loopback address. /// /// True if it is. public virtual bool IsLoopback() { return IPAddress.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 performs 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 is not 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 is not null) { return !Address.Equals(IPAddress.None) && Address.Equals(other.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(); } }