IPObject.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. using System;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. namespace MediaBrowser.Common.Net
  5. {
  6. /// <summary>
  7. /// Base network object class.
  8. /// </summary>
  9. public abstract class IPObject : IEquatable<IPObject>
  10. {
  11. /// <summary>
  12. /// The network address of this object.
  13. /// </summary>
  14. private IPObject? _networkAddress;
  15. /// <summary>
  16. /// Gets or sets a user defined value that is associated with this object.
  17. /// </summary>
  18. public int Tag { get; set; }
  19. /// <summary>
  20. /// Gets or sets the object's IP address.
  21. /// </summary>
  22. public abstract IPAddress Address { get; set; }
  23. /// <summary>
  24. /// Gets the object's network address.
  25. /// </summary>
  26. public IPObject NetworkAddress => _networkAddress ??= CalculateNetworkAddress();
  27. /// <summary>
  28. /// Gets or sets the object's IP address.
  29. /// </summary>
  30. public abstract byte PrefixLength { get; set; }
  31. /// <summary>
  32. /// Gets the AddressFamily of this object.
  33. /// </summary>
  34. public AddressFamily AddressFamily
  35. {
  36. get
  37. {
  38. // Keep terms separate as Address performs other functions in inherited objects.
  39. IPAddress address = Address;
  40. return address.Equals(IPAddress.None) ? AddressFamily.Unspecified : address.AddressFamily;
  41. }
  42. }
  43. /// <summary>
  44. /// Returns the network address of an object.
  45. /// </summary>
  46. /// <param name="address">IP Address to convert.</param>
  47. /// <param name="prefixLength">Subnet prefix.</param>
  48. /// <returns>IPAddress.</returns>
  49. public static (IPAddress Address, byte PrefixLength) NetworkAddressOf(IPAddress address, byte prefixLength)
  50. {
  51. ArgumentNullException.ThrowIfNull(address);
  52. if (address.IsIPv4MappedToIPv6)
  53. {
  54. address = address.MapToIPv4();
  55. }
  56. if (IPAddress.IsLoopback(address))
  57. {
  58. return (address, prefixLength);
  59. }
  60. // An ip address is just a list of bytes, each one representing a segment on the network.
  61. // This separates the IP address into octets and calculates how many octets will need to be altered or set to zero dependant upon the
  62. // 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.
  63. // Where there is not an exact boundary (eg /23), mod is used to calculate how many bits of this value are to be kept.
  64. // GetAddressBytes
  65. Span<byte> addressBytes = stackalloc byte[address.AddressFamily == AddressFamily.InterNetwork ? 4 : 16];
  66. address.TryWriteBytes(addressBytes, out _);
  67. int div = prefixLength / 8;
  68. int mod = prefixLength % 8;
  69. if (mod != 0)
  70. {
  71. // Prefix length is counted right to left, so subtract 8 so we know how many bits to clear.
  72. mod = 8 - mod;
  73. // Shift out the bits from the octet that we don't want, by moving right then back left.
  74. addressBytes[div] = (byte)((int)addressBytes[div] >> mod << mod);
  75. // Move on the next byte.
  76. div++;
  77. }
  78. // 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)
  79. for (int octet = div; octet < addressBytes.Length; octet++)
  80. {
  81. addressBytes[octet] = 0;
  82. }
  83. // Return the network address for the prefix.
  84. return (new IPAddress(addressBytes), prefixLength);
  85. }
  86. /// <summary>
  87. /// Tests to see if the ip address is an IP6 address.
  88. /// </summary>
  89. /// <param name="address">Value to test.</param>
  90. /// <returns>True if it is.</returns>
  91. public static bool IsIP6(IPAddress address)
  92. {
  93. ArgumentNullException.ThrowIfNull(address);
  94. if (address.IsIPv4MappedToIPv6)
  95. {
  96. address = address.MapToIPv4();
  97. }
  98. return !address.Equals(IPAddress.None) && (address.AddressFamily == AddressFamily.InterNetworkV6);
  99. }
  100. /// <summary>
  101. /// Tests to see if the address in the private address range.
  102. /// </summary>
  103. /// <param name="address">Object to test.</param>
  104. /// <returns>True if it contains a private address.</returns>
  105. public static bool IsPrivateAddressRange(IPAddress address)
  106. {
  107. ArgumentNullException.ThrowIfNull(address);
  108. if (!address.Equals(IPAddress.None))
  109. {
  110. if (address.IsIPv4MappedToIPv6)
  111. {
  112. address = address.MapToIPv4();
  113. }
  114. if (address.AddressFamily == AddressFamily.InterNetwork)
  115. {
  116. // GetAddressBytes
  117. Span<byte> octet = stackalloc byte[4];
  118. address.TryWriteBytes(octet, out _);
  119. return (octet[0] == 10)
  120. || (octet[0] == 172 && octet[1] >= 16 && octet[1] <= 31) // RFC1918
  121. || (octet[0] == 192 && octet[1] == 168) // RFC1918
  122. || (octet[0] == 127); // RFC1122
  123. }
  124. else
  125. {
  126. // GetAddressBytes
  127. Span<byte> octet = stackalloc byte[16];
  128. address.TryWriteBytes(octet, out _);
  129. uint word = (uint)(octet[0] << 8) + octet[1];
  130. return (word >= 0xfe80 && word <= 0xfebf) // fe80::/10 :Local link.
  131. || (word >= 0xfc00 && word <= 0xfdff); // fc00::/7 :Unique local address.
  132. }
  133. }
  134. return false;
  135. }
  136. /// <summary>
  137. /// Returns true if the IPAddress contains an IP6 Local link address.
  138. /// </summary>
  139. /// <param name="address">IPAddress object to check.</param>
  140. /// <returns>True if it is a local link address.</returns>
  141. /// <remarks>
  142. /// See https://stackoverflow.com/questions/6459928/explain-the-instance-properties-of-system-net-ipaddress
  143. /// it appears that the IPAddress.IsIPv6LinkLocal is out of date.
  144. /// </remarks>
  145. public static bool IsIPv6LinkLocal(IPAddress address)
  146. {
  147. ArgumentNullException.ThrowIfNull(address);
  148. if (address.IsIPv4MappedToIPv6)
  149. {
  150. address = address.MapToIPv4();
  151. }
  152. if (address.AddressFamily != AddressFamily.InterNetworkV6)
  153. {
  154. return false;
  155. }
  156. // GetAddressBytes
  157. Span<byte> octet = stackalloc byte[16];
  158. address.TryWriteBytes(octet, out _);
  159. uint word = (uint)(octet[0] << 8) + octet[1];
  160. return word >= 0xfe80 && word <= 0xfebf; // fe80::/10 :Local link.
  161. }
  162. /// <summary>
  163. /// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only.
  164. /// </summary>
  165. /// <param name="cidr">Subnet mask in CIDR notation.</param>
  166. /// <param name="family">IPv4 or IPv6 family.</param>
  167. /// <returns>String value of the subnet mask in dotted decimal notation.</returns>
  168. public static IPAddress CidrToMask(byte cidr, AddressFamily family)
  169. {
  170. uint addr = 0xFFFFFFFF << (family == AddressFamily.InterNetwork ? 32 : 128 - cidr);
  171. addr = ((addr & 0xff000000) >> 24)
  172. | ((addr & 0x00ff0000) >> 8)
  173. | ((addr & 0x0000ff00) << 8)
  174. | ((addr & 0x000000ff) << 24);
  175. return new IPAddress(addr);
  176. }
  177. /// <summary>
  178. /// Convert a mask to a CIDR. IPv4 only.
  179. /// https://stackoverflow.com/questions/36954345/get-cidr-from-netmask.
  180. /// </summary>
  181. /// <param name="mask">Subnet mask.</param>
  182. /// <returns>Byte CIDR representing the mask.</returns>
  183. public static byte MaskToCidr(IPAddress mask)
  184. {
  185. ArgumentNullException.ThrowIfNull(mask);
  186. byte cidrnet = 0;
  187. if (!mask.Equals(IPAddress.Any))
  188. {
  189. // GetAddressBytes
  190. Span<byte> bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? 4 : 16];
  191. mask.TryWriteBytes(bytes, out _);
  192. var zeroed = false;
  193. for (var i = 0; i < bytes.Length; i++)
  194. {
  195. for (int v = bytes[i]; (v & 0xFF) != 0; v <<= 1)
  196. {
  197. if (zeroed)
  198. {
  199. // Invalid netmask.
  200. return (byte)~cidrnet;
  201. }
  202. if ((v & 0x80) == 0)
  203. {
  204. zeroed = true;
  205. }
  206. else
  207. {
  208. cidrnet++;
  209. }
  210. }
  211. }
  212. }
  213. return cidrnet;
  214. }
  215. /// <summary>
  216. /// Tests to see if this object is a Loopback address.
  217. /// </summary>
  218. /// <returns>True if it is.</returns>
  219. public virtual bool IsLoopback()
  220. {
  221. return IPAddress.IsLoopback(Address);
  222. }
  223. /// <summary>
  224. /// Removes all addresses of a specific type from this object.
  225. /// </summary>
  226. /// <param name="family">Type of address to remove.</param>
  227. public virtual void Remove(AddressFamily family)
  228. {
  229. // This method only performs a function in the IPHost implementation of IPObject.
  230. }
  231. /// <summary>
  232. /// Tests to see if this object is an IPv6 address.
  233. /// </summary>
  234. /// <returns>True if it is.</returns>
  235. public virtual bool IsIP6()
  236. {
  237. return IsIP6(Address);
  238. }
  239. /// <summary>
  240. /// Returns true if this IP address is in the RFC private address range.
  241. /// </summary>
  242. /// <returns>True this object has a private address.</returns>
  243. public virtual bool IsPrivateAddressRange()
  244. {
  245. return IsPrivateAddressRange(Address);
  246. }
  247. /// <summary>
  248. /// Compares this to the object passed as a parameter.
  249. /// </summary>
  250. /// <param name="ip">Object to compare to.</param>
  251. /// <returns>Equality result.</returns>
  252. public virtual bool Equals(IPAddress ip)
  253. {
  254. if (ip is not null)
  255. {
  256. if (ip.IsIPv4MappedToIPv6)
  257. {
  258. ip = ip.MapToIPv4();
  259. }
  260. return !Address.Equals(IPAddress.None) && Address.Equals(ip);
  261. }
  262. return false;
  263. }
  264. /// <summary>
  265. /// Compares this to the object passed as a parameter.
  266. /// </summary>
  267. /// <param name="other">Object to compare to.</param>
  268. /// <returns>Equality result.</returns>
  269. public virtual bool Equals(IPObject? other)
  270. {
  271. if (other is not null)
  272. {
  273. return !Address.Equals(IPAddress.None) && Address.Equals(other.Address);
  274. }
  275. return false;
  276. }
  277. /// <summary>
  278. /// Compares the address in this object and the address in the object passed as a parameter.
  279. /// </summary>
  280. /// <param name="address">Object's IP address to compare to.</param>
  281. /// <returns>Comparison result.</returns>
  282. public abstract bool Contains(IPObject address);
  283. /// <summary>
  284. /// Compares the address in this object and the address in the object passed as a parameter.
  285. /// </summary>
  286. /// <param name="address">Object's IP address to compare to.</param>
  287. /// <returns>Comparison result.</returns>
  288. public abstract bool Contains(IPAddress address);
  289. /// <inheritdoc/>
  290. public override int GetHashCode()
  291. {
  292. return Address.GetHashCode();
  293. }
  294. /// <inheritdoc/>
  295. public override bool Equals(object? obj)
  296. {
  297. return Equals(obj as IPObject);
  298. }
  299. /// <summary>
  300. /// Calculates the network address of this object.
  301. /// </summary>
  302. /// <returns>Returns the network address of this object.</returns>
  303. protected abstract IPObject CalculateNetworkAddress();
  304. }
  305. }