Source for file dns.php
Documentation is available at dns.php
* @subpackage sThread_Module
* @author JoungKyun.Kim <http://oops.org>
* @copyright (c) 2015 OOPS.ORG
* @link http://pear.oops.org/package/sThread
* @see http://www.freesoft.org/CIE/RFC/1035/39.htm
* DNS 모듈에 사용할 수 있는 모듈 option은 다음과 같다.
* <li><b>query:</b> 질의할 도메인 또는 IP주소</li>
* <li><b>record:</b> 질의할 record type</li>
* sThread::execute ('kns.kornet.net:53|query=>a.com,record=>A', 2, 'udp');
* @subpackage sThread_Module
* @author JoungKyun.Kim <http://oops.org>
* @copyright (c) 2015 OOPS.ORG
* @link http://pear.oops.org/package/sThread
* @see http://www.freesoft.org/CIE/RFC/1035/39.htm
* 이 변수의 값이 true로 셋팅이 되면, clear_session
* method를 만들어 줘야 한다. 반대로 false 상태에서는
* clear_session method가 존재하지 않아도 상관이 없다.
// {{{ Per module properties
* @see http://msdn.microsoft.com/en-us/library/windows/desktop/cc982162(v=vs.85).aspx
static private $header_member = array ('id', 'flags', 'noq', 'noans', 'noauth', 'noadd');
// {{{ (void) sThread_DNS::__construct (void)
$this->port = &self::$port;
$this->dns = &self::$dns;
// {{{ (void) sThread_DNS::init (void)
self::$clearsession = true;
// {{{ (int) sThread_DNS::check_buf_status ($status)
* 현재 상태가 event read 상태인지 event write 상태인지
return Vari::EVENT_READY_SEND;
case self::DNS_RESPONSE :
return Vari::EVENT_READY_RECV;
return Vari::EVENT_READY_CLOSE;
return Vari::EVENT_UNKNOWN;
// {{{ (string) sThread_DNS::call_status ($status, $call = false)
* 현재의 status(integer) 또는 현재 status의 handler 이름을
* @param boolean true로 설정했을 경우 현재 status의 handler
case self::DNS_RESPONSE :
$r = Vari::EVENT_UNKNOWN;
if ( $call !== false && $r !== Vari::EVENT_UNKNOWN )
// {{{ (boolean) sThread_DNS::change_status (&$sess, $key)
* @param boolean 변경한 상태가 마지막 단계일 경우 false를
* @param stdClass sThread 세션 변수 reference
if ( $sess->status[$key] === self::DNS_CLOSE )
// {{{ (void) sThread_DNS::set_last_status (&$sess, $key)
* @param stdClass sThread 세션 변수 reference
$sess->status[$key] = self::DNS_CLOSE;
// {{{ (boolean) sThread_DNS::clear_session ($key) {
* session에서 사용한 변수(self::$sess)의 값을 정리한다.
* self::$clearsession == false 일 경우, clear_session method
// self::$dns 자체는 unset되지 않고, member들만 unset
* Handler는 call_status 메소드에 정의된 값들 중
* Vari::EVENT_UNKNOWN를 제외한 모든 status의 constant string을
* Handler 이름은 sThread_MODULE::call_status 메소드를
* handler는 다음의 구조를 가지며, 실제로 전송을 하거나 받는
* handler_name (&$ses, $key)
* write handler를 실제로 전송을 하지 않고 전송할
* handler_name (&$sess, $key, $recv)
* read handler의 반환값은 다음과 같이 지정을 해야 한다.
* false => 전송 받을 것이 남아 있음
* 이 의미는 sThread가 read handler에서 결과값에 따라
* true는 다음 단계로 전환을 하고, false는 현 status를
* 유지하며, null의 경우 connection을 종료를 한다.
// {{{ (string) sThread_DNS::dns_request (&$sess, $key)
* @param stdClass 세션 object
list ($host, $port, $type) = $sess->addr[$key];
Vari::$res->status[$key] = array (
"[DNS] Query domain is null"
$opt->record = self::QTYPE_A;
$opt->record = self::QTYPE_MX;
$opt->record = self::QTYPE_PTR;
$opt->record = self::QTYPE_NS;
$opt->record = self::QTYPE_CNAME;
$opt->record = self::QTYPE_SOA;
$opt->record = self::QTYPE_TXT;
$opt->record = self::QTYPE_AAAA;
$err = sprintf ('[DNS] Invalid query type : "%s"', $opt->record);
Vari::$res->status[$key] = array (
"{ $host}:{$port}", false, $err
$send = self::query ($key, $opt->query, $opt->record);
Vari::$res->status[$key] = array (
"{ $host}:{$port}", false, self::$dns[$key]->err
// {{{ (boolean) sThread_DNS::dns_response (&$sess, $key, $recv)
* @return bool|null결과 값은 다음과 같다.
* <li>true: 모든 전송이 완료</li>
* <li>false: 전송할 것이 남아 있음. readcallback에서
* false를 받으면 status를 유지한다.</li>
* <li>null: 전송 중 에러 발생</li>
* @param stdClass 세션 object
* @param mixed read callback에서 전송받은 누적 데이터
list ($host, $port, $type) = $sess->addr[$key];
$sess->recv[$key] .= $recv;
$rlen = strlen ($sess->recv[$key]);
// at least, dns header size is 32byte
self::$dns[$key]->recv->data = $sess->recv[$key];
self::$dns[$key]->recv->length = $rlen;
//if ( ($r = self::recv_header ($key, $sess->recv[$key])) === false ) {
if ( ($r = self::recv_parse ($key, $sess->recv[$key])) === false ) {
Vari::$res->status[$key] = array (
Vari::binaryDecode ($sess->recv[$key], true);
if ( self::$dns[$key]->recv->header->flags->rcode != 'NOERROR' ) {
$err = sprintf ('[DNS] Return RCODE flag "%s"', self::$dns[$key]->recv->header->flags->rcode);
Vari::$res->status[$key] = array ("{ $host}:{$port}", false, $err);
if ( self::$dns[$key]->recv->header->noans == 0 ) {
$err = '[DNS] No return result';
Vari::$res->status[$key] = array ("{ $host}:{$port}", false, $err);
if ( Vari::$result === true ) {
unset (self::$dns[0]->recv->resource->data);
$sess->data[$key] = self::$dns[$key]->recv->resource;
* ********************************************************************************
* ********************************************************************************
// {{{ private (binary|string) sThread_DNS::random_id ($encode = false)
private function random_id ($encode = false) {
return $encode ? pack ('n', $id) : $id;
// {{{ private (string) sThread_DNS::query_type ($v, $convert = false)
// if $convert set false, don't convert to decimal
private function query_type ($v, $convert = true) {
return sprintf ('Unknown: 0x%02x', $v);
// {{{ private (void) sThread_DNS::query_class ($v, $convert = false)
// if $convert set true, convert binary code
private function query_class ($v, $convert = true) {
return sprintf ('Unknown: 0x%02x', $v);
return sprintf ('Reserved: 0x%02x', $v);
/* {{{ private (void) sThread_DNS::make_header (&$buf)
* DNS Header Packet structure
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-------------------------------------------------------------+
* | Identification | flags |
* +------------------------------+------------------------------+
* | number of questions | number of answer RRs |
* +------------------------------+------------------------------+
* | number of authority RRs | number of additional RRs |
* +------------------------------+------------------------------+
* type => OP_QUERY nomal query
* OP_IQUERY inverse query
private function make_header ($key, &$buf) {
self::$dns[$key]->header_id = self::random_id (true);
$buf = self::$dns[$key]->header_id; // Identification
$buf .= pack ('n', 0x0100); // flags
$buf .= pack ('n', 1); // number of questions
$buf .= pack ('x6'); // rest
// {{{ private (binary) sThread_DNS::dns_string ($v)
// {{{ private (void) sThread_DNS::make_question (&$buf, $domain)
private function make_question (&$buf, $domain) {
$buf .= self::dns_string ($domain);
// {{{ private (object|boolean) sThread_DNS::query ($domain, $type)
private function query ($key, $domain, $type) {
self::make_header ($key, $buf);
if ( preg_match ('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $domain) )
$query_type = self::OP_IQUERY;
$query_type = self::OP_QUERY;
if ( $query_type === self::OP_IQUERY ) {
if ( count ($ip) !== 4 ) {
self::$dns[$key]->err = sprintf ('[DNS] %s is invalid ip format', $domain);
self::$dns[$key]->err = sprintf ('[DNS] %s is invalid ip format', $domain);
self::make_question ($buf, isset ($arpa) ? $arpa : $domain);
$buf .= pack ('n', $type);
$buf .= pack ('n', self::QCLASS_IN);
$header->length = strlen ($buf);
// {{{ private (string) sThread_DNS::recv_flags_opcode ($v)
private function recv_flags_opcode ($v) {
// {{{ private (void) sThread_DNS::recv_flags_rcode ($v)
private function recv_flags_rcode ($v) {
return 'NOT IMPLEMENTED';
// reserved for future use
/* {{{ private (void) sThread_DNS::recv_header_flags ($key, $v)
* +----+--------+----+----+----+----+--------+-------+
* | QR | opcode | AA | TC | RD | RA | (zero) | rcode |
* +----+--------+----+----+----+----+--------+-------+
private function recv_header_flags ($key, $v) {
$buf = &self::$dns[$key]->recv->header->flags;
$buf->opcode = self::recv_flags_opcode (substr ($v, 1, 4));
$buf->rcode = self::recv_flags_rcode (substr ($v, 12));
// {{{ private (boolean) sThread_DNS::recv_header ($key, $v)
private function recv_header ($key, $v) {
self::$dns[$key]->err = '[DNS] Recived header is over 12 characters';
if ( self::$dns[$key]->header_id !== substr ($v, 0, 2) ) {
$header_id = unpack ('n', self::$dns[$key]->header_id);
$recv_id = unpack ('n', substr ($v, 0, 2));
self::$dns[$key]->err = sprintf (
'[DNS] Don\'t match packet id (send: 0x%04x, recv 0x%04x)',
$header_id[1], $recv_id[1]
self::$dns[$key]->recv->header->data = substr ($v, 0, 12);
self::$dns[$key]->recv->header->length = 12;
$buf = unpack ('n*', self::$dns[$key]->recv->header->data);
for ( $i= 0; $i< 6; $i++ ) {
if ( self::$header_member[$i] == 'flags' ) {
Vari::objectInit (self::$dns[$key]->recv->header->{self::$header_member[$i]});
self::$dns[$key]->recv->header->{self::$header_member[$i]}->data = $buf[$i+ 1];
self::recv_header_flags ($key, $buf[$i+ 1]);
self::$dns[$key]->recv->header->{self::$header_member[$i]} = $buf[$i+ 1];
// {{{ private (object) sThread_DNS::length_coded_string (&$v)
private function length_coded_string (&$v) {
$r->data = substr ($v, 1, $len);
// {{{ private (void) sThread_DNS::recv_question ($key, $v)
private function recv_question ($key, $v) {
$ques = &self::$dns[$key]->recv->question;
while ( ($rr = self::length_coded_string ($z)) != null ) {
$ques->qname .= $rr->data . '.';
$ques->length += $rr->length;
$ques->type = self::query_type (substr ($z, 0, 2));
$ques->class = self::query_class (substr ($z, 2, 2));
$ques->data = substr ($v, 0, $ques->length);
// {{{ private (void) sThread_DNS::recv_rdata ($key, &$v)
private function recv_rdata ($key, &$v) {
$buf = self::$dns[$key]->recv->data;
$ip = unpack ('N', $v->rdata);
if ( strlen ($v->rdata) != 16 ) {
# Machine byte의 영향을 받을 수 있다. unpack을 사용하는 것이
# 좋을까? 일단은 이해를 위해서 나둔다. (이건 Intel용)
//$ipv6 = unpack ('n8', $v->rdata);
for ( $i= 0; $i< 16; $i+= 2 )
$rdata = vsprintf("%x:%x:%x:%x:%x:%x:%x:%x", $ipv6);
$mx = unpack ('n', $v->rdata[0] . $v->rdata[1]);
$v->rdata = substr ($v->rdata, 2);
for ( $i= 0; $i< $vlen; $i++ ) {
$len = ord ($v->rdata[$i]);
$pos = ord ($v->rdata[$i+ 1]);
$rlen = ord ($buf[$pos]);
$rdata .= substr ($buf, $pos + 1, $rlen) . '.';
$rlen = ord ($buf[$pos]);
$pos = ord ($buf[$pos + 1]);
$rlen = ord ($buf[$pos]);
$rdata .= substr ($v->rdata, $i + 1, $len) . '.';
// ----------------------------
// | MNAME (length(1) + string |
// ----------------------------
// | RNAME (length(1) + string |
// ----------------------------
// ----------------------------
// ----------------------------
// ----------------------------
// ----------------------------
// ----------------------------
for ( $i= 0; $i< $vlen; $i++ ) {
$len = ord ($v->rdata[$i]);
// 0x0c가 나오면 다음 자리에 전체 데이터
// (self::$dns[$key]->recv->data)에서 몇번째인지의
$pos = ord ($v->rdata[$i+ 1]);
$rlen = ord ($buf[$pos]);
$rdata .= substr ($buf, $pos + 1, $rlen) . '.';
$rlen = ord ($buf[$pos]);
$pos = ord ($buf[$pos + 1]);
$rlen = ord ($buf[$pos]);
$rdata .= substr ($v->rdata, $i + 1, $len) . '.';
if ( ($soa_field = @unpack ('N5', substr ($v->rdata, $i + 1))) ) {
foreach ( $soa_field as $vv )
$v->rdata = rtrim ($rdata);
$v->rdata = substr ($v->rdata, 1);
// {{{ private (void) sThread_DNS::recv_resource ($key, $v)
private function recv_resource ($key, $v) {
$header = &self::$dns[$key]->recv->header;
$question = &self::$dns[$key]->recv->question;
$start = $header->length + $question->length + 1;
self::$dns[$key]->recv->resource->data = substr ($v, $start);
self::$dns[$key]->recv->resource->length = strlen (self::$dns[$key]->recv->resource->data);
$res = &self::$dns[$key]->recv->resource;
$idx = 0; // position of resource
$idn = 0; // value of position of resource
$pnt = 0; // position of question
$limit = $limitchk = $header->noans ? $header->noans : 1;
for ( $i= 0; $i< $limit; $i++ ) {
$vname = ($limit > $limitchk) ? 'ns' : 'v';
$pnt = ord ($buf[++ $idx]);
$qpnt = $pnt - $header->length;
$qbuf = substr ($question->data, $qpnt);
while ( ($rr = self::length_coded_string ($qbuf)) != null ) {
$res->{$vname}[$i]->name .= $rr->data . '.';
$res->{$vname}[$i]->length += $rr->length;
$idx += $res->v[$i]->length;
$rdata = unpack ('n2type/N1ttl/n1rdlen', substr ($buf, $idx + 1));
$res->{$vname}[$i]->type = self::query_type ($rdata['type1'], false);
$res->{$vname}[$i]->class = self::query_class ($rdata['type2'], false);
$res->{$vname}[$i]->ttl = $rdata['ttl'];
$res->{$vname}[$i]->rdlen = $rdata['rdlen'];
$res->{$vname}[$i]->rdata = substr ($buf, $idx + 10 + 1, $res->{$vname}[$i]->rdlen);
self::recv_rdata ($key, $res->{$vname}[$i]);
$buf = substr ($buf, $idx + 10 + $res->{$vname}[$i]->rdlen + 1);
if ( ($i+ 1 == $limit) && $buf )
// {{{ private (void) sThread_DNS::recv_parse ($v)
private function recv_parse ($key, $v) {
self::$dns[$key]->recv->data = $v;
self::$dns[$key]->recv->length = strlen ($v);
$r = self::recv_header ($key, $v);
if ( $r !== false && Vari::$result === true ) {
self::recv_question ($key, $v);
self::recv_resource ($key, $v);
//print_r (self::$dns[$key]->recv);
|