release: version 1.0.0

This commit is contained in:
骑着蜗牛追导弹 2024-11-07 23:54:55 +08:00
parent 4cc618c2ab
commit af0bb44e33
19 changed files with 1307 additions and 119 deletions

View File

@ -1,25 +1,98 @@
# kenaito-dns # kenaito-dns
DNS服务器可通过Web管理界面随意设置灵活的解析规则。为了纯血自研devops平台而生。 ## 背景
# Go代理地址配置 Bind9不能直接支持API的方式添加解析记录 通过脚本修改Bind服务器配置这种事情实在是太冒险了而且没有发现有开源的、性能嘎嘎好的DNS服务器项目。
## 简介
一个轻量级 DNS 服务器让变更解析记录简单、优雅为了纯血自研devops平台而生。
## 环境依赖
- gcc
- go version >= 1.20
## 接口文档
[在线阅读](https://oss.odboy.cn/blog/files/onlinedoc/kenaito-dns.html)
## 项目结构
- constant 常量
- controller api接口
- core dns解析
- dao 数据库交互
- domain 各种领域模型
- util 工具函数
## 项目耗时
``
more than 7 hours
``
## 主要特性
- 纳秒级、毫秒级( <= 5 )响应时间
- 支持API变更解析记录
- 支持解析记录回滚
- 支持A、AAAA、MX、TXT、CNAME记录解析
## 运行配置
#### Go代理地址配置
[去看看](https://blog.odboy.cn/go%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE%E5%9B%BD%E5%86%85%E6%BA%90-by-odboy/) [去看看](https://blog.odboy.cn/go%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE%E5%9B%BD%E5%86%85%E6%BA%90-by-odboy/)
# nslookup 命令不存在解决 #### window安装gcc(记得配置环境变量哦, 记得重启电脑哦)
- [去看看](https://github.com/niXman/mingw-builds-binaries/releases)
- [去下载](https://github.com/niXman/mingw-builds-binaries/releases/download/14.2.0-rt_v12-rev0/x86_64-14.2.0-release-posix-seh-msvcrt-rt_v12-rev0.7z)
- [国内下载](https://oss.odboy.cn/blog/files/windows-gcc/x86_64-14.2.0-release-posix-seh-msvcrt-rt_v12-rev0.7z)
#### window验证gcc
```shell
gcc -v
```
## 问题解决
#### nslookup 命令不存在解决
```shell ```shell
yum install bind-utils -y yum install bind-utils -y
``` ```
# nslookup指定dns服务器查询 #### nslookup指定dns服务器查询
```shell ```shell
# 这里dns服务器为 192.168.1.103 # 这里dns服务器为 192.168.1.103
nslookup example.com 192.168.1.103 nslookup example.com 192.168.1.103
``` ```
# 本程序所用依赖,感谢开源者的无私奉献 ## 特别鸣谢
- [数据库操作](http://xorm.topgoer.com/) - [数据库操作 - xorm](http://xorm.topgoer.com/)
- [DNS解析](https://github.com/miekg/dns) - [DNS解析 - miekg/dns](https://github.com/miekg/dns)
- [Web - gin](https://gin-gonic.com/zh-cn/docs/quickstart/)
## 代码托管
- Gitea: [https://gitea.odboy.cn/odboy/kenaito-dns](https://gitea.odboy.cn/odboy/kenaito-dns)
- Github: [https://github.com/odboy-tianjun/kenaito-dns](https://github.com/odboy-tianjun/kenaito-dns)
- Gitee: [https://gitee.com/odboy/kenaito-dns](https://gitee.com/odboy/kenaito-dns)
## 微信交流群
![wxcode](https://oss.odboy.cn/blog/files/userinfo/MyWxCode.png)
(扫码添加微信备注kenaito-dns邀您加入群聊)
加入群聊的好处:
- 第一时间收到项目更新通知。
- 第一时间收到项目 bug 通知。
- 第一时间收到新增开源案例通知。
- 和众多大佬一起互相 (huá shuǐ) 交流 (mō yú)。

67
RRType.md Normal file
View File

@ -0,0 +1,67 @@
# 记录类型解析
- A记录
```text
将域名解析指向一个IPv4地址通常为网站服务器的IPv4地址例如223.5.5.x 。
如果您的域名要解析指向多个IPv4地址可以通过添加多条”主机记录”相同但“记录值”不同的解析记录实现。
```
- AAAA
```text
将域名解析指向一个IPv6地址通常为网站服务器的IPv6地址例如ff03:0:0:0:0:0:0:c1 。
如果您的域名要解析指向多个IPv6地址可以通过添加多条“主机记录”相同但“记录值”不同的解析记录实现。
```
- CNAME记
```text
将域名解析指向另一个域名, 由另一个域名提供 IP 地址解析结果。在使用CDN、企业邮箱、全局流量管理等产品时通常需要通过配置CNAME记录解析到这类产品的CNAME接入域名
如果您的域名要解析指向多个域名,可以通过添加多条“主机记录”相同但“记录值”不同的解析记录实现。
```
- NS
```text
如果需要把子域名交给其他DNS服务商解析可以通过添加NS记录实现。通常其他DNS服务商地址会有多个可以通过配置主机记录子域名相同但记录值不同的多条NS记录实现。
```
- MX
```text
MX全称为mail exchanger用于电子邮件系统发邮件时根据收信人的地址后缀来定位邮件服务器地址。
```
- SRV
```text
SRV 记录用来标识某服务器提供了特定服务通过SRV记录还可以获取对应服务的地址、端口、优先级、权重等信息。常见于微软系统的目录管理。
SRV记录的主机记录格式通常为“服务的名字.协议的类型”例如_sip._tcp
```
- TXT
```text
如果希望对域名进行标识和说明,可以使用 TXT 记录。 TXT 记录常用来做SSL数字证书签发验证、SPF 记录(反垃圾邮件)
```
# 主机记录参数Name说明
```text
主机记录就是域名前缀,常用主机记录及含义如下:
www表示域名 www.odboy.cn
@表示主域名 odboy.cn
*泛解析,表示满足格式 *.odboy.cn 的所有域名记录类型为“显性URL”时不允许设置泛解析
mail表示域名 mail.odboy.cn ,常用于邮箱业务的解析设置
m表示域名 m.odboy.cn ,常用于手机网站
二级域名如abc.odboy.cn则写abc
多级域名如ab.cd.odboy.cn则写ab.cd
```
# TTL参数ttl说明
```text
TTL通常指全球运营商LocalDNS在获得权威DNS返回的解析结果后将解析结果缓存的有效时间也称为解析结果在LocalDNS的生存时间。通常TTL值越小解析变更对终端用户生效更快。
建议将TTL设置为默认10分钟过小的TTL值会导致Localdns缓存解析结果时间较短并影响终端用户访问域名的解析速度。日常运维时可以先将TTL调整为1分钟待2小时后修改记录值并将TTL调整为10分钟这有助于解析快速生效。
```

6
config/app.go Normal file
View File

@ -0,0 +1,6 @@
package config
const (
AppVersion = "1.0.0"
AppTimeFormat = "2006/01/02 15:04:05.999999"
)

18
config/database.go Normal file
View File

@ -0,0 +1,18 @@
package config
/*
* @Description 数据库配置
* @Author www.odboy.cn
* @Date 20241108
*/
import "xorm.io/core"
const (
DataSourceDriverName = "sqlite3" // 驱动名称
DataSourceName = "dns.sqlite3" // 数据源名称
DataSourceMaxOpenConnectionSize = 30 // 最大db连接数
DataSourceMaxIdleConnectionSize = 10 // 最大db连接空闲数
DataSourceConnMaxLifetime = 30 // 超过空闲数连接存活时间
DataSourceShowLog = true // 是否显示SQL语句
DataSourceLogLevel = core.LOG_DEBUG // 日志级别
)

10
config/dns_server.go Normal file
View File

@ -0,0 +1,10 @@
package config
/*
* @Description DNS服务配置
* @Author www.odboy.cn
* @Date 20241108
*/
const (
DnsServerPort = ":53"
)

15
config/web_server.go Normal file
View File

@ -0,0 +1,15 @@
package config
/*
* @Description Web服务配置
* @Author www.odboy.cn
* @Date 20241108
*/
import "github.com/gin-gonic/gin"
const (
WebServerPort = ":18001"
WebMode = gin.ReleaseMode
WebReadTimeout = 10
WebWriteTimeout = 10
)

19
constant/rr_type.go Normal file
View File

@ -0,0 +1,19 @@
package constant
/*
* @Description 解析记录类型 常量
* @Author www.odboy.cn
* @Date 20241107
*/
const (
// R_A A记录
R_A = "A"
// R_AAAA AAAA记录
R_AAAA = "AAAA"
// R_CNAME CNAME记录
R_CNAME = "CNAME"
// R_MX MX记录
R_MX = "MX"
// R_TXT TXT记录
R_TXT = "TXT"
)

View File

@ -0,0 +1,248 @@
package controller
/*
* @Description Web控制层
* @Author www.odboy.cn
* @Date 20241108
*/
import (
"fmt"
"github.com/gin-gonic/gin"
"kenaito-dns/constant"
"kenaito-dns/dao"
"kenaito-dns/domain"
"kenaito-dns/util"
"net/http"
"strings"
)
func InitRestFunc(r *gin.Engine) {
// 健康检查
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
// 创建RR记录
r.POST("/create", func(c *gin.Context) {
var jsonObj domain.CreateResolveRecord
err := c.ShouldBindJSON(&jsonObj)
newRecord, isErr := validRequestBody(c, err, jsonObj.Name, jsonObj.Type, jsonObj.Ttl, jsonObj.Value, false)
if isErr {
return
}
if dao.IsResolveRecordExist(newRecord) {
c.JSON(http.StatusBadRequest, gin.H{"message": "记录 " + newRecord.Name + " " + newRecord.RecordType + " " + newRecord.Value + " 已存在"})
return
}
newRecord.Ttl = jsonObj.Ttl
executeResult, err, oldVersion, newVersion := dao.BackupResolveRecord(newRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("添加"+newRecord.RecordType+"记录失败, %v", err)})
return
}
executeResult, _ = dao.SaveResolveRecord(newRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("添加"+newRecord.RecordType+"记录失败, %v", err)})
return
}
body := make(map[string]interface{})
body["oldVersion"] = oldVersion
body["newVersion"] = newVersion
c.JSON(http.StatusOK, gin.H{
"message": "添加" + newRecord.RecordType + "记录成功",
"body": body,
})
return
})
// 删除RR记录
r.POST("/remove", func(c *gin.Context) {
var jsonObj domain.RemoveResolveRecord
err := c.ShouldBindJSON(&jsonObj)
newRecord, isErr := validRequestBody(c, err, jsonObj.Name, jsonObj.Type, 0, jsonObj.Value, true)
if isErr {
return
}
if !dao.IsResolveRecordExist(newRecord) {
c.JSON(http.StatusBadRequest, gin.H{"message": "记录 " + newRecord.Name + " " + newRecord.RecordType + " " + newRecord.Value + " 不存在"})
return
}
executeResult, err, oldVersion, newVersion := dao.BackupResolveRecord(newRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("删除"+newRecord.RecordType+"记录失败, %v", err)})
return
}
executeResult, err = dao.RemoveResolveRecord(newRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("删除"+newRecord.RecordType+"记录失败, %v", err)})
return
}
body := make(map[string]interface{})
body["oldVersion"] = oldVersion
body["newVersion"] = newVersion
c.JSON(http.StatusOK, gin.H{
"message": "删除" + newRecord.RecordType + "记录成功",
"body": body,
})
return
})
// 修改RR记录
r.POST("/modify", func(c *gin.Context) {
var jsonObj domain.ModifyResolveRecord
err := c.ShouldBindJSON(&jsonObj)
if jsonObj.Id == 0 {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数ID(id)必填"})
return
}
newRecord, isErr := validRequestBody(c, err, jsonObj.Name, jsonObj.Type, jsonObj.Ttl, jsonObj.Value, false)
if isErr {
return
}
if !dao.IsResolveRecordExistById(jsonObj.Id) {
c.JSON(http.StatusBadRequest, gin.H{"message": "记录 " + newRecord.Name + " " + newRecord.RecordType + " " + newRecord.Value + " 不存在"})
return
}
if dao.IsUpdResolveRecordExist(jsonObj.Id, newRecord) {
c.JSON(http.StatusBadRequest, gin.H{"message": "记录 " + newRecord.Name + " " + newRecord.RecordType + " " + newRecord.Value + " 已存在"})
return
}
executeResult, err, oldVersion, newVersion := dao.BackupResolveRecord(newRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("修改"+newRecord.RecordType+"记录失败, %v", err)})
return
}
executeResult, err = dao.ModifyResolveRecordById(jsonObj.Id, newRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("更新"+newRecord.RecordType+"记录失败, %v", err)})
return
}
body := make(map[string]interface{})
body["oldVersion"] = oldVersion
body["newVersion"] = newVersion
c.JSON(http.StatusOK, gin.H{
"message": "更新" + newRecord.RecordType + "记录成功",
"body": body,
})
return
})
// 分页查询RR记录
r.POST("/queryPage", func(c *gin.Context) {
var jsonObj domain.QueryPageArgs
err := c.ShouldBindJSON(&jsonObj)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return
}
records := dao.FindResolveRecordPage(jsonObj.Page, jsonObj.PageSize, &jsonObj)
c.JSON(http.StatusOK, gin.H{"message": "分页查询RR记录成功", "body": records})
return
})
// 根据id查询RR记录明细
r.POST("/queryById", func(c *gin.Context) {
var jsonObj domain.QueryByIdArgs
err := c.ShouldBindJSON(&jsonObj)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return
}
records := dao.FindResolveRecordById(jsonObj.Id)
c.JSON(http.StatusOK, gin.H{"message": "根据id查询RR记录明细成功", "body": records})
return
})
// 查询变更历史记录
r.POST("/queryVersionList", func(c *gin.Context) {
records := dao.FindResolveVersion()
c.JSON(http.StatusOK, gin.H{"message": "查询变更历史记录列表成功", "body": records})
return
})
// 回滚到某一版本
r.POST("/rollback", func(c *gin.Context) {
var jsonObj domain.RollbackVersionArgs
err := c.ShouldBindJSON(&jsonObj)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return
}
versions := dao.FindResolveRecordByVersion(jsonObj.Version)
if len(versions) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("版本号 %d 不存在, 回滚失败", jsonObj.Version)})
return
}
executeResult, err := dao.ModifyResolveVersion(jsonObj.Version)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("回滚失败, %v", err)})
return
}
body := make(map[string]interface{})
body["currentVersion"] = jsonObj.Version
c.JSON(http.StatusOK, gin.H{
"message": "回滚成功",
"body": body,
})
return
})
}
func validRequestBody(c *gin.Context, err error, name string, recordType string, ttl int, value string, isDelete bool) (*dao.ResolveRecord, bool) {
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return nil, true
}
if util.IsBlank(name) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数主机记录(name)必填, 例如: www.odboy.cn 或 odboy.cn"})
return nil, true
}
if util.IsBlank(recordType) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录类型(type)必填, 目前支持 A、AAAA 记录"})
return nil, true
}
if util.IsBlank(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)必填"})
return nil, true
}
if !isDelete {
if ttl < 10 {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数缓存有效时间(ttl)有误必须大于等于10"})
return nil, true
}
}
if !util.IsValidName(name) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数主机记录(name)有误,无效的主机记录"})
return nil, true
}
switch recordType {
case constant.R_A:
if !util.IsIPv4(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误无效的IPv4地址"})
return nil, true
}
case constant.R_AAAA:
if !util.IsIPv6(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误无效的IPv6地址"})
return nil, true
}
case constant.R_CNAME:
if !util.IsValidDomain(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误,无效的主机记录"})
return nil, true
}
case constant.R_MX:
if !util.IsValidDomain(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误,无效的主机记录"})
return nil, true
}
case constant.R_TXT:
if len(value) > 512 {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误,长度必须 <= 512"})
return nil, true
}
default:
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(type)有误,不支持的记录类型: " + recordType})
return nil, true
}
newRecord := new(dao.ResolveRecord)
newRecord.Name = strings.TrimSpace(name)
newRecord.RecordType = strings.TrimSpace(recordType)
newRecord.Value = strings.TrimSpace(value)
return newRecord, false
}

156
core/handler.go Normal file
View File

@ -0,0 +1,156 @@
package core
/*
* @Description DNS解析处理入口
* @Author www.odboy.cn
* @Date 20241107
*/
import (
"fmt"
"github.com/miekg/dns"
"kenaito-dns/constant"
"kenaito-dns/dao"
"net"
)
func HandleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
msg := new(dns.Msg)
msg.SetReply(r)
// 将 DNS 响应标记为权威应答
msg.Authoritative = true
// 将 DNS 响应标记为递归可用
msg.RecursionAvailable = true
// 遍历请求中的问题部分,生成相应的回答
for _, question := range r.Question {
switch question.Qtype {
case dns.TypeA:
handleARecord(question, msg)
case dns.TypeAAAA:
handleAAAARecord(question, msg)
case dns.TypeCNAME:
handleCNAMERecord(question, msg)
case dns.TypeMX:
handleMXRecord(question, msg)
case dns.TypeTXT:
handleTXTRecord(question, msg)
}
}
// 发送响应
err := w.WriteMsg(msg)
if err != nil {
fmt.Printf("=== Handle Failed === %v \n", err)
}
}
// 构建 A 记录 IPV4
func handleARecord(q dns.Question, msg *dns.Msg) {
name := q.Name
queryName := name[0 : len(name)-1]
records := dao.FindResolveRecordByNameType(queryName, constant.R_A)
if len(records) > 0 {
for _, record := range records {
fmt.Printf("=== A记录 === 请求解析的域名:%s,解析的目标IP地址:%s\n", name, record.Value)
ip := net.ParseIP(record.Value)
rr := &dns.A{
Hdr: dns.RR_Header{
Name: name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: uint32(record.Ttl) * 60,
},
A: ip,
}
msg.Answer = append(msg.Answer, rr)
}
}
}
// 构建 AAAA 记录 IPV6
func handleAAAARecord(q dns.Question, msg *dns.Msg) {
name := q.Name
queryName := name[0 : len(name)-1]
records := dao.FindResolveRecordByNameType(queryName, constant.R_AAAA)
if len(records) > 0 {
for _, record := range records {
fmt.Printf("=== AAAA记录 === 请求解析的域名:%s,解析的目标IP地址:%s\n", name, record.Value)
ip := net.ParseIP(record.Value)
rr := &dns.AAAA{
Hdr: dns.RR_Header{
Name: name,
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
Ttl: uint32(record.Ttl) * 60,
},
AAAA: ip,
}
msg.Answer = append(msg.Answer, rr)
}
}
}
// 构建 CNAME 记录
func handleCNAMERecord(q dns.Question, msg *dns.Msg) {
name := q.Name
queryName := name[0 : len(name)-1]
records := dao.FindResolveRecordByNameType(queryName, constant.R_CNAME)
if len(records) > 0 {
for _, record := range records {
fmt.Printf("=== CNAME记录 === 请求解析的域名:%s,解析的目标域名:%s\n", name, record.Value)
rr := &dns.CNAME{
Hdr: dns.RR_Header{
Name: name,
Rrtype: dns.TypeCNAME,
Class: dns.ClassINET,
Ttl: uint32(record.Ttl) * 60,
},
Target: record.Value + ".",
}
msg.Answer = append(msg.Answer, rr)
}
}
}
// 构建 MX 记录
func handleMXRecord(q dns.Question, msg *dns.Msg) {
name := q.Name
queryName := name[0 : len(name)-1]
records := dao.FindResolveRecordByNameType(queryName, constant.R_MX)
if len(records) > 0 {
for _, record := range records {
fmt.Printf("=== MX记录 === 请求解析的域名:%s,解析的目标域名:%s, MX优先级: 10\n", name, record.Value)
rr := &dns.MX{
Hdr: dns.RR_Header{
Name: name,
Rrtype: dns.TypeMX,
Class: dns.ClassINET,
Ttl: uint32(record.Ttl) * 60,
},
Preference: 10,
Mx: record.Value + ".",
}
msg.Answer = append(msg.Answer, rr)
}
}
}
// 构建 TXT 记录
func handleTXTRecord(q dns.Question, msg *dns.Msg) {
name := q.Name
queryName := name[0 : len(name)-1]
records := dao.FindResolveRecordByNameType(queryName, constant.R_TXT)
if len(records) > 0 {
for _, record := range records {
fmt.Printf("=== TXT记录 === 请求解析的域名:%s,解析的目标值:%s\n", name, record.Value)
rr := &dns.TXT{
Hdr: dns.RR_Header{
Name: name,
Rrtype: dns.TypeTXT,
Class: dns.ClassINET,
Ttl: uint32(record.Ttl) * 60,
},
Txt: []string{record.Value},
}
msg.Answer = append(msg.Answer, rr)
}
}
}

39
dao/database.go Normal file
View File

@ -0,0 +1,39 @@
package dao
/*
* @Description 连接数据库
* @Author www.odboy.cn
* @Date 20241107
*/
import (
"fmt"
"github.com/go-xorm/xorm"
_ "github.com/mattn/go-sqlite3"
"kenaito-dns/config"
"time"
)
var (
Engine *xorm.Engine
)
func init() {
var err error
Engine, err = xorm.NewEngine(config.DataSourceDriverName, config.DataSourceName)
if err != nil {
fmt.Println(err)
}
fmt.Println("[xorm] [info] " + time.Now().Format(config.AppTimeFormat) + " 数据库引擎创建成功(database engine create success)")
// 连接池配置
Engine.SetMaxOpenConns(config.DataSourceMaxOpenConnectionSize)
Engine.SetMaxIdleConns(config.DataSourceMaxIdleConnectionSize)
Engine.SetConnMaxLifetime(config.DataSourceConnMaxLifetime * time.Minute)
// 日志相关
Engine.ShowSQL(config.DataSourceShowLog)
Engine.Logger().SetLevel(config.DataSourceLogLevel)
err = Engine.Ping()
if err != nil {
fmt.Println(err)
}
fmt.Println("[xorm] [info] " + time.Now().Format(config.AppTimeFormat) + " 数据库连接成功(database engine connect success)")
}

42
dao/resolve_config.go Normal file
View File

@ -0,0 +1,42 @@
package dao
/*
* @Description 解析配置定义与操作
* @Author www.odboy.cn
* @Date 20241107
*/
import (
"fmt"
)
type ResolveVersion struct {
Id int `xorm:"pk not null integer 'id' autoincr"`
CurrentVersion int `xorm:"not null integer 'curr_version'"`
}
func getResolveVersion() int {
var records []ResolveVersion
err := Engine.Table("resolve_config").Where("`id` = ?", 1).Find(&records)
if err != nil {
fmt.Println(err)
return 0
}
if len(records) == 0 {
return 0
}
return records[0].CurrentVersion
}
func ModifyResolveVersion(currentVersion int) (bool, error) {
wrapper := new(ResolveVersion)
wrapper.Id = 1
updateRecord := new(ResolveVersion)
updateRecord.CurrentVersion = currentVersion
_, err := Engine.Table("resolve_config").Update(updateRecord, wrapper)
if err != nil {
fmt.Println(err)
return false, err
}
return true, nil
}

184
dao/resolve_record.go Normal file
View File

@ -0,0 +1,184 @@
package dao
/*
* @Description 解析记录定义与操作
* @Author www.odboy.cn
* @Date 20241107
*/
import (
"fmt"
"kenaito-dns/domain"
"kenaito-dns/util"
"strings"
)
type ResolveRecord struct {
Id int `xorm:"pk not null integer 'id' autoincr"`
Name string `xorm:"not null text 'name'"`
RecordType string `xorm:"not null text 'record_type'"`
Ttl int `xorm:"not null integer 'ttl'"`
Value string `xorm:"not null text 'value'"`
Version int `xorm:"not null integer 'version'"`
}
func FindResolveRecordById(id int) []ResolveRecord {
var records []ResolveRecord
err := Engine.Table("resolve_record").Where("`id` = ?", id).Find(&records)
if err != nil {
fmt.Println(err)
}
return records
}
func FindResolveRecordByVersion(version int) []ResolveRecord {
var records []ResolveRecord
err := Engine.Table("resolve_record").Where("`version` = ?", version).Find(&records)
if err != nil {
fmt.Println(err)
}
return records
}
func FindResolveRecordByNameType(name string, recordType string) []ResolveRecord {
var records []ResolveRecord
err := Engine.Table("resolve_record").Where("`name` = ? and `record_type` = ? and `version` = ?", name, recordType, getResolveVersion()).Find(&records)
if err != nil {
fmt.Println(err)
}
return records
}
func FindResolveRecordPage(pageNo int, pageSize int, args *domain.QueryPageArgs) []*ResolveRecord {
// 每页显示5条记录
if pageSize <= 5 {
pageSize = 5
}
// 要查询的页码
if pageNo <= 0 {
pageNo = 1
}
// 计算跳过的记录数
offset := (pageNo - 1) * pageSize
records := make([]*ResolveRecord, 0)
session := Engine.Table("resolve_record").Where("")
if args != nil {
if !util.IsBlank(args.Name) {
qs := "%" + strings.TrimSpace(args.Name) + "%"
session.And("`name` LIKE ?", qs)
}
if !util.IsBlank(args.Type) {
qs := strings.TrimSpace(args.Type)
session.And("`record_type` = ?", qs)
}
if !util.IsBlank(args.Value) {
qs := strings.TrimSpace(args.Value)
session.And("`value` = ?", qs)
}
}
session.And("`version` = ?", getResolveVersion())
err := session.Limit(pageSize, offset).Find(&records)
if err != nil {
fmt.Println(err)
}
return records
}
func SaveResolveRecord(wrapper *ResolveRecord) (bool, error) {
_, err := Engine.Table("resolve_record").Insert(wrapper)
if err != nil {
fmt.Println(err)
return false, err
}
return true, nil
}
func BackupResolveRecord(record *ResolveRecord) (bool, error, int, int) {
var backupRecords []*ResolveRecord
oldVersion := getResolveVersion()
newVersion := getResolveVersion() + 1
oldRecords := FindResolveRecordByVersion(oldVersion)
for _, oldRecord := range oldRecords {
newRecord := new(ResolveRecord)
newRecord.Name = oldRecord.Name
newRecord.RecordType = oldRecord.RecordType
newRecord.Ttl = oldRecord.Ttl
newRecord.Value = oldRecord.Value
newRecord.Version = newVersion
backupRecords = append(backupRecords, newRecord)
}
record.Version = newVersion
if len(backupRecords) > 0 {
_, err := Engine.Table("resolve_record").Insert(backupRecords)
if err != nil {
return false, err, 0, 0
}
}
updRecord := new(ResolveVersion)
updRecord.CurrentVersion = newVersion
condition := new(ResolveVersion)
condition.Id = 1
_, err := Engine.Table("resolve_config").Update(updRecord, condition)
if err != nil {
return false, err, 0, 0
}
return true, nil, oldVersion, newVersion
}
func RemoveResolveRecord(wrapper *ResolveRecord) (bool, error) {
_, err := Engine.Table("resolve_record").Delete(wrapper)
if err != nil {
fmt.Println(err)
return false, err
}
return true, nil
}
func IsResolveRecordExist(wrapper *ResolveRecord) bool {
wrapper.Version = getResolveVersion()
count, err := Engine.Table("resolve_record").Count(wrapper)
if err != nil {
fmt.Println(err)
return false
}
return count > 0
}
func IsResolveRecordExistById(id int) bool {
wrapper := new(ResolveRecord)
wrapper.Id = id
return IsResolveRecordExist(wrapper)
}
func IsUpdResolveRecordExist(id int, wrapper *ResolveRecord) bool {
r := new(ResolveRecord)
r.Name = wrapper.Name
r.RecordType = wrapper.RecordType
r.Value = wrapper.Value
r.Version = getResolveVersion()
count, err := Engine.Table("resolve_record").Where("id != ?", id).Count(r)
if err != nil {
fmt.Println(err)
return false
}
return count > 0
}
func ModifyResolveRecordById(id int, updateRecord *ResolveRecord) (bool, error) {
wrapper := new(ResolveRecord)
wrapper.Id = id
_, err := Engine.Table("resolve_record").Update(updateRecord, wrapper)
if err != nil {
fmt.Println(err)
return false, err
}
return true, nil
}
func FindResolveVersion() []int {
var records []int
err := Engine.Select("distinct version").Cols("version").Table("resolve_record").Find(&records)
if err != nil {
fmt.Println(err)
}
return records
}

BIN
dns.sqlite3 Normal file

Binary file not shown.

43
domain/resolve_record.go Normal file
View File

@ -0,0 +1,43 @@
package domain
/*
* @Description 领域模型定义
* @Author www.odboy.cn
* @Date 20241108
*/
type CreateResolveRecord struct {
Name string `json:"name" binding:"required"`
Type string `json:"type" binding:"required"`
Ttl int `json:"ttl" binding:"required"`
Value string `json:"value" binding:"required"`
}
type RemoveResolveRecord struct {
Name string `json:"name" binding:"required"`
Type string `json:"type" binding:"required"`
Value string `json:"value" binding:"required"`
}
type ModifyResolveRecord struct {
Id int `json:"id" binding:"required"`
Name string `json:"name" binding:"required"`
Type string `json:"type" binding:"required"`
Ttl int `json:"ttl" binding:"required"`
Value string `json:"value" binding:"required"`
}
type QueryPageArgs struct {
Page int `json:"page" binding:"required"`
PageSize int `json:"pageSize" binding:"required"`
Name string `json:"name"`
Type string `json:"type"`
Value string `json:"value"`
}
type QueryByIdArgs struct {
Id int `json:"id" binding:"required"`
}
type RollbackVersionArgs struct {
Version int `json:"version" binding:"required"`
}

42
go.mod
View File

@ -1,13 +1,47 @@
module awesomeProject module kenaito-dns
go 1.19 go 1.20
require github.com/miekg/dns v1.1.62
require ( require (
github.com/gin-gonic/gin v1.10.0
github.com/go-xorm/xorm v0.7.9
github.com/mattn/go-sqlite3 v1.14.24
github.com/miekg/dns v1.1.62
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/tools v0.22.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect
xorm.io/core v0.7.2-0.20190928055935-90aeac8d08eb // indirect
) )

231
go.sum
View File

@ -1,12 +1,243 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/go-xorm/xorm v0.7.9 h1:LZze6n1UvRmM5gpL9/U9Gucwqo6aWlFVlfcHKH10qA0=
github.com/go-xorm/xorm v0.7.9/go.mod h1:XiVxrMMIhFkwSkh96BW7PACl7UhLtx2iJIHMdmjh5sQ=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/core v0.7.2-0.20190928055935-90aeac8d08eb h1:msX3zG3BPl8Ti+LDzP33/9K7BzO/WqFXk610K1kYKfo=
xorm.io/core v0.7.2-0.20190928055935-90aeac8d08eb/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=

View File

@ -1,80 +0,0 @@
package main
import (
"fmt"
"github.com/miekg/dns"
"net"
)
// 构建 A 记录的函数 IPV4
func handleARecord(q dns.Question, msg *dns.Msg) {
name := q.Name
targetIp := "192.235.111.111"
fmt.Printf("请求解析的域名:%s,解析的目标IP地址:%s\n", name, targetIp)
ip := net.ParseIP(targetIp)
rr := &dns.A{
Hdr: dns.RR_Header{
Name: name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 60,
},
A: ip,
}
msg.Answer = append(msg.Answer, rr)
}
//// 构建 A 记录的函数 IPV6
//func handleAAAARecord(q dns.Question, msg *dns.Msg) {
// ip := net.ParseIP("rsdw::8888")
// rr := &dns.AAAA{
// Hdr: dns.RR_Header{
// Name: q.Name,
// Rrtype: dns.TypeAAAA,
// Class: dns.ClassINET,
// Ttl: 60,
// },
// AAAA: ip,
// }
// msg.Answer = append(msg.Answer, rr)
//}
//func handleCNAMERecord(q dns.Question, msg *dns.Msg) {
// rr := &dns.CNAME{
// Hdr: dns.RR_Header{
// Name: q.Name,
// Rrtype: dns.TypeCNAME,
// Class: dns.ClassINET,
// Ttl: 60,
// },
// Target: "example.com.",
// }
// msg.Answer = append(msg.Answer, rr)
//}
//
//func handleMXRecord(q dns.Question, msg *dns.Msg) {
// rr := &dns.MX{
// Hdr: dns.RR_Header{
// Name: q.Name,
// Rrtype: dns.TypeMX,
// Class: dns.ClassINET,
// Ttl: 60,
// },
// Preference: 10,
// Mx: "mail.example.com.",
// }
// msg.Answer = append(msg.Answer, rr)
//}
//
//func handleTXTRecord(q dns.Question, msg *dns.Msg) {
// rr := &dns.TXT{
// Hdr: dns.RR_Header{
// Name: q.Name,
// Rrtype: dns.TypeTXT,
// Class: dns.ClassINET,
// Ttl: 60,
// },
// Txt: []string{"v=spf1 include:_spf.example.com ~all"},
// }
// msg.Answer = append(msg.Answer, rr)
//}

91
main.go
View File

@ -1,44 +1,81 @@
package main package main
/*
* @Description 服务入口
* @Author www.odboy.cn
* @Date 20241107
*/
import ( import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/miekg/dns" "github.com/miekg/dns"
"log" "kenaito-dns/config"
"kenaito-dns/controller"
"kenaito-dns/core"
"net/http"
"time"
) )
func main() { func main() {
fmt.Println("[app] [info] kenaito-dns version = " + config.AppVersion)
go initDNSServer()
initRestfulServer()
}
func initDNSServer() {
// 注册 DNS 请求处理函数 // 注册 DNS 请求处理函数
dns.HandleFunc(".", handleDNSRequest) dns.HandleFunc(".", core.HandleDNSRequest)
// 设置服务器地址和协议 // 设置服务器地址和协议
server := &dns.Server{Addr: ":53", Net: "udp"} server := &dns.Server{Addr: config.DnsServerPort, Net: "udp"}
// 开始监听 // 开始监听
log.Printf("Starting DNS server on %s\n", server.Addr) fmt.Printf("[dns] [info] Starting DNS server on %s\n", server.Addr)
if err := server.ListenAndServe(); err != nil { if err := server.ListenAndServe(); err != nil {
log.Fatalf("Failed to start server: %s\n", err.Error()) fmt.Printf("[dns] [error] Failed to start DNS server: %s\n", err.Error())
} }
} }
func handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { func initRestfulServer() {
msg := new(dns.Msg) gin.SetMode(config.WebMode)
msg.SetReply(r) // 创建一个新的 Gin 引擎实例,使用 gin.New() 方法来创建一个不包含任何默认中间件的实例
// 将 DNS 响应标记为权威应答 router := gin.New()
msg.Authoritative = true // LoggerWithFormatter 中间件会写入日志到 gin.DefaultWriter
// 将 DNS 响应标记为递归可用 // 默认 gin.DefaultWriter = os.Stdout
// msg.RecursionAvailable = true // 自定义的日志格式化函数
// 遍历请求中的问题部分,生成相应的回答 router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
for _, question := range r.Question { // 自定义日志格式
switch question.Qtype { return fmt.Sprintf("[gin] [info] %s [Request] [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
case dns.TypeA: // 请求时间戳
handleARecord(question, msg) param.TimeStamp.Format(config.AppTimeFormat),
//case dns.TypeAAAA: // 客户端 IP 地址
// handleAAAARecord(question, msg) param.ClientIP,
//case dns.TypeCNAME: // 请求方法 (GET, POST 等)
// handleCNAMERecord(question, msg) param.Method,
//case dns.TypeMX: // 请求路径
// handleMXRecord(question, msg) param.Path,
//case dns.TypeTXT: // 请求协议
// handleTXTRecord(question, msg) param.Request.Proto,
// 响应状态码
param.StatusCode,
// 请求延迟时间
param.Latency,
// 用户代理
param.Request.UserAgent(),
// 错误信息(如果有的话)
param.ErrorMessage,
)
}))
// 使用 Recovery 中间件,处理任何出现的错误,并防止服务崩溃
router.Use(gin.Recovery())
server := &http.Server{
Addr: config.WebServerPort,
Handler: router,
ReadTimeout: config.WebReadTimeout * time.Second,
WriteTimeout: config.WebWriteTimeout * time.Second,
} }
controller.InitRestFunc(router)
fmt.Printf("[gin] [info] Start Gin server: %s\n", config.WebServerPort)
err := server.ListenAndServe()
if err != nil {
fmt.Printf("[gin] [error] Failed to start Gin server: %s\n", config.WebServerPort)
} }
// 发送响应
w.WriteMsg(msg)
} }

46
util/strtool.go Normal file
View File

@ -0,0 +1,46 @@
package util
/*
* @Description 工具类
* @Author www.odboy.cn
* @Date 20241107
*/
import (
"net"
"regexp"
"strings"
)
// IsBlank 检查字符串是否空
func IsBlank(s string) bool {
return strings.TrimSpace(s) == ""
}
// IsValidName 判断字符串是否是有效的域名
func IsValidName(s string) bool {
// 定义域名的正则表达式
domainRegex := `^([a-zA-Z0-9][a-zA-Z0-9\-]{1,61}[a-zA-Z0-9]\.)+[a-zA-Z0-9]{2,6}$`
re := regexp.MustCompile(domainRegex)
return re.MatchString(s)
}
// IsIPv4 判断字符串是否是ipv4地址
func IsIPv4(ipAddr string) bool {
ip := net.ParseIP(ipAddr)
return ip != nil && strings.Contains(ipAddr, ".")
}
// IsIPv6 判断字符串是否是ipv6地址
func IsIPv6(ipAddr string) bool {
ip := net.ParseIP(ipAddr)
return ip != nil && strings.Contains(ipAddr, ":")
}
// IsValidDomain 判断域名是否正常解析
func IsValidDomain(domain string) bool {
_, err := net.LookupHost(domain)
if err != nil {
return false
}
return true
}