Workerman的DNS协议,实现了简单的DNS协议解析和响应,通过本协议支持,您可以利用Workerman实现基于PHP的Dns服务器
Go to file
以诺书 9130d8c2b0 0.1.1 2024-02-06 17:49:08 +08:00
.history save 2022-12-15 18:09:24 +08:00
vendor 0.1.1 2024-02-06 17:49:08 +08:00
.gitignore 忽略log 2024-01-29 16:33:39 +08:00
Dns.php 0.1.1 2024-02-06 17:49:08 +08:00
composer.json save 2022-12-15 18:09:24 +08:00
composer.lock save 2022-12-15 18:09:24 +08:00
license Add license file, and modify readme.md.Add Flag&Raw mode 2024-01-31 01:08:38 +08:00
php-ipv6.php first commit 2022-12-16 20:36:45 +08:00
readme.md 0.1.1 2024-02-06 17:49:08 +08:00
start.php 0.1.1 2024-02-06 17:49:08 +08:00
test.php 0.1.1 2024-02-06 17:49:08 +08:00

readme.md

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手册

Workerman框架

通过Composer安装Workerman框架

composer require workerman/workerman

下载Dns.php

可通过

https://git.laysense.com/enoch/Workerman-DNS/raw/branch/master/Dns.php

raw.githubusercontent.com/ywnsya/Workerman-DNS/master/Dns.php

或从Releases中

Workerman-DNS

下载Dns.php文件

下载IPv6支持库

如您需要使用到IPv6功能(您也可以用其他库代替不影响DNS协议的使用)请下载IPv6支持库

地址为:

git.laysense.com/enoch/Workerman-DNS/raw/branch/master/php-ipv6.php

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
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']应为一个数组。

$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
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地址。

$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域名客户端将向另一个域名发送请求。

		$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

#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之内

$send['type']='TEXT';
$send['detail'][1]='text1-test';
$send['detail'][2]='text2-test';
$send['ttl']=600;

$send['detail']为数组。值为TEXT记录内容(字符串)

MX

MX邮件交换记录建设邮箱时常用。

    $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服务器解析。

$send['type']='NS';
$send['detail'][1]='coco.bunny.net';
$send['detail'][2]='kiki.bunny.net';
$send['ttl']=600;

$send['detail']为数组值为DNS服务器名称。

SOA

SOA有自动和手动两种

  • 自动
	  $send['type']='SOA';
    $send['detail']= array();
    $send['detail']['type']='auto';#自动获取上级的SOA
    $send['detail']['name']="$name";

自动则指定 $send['detail']['type']='auto' 将自行从上级DNS服务器获取SOA信息。以上代码无需改动直接使用即可。

  • 手动
$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部分也可以使用自动和手动两种。

$send['type']='none';
$send['detail']= array();
$send['detail']['type']='auto';
$send['detail']['name']="$name";
		$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

    $send['type']='flag';
    $send['flag']='NXDOMAIN';
    

    NXDOMAIN表示域名不存在使用flag类型返回NXDOMAIN时不会附带SOA,当作为权威DNS时可能造成LDNS发生错误,不建议使用,建议使用none类型替代

  • SERVFAIL

    $send['type']='flag';
    $send['flag']='SERVFAIL';
    

    SERVFAIL表示解析遇到错误,类似于HTTP的503

  • REFUSE

    $send['type']='flag';
    $send['flag']='REFUSE';
    

    REFUSE表示服务器拒绝此请求,可用于限定客户端的IP范围

Flag类型无需返回$send['detail']

Raw

Raw是WorkermanDNS自定义的一种类型将不再处理直接以16进制形式响应从而无需进行16进制拼合以加快响应速度。一般是在第一次响应完毕后通过截取记录和请求写入缓存(如Redis或共享变量等)后续遇到相同请求且在TTL内直接返回。

$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结构类似如下

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中的代码

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

  • Github

https://github.com/ywnsya/Workerman-DNS

License

Apache License 2.0