Subnet.php 9.8 KB

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