Packet.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. <?php
  2. /*
  3. * License Information:
  4. *
  5. * Net_DNS: A resolver library for PHP
  6. * Copyright (c) 2002-2003 Eric Kilfoil eric@ypass.net
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU Lesser General Public
  10. * License as published by the Free Software Foundation; either
  11. * version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * Lesser General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Lesser General Public
  19. * License along with this library; if not, write to the Free Software
  20. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  21. */
  22. /* Net_DNS_Packet object definition {{{ */
  23. /**
  24. * A object represation of a DNS packet (RFC1035)
  25. *
  26. * This object is used to manage a DNS packet. It contains methods for
  27. * DNS packet compression as defined in RFC1035, as well as parsing a DNS
  28. * packet response from a DNS server, or building a DNS packet from the
  29. * instance variables contained in the class.
  30. *
  31. * @package Net_DNS
  32. */
  33. class Net_DNS_Packet
  34. {
  35. /* class variable definitions {{{ */
  36. /**
  37. * debugging flag
  38. *
  39. * If set to TRUE (non-zero), debugging code will be displayed as the
  40. * packet is parsed.
  41. *
  42. * @var boolean $debug
  43. * @access public
  44. */
  45. var $debug;
  46. /**
  47. * A packet Header object.
  48. *
  49. * An object of type Net_DNS_Header which contains the header
  50. * information of the packet.
  51. *
  52. * @var object Net_DNS_Header $header
  53. * @access public
  54. */
  55. var $header;
  56. /**
  57. * A hash of compressed labels
  58. *
  59. * A list of all labels which have been compressed in the DNS packet
  60. * and the location offset of the label within the packet.
  61. *
  62. * @var array $compnames
  63. */
  64. var $compnames;
  65. /**
  66. * The origin of the packet, if the packet is a server response.
  67. *
  68. * This contains a string containing the IP address of the name server
  69. * from which the answer was given.
  70. *
  71. * @var string $answerfrom
  72. * @access public
  73. */
  74. var $answerfrom;
  75. /**
  76. * The size of the answer packet, if the packet is a server response.
  77. *
  78. * This contains a integer containing the size of the DNS packet the
  79. * server responded with if this packet was received by a DNS server
  80. * using the query() method.
  81. *
  82. * @var string $answersize
  83. * @access public
  84. */
  85. var $answersize;
  86. /**
  87. * An array of Net_DNS_Question objects
  88. *
  89. * Contains all of the questions within the packet. Each question is
  90. * stored as an object of type Net_DNS_Question.
  91. *
  92. * @var array $question
  93. * @access public
  94. */
  95. var $question;
  96. /**
  97. * An array of Net_DNS_RR ANSWER objects
  98. *
  99. * Contains all of the answer RRs within the packet. Each answer is
  100. * stored as an object of type Net_DNS_RR.
  101. *
  102. * @var array $answer
  103. * @access public
  104. */
  105. var $answer;
  106. /**
  107. * An array of Net_DNS_RR AUTHORITY objects
  108. *
  109. * Contains all of the authority RRs within the packet. Each authority is
  110. * stored as an object of type Net_DNS_RR.
  111. *
  112. * @var array $authority
  113. * @access public
  114. */
  115. var $authority;
  116. /**
  117. * An array of Net_DNS_RR ADDITIONAL objects
  118. *
  119. * Contains all of the additional RRs within the packet. Each additional is
  120. * stored as an object of type Net_DNS_RR.
  121. *
  122. * @var array $additional
  123. * @access public
  124. */
  125. var $additional;
  126. /* }}} */
  127. /* class constructor - Net_DNS_Packet($debug = FALSE) {{{ */
  128. /*
  129. * unfortunately (or fortunately), we can't follow the same
  130. * silly method for determining if name is a hostname or a packet
  131. * stream in PHP, since there is no ref() function. So we're going
  132. * to define a new method called parse to deal with this
  133. * circumstance and another method called buildQuestion to build a question.
  134. * I like it better that way anyway.
  135. */
  136. /**
  137. * Initalizes a Net_DNS_Packet object
  138. *
  139. * @param boolean $debug Turns debugging on or off
  140. */
  141. function Net_DNS_Packet($debug = FALSE)
  142. {
  143. $this->debug = $debug;
  144. $this->compnames = array();
  145. }
  146. /* }}} */
  147. /* Net_DNS_Packet::buildQuestion($name, $type = "A", $class = "IN") {{{ */
  148. /**
  149. * Adds a DNS question to the DNS packet
  150. *
  151. * @param string $name The name of the record to query
  152. * @param string $type The type of record to query
  153. * @param string $class The class of record to query
  154. * @see Net_DNS::typesbyname(), Net_DNS::classesbyname()
  155. */
  156. function buildQuestion($name, $type = 'A', $class = 'IN')
  157. {
  158. $this->header = new Net_DNS_Header();
  159. $this->header->qdcount = 1;
  160. $this->question[0] = new Net_DNS_Question($name, $type, $class);
  161. $this->answer = NULL;
  162. $this->authority = NULL;
  163. $this->additional = NULL;
  164. if ($this->debug) {
  165. $this->display();
  166. }
  167. }
  168. /* }}} */
  169. /* Net_DNS_Packet::parse($data) {{{ */
  170. /**
  171. * Parses a DNS packet returned by a DNS server
  172. *
  173. * Parses a complete DNS packet and builds an object hierarchy
  174. * containing all of the parts of the packet:
  175. * <ul>
  176. * <li>HEADER
  177. * <li>QUESTION
  178. * <li>ANSWER || PREREQUISITE
  179. * <li>ADDITIONAL || UPDATE
  180. * <li>AUTHORITY
  181. * </ul>
  182. *
  183. * @param string $data A binary string containing a DNS packet
  184. * @return boolean TRUE on success, NULL on parser error
  185. */
  186. function parse($data)
  187. {
  188. if ($this->debug) {
  189. echo ';; HEADER SECTION' . "\n";
  190. }
  191. $this->header = new Net_DNS_Header($data);
  192. if ($this->debug) {
  193. $this->header->display();
  194. }
  195. /*
  196. * Print and parse the QUESTION section of the packet
  197. */
  198. if ($this->debug) {
  199. echo "\n";
  200. $section = ($this->header->opcode == 'UPDATE') ? 'ZONE' : 'QUESTION';
  201. echo ";; $section SECTION (" . $this->header->qdcount . ' record' .
  202. ($this->header->qdcount == 1 ? '' : 's') . ")\n";
  203. }
  204. $offset = 12;
  205. $this->question = array();
  206. for ($ctr = 0; $ctr < $this->header->qdcount; $ctr++) {
  207. list($qobj, $offset) = $this->parse_question($data, $offset);
  208. if (is_null($qobj)) {
  209. return(NULL);
  210. }
  211. $this->question[count($this->question)] = $qobj;
  212. if ($this->debug) {
  213. echo ";;\n;";
  214. $qobj->display();
  215. }
  216. }
  217. /*
  218. * Print and parse the PREREQUISITE or ANSWER section of the packet
  219. */
  220. if ($this->debug) {
  221. echo "\n";
  222. $section = ($this->header->opcode == 'UPDATE') ? 'PREREQUISITE' :'ANSWER';
  223. echo ";; $section SECTION (" .
  224. $this->header->ancount . ' record' .
  225. (($this->header->ancount == 1) ? '' : 's') .
  226. ")\n";
  227. }
  228. $this->answer = array();
  229. for ($ctr = 0; $ctr < $this->header->ancount; $ctr++) {
  230. list($rrobj, $offset) = $this->parse_rr($data, $offset);
  231. if (is_null($rrobj)) {
  232. return(NULL);
  233. }
  234. array_push($this->answer, $rrobj);
  235. if ($this->debug) {
  236. $rrobj->display();
  237. }
  238. }
  239. /*
  240. * Print and parse the UPDATE or AUTHORITY section of the packet
  241. */
  242. if ($this->debug) {
  243. echo "\n";
  244. $section = ($this->header->opcode == 'UPDATE') ? 'UPDATE' : 'AUTHORITY';
  245. echo ";; $section SECTION (" .
  246. $this->header->nscount . ' record' .
  247. (($this->header->nscount == 1) ? '' : 's') .
  248. ")\n";
  249. }
  250. $this->authority = array();
  251. for ($ctr = 0; $ctr < $this->header->nscount; $ctr++) {
  252. list($rrobj, $offset) = $this->parse_rr($data, $offset);
  253. if (is_null($rrobj)) {
  254. return(NULL);
  255. }
  256. array_push($this->authority, $rrobj);
  257. if ($this->debug) {
  258. $rrobj->display();
  259. }
  260. }
  261. /*
  262. * Print and parse the ADDITIONAL section of the packet
  263. */
  264. if ($this->debug) {
  265. echo "\n";
  266. echo ';; ADDITIONAL SECTION (' .
  267. $this->header->arcount . ' record' .
  268. (($this->header->arcount == 1) ? '' : 's') .
  269. ")\n";
  270. }
  271. $this->additional = array();
  272. for ($ctr = 0; $ctr < $this->header->arcount; $ctr++) {
  273. list($rrobj, $offset) = $this->parse_rr($data, $offset);
  274. if (is_null($rrobj)) {
  275. return(NULL);
  276. }
  277. array_push($this->additional, $rrobj);
  278. if ($this->debug) {
  279. $rrobj->display();
  280. }
  281. }
  282. return(TRUE);
  283. }
  284. /* }}} */
  285. /* Net_DNS_Packet::data() {{{*/
  286. /**
  287. * Build a packet from a Packet object hierarchy
  288. *
  289. * Builds a valid DNS packet suitable for sending to a DNS server or
  290. * resolver client containing all of the data in the packet hierarchy.
  291. *
  292. * @return string A binary string containing a DNS Packet
  293. */
  294. function data()
  295. {
  296. $data = $this->header->data();
  297. for ($ctr = 0; $ctr < $this->header->qdcount; $ctr++) {
  298. $data .= $this->question[$ctr]->data($this, strlen($data));
  299. }
  300. for ($ctr = 0; $ctr < $this->header->ancount; $ctr++) {
  301. $data .= $this->answer[$ctr]->data($this, strlen($data));
  302. }
  303. for ($ctr = 0; $ctr < $this->header->nscount; $ctr++) {
  304. $data .= $this->authority[$ctr]->data($this, strlen($data));
  305. }
  306. for ($ctr = 0; $ctr < $this->header->arcount; $ctr++) {
  307. $data .= $this->additional[$ctr]->data($this, strlen($data));
  308. }
  309. return($data);
  310. }
  311. /*}}}*/
  312. /* Net_DNS_Packet::dn_comp($name, $offset) {{{*/
  313. /**
  314. * DNS packet compression method
  315. *
  316. * Returns a domain name compressed for a particular packet object, to
  317. * be stored beginning at the given offset within the packet data. The
  318. * name will be added to a running list of compressed domain names for
  319. * future use.
  320. *
  321. * @param string $name The name of the label to compress
  322. * @param integer $offset The location offset in the packet to where
  323. * the label will be stored.
  324. * @return string $compname A binary string containing the compressed
  325. * label.
  326. * @see Net_DNS_Packet::dn_expand()
  327. */
  328. function dn_comp($name, $offset)
  329. {
  330. $names = explode('.', $name);
  331. $compname = '';
  332. while (count($names)) {
  333. $dname = join('.', $names);
  334. if (isset($this->compnames[$dname])) {
  335. $compname .= pack('n', 0xc000 | $this->compnames[$dname]);
  336. break;
  337. }
  338. $this->compnames[$dname] = $offset;
  339. $first = array_shift($names);
  340. $length = strlen($first);
  341. $compname .= pack('Ca*', $length, $first);
  342. $offset += $length + 1;
  343. }
  344. if (! count($names)) {
  345. $compname .= pack('C', 0);
  346. }
  347. return($compname);
  348. }
  349. /*}}}*/
  350. /* Net_DNS_Packet::dn_expand($packet, $offset) {{{ */
  351. /**
  352. * DNS packet decompression method
  353. *
  354. * Expands the domain name stored at a particular location in a DNS
  355. * packet. The first argument is a variable containing the packet
  356. * data. The second argument is the offset within the packet where
  357. * the (possibly) compressed domain name is stored.
  358. *
  359. * @param string $packet The packet data
  360. * @param integer $offset The location offset in the packet of the
  361. * label to decompress.
  362. * @return array Returns a list of type array($name, $offset) where
  363. * $name is the name of the label which was decompressed
  364. * and $offset is the offset of the next field in the
  365. * packet. Returns array(NULL, NULL) on error
  366. */
  367. function dn_expand($packet, $offset)
  368. {
  369. $packetlen = strlen($packet);
  370. $int16sz = 2;
  371. $name = '';
  372. while (1) {
  373. if ($packetlen < ($offset + 1)) {
  374. return(array(NULL, NULL));
  375. }
  376. $a = unpack("@$offset/Cchar", $packet);
  377. $len = $a['char'];
  378. if ($len == 0) {
  379. $offset++;
  380. break;
  381. } else if (($len & 0xc0) == 0xc0) {
  382. if ($packetlen < ($offset + $int16sz)) {
  383. return(array(NULL, NULL));
  384. }
  385. $ptr = unpack("@$offset/ni", $packet);
  386. $ptr = $ptr['i'];
  387. $ptr = $ptr & 0x3fff;
  388. $name2 = Net_DNS_Packet::dn_expand($packet, $ptr);
  389. if (is_null($name2[0])) {
  390. return(array(NULL, NULL));
  391. }
  392. $name .= $name2[0];
  393. $offset += $int16sz;
  394. break;
  395. } else {
  396. $offset++;
  397. if ($packetlen < ($offset + $len)) {
  398. return(array(NULL, NULL));
  399. }
  400. $elem = substr($packet, $offset, $len);
  401. $name .= $elem . '.';
  402. $offset += $len;
  403. }
  404. }
  405. $name = ereg_replace('\.$', '', $name);
  406. return(array($name, $offset));
  407. }
  408. /*}}}*/
  409. /* Net_DNS_Packet::label_extract($packet, $offset) {{{ */
  410. /**
  411. * DNS packet decompression method
  412. *
  413. * Extracts the label stored at a particular location in a DNS
  414. * packet. The first argument is a variable containing the packet
  415. * data. The second argument is the offset within the packet where
  416. * the (possibly) compressed domain name is stored.
  417. *
  418. * @param string $packet The packet data
  419. * @param integer $offset The location offset in the packet of the
  420. * label to extract.
  421. * @return array Returns a list of type array($name, $offset) where
  422. * $name is the name of the label which was decompressed
  423. * and $offset is the offset of the next field in the
  424. * packet. Returns array(NULL, NULL) on error
  425. */
  426. function label_extract($packet, $offset)
  427. {
  428. $packetlen = strlen($packet);
  429. $name = '';
  430. if ($packetlen < ($offset + 1)) {
  431. return(array(NULL, NULL));
  432. }
  433. $a = unpack("@$offset/Cchar", $packet);
  434. $len = $a['char'];
  435. $offset++;
  436. if ($len + $offset > $packetlen) {
  437. $name = substr($packet, $offset);
  438. $offset = $packetlen;
  439. } else {
  440. $name = substr($packet, $offset, $len);
  441. $offset += $len;
  442. }
  443. return(array($name, $offset));
  444. }
  445. /*}}}*/
  446. /* Net_DNS_Packet::parse_question($data, $offset) {{{ */
  447. /**
  448. * Parses the question section of a packet
  449. *
  450. * Examines a DNS packet at the specified offset and parses the data
  451. * of the QUESTION section.
  452. *
  453. * @param string $data The packet data returned from the server
  454. * @param integer $offset The location offset of the start of the
  455. * question section.
  456. * @return array An array of type array($q, $offset) where $q
  457. * is a Net_DNS_Question object and $offset is the
  458. * location of the next section of the packet which
  459. * needs to be parsed.
  460. */
  461. function parse_question($data, $offset)
  462. {
  463. list($qname, $offset) = $this->dn_expand($data, $offset);
  464. if (is_null($qname)) {
  465. return(array(NULL, NULL));
  466. }
  467. if (strlen($data) < ($offset + 2 * 2)) {
  468. return(array(NULL, NULL));
  469. }
  470. $q = unpack("@$offset/n2int", $data);
  471. $qtype = $q['int1'];
  472. $qclass = $q['int2'];
  473. $offset += 2 * 2;
  474. $qtype = Net_DNS::typesbyval($qtype);
  475. $qclass = Net_DNS::classesbyval($qclass);
  476. $q = new Net_DNS_Question($qname, $qtype, $qclass);
  477. return(array($q, $offset));
  478. }
  479. /*}}}*/
  480. /* Net_DNS_Packet::parse_rr($data, $offset) {{{ */
  481. /**
  482. * Parses a resource record section of a packet
  483. *
  484. * Examines a DNS packet at the specified offset and parses the data
  485. * of a section which contains RRs (ANSWER, AUTHORITY, ADDITIONAL).
  486. *
  487. * @param string $data The packet data returned from the server
  488. * @param integer $offset The location offset of the start of the resource
  489. * record section.
  490. * @return array An array of type array($rr, $offset) where $rr
  491. * is a Net_DNS_RR object and $offset is the
  492. * location of the next section of the packet which
  493. * needs to be parsed.
  494. */
  495. function parse_rr($data, $offset)
  496. {
  497. list($name, $offset) = $this->dn_expand($data, $offset);
  498. if (! strlen($name)) {
  499. return(array(NULL, NULL));
  500. }
  501. if (strlen($data) < ($offset + 10)) {
  502. return(array(NULL, NULL));
  503. }
  504. $a = unpack("@$offset/n2tc/Nttl/nrdlength", $data);
  505. $type = $a['tc1'];
  506. $class = $a['tc2'];
  507. $ttl = $a['ttl'];
  508. $rdlength = $a['rdlength'];
  509. $type = Net_DNS::typesbyval($type);
  510. $class = Net_DNS::classesbyval($class);
  511. $offset += 10;
  512. if (strlen($data) < ($offset + $rdlength)) {
  513. return(array(NULL, NULL));
  514. }
  515. $rrobj = new Net_DNS_RR(array($name,
  516. $type,
  517. $class,
  518. $ttl,
  519. $rdlength,
  520. $data,
  521. $offset));
  522. if (is_null($rrobj)) {
  523. return(array(NULL, NULL));
  524. }
  525. $offset += $rdlength;
  526. return(array($rrobj, $offset));
  527. }
  528. /* }}} */
  529. /* Net_DNS_Packet::display() {{{ */
  530. /**
  531. * Prints out the packet in a human readable formatted string
  532. */
  533. function display()
  534. {
  535. echo $this->string();
  536. }
  537. /*}}}*/
  538. /* Net_DNS_Packet::string() {{{ */
  539. /**
  540. * Builds a human readable formatted string representing a packet
  541. */
  542. function string()
  543. {
  544. $retval = '';
  545. if ($this->answerfrom) {
  546. $retval .= ';; Answer received from ' . $this->answerfrom . '(' .
  547. $this->answersize . " bytes)\n;;\n";
  548. }
  549. $retval .= ";; HEADER SECTION\n";
  550. $retval .= $this->header->string();
  551. $retval .= "\n";
  552. $section = ($this->header->opcode == 'UPDATE') ? 'ZONE' : 'QUESTION';
  553. $retval .= ";; $section SECTION (" . $this->header->qdcount .
  554. ' record' . ($this->header->qdcount == 1 ? '' : 's') .
  555. ")\n";
  556. foreach ($this->question as $qr) {
  557. $retval .= ';; ' . $qr->string() . "\n";
  558. }
  559. $section = ($this->header->opcode == 'UPDATE') ? 'PREREQUISITE' : 'ANSWER';
  560. $retval .= "\n;; $section SECTION (" . $this->header->ancount .
  561. ' record' . ($this->header->ancount == 1 ? '' : 's') .
  562. ")\n";
  563. if (is_array($this->answer)) {
  564. foreach ($this->answer as $ans) {
  565. $retval .= ';; ' . $ans->string() . "\n";
  566. }
  567. }
  568. $section = ($this->header->opcode == 'UPDATE') ? 'UPDATE' : 'AUTHORITY';
  569. $retval .= "\n;; $section SECTION (" . $this->header->nscount .
  570. ' record' . ($this->header->nscount == 1 ? '' : 's') .
  571. ")\n";
  572. if (is_array($this->authority)) {
  573. foreach ($this->authority as $auth) {
  574. $retval .= ';; ' . $auth->string() . "\n";
  575. }
  576. }
  577. $retval .= "\n;; ADDITIONAL SECTION (" . $this->header->arcount .
  578. ' record' . ($this->header->arcount == 1 ? '' : 's') .
  579. ")\n";
  580. if (is_array($this->additional)) {
  581. foreach ($this->additional as $addl) {
  582. $retval .= ';; ' . $addl->string() . "\n";
  583. }
  584. }
  585. $retval .= "\n\n";
  586. return($retval);
  587. }
  588. /*}}}*/
  589. }
  590. /* }}} */
  591. /* VIM settings {{{
  592. * Local variables:
  593. * tab-width: 4
  594. * c-basic-offset: 4
  595. * soft-stop-width: 4
  596. * c indent on
  597. * End:
  598. * vim600: sw=4 ts=4 sts=4 cindent fdm=marker et
  599. * vim<600: sw=4 ts=4
  600. * }}} */
  601. ?>