Subnet.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. <?php
  2. namespace IPLib\Range;
  3. use IPLib\Address\AddressInterface;
  4. use IPLib\Address\IPv4;
  5. use IPLib\Address\IPv6;
  6. use IPLib\Address\Type as AddressType;
  7. use IPLib\Factory;
  8. /**
  9. * Represents an address range in subnet format (eg CIDR).
  10. *
  11. * @example 127.0.0.1/32
  12. * @example ::/8
  13. */
  14. class Subnet extends AbstractRange
  15. {
  16. /**
  17. * Starting address of the range.
  18. *
  19. * @var \IPLib\Address\AddressInterface
  20. */
  21. protected $fromAddress;
  22. /**
  23. * Final address of the range.
  24. *
  25. * @var \IPLib\Address\AddressInterface
  26. */
  27. protected $toAddress;
  28. /**
  29. * Number of the same bits of the range.
  30. *
  31. * @var int
  32. */
  33. protected $networkPrefix;
  34. /**
  35. * The type of the range of this IP range.
  36. *
  37. * @var int|null
  38. */
  39. protected $rangeType;
  40. /**
  41. * The 6to4 address IPv6 address range.
  42. *
  43. * @var self|null
  44. */
  45. private static $sixToFour;
  46. /**
  47. * Initializes the instance.
  48. *
  49. * @param \IPLib\Address\AddressInterface $fromAddress
  50. * @param \IPLib\Address\AddressInterface $toAddress
  51. * @param int $networkPrefix
  52. *
  53. * @internal
  54. */
  55. public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $networkPrefix)
  56. {
  57. $this->fromAddress = $fromAddress;
  58. $this->toAddress = $toAddress;
  59. $this->networkPrefix = $networkPrefix;
  60. }
  61. /**
  62. * {@inheritdoc}
  63. *
  64. * @see \IPLib\Range\RangeInterface::__toString()
  65. */
  66. public function __toString()
  67. {
  68. return $this->toString();
  69. }
  70. /**
  71. * Try get the range instance starting from its string representation.
  72. *
  73. * @param string|mixed $range
  74. * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
  75. *
  76. * @return static|null
  77. */
  78. public static function fromString($range, $supportNonDecimalIPv4 = false)
  79. {
  80. if (!is_string($range)) {
  81. return null;
  82. }
  83. $parts = explode('/', $range);
  84. if (count($parts) !== 2) {
  85. return null;
  86. }
  87. $address = Factory::addressFromString($parts[0], true, true, $supportNonDecimalIPv4);
  88. if ($address === null) {
  89. return null;
  90. }
  91. if (!preg_match('/^[0-9]{1,9}$/', $parts[1])) {
  92. return null;
  93. }
  94. $networkPrefix = (int) $parts[1];
  95. $addressBytes = $address->getBytes();
  96. $totalBytes = count($addressBytes);
  97. $numDifferentBits = $totalBytes * 8 - $networkPrefix;
  98. if ($numDifferentBits < 0) {
  99. return null;
  100. }
  101. $numSameBytes = $networkPrefix >> 3;
  102. $sameBytes = array_slice($addressBytes, 0, $numSameBytes);
  103. $differentBytesStart = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 0);
  104. $differentBytesEnd = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 255);
  105. $startSameBits = $networkPrefix % 8;
  106. if ($startSameBits !== 0) {
  107. $varyingByte = $addressBytes[$numSameBytes];
  108. $differentBytesStart[0] = $varyingByte & bindec(str_pad(str_repeat('1', $startSameBits), 8, '0', STR_PAD_RIGHT));
  109. $differentBytesEnd[0] = $differentBytesStart[0] + bindec(str_repeat('1', 8 - $startSameBits));
  110. }
  111. return new static(
  112. Factory::addressFromBytes(array_merge($sameBytes, $differentBytesStart)),
  113. Factory::addressFromBytes(array_merge($sameBytes, $differentBytesEnd)),
  114. $networkPrefix
  115. );
  116. }
  117. /**
  118. * {@inheritdoc}
  119. *
  120. * @see \IPLib\Range\RangeInterface::toString()
  121. */
  122. public function toString($long = false)
  123. {
  124. return $this->fromAddress->toString($long) . '/' . $this->networkPrefix;
  125. }
  126. /**
  127. * {@inheritdoc}
  128. *
  129. * @see \IPLib\Range\RangeInterface::getAddressType()
  130. */
  131. public function getAddressType()
  132. {
  133. return $this->fromAddress->getAddressType();
  134. }
  135. /**
  136. * {@inheritdoc}
  137. *
  138. * @see \IPLib\Range\RangeInterface::getStartAddress()
  139. */
  140. public function getStartAddress()
  141. {
  142. return $this->fromAddress;
  143. }
  144. /**
  145. * {@inheritdoc}
  146. *
  147. * @see \IPLib\Range\RangeInterface::getEndAddress()
  148. */
  149. public function getEndAddress()
  150. {
  151. return $this->toAddress;
  152. }
  153. /**
  154. * {@inheritdoc}
  155. *
  156. * @see \IPLib\Range\RangeInterface::getComparableStartString()
  157. */
  158. public function getComparableStartString()
  159. {
  160. return $this->fromAddress->getComparableString();
  161. }
  162. /**
  163. * {@inheritdoc}
  164. *
  165. * @see \IPLib\Range\RangeInterface::getComparableEndString()
  166. */
  167. public function getComparableEndString()
  168. {
  169. return $this->toAddress->getComparableString();
  170. }
  171. /**
  172. * {@inheritdoc}
  173. *
  174. * @see \IPLib\Range\RangeInterface::asSubnet()
  175. */
  176. public function asSubnet()
  177. {
  178. return $this;
  179. }
  180. /**
  181. * Get the pattern (asterisk) representation (if applicable) of this range.
  182. *
  183. * @return \IPLib\Range\Pattern|null return NULL if this range can't be represented by a pattern notation
  184. */
  185. public function asPattern()
  186. {
  187. $address = $this->getStartAddress();
  188. $networkPrefix = $this->getNetworkPrefix();
  189. switch ($address->getAddressType()) {
  190. case AddressType::T_IPv4:
  191. return $networkPrefix % 8 === 0 ? new Pattern($address, $address, 4 - $networkPrefix / 8) : null;
  192. case AddressType::T_IPv6:
  193. return $networkPrefix % 16 === 0 ? new Pattern($address, $address, 8 - $networkPrefix / 16) : null;
  194. }
  195. }
  196. /**
  197. * Get the 6to4 address IPv6 address range.
  198. *
  199. * @return self
  200. */
  201. public static function get6to4()
  202. {
  203. if (self::$sixToFour === null) {
  204. self::$sixToFour = self::fromString('2002::/16');
  205. }
  206. return self::$sixToFour;
  207. }
  208. /**
  209. * Get subnet prefix.
  210. *
  211. * @return int
  212. */
  213. public function getNetworkPrefix()
  214. {
  215. return $this->networkPrefix;
  216. }
  217. /**
  218. * {@inheritdoc}
  219. *
  220. * @see \IPLib\Range\RangeInterface::getSubnetMask()
  221. */
  222. public function getSubnetMask()
  223. {
  224. if ($this->getAddressType() !== AddressType::T_IPv4) {
  225. return null;
  226. }
  227. $bytes = array();
  228. $prefix = $this->getNetworkPrefix();
  229. while ($prefix >= 8) {
  230. $bytes[] = 255;
  231. $prefix -= 8;
  232. }
  233. if ($prefix !== 0) {
  234. $bytes[] = bindec(str_pad(str_repeat('1', $prefix), 8, '0'));
  235. }
  236. $bytes = array_pad($bytes, 4, 0);
  237. return IPv4::fromBytes($bytes);
  238. }
  239. /**
  240. * {@inheritdoc}
  241. *
  242. * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
  243. */
  244. public function getReverseDNSLookupName()
  245. {
  246. switch ($this->getAddressType()) {
  247. case AddressType::T_IPv4:
  248. $unitSize = 8; // bytes
  249. $maxUnits = 4;
  250. $isHex = false;
  251. $rxUnit = '\d+';
  252. break;
  253. case AddressType::T_IPv6:
  254. $unitSize = 4; // nibbles
  255. $maxUnits = 32;
  256. $isHex = true;
  257. $rxUnit = '[0-9A-Fa-f]';
  258. break;
  259. }
  260. $totBits = $unitSize * $maxUnits;
  261. $prefixUnits = (int) ($this->networkPrefix / $unitSize);
  262. $extraBits = ($totBits - $this->networkPrefix) % $unitSize;
  263. if ($extraBits !== 0) {
  264. $prefixUnits += 1;
  265. }
  266. $numVariants = 1 << $extraBits;
  267. $result = array();
  268. $unitsToRemove = $maxUnits - $prefixUnits;
  269. $initialPointer = preg_replace("/^(({$rxUnit})\.){{$unitsToRemove}}/", '', $this->getStartAddress()->getReverseDNSLookupName());
  270. $chunks = explode('.', $initialPointer, 2);
  271. for ($index = 0; $index < $numVariants; $index++) {
  272. if ($index !== 0) {
  273. $chunks[0] = $isHex ? dechex(1 + hexdec($chunks[0])) : (string) (1 + (int) $chunks[0]);
  274. }
  275. $result[] = implode('.', $chunks);
  276. }
  277. return $result;
  278. }
  279. }