1.4.5.2
This commit is contained in:
371
EdgeAPI/internal/dnsclients/provider_cloudns_plus.go
Normal file
371
EdgeAPI/internal/dnsclients/provider_cloudns_plus.go
Normal file
@@ -0,0 +1,371 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package dnsclients
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/cloudns"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/errors"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ClouDNSDefaultRoute = "default"
|
||||
ClouDNSAPIEndpoint = "https://api.cloudns.net"
|
||||
)
|
||||
|
||||
var clouDNSHTTPClient = &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ClouDNSProvider ClouDNS.net
|
||||
// 参考文档:https://www.cloudns.net/wiki/article/41/
|
||||
type ClouDNSProvider struct {
|
||||
BaseProvider
|
||||
|
||||
ProviderId int64
|
||||
|
||||
authId int64
|
||||
subAuthId int64
|
||||
authPassword string
|
||||
}
|
||||
|
||||
// Auth 认证
|
||||
func (this *ClouDNSProvider) Auth(params maps.Map) error {
|
||||
this.authId = params.GetInt64("authId")
|
||||
this.subAuthId = params.GetInt64("subAuthId")
|
||||
this.authPassword = params.GetString("authPassword")
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaskParams 对参数进行掩码
|
||||
func (this *ClouDNSProvider) MaskParams(params maps.Map) {
|
||||
if params == nil {
|
||||
return
|
||||
}
|
||||
params["authPassword"] = MaskString(params.GetString("authPassword"))
|
||||
}
|
||||
|
||||
// GetDomains 获取所有域名列表
|
||||
func (this *ClouDNSProvider) GetDomains() (domains []string, err error) {
|
||||
var page = 1
|
||||
|
||||
for {
|
||||
var zones = cloudns.ZonesResponse{}
|
||||
err = this.doAPI(http.MethodPost, "/dns/list-zones.json", map[string]string{
|
||||
"page": types.String(page),
|
||||
"rows-per-page": "100",
|
||||
}, &zones)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(zones) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, zone := range zones {
|
||||
if zone.Zone == "domain" {
|
||||
domains = append(domains, zone.Name)
|
||||
}
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetRecords 获取域名解析记录列表
|
||||
func (this *ClouDNSProvider) GetRecords(domain string) (records []*dnstypes.Record, err error) {
|
||||
var page = 1
|
||||
for {
|
||||
var respRecords = cloudns.RecordsResponse{}
|
||||
err = this.doAPI(http.MethodPost, "/dns/records.json", map[string]string{
|
||||
"domain-name": domain,
|
||||
"page": types.String(page),
|
||||
"rows-per-page": "100",
|
||||
}, &respRecords)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(respRecords) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, record := range respRecords {
|
||||
// 修正Record
|
||||
if record.Type == dnstypes.RecordTypeCNAME && !strings.HasSuffix(record.Record, ".") {
|
||||
record.Record += "."
|
||||
}
|
||||
|
||||
records = append(records, &dnstypes.Record{
|
||||
Id: record.Id,
|
||||
Name: record.Host,
|
||||
Type: record.Type,
|
||||
Value: record.Record,
|
||||
Route: ClouDNSDefaultRoute,
|
||||
TTL: types.Int32(record.TTL),
|
||||
})
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetRoutes 读取域名支持的线路数据
|
||||
func (this *ClouDNSProvider) GetRoutes(domain string) (routes []*dnstypes.Route, err error) {
|
||||
routes = []*dnstypes.Route{
|
||||
{Name: "默认", Code: ClouDNSDefaultRoute},
|
||||
}
|
||||
|
||||
// TODO 支持GeoDNS
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QueryRecord 查询单个记录
|
||||
func (this *ClouDNSProvider) QueryRecord(domain string, name string, recordType dnstypes.RecordType) (*dnstypes.Record, error) {
|
||||
var respRecords = cloudns.RecordsResponse{}
|
||||
err := this.doAPI(http.MethodPost, "/dns/records.json", map[string]string{
|
||||
"domain-name": domain,
|
||||
"host": name,
|
||||
"type": recordType,
|
||||
}, &respRecords)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(respRecords) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for _, record := range respRecords {
|
||||
// 修正Record
|
||||
if record.Type == dnstypes.RecordTypeCNAME && !strings.HasSuffix(record.Record, ".") {
|
||||
record.Record += "."
|
||||
}
|
||||
|
||||
return &dnstypes.Record{
|
||||
Id: record.Id,
|
||||
Name: record.Host,
|
||||
Type: record.Type,
|
||||
Value: record.Record,
|
||||
Route: ClouDNSDefaultRoute,
|
||||
TTL: types.Int32(record.TTL),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// QueryRecords 查询多个记录
|
||||
func (this *ClouDNSProvider) QueryRecords(domain string, name string, recordType dnstypes.RecordType) ([]*dnstypes.Record, error) {
|
||||
var respRecords = cloudns.RecordsResponse{}
|
||||
err := this.doAPI(http.MethodPost, "/dns/records.json", map[string]string{
|
||||
"domain-name": domain,
|
||||
"host": name,
|
||||
"type": recordType,
|
||||
}, &respRecords)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(respRecords) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var result = []*dnstypes.Record{}
|
||||
for _, record := range respRecords {
|
||||
// 修正Record
|
||||
if record.Type == dnstypes.RecordTypeCNAME && !strings.HasSuffix(record.Record, ".") {
|
||||
record.Record += "."
|
||||
}
|
||||
|
||||
result = append(result, &dnstypes.Record{
|
||||
Id: record.Id,
|
||||
Name: record.Host,
|
||||
Type: record.Type,
|
||||
Value: record.Record,
|
||||
Route: ClouDNSDefaultRoute,
|
||||
TTL: types.Int32(record.TTL),
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// AddRecord 设置记录
|
||||
func (this *ClouDNSProvider) AddRecord(domain string, newRecord *dnstypes.Record) error {
|
||||
var ttl = newRecord.TTL
|
||||
if ttl <= 0 {
|
||||
ttl = 1800
|
||||
}
|
||||
|
||||
var availableTTLs = []int32{60, 300, 900, 1800, 3600, 21600, 43200, 86400, 172800, 259200, 604800, 1209600, 2592000}
|
||||
var ttlFound = false
|
||||
for _, aTTL := range availableTTLs {
|
||||
if aTTL == ttl {
|
||||
ttlFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ttlFound {
|
||||
ttl = 1800
|
||||
}
|
||||
|
||||
var statusResp = &cloudns.StatusResponse{}
|
||||
err := this.doAPI(http.MethodPost, "/dns/add-record.json", map[string]string{
|
||||
"domain-name": domain,
|
||||
"record-type": newRecord.Type,
|
||||
"host": newRecord.Name,
|
||||
"record": newRecord.Value,
|
||||
"ttl": types.String(ttl),
|
||||
}, statusResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if statusResp.Status != "Success" {
|
||||
return errors.New("Failed: " + statusResp.StatusDescription)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRecord 修改记录
|
||||
func (this *ClouDNSProvider) UpdateRecord(domain string, record *dnstypes.Record, newRecord *dnstypes.Record) error {
|
||||
var ttl = newRecord.TTL
|
||||
if ttl <= 0 {
|
||||
ttl = 1800
|
||||
}
|
||||
|
||||
var availableTTLs = []int32{60, 300, 900, 1800, 3600, 21600, 43200, 86400, 172800, 259200, 604800, 1209600, 2592000}
|
||||
var ttlFound = false
|
||||
for _, aTTL := range availableTTLs {
|
||||
if aTTL == ttl {
|
||||
ttlFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ttlFound {
|
||||
ttl = 1800
|
||||
}
|
||||
|
||||
var statusResp = &cloudns.StatusResponse{}
|
||||
err := this.doAPI(http.MethodPost, "/dns/mod-record.json", map[string]string{
|
||||
"domain-name": domain,
|
||||
"record-id": record.Id,
|
||||
"record-type": newRecord.Type,
|
||||
"host": newRecord.Name,
|
||||
"record": newRecord.Value,
|
||||
"ttl": types.String(ttl),
|
||||
}, statusResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if statusResp.Status != "Success" {
|
||||
return errors.New("Failed: " + statusResp.StatusDescription)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRecord 删除记录
|
||||
func (this *ClouDNSProvider) DeleteRecord(domain string, record *dnstypes.Record) error {
|
||||
var statusResp = &cloudns.StatusResponse{}
|
||||
err := this.doAPI(http.MethodPost, "/dns/delete-record.json", map[string]string{
|
||||
"domain-name": domain,
|
||||
"record-id": record.Id,
|
||||
}, statusResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if statusResp.Status != "Success" {
|
||||
return errors.New("Failed: " + statusResp.StatusDescription)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultRoute 默认线路
|
||||
func (this *ClouDNSProvider) DefaultRoute() string {
|
||||
return ClouDNSDefaultRoute
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
func (this *ClouDNSProvider) doAPI(method string, apiPath string, params map[string]string, respPtr interface{}) error {
|
||||
var apiURL = ClouDNSAPIEndpoint + apiPath
|
||||
method = strings.ToUpper(method)
|
||||
|
||||
var query = url.Values{}
|
||||
if this.authId > 0 {
|
||||
query.Set("auth-id", types.String(this.authId))
|
||||
} else if this.subAuthId > 0 {
|
||||
query.Set("sub-auth-id", types.String(this.subAuthId))
|
||||
}
|
||||
query.Set("auth-password", this.authPassword)
|
||||
|
||||
for k, v := range params {
|
||||
query.Set(k, v)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, apiURL, strings.NewReader(query.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", teaconst.ProductName+"/"+teaconst.Version)
|
||||
if method == http.MethodPost {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
resp, err := clouDNSHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode == 0 {
|
||||
return errors.New("invalid response status '" + strconv.Itoa(resp.StatusCode) + "', response '" + string(data) + "'")
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New("response error: " + string(data))
|
||||
}
|
||||
|
||||
if respPtr != nil {
|
||||
err = json.Unmarshal(data, respPtr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode json failed: %w: %s", err, string(data))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user