IPv4.php 12 KB

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