Subnet.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. <?php
  2. namespace IPLib\Range;
  3. use IPLib\Address\AddressInterface;
  4. use IPLib\Address\IPv4;
  5. use IPLib\Address\Type as AddressType;
  6. use IPLib\Factory;
  7. use IPLib\ParseStringFlag;
  8. use IPLib\Service\BinaryMath;
  9. /**
  10. * Represents an address range in subnet format (eg CIDR).
  11. *
  12. * @example 127.0.0.1/32
  13. * @example ::/8
  14. *
  15. * @phpstan-consistent-constructor
  16. */
  17. class Subnet extends AbstractRange
  18. {
  19. /**
  20. * Starting address of the range.
  21. *
  22. * @var \IPLib\Address\AddressInterface
  23. */
  24. protected $fromAddress;
  25. /**
  26. * Final address of the range.
  27. *
  28. * @var \IPLib\Address\AddressInterface
  29. */
  30. protected $toAddress;
  31. /**
  32. * Number of the same bits of the range.
  33. *
  34. * @var int
  35. */
  36. protected $networkPrefix;
  37. /**
  38. * The type of the range of this IP range.
  39. *
  40. * @var int|false|null false if this range crosses multiple range types, null if yet to be determined
  41. *
  42. * @since 1.5.0
  43. */
  44. protected $rangeType;
  45. /**
  46. * The 6to4 address IPv6 address range.
  47. *
  48. * @var self|null
  49. */
  50. private static $sixToFour;
  51. /**
  52. * Initializes the instance.
  53. *
  54. * @param \IPLib\Address\AddressInterface $fromAddress
  55. * @param \IPLib\Address\AddressInterface $toAddress
  56. * @param int $networkPrefix
  57. *
  58. * @internal
  59. */
  60. public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $networkPrefix)
  61. {
  62. $this->fromAddress = $fromAddress;
  63. $this->toAddress = $toAddress;
  64. $this->networkPrefix = $networkPrefix;
  65. }
  66. /**
  67. * {@inheritdoc}
  68. *
  69. * @see \IPLib\Range\RangeInterface::__toString()
  70. */
  71. public function __toString()
  72. {
  73. return $this->toString();
  74. }
  75. /**
  76. * @deprecated since 1.17.0: use the parseString() method instead.
  77. * For upgrading:
  78. * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
  79. *
  80. * @param string|mixed $range
  81. * @param bool $supportNonDecimalIPv4
  82. *
  83. * @return static|null
  84. *
  85. * @see \IPLib\Range\Subnet::parseString()
  86. * @since 1.10.0 added the $supportNonDecimalIPv4 argument
  87. */
  88. public static function fromString($range, $supportNonDecimalIPv4 = false)
  89. {
  90. return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
  91. }
  92. /**
  93. * Try get the range instance starting from its string representation.
  94. *
  95. * @param string|mixed $range
  96. * @param int $flags A combination or zero or more flags
  97. *
  98. * @return static|null
  99. *
  100. * @see \IPLib\ParseStringFlag
  101. * @since 1.17.0
  102. */
  103. public static function parseString($range, $flags = 0)
  104. {
  105. if (!is_string($range)) {
  106. return null;
  107. }
  108. $parts = explode('/', $range);
  109. if (count($parts) !== 2) {
  110. return null;
  111. }
  112. $flags = (int) $flags;
  113. if (strpos($parts[0], ':') === false && $flags & ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT) {
  114. $missingDots = 3 - substr_count($parts[0], '.');
  115. if ($missingDots > 0) {
  116. $parts[0] .= str_repeat('.0', $missingDots);
  117. }
  118. }
  119. $address = Factory::parseAddressString($parts[0], $flags);
  120. if ($address === null) {
  121. return null;
  122. }
  123. if (!preg_match('/^[0-9]{1,9}$/D', $parts[1])) {
  124. return null;
  125. }
  126. $networkPrefix = (int) $parts[1];
  127. $addressBytes = $address->getBytes();
  128. $totalBytes = count($addressBytes);
  129. $numDifferentBits = $totalBytes * 8 - $networkPrefix;
  130. if ($numDifferentBits < 0) {
  131. return null;
  132. }
  133. $numSameBytes = $networkPrefix >> 3;
  134. $sameBytes = array_slice($addressBytes, 0, $numSameBytes);
  135. $differentBytesStart = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 0);
  136. $differentBytesEnd = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 255);
  137. $startSameBits = $networkPrefix % 8;
  138. if ($startSameBits !== 0) {
  139. $varyingByte = $addressBytes[$numSameBytes];
  140. $differentBytesStart[0] = $varyingByte & (int) bindec(str_pad(str_repeat('1', $startSameBits), 8, '0', STR_PAD_RIGHT));
  141. $differentBytesEnd[0] = $differentBytesStart[0] + (int) bindec(str_repeat('1', 8 - $startSameBits));
  142. }
  143. $fromAddress = Factory::addressFromBytes(array_merge($sameBytes, $differentBytesStart));
  144. /** @var \IPLib\Address\AddressInterface $fromAddress */
  145. $toAddress = Factory::addressFromBytes(array_merge($sameBytes, $differentBytesEnd));
  146. /** @var \IPLib\Address\AddressInterface $toAddress */
  147. return new static($fromAddress, $toAddress, $networkPrefix);
  148. }
  149. /**
  150. * {@inheritdoc}
  151. *
  152. * @see \IPLib\Range\RangeInterface::toString()
  153. */
  154. public function toString($long = false)
  155. {
  156. return $this->fromAddress->toString($long) . '/' . $this->networkPrefix;
  157. }
  158. /**
  159. * {@inheritdoc}
  160. *
  161. * @see \IPLib\Range\RangeInterface::getAddressType()
  162. */
  163. public function getAddressType()
  164. {
  165. return $this->fromAddress->getAddressType();
  166. }
  167. /**
  168. * {@inheritdoc}
  169. *
  170. * @see \IPLib\Range\RangeInterface::getStartAddress()
  171. */
  172. public function getStartAddress()
  173. {
  174. return $this->fromAddress;
  175. }
  176. /**
  177. * {@inheritdoc}
  178. *
  179. * @see \IPLib\Range\RangeInterface::getEndAddress()
  180. */
  181. public function getEndAddress()
  182. {
  183. return $this->toAddress;
  184. }
  185. /**
  186. * {@inheritdoc}
  187. *
  188. * @see \IPLib\Range\RangeInterface::getComparableStartString()
  189. */
  190. public function getComparableStartString()
  191. {
  192. return $this->fromAddress->getComparableString();
  193. }
  194. /**
  195. * {@inheritdoc}
  196. *
  197. * @see \IPLib\Range\RangeInterface::getComparableEndString()
  198. */
  199. public function getComparableEndString()
  200. {
  201. return $this->toAddress->getComparableString();
  202. }
  203. /**
  204. * {@inheritdoc}
  205. *
  206. * @see \IPLib\Range\RangeInterface::asSubnet()
  207. */
  208. public function asSubnet()
  209. {
  210. return $this;
  211. }
  212. /**
  213. * {@inheritdoc}
  214. *
  215. * @see \IPLib\Range\RangeInterface::asPattern()
  216. * @since 1.8.0
  217. */
  218. public function asPattern()
  219. {
  220. $startAddress = $this->getStartAddress();
  221. $networkPrefix = $this->getNetworkPrefix();
  222. switch ($startAddress->getAddressType()) {
  223. case AddressType::T_IPv4:
  224. return $networkPrefix % 8 === 0 ? new Pattern($startAddress, $this->getEndAddress(), 4 - $networkPrefix / 8) : null;
  225. case AddressType::T_IPv6:
  226. return $networkPrefix % 16 === 0 ? new Pattern($startAddress, $this->getEndAddress(), 8 - $networkPrefix / 16) : null;
  227. }
  228. }
  229. /**
  230. * Get the 6to4 address IPv6 address range.
  231. *
  232. * @return self
  233. *
  234. * @since 1.5.0
  235. */
  236. public static function get6to4()
  237. {
  238. if (self::$sixToFour === null) {
  239. $subnet = self::parseString('2002::/16');
  240. /** @var Subnet $subnet */
  241. self::$sixToFour = $subnet;
  242. }
  243. return self::$sixToFour;
  244. }
  245. /**
  246. * {@inheritdoc}
  247. *
  248. * @see \IPLib\Range\RangeInterface::getNetworkPrefix()
  249. * @since 1.7.0
  250. */
  251. public function getNetworkPrefix()
  252. {
  253. return $this->networkPrefix;
  254. }
  255. /**
  256. * {@inheritdoc}
  257. *
  258. * @see \IPLib\Range\RangeInterface::getSubnetMask()
  259. */
  260. public function getSubnetMask()
  261. {
  262. if ($this->getAddressType() !== AddressType::T_IPv4) {
  263. return null;
  264. }
  265. $bytes = array();
  266. $prefix = $this->getNetworkPrefix();
  267. while ($prefix >= 8) {
  268. $bytes[] = 255;
  269. $prefix -= 8;
  270. }
  271. if ($prefix !== 0) {
  272. $bytes[] = bindec(str_pad(str_repeat('1', $prefix), 8, '0'));
  273. }
  274. $bytes = array_pad($bytes, 4, 0);
  275. return IPv4::fromBytes($bytes);
  276. }
  277. /**
  278. * {@inheritdoc}
  279. *
  280. * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
  281. */
  282. public function getReverseDNSLookupName()
  283. {
  284. switch ($this->getAddressType()) {
  285. case AddressType::T_IPv4:
  286. $unitSize = 8; // bytes
  287. $maxUnits = 4;
  288. $isHex = false;
  289. $rxUnit = '\d+';
  290. break;
  291. case AddressType::T_IPv6:
  292. $unitSize = 4; // nibbles
  293. $maxUnits = 32;
  294. $isHex = true;
  295. $rxUnit = '[0-9A-Fa-f]';
  296. break;
  297. }
  298. $totBits = $unitSize * $maxUnits;
  299. $prefixUnits = (int) ($this->networkPrefix / $unitSize);
  300. $extraBits = ($totBits - $this->networkPrefix) % $unitSize;
  301. if ($extraBits !== 0) {
  302. $prefixUnits += 1;
  303. }
  304. $numVariants = 1 << $extraBits;
  305. $result = array();
  306. $unitsToRemove = $maxUnits - $prefixUnits;
  307. $initialPointer = preg_replace("/^(({$rxUnit})\.){{$unitsToRemove}}/", '', $this->getStartAddress()->getReverseDNSLookupName());
  308. /** @var string $initialPointer */
  309. $chunks = explode('.', $initialPointer, 2);
  310. for ($index = 0; $index < $numVariants; $index++) {
  311. if ($index !== 0) {
  312. $chunks[0] = $isHex ? dechex(1 + (int) hexdec($chunks[0])) : (string) (1 + (int) $chunks[0]);
  313. }
  314. $result[] = implode('.', $chunks);
  315. }
  316. return $result;
  317. }
  318. /**
  319. * {@inheritdoc}
  320. *
  321. * @see \IPLib\Range\RangeInterface::getSize()
  322. */
  323. public function getSize()
  324. {
  325. $fromAddress = $this->fromAddress;
  326. $maxPrefix = $fromAddress::getNumberOfBits();
  327. $prefix = $this->getNetworkPrefix();
  328. return pow(2, $maxPrefix - $prefix);
  329. }
  330. /**
  331. * {@inheritdoc}
  332. *
  333. * @see \IPLib\Range\RangeInterface::getExactSize()
  334. */
  335. public function getExactSize()
  336. {
  337. $fromAddress = $this->fromAddress;
  338. $maxPrefix = $fromAddress::getNumberOfBits();
  339. $prefix = $this->getNetworkPrefix();
  340. return BinaryMath::getInstance()->pow2string($maxPrefix - $prefix);
  341. }
  342. }