diff --git a/README.md b/README.md index 55af68c..7fb76c3 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,98 @@ # 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/) -# 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 yum install bind-utils -y ``` -# nslookup指定dns服务器查询 +#### nslookup指定dns服务器查询 ```shell # 这里dns服务器为 192.168.1.103 nslookup example.com 192.168.1.103 ``` -# 本程序所用依赖,感谢开源者的无私奉献 +## 特别鸣谢 -- [数据库操作](http://xorm.topgoer.com/) -- [DNS解析](https://github.com/miekg/dns) \ No newline at end of file +- [数据库操作 - xorm](http://xorm.topgoer.com/) +- [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ú)。 \ No newline at end of file diff --git a/RRType.md b/RRType.md new file mode 100644 index 0000000..8397c65 --- /dev/null +++ b/RRType.md @@ -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分钟,这有助于解析快速生效。 +``` diff --git a/config/app.go b/config/app.go new file mode 100644 index 0000000..4c65cba --- /dev/null +++ b/config/app.go @@ -0,0 +1,6 @@ +package config + +const ( + AppVersion = "1.0.0" + AppTimeFormat = "2006/01/02 15:04:05.999999" +) diff --git a/config/database.go b/config/database.go new file mode 100644 index 0000000..c8dc45f --- /dev/null +++ b/config/database.go @@ -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 // 日志级别 +) diff --git a/config/dns_server.go b/config/dns_server.go new file mode 100644 index 0000000..f71ea0a --- /dev/null +++ b/config/dns_server.go @@ -0,0 +1,10 @@ +package config + +/* + * @Description DNS服务配置 + * @Author www.odboy.cn + * @Date 20241108 + */ +const ( + DnsServerPort = ":53" +) diff --git a/config/web_server.go b/config/web_server.go new file mode 100644 index 0000000..8725c0d --- /dev/null +++ b/config/web_server.go @@ -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 +) diff --git a/constant/rr_type.go b/constant/rr_type.go new file mode 100644 index 0000000..8559bf5 --- /dev/null +++ b/constant/rr_type.go @@ -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" +) diff --git a/controller/resolve_record.go b/controller/resolve_record.go new file mode 100644 index 0000000..bf39d4c --- /dev/null +++ b/controller/resolve_record.go @@ -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 +} diff --git a/core/handler.go b/core/handler.go new file mode 100644 index 0000000..2745ec9 --- /dev/null +++ b/core/handler.go @@ -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) + } + } +} diff --git a/dao/database.go b/dao/database.go new file mode 100644 index 0000000..2c84caa --- /dev/null +++ b/dao/database.go @@ -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)") +} diff --git a/dao/resolve_config.go b/dao/resolve_config.go new file mode 100644 index 0000000..242c462 --- /dev/null +++ b/dao/resolve_config.go @@ -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 +} diff --git a/dao/resolve_record.go b/dao/resolve_record.go new file mode 100644 index 0000000..a36ed64 --- /dev/null +++ b/dao/resolve_record.go @@ -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 +} diff --git a/dns.sqlite3 b/dns.sqlite3 new file mode 100644 index 0000000..5b88830 Binary files /dev/null and b/dns.sqlite3 differ diff --git a/domain/resolve_record.go b/domain/resolve_record.go new file mode 100644 index 0000000..c1125df --- /dev/null +++ b/domain/resolve_record.go @@ -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"` +} diff --git a/go.mod b/go.mod index 07f4754..ba05bcd 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,47 @@ -module awesomeProject +module kenaito-dns -go 1.19 - -require github.com/miekg/dns v1.1.62 +go 1.20 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/net v0.27.0 // indirect golang.org/x/sync v0.7.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 + 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 ) diff --git a/go.sum b/go.sum index 95e8194..95e8940 100644 --- a/go.sum +++ b/go.sum @@ -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/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/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/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/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/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/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= diff --git a/handler.go b/handler.go deleted file mode 100644 index 1d3d92f..0000000 --- a/handler.go +++ /dev/null @@ -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) -//} diff --git a/main.go b/main.go index c219bec..d73bdea 100644 --- a/main.go +++ b/main.go @@ -1,44 +1,81 @@ package main +/* + * @Description 服务入口 + * @Author www.odboy.cn + * @Date 20241107 + */ import ( + "fmt" + "github.com/gin-gonic/gin" "github.com/miekg/dns" - "log" + "kenaito-dns/config" + "kenaito-dns/controller" + "kenaito-dns/core" + "net/http" + "time" ) func main() { + fmt.Println("[app] [info] kenaito-dns version = " + config.AppVersion) + go initDNSServer() + initRestfulServer() +} + +func initDNSServer() { // 注册 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 { - 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) { - 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) - } +func initRestfulServer() { + gin.SetMode(config.WebMode) + // 创建一个新的 Gin 引擎实例,使用 gin.New() 方法来创建一个不包含任何默认中间件的实例 + router := gin.New() + // LoggerWithFormatter 中间件会写入日志到 gin.DefaultWriter + // 默认 gin.DefaultWriter = os.Stdout + // 自定义的日志格式化函数 + router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + // 自定义日志格式 + return fmt.Sprintf("[gin] [info] %s [Request] [%s] \"%s %s %s %d %s \"%s\" %s\"\n", + // 请求时间戳 + param.TimeStamp.Format(config.AppTimeFormat), + // 客户端 IP 地址 + param.ClientIP, + // 请求方法 (GET, POST 等) + param.Method, + // 请求路径 + param.Path, + // 请求协议 + 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) } diff --git a/util/strtool.go b/util/strtool.go new file mode 100644 index 0000000..d29b8bf --- /dev/null +++ b/util/strtool.go @@ -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 +}