Workerman-DNS/readme.md
2024-02-06 17:49:08 +08:00

544 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WorkermanDNS DOC
# 有什么用
让您能够使用Workerman编写DNS服务器。
该库仅提供DNS协议的响应方式和DNS请求的解析。记录的查询、储存、缓存都需要您自行编写完成。
# 支持特性
- 支持的协议:
A AAAA CNAME MX PTR NS TXT SOA
- 支持的Flag标签
- 8180OK≈HTTP200 :正常
- 8182SERVFAIL≈HTTP503 :服务器错误
- 8183NXDOMAIN≈HTTP404 :记录不存在
- 8185REFUSE≈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 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**