feat: 新增Eranet域名服务商支持 (#1500)

* fix: 兼容ipv6的设置

* feat: 添加Eranet域名服务商支持

---------

Co-authored-by: dsuzejian <dsuzejian@now.cn>
This commit is contained in:
suguer
2025-07-08 16:48:10 +08:00
committed by GitHub
parent d6ebbf4ff9
commit 55ad21087c
6 changed files with 224 additions and 2 deletions

View File

@ -18,7 +18,7 @@
## 特性 ## 特性
- 支持Mac、Windows、Linux系统支持ARM、x86架构 - 支持Mac、Windows、Linux系统支持ARM、x86架构
- 支持的域名服务商 `阿里云` `腾讯云` `Dnspod` `Cloudflare` `华为云` `Callback` `百度云` `Porkbun` `GoDaddy` `Namecheap` `NameSilo` `Dynadot` `DNSLA` `时代互联` - 支持的域名服务商 `阿里云` `腾讯云` `Dnspod` `Cloudflare` `华为云` `Callback` `百度云` `Porkbun` `GoDaddy` `Namecheap` `NameSilo` `Dynadot` `DNSLA` `时代互联` `Eranet`
- 支持接口/网卡/[命令](https://github.com/jeessy2/ddns-go/wiki/通过命令获取IP参考)获取IP - 支持接口/网卡/[命令](https://github.com/jeessy2/ddns-go/wiki/通过命令获取IP参考)获取IP
- 支持以服务的方式运行 - 支持以服务的方式运行
- 默认间隔5分钟同步一次 - 默认间隔5分钟同步一次

View File

@ -16,7 +16,7 @@ Automatically obtain your public IPv4 or IPv6 address and resolve it to the corr
## Features ## Features
- Support Mac, Windows, Linux system, support ARM, x86 architecture - Support Mac, Windows, Linux system, support ARM, x86 architecture
- Support domain service providers `Aliyun` `Tencent` `Dnspod` `Cloudflare` `Huawei` `Callback` `Baidu` `Porkbun` `GoDaddy` `Namecheap` `NameSilo` `Dynadot` `DNSLA` `Nowcn` - Support domain service providers `Aliyun` `Tencent` `Dnspod` `Cloudflare` `Huawei` `Callback` `Baidu` `Porkbun` `GoDaddy` `Namecheap` `NameSilo` `Dynadot` `DNSLA` `Nowcn` `Eranet`
- Support interface / netcard / command to get IP - Support interface / netcard / command to get IP
- Support running as a service - Support running as a service
- Default interval is 5 minutes - Default interval is 5 minutes

207
dns/eranet.go Normal file
View File

@ -0,0 +1,207 @@
package dns
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/jeessy2/ddns-go/v6/config"
"github.com/jeessy2/ddns-go/v6/util"
)
const (
eranetRecordListAPI string = "http://api.eranet.com:2080/api/dns/describe-record-index.json"
eranetRecordModifyURL string = "http://api.eranet.com:2080/api/dns/update-domain-record.json"
eranetRecordCreateAPI string = "http://api.eranet.com:2080/api/dns/add-domain-record.json"
)
// https://partner.tnet.hk/adminCN/mode_Http_Api_detail.php
// Eranet DNS实现
type Eranet struct {
DNS config.DNS
Domains config.Domains
TTL string
}
type EranetRecord struct {
ID int `json:"id"`
Domain string
Host string
Type string
Value string
State int
// Name string
// Enabled string
}
type EranetRecordListResp struct {
EranetStatus
Data []EranetRecord
}
type EranetStatus struct {
RequestId string `json:"RequestId"`
Id int `json:"Id"`
Error string `json:"error"`
}
// Init 初始化
func (eranet *Eranet) Init(dnsConf *config.DnsConfig, ipv4cache *util.IpCache, ipv6cache *util.IpCache) {
eranet.Domains.Ipv4Cache = ipv4cache
eranet.Domains.Ipv6Cache = ipv6cache
eranet.DNS = dnsConf.DNS
eranet.Domains.GetNewIp(dnsConf)
if dnsConf.TTL == "" {
// 默认600s
eranet.TTL = "600"
} else {
eranet.TTL = dnsConf.TTL
}
}
// AddUpdateDomainRecords 添加或更新IPv4/IPv6记录
func (eranet *Eranet) AddUpdateDomainRecords() config.Domains {
eranet.addUpdateDomainRecords("A")
eranet.addUpdateDomainRecords("AAAA")
return eranet.Domains
}
func (eranet *Eranet) addUpdateDomainRecords(recordType string) {
ipAddr, domains := eranet.Domains.GetNewIpResult(recordType)
if ipAddr == "" {
return
}
for _, domain := range domains {
result, err := eranet.getRecordList(domain, recordType)
if err != nil {
util.Log("查询域名信息发生异常! %s", err)
domain.UpdateStatus = config.UpdatedFailed
return
}
if len(result.Data) > 0 {
// 默认第一个
recordSelected := result.Data[0]
params := domain.GetCustomParams()
if params.Has("Id") {
for i := 0; i < len(result.Data); i++ {
if strconv.Itoa(result.Data[i].ID) == params.Get("Id") {
recordSelected = result.Data[i]
}
}
}
// 更新
eranet.modify(recordSelected, domain, recordType, ipAddr)
} else {
// 新增
eranet.create(domain, recordType, ipAddr)
}
}
}
// create 创建DNS记录
func (eranet *Eranet) create(domain *config.Domain, recordType string, ipAddr string) {
param := map[string]any{
"Domain": domain.DomainName,
"Host": domain.GetSubDomain(),
"Type": recordType,
"Value": ipAddr,
"Ttl": eranet.TTL,
}
res, err := eranet.request(eranetRecordCreateAPI, param)
if err != nil {
util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, err.Error())
domain.UpdateStatus = config.UpdatedFailed
} else if res.Error != "" {
util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, res.Error)
domain.UpdateStatus = config.UpdatedFailed
} else {
util.Log("新增域名解析 %s 成功! IP: %s", domain, ipAddr)
domain.UpdateStatus = config.UpdatedSuccess
}
}
// modify 修改DNS记录
func (eranet *Eranet) modify(record EranetRecord, domain *config.Domain, recordType string, ipAddr string) {
// 相同不修改
if record.Value == ipAddr {
util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain)
return
}
param := map[string]any{
"Id": record.ID,
"Domain": domain.DomainName,
"Host": domain.GetSubDomain(),
"Type": recordType,
"Value": ipAddr,
"Ttl": eranet.TTL,
}
res, err := eranet.request(eranetRecordModifyURL, param)
if err != nil {
util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err.Error())
domain.UpdateStatus = config.UpdatedFailed
} else if res.Error != "" {
util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, res.Error)
domain.UpdateStatus = config.UpdatedFailed
} else {
util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr)
domain.UpdateStatus = config.UpdatedSuccess
}
}
// request 发送HTTP请求
func (eranet *Eranet) request(apiAddr string, param map[string]any) (status EranetStatus, err error) {
param["auth-userid"] = eranet.DNS.ID
param["api-key"] = eranet.DNS.Secret
fullURL := apiAddr + "?" + eranet.queryParams(param)
client := util.CreateHTTPClient()
resp, err := client.Get(fullURL)
// 处理响应
err = util.GetHTTPResponse(resp, err, &status)
return
}
// getRecordList 获取域名记录列表
func (eranet *Eranet) getRecordList(domain *config.Domain, typ string) (result EranetRecordListResp, err error) {
param := map[string]any{
"Domain": domain.DomainName,
"auth-userid": eranet.DNS.ID,
"api-key": eranet.DNS.Secret,
}
fullURL := eranetRecordListAPI + "?" + eranet.queryParams(param)
client := util.CreateHTTPClient()
resp, err := client.Get(fullURL)
var response EranetRecordListResp
result = EranetRecordListResp{
Data: make([]EranetRecord, 0),
}
err = util.GetHTTPResponse(resp, err, &response)
for _, v := range response.Data {
if v.Host == domain.GetSubDomain() {
result.Data = append(result.Data, v)
break
}
}
return
}
func (eranet *Eranet) queryParams(param map[string]any) string {
var queryParams []string
for key, value := range param {
// 只对键进行URL编码值保持原样特别是@符号)
encodedKey := url.QueryEscape(key)
valueStr := fmt.Sprintf("%v", value)
// 对值进行选择性编码,保留@符号
encodedValue := strings.ReplaceAll(url.QueryEscape(valueStr), "%40", "@")
encodedValue = strings.ReplaceAll(encodedValue, "%3A", ":")
queryParams = append(queryParams, encodedKey+"="+encodedValue)
}
return strings.Join(queryParams, "&")
}

View File

@ -92,6 +92,8 @@ func RunOnce() {
dnsSelected = &Spaceship{} dnsSelected = &Spaceship{}
case "nowcn": case "nowcn":
dnsSelected = &Nowcn{} dnsSelected = &Nowcn{}
case "eranet":
dnsSelected = &Eranet{}
default: default:
dnsSelected = &Alidns{} dnsSelected = &Alidns{}
} }

View File

@ -203,6 +203,7 @@ func (nowcn *Nowcn) queryParams(param map[string]any) string {
valueStr := fmt.Sprintf("%v", value) valueStr := fmt.Sprintf("%v", value)
// 对值进行选择性编码,保留@符号 // 对值进行选择性编码,保留@符号
encodedValue := strings.ReplaceAll(url.QueryEscape(valueStr), "%40", "@") encodedValue := strings.ReplaceAll(url.QueryEscape(valueStr), "%40", "@")
encodedValue = strings.ReplaceAll(encodedValue, "%3A", ":")
queryParams = append(queryParams, encodedKey+"="+encodedValue) queryParams = append(queryParams, encodedKey+"="+encodedValue)
} }
return strings.Join(queryParams, "&") return strings.Join(queryParams, "&")

View File

@ -204,6 +204,18 @@ const DNS_PROVIDERS = {
"zh-cn": "<a target='_blank' href='https://www.now.cn/'>获取 api-key</a>", "zh-cn": "<a target='_blank' href='https://www.now.cn/'>获取 api-key</a>",
} }
}, },
eranet: {
name: {
"en": "Eranet",
"zh-cn": "Eranet",
},
idLabel: "auth-userid",
secretLabel: "api-key",
helpHtml: {
"en": "<a target='_blank' href='https://partner.eranet.com/admin/mode_Http_Api_detail.php'>api-key</a>",
"zh-cn": "<a target='_blank' href='https://partner.eranet.com/admin/mode_Http_Api_detail.php'>获取 api-key</a>",
}
},
}; };
const SVG_CODE = { const SVG_CODE = {