IPv4.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. <?php
  2. namespace IPLib\Address;
  3. use IPLib\ParseStringFlag;
  4. use IPLib\Range\RangeInterface;
  5. use IPLib\Range\Subnet;
  6. use IPLib\Range\Type as RangeType;
  7. use IPLib\Service\BinaryMath;
  8. use IPLib\Service\NumberInChunks;
  9. /**
  10. * An IPv4 address.
  11. *
  12. * @phpstan-consistent-constructor
  13. */
  14. class IPv4 implements AddressInterface
  15. {
  16. /**
  17. * The string representation of the address.
  18. *
  19. * @var string
  20. *
  21. * @example '127.0.0.1'
  22. */
  23. protected $address;
  24. /**
  25. * The byte list of the IP address.
  26. *
  27. * @var int[]|null
  28. */
  29. protected $bytes;
  30. /**
  31. * The type of the range of this IP address.
  32. *
  33. * @var int|null
  34. */
  35. protected $rangeType;
  36. /**
  37. * A string representation of this address than can be used when comparing addresses and ranges.
  38. *
  39. * @var string|null
  40. */
  41. protected $comparableString;
  42. /**
  43. * An array containing RFC designated address ranges.
  44. *
  45. * @var \IPLib\Address\AssignedRange[]|null
  46. */
  47. private static $reservedRanges;
  48. /**
  49. * Initializes the instance.
  50. *
  51. * @param string $address
  52. */
  53. protected function __construct($address)
  54. {
  55. $this->address = $address;
  56. $this->bytes = null;
  57. $this->rangeType = null;
  58. $this->comparableString = null;
  59. }
  60. /**
  61. * {@inheritdoc}
  62. *
  63. * @see \IPLib\Address\AddressInterface::__toString()
  64. */
  65. public function __toString()
  66. {
  67. return $this->address;
  68. }
  69. /**
  70. * {@inheritdoc}
  71. *
  72. * @see \IPLib\Address\AddressInterface::getNumberOfBits()
  73. */
  74. public static function getNumberOfBits()
  75. {
  76. return 32;
  77. }
  78. /**
  79. * @deprecated since 1.17.0: use the parseString() method instead.
  80. * For upgrading:
  81. * - if $mayIncludePort is true, use the ParseStringFlag::MAY_INCLUDE_PORT flag
  82. * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
  83. *
  84. * @param string|mixed $address the address to parse
  85. * @param bool $mayIncludePort
  86. * @param bool $supportNonDecimalIPv4
  87. *
  88. * @return static|null
  89. *
  90. * @see \IPLib\Address\IPv4::parseString()
  91. * @since 1.1.0 added the $mayIncludePort argument
  92. * @since 1.10.0 added the $supportNonDecimalIPv4 argument
  93. */
  94. public static function fromString($address, $mayIncludePort = true, $supportNonDecimalIPv4 = false)
  95. {
  96. return static::parseString($address, 0 | ($mayIncludePort ? ParseStringFlag::MAY_INCLUDE_PORT : 0) | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
  97. }
  98. /**
  99. * Parse a string and returns an IPv4 instance if the string is valid, or null otherwise.
  100. *
  101. * @param string|mixed $address the address to parse
  102. * @param int $flags A combination or zero or more flags
  103. *
  104. * @return static|null
  105. *
  106. * @see \IPLib\ParseStringFlag
  107. * @since 1.17.0
  108. */
  109. public static function parseString($address, $flags = 0)
  110. {
  111. if (!is_string($address)) {
  112. return null;
  113. }
  114. $flags = (int) $flags;
  115. $matches = null;
  116. if ($flags & ParseStringFlag::ADDRESS_MAYBE_RDNS) {
  117. if (preg_match('/^([12]?[0-9]{1,2}\.[12]?[0-9]{1,2}\.[12]?[0-9]{1,2}\.[12]?[0-9]{1,2})\.in-addr\.arpa\.?$/i', $address, $matches)) {
  118. $address = implode('.', array_reverse(explode('.', $matches[1])));
  119. $flags = $flags & ~(ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED);
  120. }
  121. }
  122. if ($flags & ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED) {
  123. if (strpos($address, '.') === 0) {
  124. return null;
  125. }
  126. $lengthNonHex = '{1,11}';
  127. $lengthHex = '{1,8}';
  128. $chunk234Optional = true;
  129. } else {
  130. if (!strpos($address, '.')) {
  131. return null;
  132. }
  133. $lengthNonHex = '{1,3}';
  134. $lengthHex = '{1,2}';
  135. $chunk234Optional = false;
  136. }
  137. $rxChunk1 = "0?[0-9]{$lengthNonHex}";
  138. if ($flags & ParseStringFlag::IPV4_MAYBE_NON_DECIMAL) {
  139. $rxChunk1 = "(?:0[Xx]0*[0-9A-Fa-f]{$lengthHex})|(?:{$rxChunk1})";
  140. $onlyDecimal = false;
  141. } else {
  142. $onlyDecimal = true;
  143. }
  144. $rxChunk1 = "0*?({$rxChunk1})";
  145. $rxChunk234 = "\.{$rxChunk1}";
  146. if ($chunk234Optional) {
  147. $rxChunk234 = "(?:{$rxChunk234})?";
  148. }
  149. $rx = "{$rxChunk1}{$rxChunk234}{$rxChunk234}{$rxChunk234}";
  150. if ($flags & ParseStringFlag::MAY_INCLUDE_PORT) {
  151. $rx .= '(?::\d+)?';
  152. }
  153. if (!preg_match('/^' . $rx . '$/', $address, $matches)) {
  154. return null;
  155. }
  156. $math = new \IPLib\Service\UnsignedIntegerMath();
  157. $nums = array();
  158. $maxChunkIndex = count($matches) - 1;
  159. for ($i = 1; $i <= $maxChunkIndex; $i++) {
  160. $numBytes = $i === $maxChunkIndex ? 5 - $i : 1;
  161. $chunkBytes = $math->getBytes($matches[$i], $numBytes, $onlyDecimal);
  162. if ($chunkBytes === null) {
  163. return null;
  164. }
  165. $nums = array_merge($nums, $chunkBytes);
  166. }
  167. return new static(implode('.', $nums));
  168. }
  169. /**
  170. * Parse an array of bytes and returns an IPv4 instance if the array is valid, or null otherwise.
  171. *
  172. * @param array<int|mixed> $bytes
  173. *
  174. * @return static|null
  175. */
  176. public static function fromBytes(array $bytes)
  177. {
  178. $result = null;
  179. if (count($bytes) === 4) {
  180. $chunks = array_map(
  181. function ($byte) {
  182. return (is_int($byte) && $byte >= 0 && $byte <= 255) ? (string) $byte : false;
  183. },
  184. $bytes
  185. );
  186. if (in_array(false, $chunks, true) === false) {
  187. $result = new static(implode('.', $chunks));
  188. }
  189. }
  190. return $result;
  191. }
  192. /**
  193. * {@inheritdoc}
  194. *
  195. * @see \IPLib\Address\AddressInterface::toString()
  196. */
  197. public function toString($long = false)
  198. {
  199. if ($long) {
  200. return $this->getComparableString();
  201. }
  202. return $this->address;
  203. }
  204. /**
  205. * Get the octal representation of this IP address.
  206. *
  207. * @param bool $long
  208. *
  209. * @return string
  210. *
  211. * @since 1.10.0
  212. *
  213. * @example if $long == false: if the decimal representation is '0.7.8.255': '0.7.010.0377'
  214. * @example if $long == true: if the decimal representation is '0.7.8.255': '0000.0007.0010.0377'
  215. */
  216. public function toOctal($long = false)
  217. {
  218. $chunks = array();
  219. foreach ($this->getBytes() as $byte) {
  220. if ($long) {
  221. $chunks[] = sprintf('%04o', $byte);
  222. } else {
  223. $chunks[] = '0' . decoct($byte);
  224. }
  225. }
  226. return implode('.', $chunks);
  227. }
  228. /**
  229. * Get the hexadecimal representation of this IP address.
  230. *
  231. * @param bool $long
  232. *
  233. * @return string
  234. *
  235. * @since 1.10.0
  236. *
  237. * @example if $long == false: if the decimal representation is '0.9.10.255': '0.9.0xa.0xff'
  238. * @example if $long == true: if the decimal representation is '0.9.10.255': '0x00.0x09.0x0a.0xff'
  239. */
  240. public function toHexadecimal($long = false)
  241. {
  242. $chunks = array();
  243. foreach ($this->getBytes() as $byte) {
  244. if ($long) {
  245. $chunks[] = sprintf('0x%02x', $byte);
  246. } else {
  247. $chunks[] = '0x' . dechex($byte);
  248. }
  249. }
  250. return implode('.', $chunks);
  251. }
  252. /**
  253. * {@inheritdoc}
  254. *
  255. * @see \IPLib\Address\AddressInterface::getBytes()
  256. */
  257. public function getBytes()
  258. {
  259. if ($this->bytes === null) {
  260. $this->bytes = array_map(
  261. function ($chunk) {
  262. return (int) $chunk;
  263. },
  264. explode('.', $this->address)
  265. );
  266. }
  267. return $this->bytes;
  268. }
  269. /**
  270. * {@inheritdoc}
  271. *
  272. * @see \IPLib\Address\AddressInterface::getBits()
  273. */
  274. public function getBits()
  275. {
  276. $parts = array();
  277. foreach ($this->getBytes() as $byte) {
  278. $parts[] = sprintf('%08b', $byte);
  279. }
  280. return implode('', $parts);
  281. }
  282. /**
  283. * {@inheritdoc}
  284. *
  285. * @see \IPLib\Address\AddressInterface::getAddressType()
  286. */
  287. public function getAddressType()
  288. {
  289. return Type::T_IPv4;
  290. }
  291. /**
  292. * {@inheritdoc}
  293. *
  294. * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
  295. */
  296. public static function getDefaultReservedRangeType()
  297. {
  298. return RangeType::T_PUBLIC;
  299. }
  300. /**
  301. * {@inheritdoc}
  302. *
  303. * @see \IPLib\Address\AddressInterface::getReservedRanges()
  304. */
  305. public static function getReservedRanges()
  306. {
  307. if (self::$reservedRanges === null) {
  308. $reservedRanges = array();
  309. foreach (array(
  310. // RFC 5735
  311. '0.0.0.0/8' => array(RangeType::T_THISNETWORK, array('0.0.0.0/32' => RangeType::T_UNSPECIFIED)),
  312. // RFC 5735
  313. '10.0.0.0/8' => array(RangeType::T_PRIVATENETWORK),
  314. // RFC 6598
  315. '100.64.0.0/10' => array(RangeType::T_CGNAT),
  316. // RFC 5735
  317. '127.0.0.0/8' => array(RangeType::T_LOOPBACK),
  318. // RFC 5735
  319. '169.254.0.0/16' => array(RangeType::T_LINKLOCAL),
  320. // RFC 5735
  321. '172.16.0.0/12' => array(RangeType::T_PRIVATENETWORK),
  322. // RFC 5735
  323. '192.0.0.0/24' => array(RangeType::T_RESERVED),
  324. // RFC 5735
  325. '192.0.2.0/24' => array(RangeType::T_RESERVED),
  326. // RFC 5735
  327. '192.88.99.0/24' => array(RangeType::T_ANYCASTRELAY),
  328. // RFC 5735
  329. '192.168.0.0/16' => array(RangeType::T_PRIVATENETWORK),
  330. // RFC 5735
  331. '198.18.0.0/15' => array(RangeType::T_RESERVED),
  332. // RFC 5735
  333. '198.51.100.0/24' => array(RangeType::T_RESERVED),
  334. // RFC 5735
  335. '203.0.113.0/24' => array(RangeType::T_RESERVED),
  336. // RFC 5735
  337. '224.0.0.0/4' => array(RangeType::T_MULTICAST),
  338. // RFC 5735
  339. '240.0.0.0/4' => array(RangeType::T_RESERVED, array('255.255.255.255/32' => RangeType::T_LIMITEDBROADCAST)),
  340. ) as $range => $data) {
  341. $exceptions = array();
  342. if (isset($data[1])) {
  343. foreach ($data[1] as $exceptionRange => $exceptionType) {
  344. $subnet = Subnet::parseString($exceptionRange);
  345. /** @var Subnet $subnet */
  346. $exceptions[] = new AssignedRange($subnet, $exceptionType);
  347. }
  348. }
  349. $subnet = Subnet::parseString($range);
  350. /** @var Subnet $subnet */
  351. $reservedRanges[] = new AssignedRange($subnet, $data[0], $exceptions);
  352. }
  353. self::$reservedRanges = $reservedRanges;
  354. }
  355. return self::$reservedRanges;
  356. }
  357. /**
  358. * {@inheritdoc}
  359. *
  360. * @see \IPLib\Address\AddressInterface::getRangeType()
  361. */
  362. public function getRangeType()
  363. {
  364. if ($this->rangeType === null) {
  365. $rangeType = null;
  366. foreach (static::getReservedRanges() as $reservedRange) {
  367. $rangeType = $reservedRange->getAddressType($this);
  368. if ($rangeType !== null) {
  369. break;
  370. }
  371. }
  372. $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType;
  373. }
  374. return $this->rangeType;
  375. }
  376. /**
  377. * Create an IPv6 representation of this address (in 6to4 notation).
  378. *
  379. * @return \IPLib\Address\IPv6
  380. */
  381. public function toIPv6()
  382. {
  383. $myBytes = $this->getBytes();
  384. $ipv6 = IPv6::parseString('2002:' . sprintf('%02x', $myBytes[0]) . sprintf('%02x', $myBytes[1]) . ':' . sprintf('%02x', $myBytes[2]) . sprintf('%02x', $myBytes[3]) . '::');
  385. /** @var IPv6 $ipv6 */
  386. return $ipv6;
  387. }
  388. /**
  389. * Create an IPv6 representation of this address (in IPv6 IPv4-mapped notation).
  390. *
  391. * @return \IPLib\Address\IPv6
  392. *
  393. * @since 1.11.0
  394. */
  395. public function toIPv6IPv4Mapped()
  396. {
  397. $ipv6 = IPv6::fromBytes(array_merge(array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff), $this->getBytes()));
  398. /** @var IPv6 $ipv6 */
  399. return $ipv6;
  400. }
  401. /**
  402. * {@inheritdoc}
  403. *
  404. * @see \IPLib\Address\AddressInterface::getComparableString()
  405. */
  406. public function getComparableString()
  407. {
  408. if ($this->comparableString === null) {
  409. $chunks = array();
  410. foreach ($this->getBytes() as $byte) {
  411. $chunks[] = sprintf('%03d', $byte);
  412. }
  413. $this->comparableString = implode('.', $chunks);
  414. }
  415. return $this->comparableString;
  416. }
  417. /**
  418. * {@inheritdoc}
  419. *
  420. * @see \IPLib\Address\AddressInterface::matches()
  421. */
  422. public function matches(RangeInterface $range)
  423. {
  424. return $range->contains($this);
  425. }
  426. /**
  427. * {@inheritdoc}
  428. *
  429. * @see \IPLib\Address\AddressInterface::getAddressAtOffset()
  430. */
  431. public function getAddressAtOffset($n)
  432. {
  433. if (is_int($n)) {
  434. $thatChunks = NumberInChunks::fromInteger($n, NumberInChunks::CHUNKSIZE_BYTES);
  435. } elseif (($s = BinaryMath::getInstance()->normalizeIntegerString($n)) !== '') {
  436. $thatChunks = NumberInChunks::fromNumericString($s, NumberInChunks::CHUNKSIZE_BYTES);
  437. } else {
  438. return null;
  439. }
  440. $myBytes = $this->getBytes();
  441. while (isset($myBytes[1]) && $myBytes[0] === 0) {
  442. array_shift($myBytes);
  443. }
  444. $myChunks = new NumberInChunks(false, $myBytes, NumberInChunks::CHUNKSIZE_BYTES);
  445. $result = $myChunks->add($thatChunks);
  446. if ($result->negative || count($result->chunks) > 4) {
  447. return null;
  448. }
  449. return static::fromBytes(array_pad($result->chunks, -4, 0));
  450. }
  451. /**
  452. * {@inheritdoc}
  453. *
  454. * @see \IPLib\Address\AddressInterface::getNextAddress()
  455. */
  456. public function getNextAddress()
  457. {
  458. return $this->getAddressAtOffset(1);
  459. }
  460. /**
  461. * {@inheritdoc}
  462. *
  463. * @see \IPLib\Address\AddressInterface::getPreviousAddress()
  464. */
  465. public function getPreviousAddress()
  466. {
  467. return $this->getAddressAtOffset(-1);
  468. }
  469. /**
  470. * {@inheritdoc}
  471. *
  472. * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName()
  473. */
  474. public function getReverseDNSLookupName()
  475. {
  476. return implode(
  477. '.',
  478. array_reverse($this->getBytes())
  479. ) . '.in-addr.arpa';
  480. }
  481. /**
  482. * {@inheritdoc}
  483. *
  484. * @see \IPLib\Address\AddressInterface::shift()
  485. */
  486. public function shift($bits)
  487. {
  488. $bits = (int) $bits;
  489. if ($bits === 0) {
  490. return $this;
  491. }
  492. $absBits = abs($bits);
  493. if ($absBits >= 32) {
  494. return new self('0.0.0.0');
  495. }
  496. $pad = str_repeat('0', $absBits);
  497. $paddedBits = $this->getBits();
  498. if ($bits > 0) {
  499. $paddedBits = $pad . substr($paddedBits, 0, -$bits);
  500. } else {
  501. $paddedBits = substr($paddedBits, $absBits) . $pad;
  502. }
  503. $bytes = array_map('bindec', str_split($paddedBits, 8));
  504. return new static(implode('.', $bytes));
  505. }
  506. /**
  507. * {@inheritdoc}
  508. *
  509. * @see \IPLib\Address\AddressInterface::add()
  510. */
  511. public function add(AddressInterface $other)
  512. {
  513. if (!$other instanceof self) {
  514. return null;
  515. }
  516. $myBytes = $this->getBytes();
  517. $otherBytes = $other->getBytes();
  518. $sum = array_fill(0, 4, 0);
  519. $carry = 0;
  520. for ($index = 3; $index >= 0; $index--) {
  521. $byte = $myBytes[$index] + $otherBytes[$index] + $carry;
  522. if ($byte > 0xFF) {
  523. $carry = $byte >> 8;
  524. $byte &= 0xFF;
  525. } else {
  526. $carry = 0;
  527. }
  528. $sum[$index] = $byte;
  529. }
  530. if ($carry !== 0) {
  531. return null;
  532. }
  533. return new static(implode('.', $sum));
  534. }
  535. }