mirror of
https://github.com/jeessy2/ddns-go.git
synced 2025-10-20 15:33:46 +08:00
202 lines
5.2 KiB
Go
202 lines
5.2 KiB
Go
package dns
|
||
|
||
import (
|
||
"encoding/xml"
|
||
"io"
|
||
"net/http"
|
||
"strings"
|
||
|
||
"github.com/jeessy2/ddns-go/v6/config"
|
||
"github.com/jeessy2/ddns-go/v6/util"
|
||
)
|
||
|
||
const (
|
||
nameSiloListRecordEndpoint = "https://www.namesilo.com/api/dnsListRecords?version=1&type=xml&key=#{password}&domain=#{domain}"
|
||
nameSiloAddRecordEndpoint = "https://www.namesilo.com/api/dnsAddRecord?version=1&type=xml&key=#{password}&domain=#{domain}&rrhost=#{host}&rrtype=#{recordType}&rrvalue=#{ip}&rrttl=3600"
|
||
nameSiloUpdateRecordEndpoint = "https://www.namesilo.com/api/dnsUpdateRecord?version=1&type=xml&key=#{password}&domain=#{domain}&rrhost=#{host}&rrid=#{recordID}&rrvalue=#{ip}&rrttl=3600"
|
||
)
|
||
|
||
// NameSilo Domain
|
||
type NameSilo struct {
|
||
DNS config.DNS
|
||
Domains config.Domains
|
||
lastIpv4 string
|
||
lastIpv6 string
|
||
}
|
||
|
||
// NameSiloResp 修改域名解析结果
|
||
type NameSiloResp struct {
|
||
XMLName xml.Name `xml:"namesilo"`
|
||
Request Request `xml:"request"`
|
||
Reply ReplyResponse `xml:"reply"`
|
||
}
|
||
|
||
type ReplyResponse struct {
|
||
Code int `xml:"code"`
|
||
Detail string `xml:"detail"`
|
||
RecordID string `xml:"record_id"`
|
||
}
|
||
|
||
type NameSiloDNSListRecordResp struct {
|
||
XMLName xml.Name `xml:"namesilo"`
|
||
Request Request `xml:"request"`
|
||
Reply Reply `xml:"reply"`
|
||
}
|
||
|
||
type Request struct {
|
||
Operation string `xml:"operation"`
|
||
IP string `xml:"ip"`
|
||
}
|
||
|
||
type Reply struct {
|
||
Code int `xml:"code"`
|
||
Detail string `xml:"detail"`
|
||
ResourceItems []ResourceRecord `xml:"resource_record"`
|
||
}
|
||
|
||
type ResourceRecord struct {
|
||
RecordID string `xml:"record_id"`
|
||
Type string `xml:"type"`
|
||
Host string `xml:"host"`
|
||
Value string `xml:"value"`
|
||
TTL int `xml:"ttl"`
|
||
Distance int `xml:"distance"`
|
||
}
|
||
|
||
// Init 初始化
|
||
func (ns *NameSilo) Init(dnsConf *config.DnsConfig, ipv4cache *util.IpCache, ipv6cache *util.IpCache) {
|
||
ns.Domains.Ipv4Cache = ipv4cache
|
||
ns.Domains.Ipv6Cache = ipv6cache
|
||
ns.lastIpv4 = ipv4cache.Addr
|
||
ns.lastIpv6 = ipv6cache.Addr
|
||
|
||
ns.DNS = dnsConf.DNS
|
||
ns.Domains.GetNewIp(dnsConf)
|
||
}
|
||
|
||
// AddUpdateDomainRecords 添加或更新IPv4/IPv6记录
|
||
func (ns *NameSilo) AddUpdateDomainRecords() config.Domains {
|
||
ns.addUpdateDomainRecords("A")
|
||
ns.addUpdateDomainRecords("AAAA")
|
||
return ns.Domains
|
||
}
|
||
|
||
func (ns *NameSilo) addUpdateDomainRecords(recordType string) {
|
||
ipAddr, domains := ns.Domains.GetNewIpResult(recordType)
|
||
|
||
if ipAddr == "" {
|
||
return
|
||
}
|
||
|
||
for _, domain := range domains {
|
||
// 有可能有人填写@.example.com
|
||
if domain.SubDomain == "@" {
|
||
domain.SubDomain = ""
|
||
}
|
||
// 拿到DNS记录列表,从列表中去取对应域名的id,有id进行修改,没ID进行新增
|
||
records, err := ns.listRecords(domain)
|
||
if err != nil {
|
||
util.Log("查询域名信息发生异常! %s", err)
|
||
domain.UpdateStatus = config.UpdatedFailed
|
||
return
|
||
}
|
||
items := records.Reply.ResourceItems
|
||
record := findResourceRecord(items, recordType, domain.String())
|
||
var isAdd bool
|
||
var recordID string
|
||
if record == nil {
|
||
isAdd = true
|
||
} else {
|
||
recordID = record.RecordID
|
||
if record.Value == ipAddr {
|
||
util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain)
|
||
continue
|
||
}
|
||
}
|
||
ns.modify(domain, recordID, recordType, ipAddr, isAdd)
|
||
}
|
||
}
|
||
|
||
// 修改
|
||
func (ns *NameSilo) modify(domain *config.Domain, recordID, recordType, ipAddr string, isAdd bool) {
|
||
var err error
|
||
var result string
|
||
var requestType string
|
||
if isAdd {
|
||
requestType = "新增"
|
||
result, err = ns.request(ipAddr, domain, "", recordType, nameSiloAddRecordEndpoint)
|
||
} else {
|
||
requestType = "更新"
|
||
result, err = ns.request(ipAddr, domain, recordID, "", nameSiloUpdateRecordEndpoint)
|
||
}
|
||
if err != nil {
|
||
util.Log("异常信息: %s", err)
|
||
domain.UpdateStatus = config.UpdatedFailed
|
||
return
|
||
}
|
||
var resp NameSiloResp
|
||
xml.Unmarshal([]byte(result), &resp)
|
||
if resp.Reply.Code == 300 {
|
||
util.Log(requestType+"域名解析 %s 成功! IP: %s\n", domain, ipAddr)
|
||
domain.UpdateStatus = config.UpdatedSuccess
|
||
} else {
|
||
util.Log(requestType+"域名解析 %s 失败! 异常信息: %s", domain, resp.Reply.Detail)
|
||
domain.UpdateStatus = config.UpdatedFailed
|
||
}
|
||
}
|
||
|
||
func (ns *NameSilo) listRecords(domain *config.Domain) (*NameSiloDNSListRecordResp, error) {
|
||
result, err := ns.request("", domain, "", "", nameSiloListRecordEndpoint)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var resp NameSiloDNSListRecordResp
|
||
if err = xml.Unmarshal([]byte(result), &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &resp, nil
|
||
}
|
||
|
||
// request 统一请求接口
|
||
func (ns *NameSilo) request(ipAddr string, domain *config.Domain, recordID, recordType, url string) (result string, err error) {
|
||
url = strings.NewReplacer(
|
||
"#{host}", domain.SubDomain,
|
||
"#{domain}", domain.DomainName,
|
||
"#{password}", ns.DNS.Secret,
|
||
"#{recordID}", recordID,
|
||
"#{recordType}", recordType,
|
||
"#{ip}", ipAddr,
|
||
).Replace(url)
|
||
req, err := http.NewRequest(
|
||
http.MethodGet,
|
||
url,
|
||
http.NoBody,
|
||
)
|
||
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
client := util.CreateHTTPClient()
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
defer resp.Body.Close()
|
||
data, err := io.ReadAll(resp.Body)
|
||
result = string(data)
|
||
return
|
||
}
|
||
|
||
func findResourceRecord(data []ResourceRecord, recordType, domain string) *ResourceRecord {
|
||
for i := 0; i < len(data); i++ {
|
||
if data[i].Host == domain && data[i].Type == recordType {
|
||
return &data[i]
|
||
}
|
||
}
|
||
return nil
|
||
}
|