修复编译错误
This commit is contained in:
332
common/dnspod/provider.go
Normal file
332
common/dnspod/provider.go
Normal file
@@ -0,0 +1,332 @@
|
||||
package dnspod
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/libdns/libdns"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
const defaultAPIEndpoint = "https://dnsapi.cn"
|
||||
|
||||
type Provider struct {
|
||||
APIToken string
|
||||
APIEndpoint string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
type apiStatus struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type apiRecord struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
TTL string `json:"ttl"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type createRecordResponse struct {
|
||||
Status apiStatus `json:"status"`
|
||||
Record struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"record"`
|
||||
}
|
||||
|
||||
type listRecordsResponse struct {
|
||||
Status apiStatus `json:"status"`
|
||||
Records []apiRecord `json:"records"`
|
||||
}
|
||||
|
||||
func (p *Provider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) {
|
||||
records, err := p.listRecords(ctx, normalizeZone(zone), "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results := make([]libdns.Record, 0, len(records))
|
||||
for _, record := range records {
|
||||
results = append(results, record.toLibdnsRecord())
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (p *Provider) AppendRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
||||
zone = normalizeZone(zone)
|
||||
if zone == "" {
|
||||
return nil, E.New("DNSPod zone is empty")
|
||||
}
|
||||
if strings.TrimSpace(p.APIToken) == "" {
|
||||
return nil, E.New("DNSPod API token is empty")
|
||||
}
|
||||
created := make([]libdns.Record, 0, len(records))
|
||||
for _, record := range records {
|
||||
requestRecord, err := normalizeInputRecord(record)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
params := url.Values{
|
||||
"domain": []string{zone},
|
||||
"sub_domain": []string{requestRecord.Name},
|
||||
"record_type": []string{requestRecord.Type},
|
||||
"record_line": []string{"默认"},
|
||||
"value": []string{requestRecord.Value},
|
||||
}
|
||||
if requestRecord.TTL > 0 {
|
||||
params.Set("ttl", strconv.FormatInt(int64(requestRecord.TTL/time.Second), 10))
|
||||
}
|
||||
var response createRecordResponse
|
||||
err = p.doForm(ctx, "Record.Create", params, &response)
|
||||
if err != nil {
|
||||
if requestRecord.Type == "TXT" && strings.Contains(err.Error(), "104") {
|
||||
existing, listErr := p.listRecords(ctx, zone, requestRecord.Name, requestRecord.Type)
|
||||
if listErr != nil {
|
||||
return created, err
|
||||
}
|
||||
for _, candidate := range existing {
|
||||
if requestRecord.matches(candidate) {
|
||||
created = append(created, candidate.toLibdnsRecord())
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
requestRecord.ID = response.Record.ID
|
||||
created = append(created, requestRecord.toLibdnsRecord())
|
||||
}
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func (p *Provider) DeleteRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
||||
zone = normalizeZone(zone)
|
||||
if zone == "" {
|
||||
return nil, E.New("DNSPod zone is empty")
|
||||
}
|
||||
if strings.TrimSpace(p.APIToken) == "" {
|
||||
return nil, E.New("DNSPod API token is empty")
|
||||
}
|
||||
deleted := make([]libdns.Record, 0, len(records))
|
||||
for _, record := range records {
|
||||
requestRecord, err := normalizeInputRecord(record)
|
||||
if err != nil {
|
||||
return deleted, err
|
||||
}
|
||||
candidates, err := p.listRecords(ctx, zone, requestRecord.Name, requestRecord.Type)
|
||||
if err != nil {
|
||||
return deleted, err
|
||||
}
|
||||
for _, candidate := range candidates {
|
||||
if !requestRecord.matches(candidate) {
|
||||
continue
|
||||
}
|
||||
err = p.doForm(ctx, "Record.Remove", url.Values{
|
||||
"domain": []string{zone},
|
||||
"record_id": []string{candidate.ID},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return deleted, err
|
||||
}
|
||||
deleted = append(deleted, candidate.toLibdnsRecord())
|
||||
}
|
||||
}
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
func (p *Provider) listRecords(ctx context.Context, zone, subDomain, recordType string) ([]apiRecord, error) {
|
||||
params := url.Values{
|
||||
"domain": []string{zone},
|
||||
}
|
||||
if subDomain != "" {
|
||||
params.Set("sub_domain", subDomain)
|
||||
}
|
||||
if recordType != "" {
|
||||
params.Set("record_type", recordType)
|
||||
}
|
||||
var response listRecordsResponse
|
||||
err := p.doForm(ctx, "Record.List", params, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Records, nil
|
||||
}
|
||||
|
||||
func (p *Provider) doForm(ctx context.Context, action string, params url.Values, target any) error {
|
||||
endpoint := strings.TrimRight(strings.TrimSpace(p.APIEndpoint), "/")
|
||||
if endpoint == "" {
|
||||
endpoint = defaultAPIEndpoint
|
||||
}
|
||||
body := url.Values{
|
||||
"login_token": []string{strings.TrimSpace(p.APIToken)},
|
||||
"format": []string{"json"},
|
||||
}
|
||||
for key, values := range params {
|
||||
for _, value := range values {
|
||||
body.Add(key, value)
|
||||
}
|
||||
}
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint+"/"+action, strings.NewReader(body.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
client := p.HTTPClient
|
||||
if client == nil {
|
||||
client = &http.Client{Timeout: 30 * time.Second}
|
||||
}
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return E.New("DNSPod ", action, " failed: HTTP ", response.StatusCode)
|
||||
}
|
||||
data, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if target == nil {
|
||||
var wrapper struct {
|
||||
Status apiStatus `json:"status"`
|
||||
}
|
||||
if err = json.Unmarshal(data, &wrapper); err != nil {
|
||||
return err
|
||||
}
|
||||
if wrapper.Status.Code != "1" {
|
||||
return E.New("DNSPod ", action, " failed: ", wrapper.Status.Code, " ", strings.TrimSpace(wrapper.Status.Message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err = json.Unmarshal(data, target); err != nil {
|
||||
return err
|
||||
}
|
||||
switch result := target.(type) {
|
||||
case *createRecordResponse:
|
||||
if result.Status.Code != "1" {
|
||||
return E.New("DNSPod ", action, " failed: ", result.Status.Code, " ", strings.TrimSpace(result.Status.Message))
|
||||
}
|
||||
case *listRecordsResponse:
|
||||
if result.Status.Code != "1" {
|
||||
return E.New("DNSPod ", action, " failed: ", result.Status.Code, " ", strings.TrimSpace(result.Status.Message))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeZone(zone string) string {
|
||||
return strings.TrimSuffix(strings.TrimSpace(zone), ".")
|
||||
}
|
||||
|
||||
func normalizeRecordName(name string) string {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return "@"
|
||||
}
|
||||
return strings.TrimSuffix(name, ".")
|
||||
}
|
||||
|
||||
func normalizeRecordValue(record libdns.Record) string {
|
||||
switch typed := record.(type) {
|
||||
case libdns.TXT:
|
||||
return typed.Text
|
||||
case *libdns.TXT:
|
||||
return typed.Text
|
||||
default:
|
||||
return record.RR().Data
|
||||
}
|
||||
}
|
||||
|
||||
type normalizedRecord struct {
|
||||
ID string
|
||||
Name string
|
||||
Type string
|
||||
TTL time.Duration
|
||||
Value string
|
||||
}
|
||||
|
||||
func normalizeInputRecord(record libdns.Record) (normalizedRecord, error) {
|
||||
rr := record.RR()
|
||||
name := normalizeRecordName(rr.Name)
|
||||
if name == "" {
|
||||
return normalizedRecord{}, E.New("DNSPod record name is empty")
|
||||
}
|
||||
recordType := strings.ToUpper(strings.TrimSpace(rr.Type))
|
||||
if recordType == "" {
|
||||
return normalizedRecord{}, E.New("DNSPod record type is empty")
|
||||
}
|
||||
return normalizedRecord{
|
||||
Name: name,
|
||||
Type: recordType,
|
||||
TTL: rr.TTL,
|
||||
Value: normalizeRecordValue(record),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r normalizedRecord) matches(candidate apiRecord) bool {
|
||||
if normalizeRecordName(candidate.Name) != r.Name {
|
||||
return false
|
||||
}
|
||||
if r.Type != "" && !strings.EqualFold(candidate.Type, r.Type) {
|
||||
return false
|
||||
}
|
||||
if r.Value != "" && candidate.Value != r.Value {
|
||||
return false
|
||||
}
|
||||
if r.TTL > 0 {
|
||||
candidateTTL, _ := strconv.ParseInt(candidate.TTL, 10, 64)
|
||||
if time.Duration(candidateTTL)*time.Second != r.TTL {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r normalizedRecord) toLibdnsRecord() libdns.Record {
|
||||
record := apiRecord{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Type: r.Type,
|
||||
TTL: strconv.FormatInt(int64(r.TTL/time.Second), 10),
|
||||
Value: r.Value,
|
||||
}
|
||||
return record.toLibdnsRecord()
|
||||
}
|
||||
|
||||
func (r apiRecord) toLibdnsRecord() libdns.Record {
|
||||
ttlSeconds, _ := strconv.ParseInt(strings.TrimSpace(r.TTL), 10, 64)
|
||||
ttl := time.Duration(ttlSeconds) * time.Second
|
||||
name := normalizeRecordName(r.Name)
|
||||
recordType := strings.ToUpper(strings.TrimSpace(r.Type))
|
||||
if recordType == "TXT" {
|
||||
return libdns.TXT{
|
||||
Name: name,
|
||||
TTL: ttl,
|
||||
Text: r.Value,
|
||||
}
|
||||
}
|
||||
rr := libdns.RR{
|
||||
Name: name,
|
||||
TTL: ttl,
|
||||
Type: recordType,
|
||||
Data: r.Value,
|
||||
}
|
||||
parsed, err := rr.Parse()
|
||||
if err != nil {
|
||||
return rr
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
boxdnspod "github.com/sagernet/sing-box/common/dnspod"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -17,7 +18,6 @@ import (
|
||||
"github.com/libdns/acmedns"
|
||||
"github.com/libdns/alidns"
|
||||
"github.com/libdns/cloudflare"
|
||||
"github.com/libdns/dnspod"
|
||||
"github.com/libdns/tencentcloud"
|
||||
"github.com/mholt/acmez/v3/acme"
|
||||
"go.uber.org/zap"
|
||||
@@ -106,7 +106,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
||||
Region: dnsOptions.TencentCloudOptions.Region,
|
||||
}
|
||||
case C.DNSProviderDNSPod:
|
||||
solver.DNSProvider = &dnspod.Provider{
|
||||
solver.DNSProvider = &boxdnspod.Provider{
|
||||
APIToken: dnsOptions.DNSPodOptions.APIToken,
|
||||
}
|
||||
case C.DNSProviderACMEDNS:
|
||||
|
||||
1
go.mod
1
go.mod
@@ -20,7 +20,6 @@ require (
|
||||
github.com/libdns/acmedns v0.5.0
|
||||
github.com/libdns/alidns v1.0.6
|
||||
github.com/libdns/cloudflare v0.2.2
|
||||
github.com/libdns/dnspod v0.0.3
|
||||
github.com/libdns/libdns v1.1.1
|
||||
github.com/libdns/tencentcloud v1.4.3
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
|
||||
5
go.sum
5
go.sum
@@ -124,8 +124,12 @@ github.com/libdns/alidns v1.0.6 h1:/Ii428ty6WHFJmE24rZxq2taq++gh7rf9jhgLfp8PmM=
|
||||
github.com/libdns/alidns v1.0.6/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec=
|
||||
github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI=
|
||||
github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=
|
||||
github.com/libdns/dnspod v0.0.3/go.mod h1:XLnqMmK7QlLPEbHwcOxbRvlzRvDgaaUlthRNFOPjXPI=
|
||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
||||
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/libdns/tencentcloud v1.4.3 h1:xJHYLL1TdPeOtUr6Bu6dHTd1TU6/VFm7BFc2EAzAlvc=
|
||||
github.com/libdns/tencentcloud v1.4.3/go.mod h1:Be9gY3tDa12DuAPU79RV9NZIcjY6qg5s7zKPsP26yAM=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
|
||||
@@ -142,6 +146,7 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc
|
||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xxJEnQE=
|
||||
github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/certificate"
|
||||
boxdnspod "github.com/sagernet/sing-box/common/dnspod"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
boxtls "github.com/sagernet/sing-box/common/tls"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
@@ -30,7 +31,6 @@ import (
|
||||
"github.com/caddyserver/zerossl"
|
||||
"github.com/libdns/alidns"
|
||||
"github.com/libdns/cloudflare"
|
||||
"github.com/libdns/dnspod"
|
||||
"github.com/libdns/libdns"
|
||||
"github.com/libdns/tencentcloud"
|
||||
"github.com/mholt/acmez/v3/acme"
|
||||
@@ -250,7 +250,7 @@ func newDNSSolver(dnsOptions *option.ACMEProviderDNS01ChallengeOptions, logger *
|
||||
Region: dnsOptions.TencentCloudOptions.Region,
|
||||
}
|
||||
case C.DNSProviderDNSPod:
|
||||
solver.DNSProvider = &dnspod.Provider{
|
||||
solver.DNSProvider = &boxdnspod.Provider{
|
||||
APIToken: dnsOptions.DNSPodOptions.APIToken,
|
||||
}
|
||||
case C.DNSProviderACMEDNS:
|
||||
|
||||
Reference in New Issue
Block a user