1
0

IPAddressExt.cs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. using System.Collections;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Linq;
  4. using System.Net;
  5. using System.Net.Sockets;
  6. using NpgsqlTypes;
  7. namespace Content.Server.IP
  8. {
  9. public static class IPAddressExt
  10. {
  11. // Npgsql used to map inet types as a tuple like this.
  12. // I'm upgrading the dependencies and I don't wanna rewrite a bunch of DB code, so a few helpers it shall be.
  13. [return: NotNullIfNotNull(nameof(tuple))]
  14. public static NpgsqlInet? ToNpgsqlInet(this (IPAddress, int)? tuple)
  15. {
  16. if (tuple == null)
  17. return null;
  18. return new NpgsqlInet(tuple.Value.Item1, (byte) tuple.Value.Item2);
  19. }
  20. [return: NotNullIfNotNull(nameof(inet))]
  21. public static (IPAddress, int)? ToTuple(this NpgsqlInet? inet)
  22. {
  23. if (inet == null)
  24. return null;
  25. return (inet.Value.Address, inet.Value.Netmask);
  26. }
  27. // Taken from https://stackoverflow.com/a/56461160/4678631
  28. public static bool IsInSubnet(this System.Net.IPAddress address, string subnetMask)
  29. {
  30. var slashIdx = subnetMask.IndexOf("/", StringComparison.Ordinal);
  31. if (slashIdx == -1)
  32. {
  33. // We only handle netmasks in format "IP/PrefixLength".
  34. throw new NotSupportedException("Only SubNetMasks with a given prefix length are supported.");
  35. }
  36. // First parse the address of the netmask before the prefix length.
  37. var maskAddress = System.Net.IPAddress.Parse(subnetMask[..slashIdx]);
  38. if (maskAddress.AddressFamily != address.AddressFamily)
  39. {
  40. // We got something like an IPV4-Address for an IPv6-Mask. This is not valid.
  41. return false;
  42. }
  43. // Now find out how long the prefix is.
  44. int maskLength = int.Parse(subnetMask[(slashIdx + 1)..]);
  45. return address.IsInSubnet(maskAddress, maskLength);
  46. }
  47. public static bool IsInSubnet(this System.Net.IPAddress address, (System.Net.IPAddress maskAddress, int maskLength) tuple)
  48. {
  49. return address.IsInSubnet(tuple.maskAddress, tuple.maskLength);
  50. }
  51. public static bool IsInSubnet(this System.Net.IPAddress address, System.Net.IPAddress maskAddress, int maskLength)
  52. {
  53. if (maskAddress.AddressFamily != address.AddressFamily)
  54. {
  55. // We got something like an IPV4-Address for an IPv6-Mask. This is not valid.
  56. return false;
  57. }
  58. if (maskAddress.AddressFamily == AddressFamily.InterNetwork)
  59. {
  60. // Convert the mask address to an unsigned integer.
  61. var maskAddressBits = BitConverter.ToUInt32(maskAddress.GetAddressBytes().Reverse().ToArray(), 0);
  62. // And convert the IpAddress to an unsigned integer.
  63. var ipAddressBits = BitConverter.ToUInt32(address.GetAddressBytes().Reverse().ToArray(), 0);
  64. // Get the mask/network address as unsigned integer.
  65. uint mask = uint.MaxValue << (32 - maskLength);
  66. // https://stackoverflow.com/a/1499284/3085985
  67. // Bitwise AND mask and MaskAddress, this should be the same as mask and IpAddress
  68. // as the end of the mask is 0000 which leads to both addresses to end with 0000
  69. // and to start with the prefix.
  70. return (maskAddressBits & mask) == (ipAddressBits & mask);
  71. }
  72. if (maskAddress.AddressFamily == AddressFamily.InterNetworkV6)
  73. {
  74. // Convert the mask address to a BitArray.
  75. var maskAddressBits = new BitArray(maskAddress.GetAddressBytes());
  76. // And convert the IpAddress to a BitArray.
  77. var ipAddressBits = new BitArray(address.GetAddressBytes());
  78. if (maskAddressBits.Length != ipAddressBits.Length)
  79. {
  80. return false;
  81. }
  82. // Compare the prefix bits.
  83. for (int maskIndex = 0; maskIndex < maskLength; maskIndex++)
  84. {
  85. if (ipAddressBits[maskIndex] != maskAddressBits[maskIndex])
  86. {
  87. return false;
  88. }
  89. }
  90. return true;
  91. }
  92. throw new NotSupportedException("Only InterNetworkV6 or InterNetwork address families are supported.");
  93. }
  94. }
  95. }