├── .gitignore ├── README.md ├── composer.json ├── config ├── config.php └── dns_record.json ├── libs ├── AbstractStorageProvider.php ├── Providers │ ├── HostsProvider.php │ ├── JsonProvider.php │ └── RecursiveProvider.php ├── RecordTypeEnum.php ├── Server.php └── StackableResolver.php └── server.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | composer.phar 3 | .project 4 | /vendor/ 5 | .README.md.html 6 | /.idea 7 | /tests/clover.xml 8 | /nbproject 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AwayDNS - A pure PHP DNS Server 2 | ============== 3 | 4 | This is an Authoritative DNS Server written in pure PHP using swoole extension. 5 | It will listen to DNS request on the default port (Default: port 53) and give answers about any donamin that it has DNS records for. 6 | This class can be used to give DNS responses dynamically based on your pre-existing PHP code. 7 | 8 | Support Record Types 9 | ==================== 10 | 11 | * A 12 | * NS 13 | * CNAME 14 | * SOA 15 | * PTR 16 | * MX 17 | * TXT 18 | * AAAA 19 | * OPT 20 | * AXFR 21 | * ANY 22 | 23 | PHP Requirements 24 | ================ 25 | 26 | * `PHP 5.4+` 27 | * PHP extension: `swoole` 28 | 29 | Thanks 30 | ================ 31 | This project is based on yswery/PHP-DNS-SERVER 32 | 33 | License 34 | ================ 35 | MIT License -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "away/dns", 3 | "description": "A DNS server implementation in pure PHP using swoole extension.", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Tom", 8 | "email": "tom@awaysoft.com" 9 | } 10 | ], 11 | "require": {}, 12 | "autoload": { 13 | "psr-4": { 14 | "away\\DNS\\": "libs/" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'ip' => '0.0.0.0', 5 | 'port' => 53, 6 | 'work_num' => 1 7 | ], 8 | 'providers' => [ 9 | 'hosts' => [ 10 | // 'path' => '/etc/hosts', /* Load system default file when no setting */ 11 | 'default_ttl' => 300, 12 | 'refresh_time' => 60, 13 | ], 14 | 'json' => [ 15 | 'data' => 'dns_record.json', 16 | 'default_ttl' => 300, 17 | ], 18 | //'recursive' => [ 19 | 20 | //] 21 | ] 22 | ]; -------------------------------------------------------------------------------- /config/dns_record.json: -------------------------------------------------------------------------------- 1 | { 2 | "test.com": { 3 | "A": "111.111.111.111", 4 | "MX": "mx.test.com", 5 | "NS": [ 6 | "ns1.test.com", 7 | "ns2.test.com" 8 | ], 9 | "TXT": "Some text.", 10 | "AAAA": "DEAD:01::BEEF", 11 | "CNAME": "www2.test.com", 12 | "SOA": { 13 | "mname": "ns1.test.com", 14 | "rname": "admin.test.com", 15 | "serial": "2014111100", 16 | "retry": "7200", 17 | "refresh": "1800", 18 | "expire": "8600", 19 | "minimum-ttl": "300" 20 | } 21 | }, 22 | "test2.com": { 23 | "A": [ 24 | "111.111.111.111", 25 | "112.112.112.112" 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /libs/AbstractStorageProvider.php: -------------------------------------------------------------------------------- 1 | path); 73 | if (!$data) { 74 | return ; 75 | } 76 | $this->dns_records = $this->parseHostsString($data); 77 | } 78 | 79 | public function __construct($config) 80 | { 81 | $this->path = isset($config['path']) ? $config['path'] : $this->getSystemPath(); 82 | $this->DS_TTL = isset($config['default_ttl']) ? $config['default_ttl'] : 300; 83 | $this->refresh_time = isset($config['refresh_time']) ? $config['refresh_time'] : 60; 84 | $this->refreshData(); 85 | } 86 | 87 | public function get_answer($question) 88 | { 89 | $answer = array(); 90 | $domain = trim($question[0]['qname'], '.'); 91 | $type = RecordTypeEnum::get_name($question[0]['qtype']); 92 | 93 | if(isset($this->dns_records[$domain]) && isset($this->dns_records[$domain][$type])) { 94 | if(is_array($this->dns_records[$domain][$type])) { 95 | foreach($this->dns_records[$domain][$type] as $ip) { 96 | $answer[] = array( 97 | 'name' => $question[0]['qname'], 98 | 'class' => $question[0]['qclass'], 99 | 'ttl' => $this->DS_TTL, 100 | 'data' => array( 101 | 'type' => $question[0]['qtype'], 102 | 'value' => $ip 103 | ) 104 | ); 105 | } 106 | } 107 | } 108 | 109 | return $answer; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /libs/Providers/JsonProvider.php: -------------------------------------------------------------------------------- 1 | DS_TTL = $config['default_ttl']; 35 | $this->dns_records = $dns_records; 36 | } 37 | 38 | public function get_answer($question) 39 | { 40 | $answer = array(); 41 | $domain = trim($question[0]['qname'], '.'); 42 | $type = RecordTypeEnum::get_name($question[0]['qtype']); 43 | 44 | if(isset($this->dns_records[$domain]) &&isset($this->dns_records[$domain][$type])) { 45 | if(is_array($this->dns_records[$domain][$type]) && $type != 'SOA') { 46 | foreach($this->dns_records[$domain][$type] as $ip) { 47 | $answer[] = array( 48 | 'name' => $question[0]['qname'], 49 | 'class' => $question[0]['qclass'], 50 | 'ttl' => $this->DS_TTL, 51 | 'data' => array( 52 | 'type' => $question[0]['qtype'], 53 | 'value' => $ip 54 | ) 55 | ); 56 | } 57 | } else { 58 | $answer[] = array( 59 | 'name' => $question[0]['qname'], 60 | 'class' => $question[0]['qclass'], 61 | 'ttl' => $this->DS_TTL, 62 | 'data' => array( 63 | 'type' => $question[0]['qtype'], 64 | 'value' => $this->dns_records[$domain][$type] 65 | ) 66 | ); 67 | } 68 | } 69 | 70 | return $answer; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /libs/Providers/RecursiveProvider.php: -------------------------------------------------------------------------------- 1 | 'ip', 13 | 'DNS_AAAA' => 'ipv6', 14 | 'DNS_CNAME' => 'target', 15 | 'DNS_TXT' => 'txt', 16 | 'DNS_MX' => 'target', 17 | 'DNS_NS' => 'target', 18 | 'DNS_SOA' => array('mname', 'rname', 'serial', 'retry', 'refresh', 'expire', 'minimum-ttl'), 19 | 'DNS_PTR' => 'target', 20 | ); 21 | 22 | public function get_answer($question) 23 | { 24 | $answer = array(); 25 | $domain = trim($question[0]['qname'], '.'); 26 | $type = RecordTypeEnum::get_name($question[0]['qtype']); 27 | 28 | $records = $this->get_records_recursivly($domain, $type); 29 | foreach($records as $record) { 30 | $answer[] = array('name' => $question[0]['qname'], 'class' => $question[0]['qclass'], 'ttl' => $record['ttl'], 'data' => array('type' => $question[0]['qtype'], 'value' => $record['answer'])); 31 | } 32 | 33 | return $answer; 34 | } 35 | 36 | private function get_records_recursivly($domain, $type) 37 | { 38 | $result = array(); 39 | $dns_const_name = $this->get_dns_cost_name($type); 40 | 41 | if (!$dns_const_name) { 42 | throw new Exception('Not supported dns type to query.'); 43 | } 44 | 45 | $dns_answer_name = $this->dns_answer_names[$dns_const_name]; 46 | $records = dns_get_record($domain, constant($dns_const_name)); 47 | 48 | foreach($records as $record) { 49 | if(is_array($dns_answer_name)) { 50 | foreach($dns_answer_name as $name) { 51 | $answer[$name] = $record[$name]; 52 | } 53 | } else{ 54 | $answer = $record[$dns_answer_name]; 55 | } 56 | $result[] = array('answer' => $answer, 'ttl' => $record['ttl']); 57 | } 58 | 59 | return $result; 60 | } 61 | 62 | private function get_dns_cost_name($type) 63 | { 64 | $const_name = "DNS_".strtoupper($type); 65 | $name = defined($const_name) ? $const_name : false; 66 | 67 | return $name; 68 | } 69 | 70 | } 71 | 72 | -------------------------------------------------------------------------------- /libs/RecordTypeEnum.php: -------------------------------------------------------------------------------- 1 | 1, 12 | 'NS' => 2, 13 | 'CNAME' => 5, 14 | 'SOA' => 6, 15 | 'PTR' => 12, 16 | 'MX' => 15, 17 | 'TXT' => 16, 18 | 'AAAA' => 28, 19 | 'OPT' => 41, 20 | 'AXFR' => 252, 21 | 'ANY' => 255, 22 | ); 23 | 24 | const TYPE_A = 1; 25 | const TYPE_NS = 2; 26 | const TYPE_CNAME = 5; 27 | const TYPE_SOA = 6; 28 | const TYPE_PTR = 12; 29 | const TYPE_MX = 15; 30 | const TYPE_TXT = 16; 31 | const TYPE_AAAA = 28; 32 | const TYPE_OPT = 41; 33 | const TYPE_AXFR = 252; 34 | const TYPE_ANY = 255; 35 | 36 | /** 37 | * @param int $typeIndex The index of the type contained in the question 38 | * @return string|false 39 | */ 40 | public static function get_name($typeIndex) 41 | { 42 | return array_search($typeIndex, self::$types); 43 | } 44 | 45 | /** 46 | * @param string $name The name of the record type, e.g. = 'A' or 'MX' or 'SOA' 47 | * @return int|false 48 | */ 49 | public static function get_type_index($name) 50 | { 51 | $key = trim(strtoupper($name)); 52 | if(!array_key_exists($key, self::$types)) return false; 53 | return self::$types[$key]; 54 | } 55 | 56 | /** 57 | * @return array 58 | */ 59 | public static function get_types() 60 | { 61 | return self::$types; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /libs/Server.php: -------------------------------------------------------------------------------- 1 | config = $config; 14 | $this->ds_storage = new StackableResolver($config); 15 | 16 | $config = $this->config['server']; 17 | 18 | ini_set('display_errors', TRUE); 19 | ini_set('error_reporting', E_ALL); 20 | 21 | set_error_handler(array($this, 'ds_error'), E_ALL); 22 | set_time_limit(0); 23 | 24 | if(!class_exists('swoole_server')) { 25 | $this->ds_error(E_USER_ERROR, 'Swoole extension or function not found.', __FILE__, __LINE__); 26 | } 27 | 28 | $this->swoole_server = new \swoole_server($config['ip'], $config['port'], SWOOLE_PROCESS, SWOOLE_SOCK_UDP); 29 | $this->swoole_server->set([ 30 | 'work_num' => $config['work_num'] 31 | ]); 32 | 33 | $this->swoole_server->on('Packet', function (\swoole_server $serv, $data, $addr) { 34 | $response = $this->ds_handle_query($data); 35 | 36 | $serv->sendto($addr['address'], $addr['port'], $response); 37 | }); 38 | } 39 | 40 | public function start() 41 | { 42 | $this->swoole_server->start(); 43 | } 44 | 45 | private function ds_handle_query($buffer) 46 | { 47 | $data = unpack('npacket_id/nflags/nqdcount/nancount/nnscount/narcount', $buffer); 48 | $flags = $this->ds_decode_flags($data['flags']); 49 | $offset = 12; 50 | 51 | $question = $this->ds_decode_question_rr($buffer, $offset, $data['qdcount']); 52 | $answer = $this->ds_decode_rr($buffer, $offset, $data['ancount']); 53 | $authority = $this->ds_decode_rr($buffer, $offset, $data['nscount']); 54 | $additional = $this->ds_decode_rr($buffer, $offset, $data['arcount']); 55 | $answer = $this->ds_storage->get_answer($question); 56 | $flags['qr'] = 1; 57 | $flags['ra'] = 0; 58 | 59 | $qdcount = count($question); 60 | $ancount = count($answer); 61 | $nscount = count($authority); 62 | $arcount = count($additional); 63 | 64 | $response = pack('nnnnnn', $data['packet_id'], $this->ds_encode_flags($flags), $qdcount, $ancount, $nscount, $arcount); 65 | $response .= ($p = $this->ds_encode_question_rr($question, strlen($response))); 66 | $response .= ($p = $this->ds_encode_rr($answer, strlen($response))); 67 | $response .= $this->ds_encode_rr($authority, strlen($response)); 68 | $response .= $this->ds_encode_rr($additional, strlen($response)); 69 | 70 | return $response; 71 | } 72 | 73 | private function ds_decode_flags($flags) 74 | { 75 | $res = array(); 76 | 77 | $res['qr'] = $flags>>15 &0x1; 78 | $res['opcode'] = $flags>>11 &0xf; 79 | $res['aa'] = $flags>>10 &0x1; 80 | $res['tc'] = $flags>>9 &0x1; 81 | $res['rd'] = $flags>>8 &0x1; 82 | $res['ra'] = $flags>>7 &0x1; 83 | $res['z'] = $flags>>4 &0x7; 84 | $res['rcode'] = $flags &0xf; 85 | 86 | return $res; 87 | } 88 | 89 | private function ds_decode_question_rr($pkt, &$offset, $count) 90 | { 91 | $res = array(); 92 | 93 | for($i = 0; $i < $count; ++$i) { 94 | if($offset > strlen($pkt)) 95 | return false; 96 | $qname = $this->ds_decode_label($pkt, $offset); 97 | $tmp = unpack('nqtype/nqclass', substr($pkt, $offset, 4)); 98 | $offset += 4; 99 | $tmp['qname'] = $qname; 100 | $res[] = $tmp; 101 | } 102 | return $res; 103 | } 104 | 105 | private function ds_decode_label($pkt, &$offset) 106 | { 107 | $end_offset = NULL; 108 | $qname = ''; 109 | 110 | while(1) { 111 | $len = ord($pkt[$offset]); 112 | $type = $len>>6 &0x2; 113 | 114 | if($type) { 115 | switch($type) { 116 | case 0x2: 117 | $new_offset = unpack('noffset', substr($pkt, $offset, 2)); 118 | $end_offset = $offset +2; 119 | $offset = $new_offset['offset'] &0x3fff; 120 | break; 121 | case 0x1: 122 | break; 123 | } 124 | continue; 125 | } 126 | 127 | if($len > (strlen($pkt) -$offset)) 128 | return NULL; 129 | 130 | if($len == 0) { 131 | if($qname == '') 132 | $qname = '.'; 133 | ++$offset; 134 | break; 135 | } 136 | $qname .= substr($pkt, $offset +1, $len) . '.'; 137 | $offset += $len +1; 138 | } 139 | 140 | if(!is_null($end_offset)) { 141 | $offset = $end_offset; 142 | } 143 | 144 | return $qname; 145 | } 146 | 147 | private function ds_decode_rr($pkt, &$offset, $count) 148 | { 149 | $res = array(); 150 | 151 | for($i = 0; $i < $count; ++$i) { 152 | // read qname 153 | $qname = $this->ds_decode_label($pkt, $offset); 154 | // read qtype & qclass 155 | $tmp = unpack('ntype/nclass/Nttl/ndlength', substr($pkt, $offset, 10)); 156 | $tmp['name'] = $qname; 157 | $offset += 10; 158 | $tmp['data'] = $this->ds_decode_type($tmp['type'], substr($pkt, $offset, $tmp['dlength'])); 159 | $offset += $tmp['dlength']; 160 | $res[] = $tmp; 161 | } 162 | 163 | return $res; 164 | } 165 | 166 | private function ds_decode_type($type, $val) 167 | { 168 | $data = array(); 169 | 170 | switch($type) { 171 | case RecordTypeEnum::TYPE_A: 172 | $data['value'] = inet_ntop($val); 173 | break; 174 | case RecordTypeEnum::TYPE_AAAA: 175 | $data['value'] = inet_ntop($val); 176 | break; 177 | case RecordTypeEnum::TYPE_NS: 178 | $foo_offset = 0; 179 | $data['value'] = $this->ds_decode_label($val, $foo_offset); 180 | break; 181 | case RecordTypeEnum::TYPE_CNAME: 182 | $foo_offset = 0; 183 | $data['value'] = $this->ds_decode_label($val, $foo_offset); 184 | break; 185 | case RecordTypeEnum::TYPE_SOA: 186 | $data['value'] = array(); 187 | $offset = 0; 188 | $data['value']['mname'] = $this->ds_decode_label($val, $offset); 189 | $data['value']['rname'] = $this->ds_decode_label($val, $offset); 190 | $next_values = unpack('Nserial/Nrefresh/Nretry/Nexpire/Nminimum', substr($val, $offset)); 191 | 192 | foreach($next_values as $var => $val) { 193 | $data['value'][$var] = $val; 194 | } 195 | 196 | break; 197 | case RecordTypeEnum::TYPE_PTR: 198 | $foo_offset = 0; 199 | $data['value'] = $this->ds_decode_label($val, $foo_offset); 200 | break; 201 | case RecordTypeEnum::TYPE_MX: 202 | $tmp = unpack('n', $val); 203 | $data['value'] = array('priority' => $tmp[0], 'host' => substr($val, 2), ); 204 | break; 205 | case RecordTypeEnum::TYPE_TXT: 206 | $len = ord($val[0]); 207 | 208 | if((strlen($val) +1) < $len) { 209 | $data['value'] = NULL; 210 | break; 211 | } 212 | 213 | $data['value'] = substr($val, 1, $len); 214 | break; 215 | case RecordTypeEnum::TYPE_AXFR: 216 | $data['value'] = NULL; 217 | break; 218 | case RecordTypeEnum::TYPE_ANY: 219 | $data['value'] = NULL; 220 | break; 221 | case RecordTypeEnum::TYPE_OPT: 222 | $data['type'] = RecordTypeEnum::TYPE_OPT; 223 | $data['value'] = array('type' => RecordTypeEnum::TYPE_OPT, 'ext_code' => $this->DS_TTL>>24 &0xff, 'udp_payload_size' => 4096, 'version' => $this->DS_TTL>>16 &0xff, 'flags' => $this->ds_decode_flags($this->DS_TTL &0xffff)); 224 | break; 225 | default: 226 | $data['value'] = $val; 227 | return false; 228 | } 229 | 230 | return $data; 231 | } 232 | 233 | private function ds_encode_flags($flags) 234 | { 235 | $val = 0; 236 | 237 | $val |= ($flags['qr'] &0x1)<<15; 238 | $val |= ($flags['opcode'] &0xf)<<11; 239 | $val |= ($flags['aa'] &0x1)<<10; 240 | $val |= ($flags['tc'] &0x1)<<9; 241 | $val |= ($flags['rd'] &0x1)<<8; 242 | $val |= ($flags['ra'] &0x1)<<7; 243 | $val |= ($flags['z'] &0x7)<<4; 244 | $val |= ($flags['rcode'] &0xf); 245 | 246 | return $val; 247 | } 248 | 249 | private function ds_encode_label($str, $offset = NULL) 250 | { 251 | $res = ''; 252 | $in_offset = 0; 253 | 254 | if($str == '.') { 255 | return "\0"; 256 | } 257 | 258 | while(1) { 259 | $pos = strpos($str, '.', $in_offset); 260 | 261 | if($pos === false) { 262 | return $res . "\0"; 263 | } 264 | 265 | $res .= chr($pos -$in_offset) . substr($str, $in_offset, $pos -$in_offset); 266 | $offset += ($pos -$in_offset) +1; 267 | $in_offset = $pos +1; 268 | } 269 | } 270 | 271 | private function ds_encode_question_rr($list, $offset) 272 | { 273 | $res = ''; 274 | 275 | foreach($list as $rr) { 276 | $lbl = $this->ds_encode_label($rr['qname'], $offset); 277 | $offset += strlen($lbl) +4; 278 | $res .= $lbl; 279 | $res .= pack('nn', $rr['qtype'], $rr['qclass']); 280 | } 281 | 282 | return $res; 283 | } 284 | 285 | private function ds_encode_rr($list, $offset) 286 | { 287 | $res = ''; 288 | 289 | foreach($list as $rr) { 290 | $lbl = $this->ds_encode_label($rr['name'], $offset); 291 | $res .= $lbl; 292 | $offset += strlen($lbl); 293 | 294 | if(!is_array($rr['data'])) { 295 | return false; 296 | } 297 | 298 | $offset += 10; 299 | $data = $this->ds_encode_type($rr['data']['type'], $rr['data']['value'], $offset); 300 | 301 | if(is_array($data)) { 302 | // overloading written data 303 | if(!isset($data['type'])) 304 | $data['type'] = $rr['data']['type']; 305 | if(!isset($data['data'])) 306 | $data['data'] = ''; 307 | if(!isset($data['class'])) 308 | $data['class'] = $rr['class']; 309 | if(!isset($data['ttl'])) 310 | $data['ttl'] = $rr['ttl']; 311 | $offset += strlen($data['data']); 312 | $res .= pack('nnNn', $data['type'], $data['class'], $data['ttl'], strlen($data['data'])) . $data['data']; 313 | } else { 314 | $offset += strlen($data); 315 | $res .= pack('nnNn', $rr['data']['type'], $rr['class'], $rr['ttl'], strlen($data)) . $data; 316 | } 317 | } 318 | 319 | return $res; 320 | } 321 | 322 | private function ds_encode_type($type, $val = NULL, $offset = NULL) 323 | { 324 | switch ($type) { 325 | case RecordTypeEnum::TYPE_A: 326 | $enc = inet_pton($val); 327 | if(strlen($enc) != 4) 328 | $enc = "\0\0\0\0"; 329 | return $enc; 330 | case RecordTypeEnum::TYPE_AAAA: 331 | $enc = inet_pton($val); 332 | if(strlen($enc) != 16) 333 | $enc = str_repeat("\0", 16); 334 | return $enc; 335 | case RecordTypeEnum::TYPE_NS: 336 | $val = rtrim($val,'.').'.'; 337 | return $this->ds_encode_label($val, $offset); 338 | case RecordTypeEnum::TYPE_CNAME: 339 | $val = rtrim($val,'.').'.'; 340 | return $this->ds_encode_label($val, $offset); 341 | case RecordTypeEnum::TYPE_SOA: 342 | $res = ''; 343 | $val['mname'] = rtrim($val['mname'],'.').'.'; 344 | $val['rname'] = rtrim($val['rname'],'.').'.'; 345 | $res .= $this->ds_encode_label($val['mname'], $offset); 346 | $res .= $this->ds_encode_label($val['rname'], $offset +strlen($res)); 347 | $res .= pack('NNNNN', $val['serial'], $val['refresh'], $val['retry'], $val['expire'], $val['minimum-ttl']); 348 | return $res; 349 | case RecordTypeEnum::TYPE_PTR: 350 | $val = rtrim($val,'.').'.'; 351 | return $this->ds_encode_label($val, $offset); 352 | case RecordTypeEnum::TYPE_MX: 353 | $val = rtrim($val,'.').'.'; 354 | return pack('n', 10) . $this->ds_encode_label($val, $offset +2); 355 | case RecordTypeEnum::TYPE_TXT: 356 | if(strlen($val) > 255) 357 | $val = substr($val, 0, 255); 358 | 359 | return chr(strlen($val)) . $val; 360 | case RecordTypeEnum::TYPE_AXFR: 361 | return ''; 362 | case RecordTypeEnum::TYPE_ANY: 363 | return ''; 364 | case RecordTypeEnum::TYPE_OPT: 365 | $res = array('class' => $val['udp_payload_size'], 'ttl' => (($val['ext_code'] &0xff)<<24) |(($val['version'] &0xff)<<16) |($this->ds_encode_flags($val['flags']) &0xffff), 'data' => '', 366 | // // TODO: encode data 367 | ); 368 | 369 | return $res; 370 | default: 371 | return $val; 372 | } 373 | } 374 | 375 | public function ds_error($code, $error, $file, $line) 376 | { 377 | if(!(error_reporting() &$code)) { 378 | return; 379 | } 380 | 381 | $codes = array(E_ERROR => 'Error', E_WARNING => 'Warning', E_PARSE => 'Parse Error', E_NOTICE => 'Notice', E_CORE_ERROR => 'Core Error', E_CORE_WARNING => 'Core Warning', E_COMPILE_ERROR => 'Compile Error', E_COMPILE_WARNING => 'Compile Warning', E_USER_ERROR => 'User Error', E_USER_WARNING => 'User Warning', E_USER_NOTICE => 'User Notice', E_STRICT => 'Strict Notice', E_RECOVERABLE_ERROR => 'Recoverable Error', E_DEPRECATED => 'Deprecated Error', E_USER_DEPRECATED => 'User Deprecated Error'); 382 | 383 | $type = isset($codes[$code]) ? $codes[$code] : 'Unknown Error'; 384 | 385 | die(sprintf('DNS Server error: [%s] "%s" in file "%s" on line "%d".%s', $type, $error, $file, $line, PHP_EOL)); 386 | } 387 | 388 | } 389 | -------------------------------------------------------------------------------- /libs/StackableResolver.php: -------------------------------------------------------------------------------- 1 | $config) { 16 | $className = '\\away\\DNS\\Providers\\' . ucfirst($name) . 'Provider'; 17 | $provider = new $className($config); 18 | array_push($this->resolvers, $provider); 19 | } 20 | } 21 | 22 | public function __construct(array $config) { 23 | if (!isset($config['providers'])) { 24 | throw new Exception('Config file has no providers info.'); 25 | } 26 | $this->init_resolvers($config['providers']); 27 | } 28 | 29 | public function get_answer($question) 30 | { 31 | foreach ($this->resolvers as $resolver) { 32 | $answer = $resolver->get_answer($question); 33 | if ($answer) { 34 | return $answer; 35 | } 36 | } 37 | 38 | return array(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | start(); 12 | --------------------------------------------------------------------------------