PageRenderTime 39ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/src/React/Dns/Protocol/Parser.php

https://github.com/jurajseffer/react
PHP | 228 lines | 165 code | 58 blank | 5 comment | 25 complexity | b178e1ab9ffae4f51410d3310d4a481d MD5 | raw file
  1. <?php
  2. namespace React\Dns\Protocol;
  3. use React\Dns\Model\Message;
  4. use React\Dns\Model\Record;
  5. /**
  6. * DNS protocol parser
  7. *
  8. * Obsolete and uncommon types and classes are not implemented.
  9. */
  10. class Parser
  11. {
  12. public function parseChunk($data, Message $message)
  13. {
  14. $message->data .= $data;
  15. if (!$message->header->get('id')) {
  16. if (!$this->parseHeader($message)) {
  17. return;
  18. }
  19. }
  20. if ($message->header->get('qdCount') != count($message->questions)) {
  21. if (!$this->parseQuestion($message)) {
  22. return;
  23. }
  24. }
  25. if ($message->header->get('anCount') != count($message->answers)) {
  26. if (!$this->parseAnswer($message)) {
  27. return;
  28. }
  29. }
  30. return $message;
  31. }
  32. public function parseHeader(Message $message)
  33. {
  34. if (strlen($message->data) < 12) {
  35. return;
  36. }
  37. $header = substr($message->data, 0, 12);
  38. $message->consumed += 12;
  39. list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', $header));
  40. $rcode = $fields & bindec('1111');
  41. $z = ($fields >> 4) & bindec('111');
  42. $ra = ($fields >> 7) & 1;
  43. $rd = ($fields >> 8) & 1;
  44. $tc = ($fields >> 9) & 1;
  45. $aa = ($fields >> 10) & 1;
  46. $opcode = ($fields >> 11) & bindec('1111');
  47. $qr = ($fields >> 15) & 1;
  48. $vars = compact('id', 'qdCount', 'anCount', 'nsCount', 'arCount',
  49. 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode');
  50. foreach ($vars as $name => $value) {
  51. $message->header->set($name, $value);
  52. }
  53. return $message;
  54. }
  55. public function parseQuestion(Message $message)
  56. {
  57. if (strlen($message->data) < 2) {
  58. return;
  59. }
  60. $consumed = $message->consumed;
  61. list($labels, $consumed) = $this->readLabels($message->data, $consumed);
  62. if (null === $labels) {
  63. return;
  64. }
  65. if (strlen($message->data) - $consumed < 4) {
  66. return;
  67. }
  68. list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
  69. $consumed += 4;
  70. $message->consumed = $consumed;
  71. $message->questions[] = array(
  72. 'name' => implode('.', $labels),
  73. 'type' => $type,
  74. 'class' => $class,
  75. );
  76. if ($message->header->get('qdCount') != count($message->questions)) {
  77. return $this->parseQuestion($message);
  78. }
  79. return $message;
  80. }
  81. public function parseAnswer(Message $message)
  82. {
  83. if (strlen($message->data) < 2) {
  84. return;
  85. }
  86. $consumed = $message->consumed;
  87. list($labels, $consumed) = $this->readLabels($message->data, $consumed);
  88. if (null === $labels) {
  89. return;
  90. }
  91. if (strlen($message->data) - $consumed < 10) {
  92. return;
  93. }
  94. list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
  95. $consumed += 4;
  96. list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4)));
  97. $consumed += 4;
  98. list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2)));
  99. $consumed += 2;
  100. $rdata = null;
  101. if (Message::TYPE_A === $type) {
  102. $ip = substr($message->data, $consumed, $rdLength);
  103. $consumed += $rdLength;
  104. $rdata = inet_ntop($ip);
  105. }
  106. if (Message::TYPE_CNAME === $type) {
  107. list($bodyLabels, $consumed) = $this->readLabels($message->data, $consumed);
  108. $rdata = implode('.', $bodyLabels);
  109. }
  110. $message->consumed = $consumed;
  111. $name = implode('.', $labels);
  112. $ttl = $this->signedLongToUnsignedLong($ttl);
  113. $record = new Record($name, $type, $class, $ttl, $rdata);
  114. $message->answers[] = $record;
  115. if ($message->header->get('anCount') != count($message->answers)) {
  116. return $this->parseAnswer($message);
  117. }
  118. return $message;
  119. }
  120. private function readLabels($data, $consumed)
  121. {
  122. $labels = array();
  123. while (true) {
  124. if ($this->isEndOfLabels($data, $consumed)) {
  125. $consumed += 1;
  126. break;
  127. }
  128. if ($this->isCompressedLabel($data, $consumed)) {
  129. list($newLabels, $consumed) = $this->getCompressedLabel($data, $consumed);
  130. $labels = array_merge($labels, $newLabels);
  131. break;
  132. }
  133. $length = ord(substr($data, $consumed, 1));
  134. $consumed += 1;
  135. if (strlen($data) - $consumed < $length) {
  136. return array(null, null);
  137. }
  138. $labels[] = substr($data, $consumed, $length);
  139. $consumed += $length;
  140. }
  141. return array($labels, $consumed);
  142. }
  143. public function isEndOfLabels($data, $consumed)
  144. {
  145. $length = ord(substr($data, $consumed, 1));
  146. return 0 === $length;
  147. }
  148. public function getCompressedLabel($data, $consumed)
  149. {
  150. list($nameOffset, $consumed) = $this->getCompressedLabelOffset($data, $consumed);
  151. list($labels) = $this->readLabels($data, $nameOffset);
  152. return array($labels, $consumed);
  153. }
  154. public function isCompressedLabel($data, $consumed)
  155. {
  156. $mask = 0xc000; // 1100000000000000
  157. list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
  158. return (bool) ($peek & $mask);
  159. }
  160. public function getCompressedLabelOffset($data, $consumed)
  161. {
  162. $mask = 0x3fff; // 0011111111111111
  163. list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
  164. return array($peek & $mask, $consumed + 2);
  165. }
  166. public function signedLongToUnsignedLong($i)
  167. {
  168. return $i & 0x80000000 ? $i - 0xffffffff : $i;
  169. }
  170. }