Compare commits

..

No commits in common. "9130d8c2b09fb30bd91a1132ab1e960ae0adb630" and "548ef4a34e1ac80ae6f5a7fac1694873e68b42e7" have entirely different histories.

8 changed files with 107 additions and 3214 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
.history .history
*.log *.log
*.pid *.pid
vendor/workerman/workerman.log

315
Dns.php
View File

@ -10,31 +10,6 @@
namespace Workerman\Protocols; namespace Workerman\Protocols;
class Dns class Dns
{ {
public static function getDomain($data,$startbyte){
$dlen=substr($data,$startbyte,2);
$startbyte=$startbyte+2;
$domain[0]='';
$i=0;
while($dlen!='00'){
$domain[$i]=hex2bin(substr($data,$startbyte,hexdec($dlen)*2));
$startbyte=$startbyte+(hexdec($dlen)*2);
$dlen=substr($data,$startbyte,2);
$startbyte=$startbyte+2;
$i++;
}
$realname=join(".",$domain);
$return=['name'=>$realname,'startbyte'=>$startbyte];
return $return;
}
public static function send($response,$query,$info){
$response=hex2bin($response);
$traffic=strlen($response);
$info=json_decode($info);
var_dump($info);
#出流量统计
#您也可以在此处保存$response,下一次通过raw类型实现快速缓存相应.
return $response;
}
/** /**
* 检查包的完整性 * 检查包的完整性
* 如果能够得到包长则返回包的在buffer中的长度否则返回0继续等待数据 * 如果能够得到包长则返回包的在buffer中的长度否则返回0继续等待数据
@ -44,7 +19,8 @@ class Dns
*/ */
public static function input($buffer) public static function input($buffer)
{ {
return 512;
return 200;
} }
/** /**
@ -54,43 +30,9 @@ class Dns
*/ */
public static function encode($buffer) public static function encode($buffer)
{ {
$buffer=json_decode($buffer); $buffer=json_decode($buffer);
$type=$buffer->type; $type=$buffer->type;
switch($type){ switch($type){
case 'raw':
return Dns::send($buffer->detail,$buffer->query,$buffer->info);
break;
case 'flag':
if($buffer->flag=='NXDOMAIN'){
$status='8183';
$questions='0001';
$AnswerRRs='0000';
$AuthorityRRs='0001';
/**
* NXDOMAIN应当返回SOA记录主要内容是TTLLDNS会在SOA的TTL到期前缓存该FLAG否则会被LDNS递归时拒绝
*/
$AdditionalRRs='0000';
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query;
return Dns::send($response,$buffer->query,$buffer->info);
}elseif($buffer->flag=='SERVFAIL'){
$status='8182';
$questions='0001';
$AnswerRRs='0000';
$AuthorityRRs='0000';
$AdditionalRRs='0000';
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query;
return Dns::send($response,$buffer->query,$buffer->info);
}elseif($buffer->flag=='REFUSE'){
$status='8185';
$questions='0001';
$AnswerRRs='0000';
$AuthorityRRs='0000';
$AdditionalRRs='0000';
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query;
return Dns::send($response,$buffer->query,$buffer->info);
}
break;
case 'A': case 'A':
$type='0001'; $type='0001';
#$lenth='0004'; #$lenth='0004';
@ -157,115 +99,11 @@ class Dns
$n=$n+1; $n=$n+1;
}; };
break; break;
case 'CNAME+A':
$type='0005';
$ns=$buffer->detail;
$nss=explode('.',$ns);
$detail[0]='';
foreach($nss as $part){
$len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT);
$tpart=bin2hex($part);
$detail[0]=$detail[0].$len.$tpart;
};
$detail[0]=$detail[0].'00';
$lenth[0]=str_pad(dechex((strlen($detail[0])/2)),4,"0",STR_PAD_LEFT);
$ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT);
$answer='';
$answer=$answer.'C00C'.$type.'0001'.$ttl.$lenth[0].$detail[0];
$ip=dns_get_record($ns,DNS_A);
$type='0001';
$n=0;
foreach($ip as $i){
$ttl=str_pad(dechex($i['ttl']),8,"0",STR_PAD_LEFT);
$i=$i['ip'];
$nss=explode('.',$i);
$detail[$n]='';
foreach($nss as $part){
$tpart=str_pad(dechex($part),2,"0",STR_PAD_LEFT);
$detail[$n]=$detail[$n].$tpart;
};
$lenth[$n]=str_pad(dechex((strlen($detail[$n])/2)),4,"0",STR_PAD_LEFT);
$n=$n+1;
};
$n=0;
foreach($detail as $c){
$rlenth='';
$rlenth=$lenth[$n];
$n=$n+1;
$answer=$answer.'C02B'.$type.'0001'.$ttl.$rlenth.$c;
}
$status='8180';
$questions='0001';
$AuthorityRRs='0000';
$AdditionalRRs='0000';
$AnswerRRs=str_pad((count((array)$ip)+1),4,"0",STR_PAD_LEFT);
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer;
return Dns::send($response,$buffer->query,$buffer->info);
break;
case 'CNAME+AAAA':
$type='0005';
$ns=$buffer->detail;
$nss=explode('.',$ns);
$detail[0]='';
foreach($nss as $part){
$len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT);
$tpart=bin2hex($part);
$detail[0]=$detail[0].$len.$tpart;
};
$detail[0]=$detail[0].'00';
$lenth[0]=str_pad(dechex((strlen($detail[0])/2)),4,"0",STR_PAD_LEFT);
$ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT);
$answer='';
$answer=$answer.'C00C'.$type.'0001'.$ttl.$lenth[0].$detail[0];
$ip=dns_get_record($ns,DNS_AAAA);
$type='001C';
$n=0;
foreach($ip as $i){
$ipv6=$i['ipv6'];
$hexstr = unpack("H*hex", inet_pton($ipv6));
$ipv6=substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hexstr['hex']), 0, -1);
$ipv6=str_replace(':','',$ipv6);
#$ipv6= bin2hex($ipv6);
$detail[$n]="$ipv6";
$lenth[$n]="0010";
$n=$n+1;
};
$n=0;
foreach($detail as $c){
$rlenth='';
$rlenth=$lenth[$n];
$n=$n+1;
$answer=$answer.'C02C'.$type.'0001'.$ttl.$rlenth.$c;
}
$status='8180';
$questions='0001';
$AuthorityRRs='0000';
$AdditionalRRs='0000';
$AnswerRRs=str_pad((count((array)$ip)+1),4,"0",STR_PAD_LEFT);
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer;
return Dns::send($response,$buffer->query,$buffer->info);
break;
case 'SOA': case 'SOA':
$type='0006'; $type='0006';
$ns=$buffer->detail; $ns=$buffer->detail;
$ns=json_decode( json_encode( $ns),true); $ns=json_decode( json_encode( $ns),true);
if($ns['type']=='auto'){ if($ns['type']=='none'){
$Rns=dns_get_record($ns['name'],DNS_SOA); $Rns=dns_get_record($ns['name'],DNS_SOA);
$Rns=$Rns[0]; $Rns=$Rns[0];
$ns=$Rns; $ns=$Rns;
@ -336,81 +174,12 @@ class Dns
$n=$n+1; $n=$n+1;
}; };
break; break;
case 'none':
$type='0006';
$ns=$buffer->detail;
$ns=json_decode( json_encode( $ns),true);
var_dump($ns);
if($ns['type']=='auto'){
$ns=$ns['name'];
$url=$ns;
while(true){
preg_match("#\.(.*)#i",$url,$match);//获取根域名
$domin = $match[1];
$soa=dns_get_record($domin,DNS_SOA);
if(array_key_exists('0',$soa)){
if(array_key_exists('mname',$soa[0])){
$qname=$domin;
$ns=$soa[0];
break;
}else{
$url=$domin;
}
}else{
$url=$domin;
}
}
}else{
$qname=$ns['qname'];
}
$nss=explode('.',$ns['mname']);
$detail='';
foreach($nss as $part){
$len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT);
$tpart=bin2hex($part);
$detail=$detail.$len.$tpart;
};
$detail=$detail.'00';
unset($nss,$len,$tpart);
$nss=explode('.',$ns['rname']);
foreach($nss as $part){
$len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT);
$tpart=bin2hex($part);
$detail=$detail.$len.$tpart;
};
$detail=$detail.'00'.str_pad(dechex($ns['serial']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['refresh']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['retry']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['expire']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['minimum-ttl']),8,"0",STR_PAD_LEFT);
$lenth=str_pad(dechex((strlen($detail)/2)),4,"0",STR_PAD_LEFT);
$ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT);
$status='8183';
$questions='0001';
$AnswerRRs='0000';
$AuthorityRRs='0001';
$AdditionalRRs='0000';
#$qname
$nss=explode('.',$qname);
$qname='';
foreach($nss as $part){
#$len=strlen($part);
$len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT);
$tpart=bin2hex($part);
$qname=$qname.$len.$tpart;
};
$qname=$qname.'00';
$answer='';
$answer=$answer.$qname.$type.'0001'.$ttl.$lenth.$detail;
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer;
return Dns::send($response,$buffer->query,$buffer->info);
break;
} }
$ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT); $ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT);
$status='8180'; $status='8180';
$questions='0001'; $questions='0001';
$AnswerRRs=str_pad(count((array)$buffer->detail),4,"0",STR_PAD_LEFT); $AnswerRRs=str_pad(count((array)$buffer->detail),4,"0",STR_PAD_LEFT);
#$AnswerRRs='0001';
$AuthorityRRs='0000'; $AuthorityRRs='0000';
$AdditionalRRs='0000'; $AdditionalRRs='0000';
$answer=''; $answer='';
@ -422,8 +191,7 @@ class Dns
$answer=$answer.'C00C'.$type.'0001'.$ttl.$rlenth.$c; $answer=$answer.'C00C'.$type.'0001'.$ttl.$rlenth.$c;
} }
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer; $response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer;
$traffic=strlen(hex2bin($response)); return hex2bin($response);
return Dns::send($response,$buffer->query,$buffer->info);
} }
/** /**
@ -434,18 +202,9 @@ class Dns
*/ */
public static function decode($buffer) public static function decode($buffer)
{ {
$traffic=strlen($buffer);#接收流量 $data=bin2hex($buffer);
$data=bin2hex($buffer); $id=substr($data,0,4);
$id=substr($data,0,4); $type=substr($data,-8,4);
$flag=substr($data,4,4);
$questions=substr($data,8,4);
$answerRRs=substr($data,12,4);
$authorityRRs=substr($data,16,4);
$additionalRRs=substr($data,20,4);
$gdomain=Dns::getDomain($data,24);
$realname=$gdomain['name'];
$startbyte=$gdomain['startbyte'];
$type=substr($data,$startbyte,4);
switch($type){ switch($type){
case '0001': case '0001':
$type='A'; $type='A';
@ -472,55 +231,21 @@ class Dns
$type='MX'; $type='MX';
break; break;
} }
$query=substr($data,24,$startbyte-16); $name=substr($data,24,-8);
#additionalRRs $namede=str_split($name,2);
if($authorityRRs=='0000'&&$additionalRRs=='0001'){ $realname='';
$addR=new \StdClass(); foreach($namede as $cha){
$startbyte=$startbyte+8; $chat=hex2bin($cha);
$addR_name=Dns::getDomain($data,$startbyte); if(!ctype_alnum($chat)){
$addR_rname=$addR_name['name']; $chat='.';
$startbyte=$addR_name['startbyte'];
if($addR_rname==''){
$addR_rname=$realname;
} }
$addR_type=substr($data,$startbyte,4); $realname=$realname.$chat;
$startbyte=$startbyte+4;
$addR->realname=$addR_rname;
$addR->type=$addR_type;
#OPT
if($addR_type=='0029'){
#dns.rr.udp_playload_size,请求定义该值后响应将可突破512byte默认限制
$addR->playloadSize=hexdec(substr($data,$startbyte,4));
$addR->rcode=substr($data,$startbyte+4,2);#dns.resp.ext_rcode
$addR->edns0v=substr($data,$startbyte+6,2);#Edns0 拓展协议版本
$addR->Z=substr($data,$startbyte+8,4);
$addR->optLen=hexdec(substr($data,$startbyte+12,4))*2;
if($addR->optLen!=0){
$startbyte=$startbyte+16;
$opt=substr($data,$startbyte,$addR->optLen);
$opt_type=substr($opt,0,4);
if($opt_type=='0008'){
$addR->opt_type='CSUBNET';
$csubnet_len=hexdec(substr($opt,4,4))*2;
$csubnet_data=substr($opt,8,$csubnet_len);
$csubnet_family=substr($csubnet_data,0,4);
#IPv4
if($csubnet_family=='0001'){
$csubnet_source=substr($csubnet_data,4,2);
$csubnet_scope=substr($csubnet_data,6,2);
$csubnet_ip=long2ip(hexdec(substr(substr($csubnet_data,8,$csubnet_len-8).'00000000',0,8)));
$addR->csubnet=['family'=>$csubnet_family,'source'=>$csubnet_source,'scope'=>$csubnet_scope,'ip'=>$csubnet_ip];
}
}
}
}
}else{
$addR=null;
} }
$realname=substr($realname,1,-1);
$query=substr($data,24);
#$returndata="$type".'|||'."$realname";
$returndata= json_encode(array('type' => $type, 'name' => "$realname", 'id'=>"$id", 'query'=>"$query",'traffic'=>$traffic,'addR'=>$addR)); $returndata= json_encode(array('type' => $type, 'name' => "$realname", 'id'=>"$id", 'query'=>"$query"));
return $returndata; return $returndata;
} }

201
license
View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2024] [Enoch]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

540
readme.md
View File

@ -1,119 +1,30 @@
# WorkermanDNS DOC # Workerman-DNS
# 有什么用 [Workerman](https://www.workerman.net/)的DNS协议实现了简单的DNS协议解析和响应通过本协议支持您可以利用[Workerman](https://www.workerman.net/)实现基于PHP的Dns服务器
让您能够使用Workerman编写DNS服务器。 目前支持以下DNS类型
该库仅提供DNS协议的响应方式和DNS请求的解析。记录的查询、储存、缓存都需要您自行编写完成。 * A
* AAAA
* CNAME
* SOA
* PTR
* MX
* TXT
# 支持特性 > 本仓库内vendor文件夹为[Workerman](https://www.workerman.net/)
>
> 您可以删除,直接将本仓库根目录下的 Dns.php 放置到您的Workerman项目中的 /vendor/workerman/workerman/Protocols 目录下
- 支持的协议: ---
A AAAA CNAME MX PTR NS TXT SOA ## 使用方式:
- 支持的Flag标签 详见start.php 文件
- 8180OK≈HTTP200 :正常
- 8182SERVFAIL≈HTTP503 :服务器错误
- 8183NXDOMAIN≈HTTP404 :记录不存在
- 8185REFUSE≈HTTP403拒绝请求
- EDNS SubnetIP支持(当通过递归服务器发送请求时可获取到真实请求服务器的IP段)
# 安装 > 注意使用53端口需要root权限
### PHP环境 #### 1.监听端口
WorkermanDNS基于Workerman PHP框架。是纯PHP编写的DNS服务器。使用php-cli环境运行。
安装环境请参照Workerman要求
[环境要求-workerman手册](https://www.workerman.net/doc/workerman/install/requirement.html)
### Workerman框架
通过Composer安装Workerman框架
`composer require workerman/workerman`
### 下载Dns.php
可通过
[https://git.laysense.com/enoch/Workerman-DNS/raw/branch/master/Dns.php](https://git.laysense.com/enoch/Workerman-DNS/raw/branch/master/Dns.php)
[raw.githubusercontent.com/ywnsya/Workerman-DNS/master/Dns.php](https://raw.githubusercontent.com/ywnsya/Workerman-DNS/master/Dns.php)
或从Releases中
[Workerman-DNS](https://git.laysense.com/enoch/Workerman-DNS/releases)
下载Dns.php文件
### 下载IPv6支持库
如您需要使用到IPv6功能(您也可以用其他库代替不影响DNS协议的使用)请下载IPv6支持库
地址为:
[git.laysense.com/enoch/Workerman-DNS/raw/branch/master/php-ipv6.php](https://git.laysense.com/enoch/Workerman-DNS/raw/branch/master/php-ipv6.php)
[raw.githubusercontent.com/ywnsya/Workerman-DNS/master/php-ipv6.php](https://raw.githubusercontent.com/ywnsya/Workerman-DNS/master/php-ipv6.php)
### 放置Dns.php文件
Dns.php文件应当放置在./vendor/workerman/workerman/Protocols下
### 放置php-ipv6.php文件
该文件放置在项目目录./下
### 创建start.php文件
在项目目录./下新建空白的start.php文件即可
### 以上步骤完成后,您的文件夹结构应该如下:
```
.
├── composer.json
├── composer.lock
├── ***php-ipv6.php***
├── ***start.php***
└── vendor
├── autoload.php
├── composer
│ ├── ……此处省略……
└── workerman
├── workerman
│ ├── ……此处省略……
│ ├── Protocols
│ │ ├── ***Dns.php***
│ │ ├── Frame.php
│ │ ├── Http
│ │ │ ├── Chunk.php
│ │ │ ├── Request.php
│ │ │ ├── Response.php
│ │ │ ├── ServerSentEvents.php
│ │ │ ├── Session
│ │ │ ├── Session.php
│ │ │ └── mime.types
│ │ ├── Http.php
│ │ ├── ProtocolInterface.php
│ │ ├── Text.php
│ │ ├── Websocket.php
│ │ └── Ws.php
│ ├── ……此处省略……
```
# 基础框架:
以下内容均在start.php中编写
请在start.php中写入
```php ```php
<?php <?php
@ -125,420 +36,69 @@ require_once __DIR__ . '/vendor/autoload.php';
$worker = new Worker('Dns://0.0.0.0:53'); $worker = new Worker('Dns://0.0.0.0:53');
$worker->transport = 'udp'; $worker->transport = 'udp';
```
#### 2.获取查询内容
```php
$worker->onMessage = function($connection, $data){ $worker->onMessage = function($connection, $data){
$data=json_decode($data); $data=json_decode($data);
$type=$data->type; #查询类型 $type=$data->type; #查询类型
$name=$data->name; #查询内容(一般是域名PTR时为倒序IP) $name=$data->name; #查询内容(一般是域名PTR时为倒序IP)
$rip=$connection->getRemoteIp(); #客户端IP $rip=$connection->getRemoteIp(); #客户端IP
if(isset($data->addR->csubnet->ip)){
$ip=$data->addR->csubnet->ip;
}else{
$ip=$rip;
}
# 请在下方编写您的DNS响应
#——————————————————————
#—————————————————————— #输出信息
#下方内容无需更改 echo "\n Type:$type \n Domain: $name\n Client IP: $rip \n";
$send['id']=$data->id;
$send['query']=$data->query;
if(!isset($send['ttl'])){
$send['ttl']=0;
} }
$send['info']=json_encode(['domain'=>$data->name,'querytype'=>$data->type,'answertype'=>$send['type'],'ip'=>$ip,'rip'=>$rip,'ttl'=>$send['ttl'],'detail'=>$send['detail']]);
$send=json_encode($send);
$connection->send($send);
};
Worker::runAll();
``` ```
### 请求变量 #### 3.响应A记录
| 变量名称 | 意义 | 示例 |
| --- | --- | --- |
| $type | 请求类型 | A |
| $name | 请求域名 | dnsservertest.com |
| $rip | 客户端IP | 192.168.1.1 |
| $data->id | 请求ID | 0001 |
| $data->traffic | 请求包大小(Byte) | 88 |
# 编写响应:
### 响应参数
您最终需要回复一个$send的数组。定义如下
| 名称 | 意义 | 类型 | 示例 | 说明 |
| --- | --- | --- | --- | --- |
| $send['type'] | 响应类型 | 字符串 | $send['type']=A | 指定响应的类型 |
| $send['detail'] | 响应内容 | 字符串、数组、多维数组 | 119.29.29.29或$send['detail'][1]='119.29.29.29'或$send['detail']= array(); | 根据不同的响应类型,返回不同的响应。具请往下阅读各种记录的响应方式 |
| $send['ttl'] | TTL缓存时间 | 数字 | $send['ttl']=600 | 并非所有记录都需要TTL未传入时默认0 |
| $send['flag'] | Flag类型 | 字符串 | $send['flag']=REFUSE | 使用flag类型时需指定的flag |
| $send['id'] | ID | 数字 | 0001 | 无需更改无需编写。 |
| $send['query'] | 请求体 | 16进制 | | 无需更改无需编写。 |
| $send['info'] | 请求和响应信息 | json | | 无需编写,基础框架内已经完成。 |
您应当根据$name变量进行响应。
以下内容请在start.php结构中间的空行内编写
### A记录
响应一个或多个IPv4地址
$send['detail']应为一个数组。
```php ```php
$worker->onMessage = function($connection, $data){
$send['type']='A'; $send['type']='A';
$send['detail'][1]='119.29.29.29'; $send['detail'][1]='119.29.29.29'; #第一条记录
$send['detail'][2]='8.8.8.8'; $send['detail'][2]='8.8.8.8'; #第二条记录
$send['ttl']=30; $send['ttl']=30;
```
以上意味着响应IP为119.29.29.29和8.8.8.8指定TTL为30秒。
- 示例
```php
<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
require_once __DIR__ . '/php-ipv6.php'; #IPv6支持
require_once __DIR__ . '/vendor/autoload.php';
$worker = new Worker('Dns://0.0.0.0:53');
$worker->transport = 'udp';
$worker->onMessage = function($connection, $data){
$data=json_decode($data);
$type=$data->type; #查询类型
$name=$data->name; #查询内容(一般是域名PTR时为倒序IP)
$rip=$connection->getRemoteIp(); #客户端IP
if($type=='A' && $name=='dnsservertest.com'){
$send['type']='A';
$send['detail'][1]='192.168.2.1';
$send['detail'][2]='192.168.2.2';
$send['detail'][3]='192.168.2.3';
$send['ttl']=60;
}
#id和query一般情况下直接返回输出即可
$send['id']=$data->id; $send['id']=$data->id;
$send['query']=$data->query; $send['query']=$data->query;
if(!isset($send['ttl'])){
$send['ttl']=0;
}
$send['info']=json_encode(['domain'=>$data->name,'querytype'=>$data->type,'answertype'=>$send['type'],'ip'=>$rip,'ttl'=>$send['ttl'],'detail'=>$send['detail']]);
$send=json_encode($send); $send=json_encode($send);
$connection->send($send); $connection->send($send);
}; };
Worker::runAll(); Worker::runAll();
``` ```
以上代码意味着对于dnsservertest.com域名的A记录请求响应192.168.2.1、2.2、2.3三个IP指定TTL为60秒。 #### 4.响应其他记录
以下内容将不再给出完整代码示例。 见start.php 内有所有记录类型的响应方式
### AAAA记录 #### 5.说明
AAAA记录响应一个或多个IPv6地址。 您应当通过获取query的 `$name`通过查询数据库等方式返回数据对于不存在的记录应当返回SOA记录
```php 您需要的时候可以通过 `dns_get_record()`向上级DNS递归查找并缓存
$ipv6=new IPv6;
$send['type']='AAAA';
$send['detail'][1]=bin2hex($ipv6->ip2bin("fe80::2c5f")); #此操作可以还原被简化的IPv6地址 协议内不再对IPv6地址进行处理请按照本方式传递16进制无":"的完整16位IPv6
$send['detail'][2]=bin2hex($ipv6->ip2bin("2001:0:2851:b9d0:2c5f:f0d9:21be:4b96"));
$send['ttl']=30;
```
$send['detail']应当为数组且值为完整的、没有连续16位16进制IPv6地址。 这一系列操作本协议不提供您可以自行通过Redis等并利用workerman实现
IPv6库可以如上方示例所述将IPv6还原完整并去除再通过bin2hex的php函数转为16进制 不建议作为根域名的NS服务器使用。
### CNAME ## 已知问题
CNAME即别名将映射到另一个或多个DNS域名客户端将向另一个域名发送请求。 本协议最早写于鄙人刚学习php的阶段现在翻出来无疑是屎山一坨代码写的和xxs一样性能不敢测试还请各位大佬包容
```php 目前已知问题是:
$send['type']='CNAME'
$send['detail'][1]='hk.dnsservertest.com';
$send['detail'][2]='jp.dnsservertest.com';
$send['ttl']=600;
```
$send['detail']一样为数组。 域名不存在时可能出现BUG
一般情况下为了提升DNS解析效率DNS服务器将同时直接返回一个A或AAAA记录要实现这个请使用CNAME+A或CNAME+AAAA类型
### CNAME+A/AAAA
```php
#CNAME+A
$send['type']='CNAME+A';
$send['detail']='ipv4.dnsservertest.com';
$send['ttl']=30;
#CNAME+AAAA
$send['type']='CNAME+AAAA';
$send['detail']='ipv6.dnsservertest.com';
$send['ttl']=30;
```
CNAME+A和CNAME+AAAA的情况下均只支持返回一条CNAME.
因此,此时的$send['detail']应当为字符串
如多条CNAME的均衡负载请通过您的代码在服务端实现。
WorkermanDNS将自动获取对应的A记录并返回。
### TEXT(TXT)
TEXT记录将返回一条文本记录。由于默认的最大包大小为512B因此应当控制TEXT记录内容在256B之内
```php
$send['type']='TEXT';
$send['detail'][1]='text1-test';
$send['detail'][2]='text2-test';
$send['ttl']=600;
```
$send['detail']为数组。值为TEXT记录内容(字符串)
### MX
MX邮件交换记录建设邮箱时常用。
```php
$send['type']='MX';
$send['detail'][1]['name']='mx.zoho.com';
$send['detail'][1]['pre']='20'; #权重
$send['detail'][2]['name']='mx2.zoho.com';
$send['detail'][2]['pre']='30'; #权重
$send['detail'][3]['name']='mx3.zoho.com';
$send['detail'][3]['pre']='50'; #权重
$send['ttl']=600;
```
$send['detail']为数组其中的每个元素也是多维数组分别有name和pre两个参数分别为MX的记录值和对应权重。
### NS
将子域名转交给其他DNS服务器解析。
```php
$send['type']='NS';
$send['detail'][1]='coco.bunny.net';
$send['detail'][2]='kiki.bunny.net';
$send['ttl']=600;
```
$send['detail']为数组值为DNS服务器名称。
### SOA
SOA有自动和手动两种
- 自动
```php
$send['type']='SOA';
$send['detail']= array();
$send['detail']['type']='auto';#自动获取上级的SOA
$send['detail']['name']="$name";
```
自动则指定 $send['detail']['type']='auto' 将自行从上级DNS服务器获取SOA信息。以上代码无需改动直接使用即可。
- 手动
```php
$send['type']='SOA';
$send['detail']= array();
$send['detail']['type']='self';
$send['detail']['mname']='dns31.hichina.com'; #主DNS服务器名
$send['detail']['rname']='hostmaster.hichina.com'; #DNS管理员邮箱
$send['detail']['serial']='2022052002'; #序列号 序列号必须递增 类似于dns记录的版本号 序列号变大时递归dns将更新记录
$send['detail']['refresh']='3600'; #区域应当被刷新前的时间间隔
$send['detail']['retry']='1200'; #刷新失败重试的时间间隔
$send['detail']['expire']='86400'; #规定在区域不再是权威的之前可以等待的时间间隔的上限
$send['detail']['minimum-ttl']='60'; #最小TTL
$send['ttl']='180'; #当前SOA记录的TTL
```
示例和说明如上。不推荐使用因为SOA记录的错误可能对递归DNS造成极大影响除非是您使用WorkermanDNS作为权威DNS时(请尽可能不要这么做)
### None
none是WorkermanDNS自定义的一种方式。将返回NXDOMAIN状态码以及SOA记录一般用于域名不存在时。同样SOA部分也可以使用自动和手动两种。
```php
$send['type']='none';
$send['detail']= array();
$send['detail']['type']='auto';
$send['detail']['name']="$name";
```
```php
$send['type']='none';
$send['detail']= array();
$send['detail']['type']='self';
$send['detail']['qname']='phpisthebestlanguage.com';#根域名
$send['detail']['mname']='dns31.hichina.com'; #主DNS服务器名
$send['detail']['rname']='hostmaster.hichina.com'; #DNS管理员邮箱
$send['detail']['serial']='2022052002'; #序列号 序列号必须递增 类似于dns记录的版本号 序列号变大时递归dns将更新记录
$send['detail']['refresh']='3600'; #区域应当被刷新前的时间间隔
$send['detail']['retry']='1200'; #刷新失败重试的时间间隔
$send['detail']['expire']='86400'; #规定在区域不再是权威的之前可以等待的时间间隔的上限
$send['detail']['minimum-ttl']='600'; #最小TTL
$send['ttl']='180'; #当前TTL
```
### Flag
Flag类型将返回DNS状态码。
- NXDOMAIN
```php
$send['type']='flag';
$send['flag']='NXDOMAIN';
```
NXDOMAIN表示域名不存在使用flag类型返回NXDOMAIN时不会附带SOA,当作为权威DNS时可能造成LDNS发生错误,不建议使用,建议使用none类型替代
- SERVFAIL
```php
$send['type']='flag';
$send['flag']='SERVFAIL';
```
SERVFAIL表示解析遇到错误,类似于HTTP的503
- REFUSE
```php
$send['type']='flag';
$send['flag']='REFUSE';
```
REFUSE表示服务器拒绝此请求,可用于限定客户端的IP范围
Flag类型无需返回$send['detail']
### Raw
Raw是WorkermanDNS自定义的一种类型将不再处理直接以16进制形式响应从而无需进行16进制拼合以加快响应速度。一般是在第一次响应完毕后通过截取记录和请求写入缓存(如Redis或共享变量等)后续遇到相同请求且在TTL内直接返回。
```php
$send['type']='raw';
$status='8180';
$questions='0001';
$AnswerRRs='0001';
$AuthorityRRs='0000';
$AdditionalRRs='0000';
$answer='c00c000c00010000001e001203646e73086c617973656e736503636f6d00';#此处示例为返回记录值为dns.laysense.com的PTR记录的16进制
$response=$data->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$data->query.$answer;
$send['detail']=$response;
```
# 流量统计与日志
### 响应(出站)
Dns.php文件中send函数下将会返回提供$query和$info两个变量以及$response。
`strlen($response)`将直接可以获取该次响应出站流量(为byte)
$query是请求包体的16进制响应内容
$info为一个json对象var_dump结构类似如下
```php
object(stdClass)#18 (6) {
["domain"]=>
string(26) "95.188.24.172.in-addr.arpa"
["querytype"]=>
string(3) "PTR"
["answertype"]=>
string(3) "PTR"
["ip"]=>
string(12) "172.24.176.1"
["ttl"]=>
int(30)
["detail"]=>
string(16) "dns.laysense.com"
}
```
以上是一个对172.24.188.95的IP地址提供值为dns.laysense.com的PTR记录的$info示例
包含了请求域名、请求类型、响应类型、客户端IP、ttl等可供您进行日志统计
### 请求(入站)
请参看上方的请求变量章节。
其中对于流量将传递$data->traffic变量其为请求包体的大小(Byte)
# EDNS Subnet IP
这是一个DNS协议的扩展。支持的递归DNS服务器会向权威DNS发送请求客户端的IP地址。
WorkermanDNS支持对该协议提供的IPv4进行解析。
通过我们在上述示例框架start.php中的代码
```php
if(isset($data->addR->csubnet->ip)){
$ip=$data->addR->csubnet->ip;
}else{
$ip=$rip;
}
```
$ip参数将在支持EDNS SubnetIP的情况下优先使用SubnetIP不存在时则使用连接到的对端IP。
对于写入后端记录时rip与ip 参数都会始终提供。当不支持EDNS SubnetIP时rip与ip相同。否则ip是EDNS SubnetIPrip是udp对端IP。
# 运行
使用
`php start.php start` 运行
`php start.php start -d` 后台运行,自带守护进程
`php start.php stop` 停止运行
`php start.php status` 查看运行状况
请注意:
DNS服务使用53端口为特权端口绝大多数情况下必须使用root权限运行。
### 版本记录
[Current]0.1.1@2024/02/06
添加Edns SubnetIP支持
0.1.0@2024/01/31
### About
作者 Enoch[enoch@laysense.com]
- Repo
[Workerman-DNS](https://git.laysense.com/enoch/Workerman-DNS)
- Github
[https://github.com/ywnsya/Workerman-DNS](https://github.com/ywnsya/Workerman-DNS)
### License
**Apache License 2.0**

110
start.php
View File

@ -20,11 +20,9 @@ $data=json_decode($data);
$type=$data->type; #查询类型 $type=$data->type; #查询类型
$name=$data->name; #查询内容(一般是域名PTR时为倒序IP) $name=$data->name; #查询内容(一般是域名PTR时为倒序IP)
$rip=$connection->getRemoteIp(); #客户端IP $rip=$connection->getRemoteIp(); #客户端IP
if(isset($data->addR->csubnet->ip)){
$ip=$data->addR->csubnet->ip; #输出信息
}else{ echo "\n Type:$type \n Domain: $name\n Client IP: $rip \n";
$ip=$rip;
}
if($type=='A'){ if($type=='A'){
@ -34,15 +32,6 @@ if($type=='A'){
$send['ttl']=30; $send['ttl']=30;
}; };
#此处进行一个CNAME+A记录返回的实例
if($type=='A' && $name=='cn.bing.com'){
$send['type']='CNAME+A';
#CNAME+A和CNAME+AAAA的情况下均只会返回一条CNAME如多条CNAME的均衡负载请通过您的代码在此处服务端实现
$send['detail']='china.bing123.com';
$send['ttl']=30;
};
if($type=='PTR'){ if($type=='PTR'){
/** /**
@ -63,7 +52,13 @@ if($type=='NS'){
$send['ttl']=600; $send['ttl']=600;
}; };
#实际情况下很少直接返回CNAME真实情况一般是CNAME+A同时还有CNAME+AAAA if($type=='CNAME'){
$send['type']='CNAME';
$send['detail'][1]='baidu.cn';
$send['detail'][2]='baidu.com';
$send['ttl']=600;
}
if($type=='CNAME'){ if($type=='CNAME'){
$send['type']='CNAME'; $send['type']='CNAME';
$send['detail'][1]='baidu.cn'; $send['detail'][1]='baidu.cn';
@ -78,13 +73,6 @@ if($type=='AAAA'){
$send['detail'][2]=bin2hex($ipv6->ip2bin("2001:0:2851:b9d0:2c5f:f0d9:21be:4b96")); $send['detail'][2]=bin2hex($ipv6->ip2bin("2001:0:2851:b9d0:2c5f:f0d9:21be:4b96"));
$send['ttl']=600; $send['ttl']=600;
} }
#此处进行一个CNAME+AAAA记录返回的实例
if($type=='AAAA' && $name=='cname6.xx.com'){
$send['type']='CNAME+AAAA';
#CNAME+A和CNAME+AAAA的情况下均只会返回一条CNAME如多条CNAME的均衡负载请通过您的代码在此处服务端实现
$send['detail']='ipv6.xx.com';
$send['ttl']=30;
}
if($type=='TEXT'){ if($type=='TEXT'){
$send['type']='TEXT'; $send['type']='TEXT';
@ -104,12 +92,13 @@ if($type=='MX'){
$send['ttl']=600; $send['ttl']=600;
} }
if($type=='SOA'){ #无记录情况下返回SOA记录或域名不存在记录防止报错
if( (!isset($send['type'])) || (!isset($send['detail'])) || (!isset($send['ttl'])) || $type=='SOA'){
$send['type']='SOA'; $send['type']='SOA';
$send['detail']= array(); $send['detail']= array();
$send['detail']['type']='auto';#自动获取上级的SOA $send['detail']['type']='none';
$send['detail']['name']=$name; $send['detail']['name']=$name;
/** /**
@ -130,81 +119,12 @@ if($type=='SOA'){
**/ **/
} };
#返回域名不存在(自动获取SOA)
if($name=='1.phpisthebestlanguage.com'){
$send['type']='none';
$send['detail']= array();
$send['detail']['type']='auto';
$send['detail']['name']="$name";
/**
* none类型将返回NXDOMAIN同时通过自动获取SOA返回SOA记录。
*/
/**
*
* 手动自行返回SOA记录(如果为权威服务器时)
* $send['detail']['type']='self';
* $send['detail']['mname']='dns31.hichina.com'; #主DNS服务器名
* $send['detail']['rname']='hostmaster.hichina.com'; #DNS管理员邮箱
* $send['detail']['serial']='2022052002'; #序列号 序列号必须递增 类似于dns记录的版本号 序列号变大时递归dns将更新记录
* $send['detail']['refresh']='3600'; #区域应当被刷新前的时间间隔
* $send['detail']['retry']='1200'; #刷新失败重试的时间间隔
* $send['detail']['expire']='86400'; #规定在区域不再是权威的之前可以等待的时间间隔的上限
* $send['detail']['minimum-ttl']='600'; #最小TTL
*
* $send['ttl']='180'; #当前TTL
**/
}
if($name=='2.phpisthebestlanguage.com'){
$send['type']='none';
$send['detail']= array();
$send['detail']['type']='self';
#手动自行返回SOA记录与NXDOMAIN时的示例
$send['detail']['qname']='phpisthebestlanguage.com';#根域名
$send['detail']['mname']='dns31.hichina.com'; #主DNS服务器名
$send['detail']['rname']='hostmaster.hichina.com'; #DNS管理员邮箱
$send['detail']['serial']='2022052002'; #序列号 序列号必须递增 类似于dns记录的版本号 序列号变大时递归dns将更新记录
$send['detail']['refresh']='3600'; #区域应当被刷新前的时间间隔
$send['detail']['retry']='1200'; #刷新失败重试的时间间隔
$send['detail']['expire']='86400'; #规定在区域不再是权威的之前可以等待的时间间隔的上限
$send['detail']['minimum-ttl']='600'; #最小TTL
$send['ttl']='180'; #当前TTL
}
if($name=='404.testdnsserver.com'){
$send['type']='flag';
$send['flag']='NXDOMAIN';
#使用flag类型返回NXDOMAIN时不会附带SOA,当作为权威DNS时可能造成LDNS发生错误,不建议使用,建议改为none类型
}
if($name=='503.testdnsserver.com'){
$send['type']='flag';
$send['flag']='SERVFAIL';
#SERVFAIL表示解析遇到错误,类似于HTTP的503
}
if($name=='403.testdnsserver.com'){
$send['type']='flag';
$send['flag']='REFUSE';
#REFUSE表示服务器拒绝此请求,可用于限定客户端的IP范围
}
if($name=='raw.testdnsserver.com'){
$send['type']='raw';
$status='8180';
$questions='0001';
$AnswerRRs='0001';
$AuthorityRRs='0000';
$AdditionalRRs='0000';
$answer='c00c000c00010000001e001203646e73086c617973656e736503636f6d00';#此处示例为返回记录值为dns.laysense.com的PTR记录的16进制
$response=$data->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$data->query.$answer;
$send['detail']=$response;
}
#id和query一般情况下直接返回输出即可 #id和query一般情况下直接返回输出即可
$send['id']=$data->id; $send['id']=$data->id;
$send['query']=$data->query; $send['query']=$data->query;
if(!isset($send['ttl'])){
$send['ttl']=0;
}
$send['info']=json_encode(['domain'=>$data->name,'querytype'=>$data->type,'answertype'=>$send['type'],'ip'=>$ip,'rip'=>$rip,'ttl'=>$send['ttl'],'detail'=>$send['detail']]);
$send=json_encode($send); $send=json_encode($send);

View File

@ -1,7 +0,0 @@
<?php
use Workerman\Worker;
use Workerman\Protocols\Dns;
require_once __DIR__ . '/php-ipv6.php'; #IPv6支持
require_once __DIR__ . '/vendor/autoload.php';
echo long2ip(hexdec(substr('7541b3'.'00000000',0,8)));

File diff suppressed because it is too large Load Diff

View File

@ -10,33 +10,6 @@
namespace Workerman\Protocols; namespace Workerman\Protocols;
class Dns class Dns
{ {
public static function getDomain($data,$startbyte){
$dlen=substr($data,$startbyte,2);
$startbyte=$startbyte+2;
$domain[0]='';
$i=0;
if($dlen!='00'){
while($dlen!='00'){
$domain[$i]=hex2bin(substr($data,$startbyte,hexdec($dlen)*2));
$startbyte=$startbyte+(hexdec($dlen)*2);
$dlen=substr($data,$startbyte,2);
$startbyte=$startbyte+2;
$i++;
}
}
$realname=join(".",$domain);
$return=['name'=>$realname,'startbyte'=>$startbyte];
return $return;
}
public static function send($response,$query,$info){
$response=hex2bin($response);
$traffic=strlen($response);
$info=json_decode($info);
var_dump($info);
#出流量统计
#您也可以在此处保存$response,下一次通过raw类型实现快速缓存相应.
return $response;
}
/** /**
* 检查包的完整性 * 检查包的完整性
* 如果能够得到包长则返回包的在buffer中的长度否则返回0继续等待数据 * 如果能够得到包长则返回包的在buffer中的长度否则返回0继续等待数据
@ -46,7 +19,8 @@ class Dns
*/ */
public static function input($buffer) public static function input($buffer)
{ {
return 512;
return 200;
} }
/** /**
@ -56,43 +30,9 @@ class Dns
*/ */
public static function encode($buffer) public static function encode($buffer)
{ {
$buffer=json_decode($buffer); $buffer=json_decode($buffer);
$type=$buffer->type; $type=$buffer->type;
switch($type){ switch($type){
case 'raw':
return Dns::send($buffer->detail,$buffer->query,$buffer->info);
break;
case 'flag':
if($buffer->flag=='NXDOMAIN'){
$status='8183';
$questions='0001';
$AnswerRRs='0000';
$AuthorityRRs='0001';
/**
* NXDOMAIN应当返回SOA记录主要内容是TTLLDNS会在SOA的TTL到期前缓存该FLAG否则会被LDNS递归时拒绝
*/
$AdditionalRRs='0000';
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query;
return Dns::send($response,$buffer->query,$buffer->info);
}elseif($buffer->flag=='SERVFAIL'){
$status='8182';
$questions='0001';
$AnswerRRs='0000';
$AuthorityRRs='0000';
$AdditionalRRs='0000';
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query;
return Dns::send($response,$buffer->query,$buffer->info);
}elseif($buffer->flag=='REFUSE'){
$status='8185';
$questions='0001';
$AnswerRRs='0000';
$AuthorityRRs='0000';
$AdditionalRRs='0000';
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query;
return Dns::send($response,$buffer->query,$buffer->info);
}
break;
case 'A': case 'A':
$type='0001'; $type='0001';
#$lenth='0004'; #$lenth='0004';
@ -159,115 +99,11 @@ class Dns
$n=$n+1; $n=$n+1;
}; };
break; break;
case 'CNAME+A':
$type='0005';
$ns=$buffer->detail;
$nss=explode('.',$ns);
$detail[0]='';
foreach($nss as $part){
$len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT);
$tpart=bin2hex($part);
$detail[0]=$detail[0].$len.$tpart;
};
$detail[0]=$detail[0].'00';
$lenth[0]=str_pad(dechex((strlen($detail[0])/2)),4,"0",STR_PAD_LEFT);
$ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT);
$answer='';
$answer=$answer.'C00C'.$type.'0001'.$ttl.$lenth[0].$detail[0];
$ip=dns_get_record($ns,DNS_A);
$type='0001';
$n=0;
foreach($ip as $i){
$ttl=str_pad(dechex($i['ttl']),8,"0",STR_PAD_LEFT);
$i=$i['ip'];
$nss=explode('.',$i);
$detail[$n]='';
foreach($nss as $part){
$tpart=str_pad(dechex($part),2,"0",STR_PAD_LEFT);
$detail[$n]=$detail[$n].$tpart;
};
$lenth[$n]=str_pad(dechex((strlen($detail[$n])/2)),4,"0",STR_PAD_LEFT);
$n=$n+1;
};
$n=0;
foreach($detail as $c){
$rlenth='';
$rlenth=$lenth[$n];
$n=$n+1;
$answer=$answer.'C02B'.$type.'0001'.$ttl.$rlenth.$c;
}
$status='8180';
$questions='0001';
$AuthorityRRs='0000';
$AdditionalRRs='0000';
$AnswerRRs=str_pad((count((array)$ip)+1),4,"0",STR_PAD_LEFT);
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer;
return Dns::send($response,$buffer->query,$buffer->info);
break;
case 'CNAME+AAAA':
$type='0005';
$ns=$buffer->detail;
$nss=explode('.',$ns);
$detail[0]='';
foreach($nss as $part){
$len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT);
$tpart=bin2hex($part);
$detail[0]=$detail[0].$len.$tpart;
};
$detail[0]=$detail[0].'00';
$lenth[0]=str_pad(dechex((strlen($detail[0])/2)),4,"0",STR_PAD_LEFT);
$ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT);
$answer='';
$answer=$answer.'C00C'.$type.'0001'.$ttl.$lenth[0].$detail[0];
$ip=dns_get_record($ns,DNS_AAAA);
$type='001C';
$n=0;
foreach($ip as $i){
$ipv6=$i['ipv6'];
$hexstr = unpack("H*hex", inet_pton($ipv6));
$ipv6=substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hexstr['hex']), 0, -1);
$ipv6=str_replace(':','',$ipv6);
#$ipv6= bin2hex($ipv6);
$detail[$n]="$ipv6";
$lenth[$n]="0010";
$n=$n+1;
};
$n=0;
foreach($detail as $c){
$rlenth='';
$rlenth=$lenth[$n];
$n=$n+1;
$answer=$answer.'C02C'.$type.'0001'.$ttl.$rlenth.$c;
}
$status='8180';
$questions='0001';
$AuthorityRRs='0000';
$AdditionalRRs='0000';
$AnswerRRs=str_pad((count((array)$ip)+1),4,"0",STR_PAD_LEFT);
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer;
return Dns::send($response,$buffer->query,$buffer->info);
break;
case 'SOA': case 'SOA':
$type='0006'; $type='0006';
$ns=$buffer->detail; $ns=$buffer->detail;
$ns=json_decode( json_encode( $ns),true); $ns=json_decode( json_encode( $ns),true);
if($ns['type']=='auto'){ if($ns['type']=='none'){
$Rns=dns_get_record($ns['name'],DNS_SOA); $Rns=dns_get_record($ns['name'],DNS_SOA);
$Rns=$Rns[0]; $Rns=$Rns[0];
$ns=$Rns; $ns=$Rns;
@ -338,81 +174,12 @@ class Dns
$n=$n+1; $n=$n+1;
}; };
break; break;
case 'none':
$type='0006';
$ns=$buffer->detail;
$ns=json_decode( json_encode( $ns),true);
var_dump($ns);
if($ns['type']=='auto'){
$ns=$ns['name'];
$url=$ns;
while(true){
preg_match("#\.(.*)#i",$url,$match);//获取根域名
$domin = $match[1];
$soa=dns_get_record($domin,DNS_SOA);
if(array_key_exists('0',$soa)){
if(array_key_exists('mname',$soa[0])){
$qname=$domin;
$ns=$soa[0];
break;
}else{
$url=$domin;
}
}else{
$url=$domin;
}
}
}else{
$qname=$ns['qname'];
}
$nss=explode('.',$ns['mname']);
$detail='';
foreach($nss as $part){
$len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT);
$tpart=bin2hex($part);
$detail=$detail.$len.$tpart;
};
$detail=$detail.'00';
unset($nss,$len,$tpart);
$nss=explode('.',$ns['rname']);
foreach($nss as $part){
$len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT);
$tpart=bin2hex($part);
$detail=$detail.$len.$tpart;
};
$detail=$detail.'00'.str_pad(dechex($ns['serial']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['refresh']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['retry']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['expire']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['minimum-ttl']),8,"0",STR_PAD_LEFT);
$lenth=str_pad(dechex((strlen($detail)/2)),4,"0",STR_PAD_LEFT);
$ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT);
$status='8183';
$questions='0001';
$AnswerRRs='0000';
$AuthorityRRs='0001';
$AdditionalRRs='0000';
#$qname
$nss=explode('.',$qname);
$qname='';
foreach($nss as $part){
#$len=strlen($part);
$len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT);
$tpart=bin2hex($part);
$qname=$qname.$len.$tpart;
};
$qname=$qname.'00';
$answer='';
$answer=$answer.$qname.$type.'0001'.$ttl.$lenth.$detail;
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer;
return Dns::send($response,$buffer->query,$buffer->info);
break;
} }
$ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT); $ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT);
$status='8180'; $status='8180';
$questions='0001'; $questions='0001';
$AnswerRRs=str_pad(count((array)$buffer->detail),4,"0",STR_PAD_LEFT); $AnswerRRs=str_pad(count((array)$buffer->detail),4,"0",STR_PAD_LEFT);
#$AnswerRRs='0001';
$AuthorityRRs='0000'; $AuthorityRRs='0000';
$AdditionalRRs='0000'; $AdditionalRRs='0000';
$answer=''; $answer='';
@ -424,8 +191,7 @@ class Dns
$answer=$answer.'C00C'.$type.'0001'.$ttl.$rlenth.$c; $answer=$answer.'C00C'.$type.'0001'.$ttl.$rlenth.$c;
} }
$response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer; $response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer;
$traffic=strlen(hex2bin($response)); return hex2bin($response);
return Dns::send($response,$buffer->query,$buffer->info);
} }
/** /**
@ -436,18 +202,9 @@ class Dns
*/ */
public static function decode($buffer) public static function decode($buffer)
{ {
$traffic=strlen($buffer);#接收流量 $data=bin2hex($buffer);
$data=bin2hex($buffer); $id=substr($data,0,4);
$id=substr($data,0,4); $type=substr($data,-8,4);
$flag=substr($data,4,4);
$questions=substr($data,8,4);
$answerRRs=substr($data,12,4);
$authorityRRs=substr($data,16,4);
$additionalRRs=substr($data,20,4);
$gdomain=Dns::getDomain($data,24);
$realname=$gdomain['name'];
$startbyte=$gdomain['startbyte'];
$type=substr($data,$startbyte,4);
switch($type){ switch($type){
case '0001': case '0001':
$type='A'; $type='A';
@ -474,55 +231,21 @@ class Dns
$type='MX'; $type='MX';
break; break;
} }
$query=substr($data,24,$startbyte-16); $name=substr($data,24,-8);
#additionalRRs $namede=str_split($name,2);
if($authorityRRs=='0000'&&$additionalRRs=='0001'){ $realname='';
$addR=new \StdClass(); foreach($namede as $cha){
$startbyte=$startbyte+8; $chat=hex2bin($cha);
$addR_name=Dns::getDomain($data,$startbyte); if(!ctype_alnum($chat)){
$addR_rname=$addR_name['name']; $chat='.';
$startbyte=$addR_name['startbyte'];
if($addR_rname==''){
$addR_rname=$realname;
} }
$addR_type=substr($data,$startbyte,4); $realname=$realname.$chat;
$startbyte=$startbyte+4;
$addR->realname=$addR_rname;
$addR->type=$addR_type;
#OPT
if($addR_type=='0029'){
#dns.rr.udp_playload_size,请求定义该值后响应将可突破512byte默认限制
$addR->playloadSize=hexdec(substr($data,$startbyte,4));
$addR->rcode=substr($data,$startbyte+4,2);#dns.resp.ext_rcode
$addR->edns0v=substr($data,$startbyte+6,2);#Edns0 拓展协议版本
$addR->Z=substr($data,$startbyte+8,4);
$addR->optLen=hexdec(substr($data,$startbyte+12,4))*2;
if($addR->optLen!=0){
$startbyte=$startbyte+16;
$opt=substr($data,$startbyte,$addR->optLen);
$opt_type=substr($opt,0,4);
if($opt_type=='0008'){
$addR->opt_type='CSUBNET';
$csubnet_len=hexdec(substr($opt,4,4))*2;
$csubnet_data=substr($opt,8,$csubnet_len);
$csubnet_family=substr($csubnet_data,0,4);
#IPv4
if($csubnet_family=='0001'){
$csubnet_source=substr($csubnet_data,4,2);
$csubnet_scope=substr($csubnet_data,6,2);
$csubnet_ip=long2ip(hexdec(substr(substr($csubnet_data,8,$csubnet_len-8).'00000000',0,8)));
$addR->csubnet=['family'=>$csubnet_family,'source'=>$csubnet_source,'scope'=>$csubnet_scope,'ip'=>$csubnet_ip];
}
}
}
}
}else{
$addR=null;
} }
$realname=substr($realname,1,-1);
$query=substr($data,24);
#$returndata="$type".'|||'."$realname";
$returndata= json_encode(array('type' => $type, 'name' => "$realname", 'id'=>"$id", 'query'=>"$query",'traffic'=>$traffic,'addR'=>$addR)); $returndata= json_encode(array('type' => $type, 'name' => "$realname", 'id'=>"$id", 'query'=>"$query"));
return $returndata; return $returndata;
} }