IPv4.php 16 KB

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