544 lines
16 KiB
Markdown
544 lines
16 KiB
Markdown
# WorkermanDNS DOC
|
||
|
||
# 有什么用
|
||
|
||
让您能够使用Workerman编写DNS服务器。
|
||
|
||
该库仅提供DNS协议的响应方式和DNS请求的解析。记录的查询、储存、缓存都需要您自行编写完成。
|
||
|
||
# 支持特性
|
||
|
||
- 支持的协议:
|
||
|
||
A AAAA CNAME MX PTR NS TXT SOA
|
||
|
||
- 支持的Flag标签:
|
||
- 8180:OK≈HTTP200 :正常
|
||
- 8182:SERVFAIL≈HTTP503 :服务器错误
|
||
- 8183:NXDOMAIN≈HTTP404 :记录不存在
|
||
- 8185:REFUSE≈HTTP403:拒绝请求
|
||
- EDNS SubnetIP支持(当通过递归服务器发送请求时,可获取到真实请求服务器的IP段)
|
||
|
||
# 安装
|
||
|
||
### PHP环境
|
||
|
||
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
|
||
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(isset($data->addR->csubnet->ip)){
|
||
$ip=$data->addR->csubnet->ip;
|
||
}else{
|
||
$ip=$rip;
|
||
}
|
||
# 请在下方编写您的DNS响应
|
||
#——————————————————————
|
||
|
||
#——————————————————————
|
||
#下方内容无需更改
|
||
$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();
|
||
```
|
||
|
||
### 请求变量
|
||
|
||
| 变量名称 | 意义 | 示例 |
|
||
| --- | --- | --- |
|
||
| $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
|
||
$send['type']='A';
|
||
$send['detail'][1]='119.29.29.29';
|
||
$send['detail'][2]='8.8.8.8';
|
||
$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;
|
||
}
|
||
|
||
$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'=>$rip,'ttl'=>$send['ttl'],'detail'=>$send['detail']]);
|
||
$send=json_encode($send);
|
||
$connection->send($send);
|
||
};
|
||
Worker::runAll();
|
||
```
|
||
|
||
以上代码意味着对于dnsservertest.com域名的A记录请求,响应192.168.2.1、2.2、2.3三个IP,指定TTL为60秒。
|
||
|
||
以下内容将不再给出完整代码示例。
|
||
|
||
### AAAA记录
|
||
|
||
AAAA记录响应一个或多个IPv6地址。
|
||
|
||
```php
|
||
$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地址。
|
||
|
||
IPv6库可以如上方示例所述,将IPv6还原完整并去除‘:’,再通过bin2hex的php函数转为16进制
|
||
|
||
### CNAME
|
||
|
||
CNAME即别名,将映射到另一个或多个DNS域名,客户端将向另一个域名发送请求。
|
||
|
||
```php
|
||
$send['type']='CNAME'
|
||
$send['detail'][1]='hk.dnsservertest.com';
|
||
$send['detail'][2]='jp.dnsservertest.com';
|
||
$send['ttl']=600;
|
||
```
|
||
|
||
$send['detail']一样为数组。
|
||
|
||
一般情况下,为了提升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 SubnetIP,rip是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** |