IPv4.php 14 KB

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