-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
243 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
package ddns | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"io" | ||
"log" | ||
"net/http" | ||
"crypto/hmac" | ||
"crypto/sha256" | ||
"encoding/hex" | ||
"strconv" | ||
"strings" | ||
"time" | ||
) | ||
|
||
const ( | ||
url = "https://dnspod.tencentcloudapi.com" | ||
) | ||
|
||
type ProviderTencentCloud struct { | ||
SecretID string | ||
SecretKey string | ||
} | ||
|
||
func (provider ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) bool { | ||
if domainConfig == nil { | ||
return false | ||
} | ||
|
||
// 当IPv4和IPv6同时成功才算作成功 | ||
var resultV4 = true | ||
var resultV6 = true | ||
if domainConfig.EnableIPv4 { | ||
if !provider.addDomainRecord(domainConfig, true) { | ||
resultV4 = false | ||
} | ||
} | ||
|
||
if domainConfig.EnableIpv6 { | ||
if !provider.addDomainRecord(domainConfig, false) { | ||
resultV6 = false | ||
} | ||
} | ||
|
||
return resultV4 && resultV6 | ||
} | ||
|
||
func (provider ProviderTencentCloud) addDomainRecord(domainConfig *DomainConfig, isIpv4 bool) bool { | ||
record, err := provider.findDNSRecord(domainConfig.FullDomain, isIpv4) | ||
if err != nil { | ||
log.Printf("查找 DNS 记录时出错: %s\n", err) | ||
return false | ||
} | ||
|
||
if errResponse, ok := record["Error"].(map[string]interface{}); ok { | ||
if errCode, ok := errResponse["Code"].(string); ok && errCode == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录 | ||
// 添加 DNS 记录 | ||
return provider.createDNSRecord(domainConfig.FullDomain, domainConfig, isIpv4) | ||
} else { | ||
log.Printf("查询 DNS 记录时出错,错误代码为: %s\n", errCode) | ||
} | ||
} | ||
|
||
// 默认情况下更新 DNS 记录 | ||
return provider.updateDNSRecord(domainConfig.FullDomain, record["RecordList"].([]interface{})[0].(map[string]interface{})["RecordId"].(float64), domainConfig, isIpv4) | ||
} | ||
|
||
func (provider ProviderTencentCloud) findDNSRecord(domain string, isIPv4 bool) (map[string]interface{}, error) { | ||
var ipType = "A" | ||
if !isIPv4 { | ||
ipType = "AAAA" | ||
} | ||
_, realDomain := SplitDomain(domain) | ||
prefix, _ := SplitDomain(domain) | ||
data := map[string]interface{}{ | ||
"RecordType": ipType, | ||
"Domain": realDomain, | ||
"RecordLine": "默认", | ||
"Subdomain": prefix, | ||
} | ||
jsonData, _ := json.Marshal(data) | ||
body, err := provider.sendRequest("DescribeRecordList", jsonData) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var res map[string]interface{} | ||
err = json.Unmarshal(body, &res) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
result := res["Response"].(map[string]interface{}) | ||
return result, nil | ||
} | ||
|
||
func (provider ProviderTencentCloud) createDNSRecord(domain string, domainConfig *DomainConfig, isIPv4 bool) bool { | ||
var ipType = "A" | ||
var ipAddr = domainConfig.Ipv4Addr | ||
if !isIPv4 { | ||
ipType = "AAAA" | ||
ipAddr = domainConfig.Ipv6Addr | ||
} | ||
_, realDomain := SplitDomain(domain) | ||
prefix, _ := SplitDomain(domain) | ||
data := map[string]interface{}{ | ||
"RecordType": ipType, | ||
"RecordLine": "默认", | ||
"Domain": realDomain, | ||
"SubDomain": prefix, | ||
"Value": ipAddr, | ||
"TTL": 600, | ||
} | ||
jsonData, _ := json.Marshal(data) | ||
_, err := provider.sendRequest("CreateRecord", jsonData) | ||
return err == nil | ||
} | ||
|
||
func (provider ProviderTencentCloud) updateDNSRecord(domain string, recordID float64, domainConfig *DomainConfig, isIPv4 bool) bool { | ||
var ipType = "A" | ||
var ipAddr = domainConfig.Ipv4Addr | ||
if !isIPv4 { | ||
ipType = "AAAA" | ||
ipAddr = domainConfig.Ipv6Addr | ||
} | ||
_, realDomain := SplitDomain(domain) | ||
prefix, _ := SplitDomain(domain) | ||
data := map[string]interface{}{ | ||
"RecordType": ipType, | ||
"RecordLine": "默认", | ||
"Domain": realDomain, | ||
"SubDomain": prefix, | ||
"Value": ipAddr, | ||
"TTL": 600, | ||
"RecordId": recordID, | ||
} | ||
jsonData, _ := json.Marshal(data) | ||
_, err := provider.sendRequest("ModifyRecord", jsonData) | ||
return err == nil | ||
} | ||
|
||
// 以下为辅助方法,如发送 HTTP 请求等 | ||
func (provider ProviderTencentCloud) sendRequest(action string, data []byte) ([]byte, error) { | ||
client := &http.Client{} | ||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
req.Header.Set("Content-Type", "application/json") | ||
req.Header.Set("X-TC-Version", "2021-03-23") | ||
|
||
provider.signRequest(provider.SecretID, provider.SecretKey, req, action, string(data)) | ||
resp, err := client.Do(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer func(Body io.ReadCloser) { | ||
err := Body.Close() | ||
if err != nil { | ||
log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s\n", err.Error()) | ||
} | ||
}(resp.Body) | ||
|
||
body, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return body, nil | ||
} | ||
|
||
// https://github.com/jeessy2/ddns-go/blob/master/util/tencent_cloud_signer.go | ||
|
||
func (provider ProviderTencentCloud) sha256hex(s string) string { | ||
b := sha256.Sum256([]byte(s)) | ||
return hex.EncodeToString(b[:]) | ||
} | ||
|
||
func (provider ProviderTencentCloud) hmacsha256(s, key string) string { | ||
hashed := hmac.New(sha256.New, []byte(key)) | ||
hashed.Write([]byte(s)) | ||
return string(hashed.Sum(nil)) | ||
} | ||
|
||
func (provider ProviderTencentCloud) WriteString(strs ...string) string { | ||
var b strings.Builder | ||
for _, str := range strs { | ||
b.WriteString(str) | ||
} | ||
|
||
return b.String() | ||
} | ||
|
||
func (provider ProviderTencentCloud) signRequest(secretId string, secretKey string, r *http.Request, action string, payload string) { | ||
algorithm := "TC3-HMAC-SHA256" | ||
service := "dnspod" | ||
host := provider.WriteString(service, ".tencentcloudapi.com") | ||
timestamp := time.Now().Unix() | ||
timestampStr := strconv.FormatInt(timestamp, 10) | ||
|
||
// 步骤 1:拼接规范请求串 | ||
canonicalHeaders := provider.WriteString("content-type:application/json\nhost:", host, "\nx-tc-action:", strings.ToLower(action), "\n") | ||
signedHeaders := "content-type;host;x-tc-action" | ||
hashedRequestPayload := provider.sha256hex(payload) | ||
canonicalRequest := provider.WriteString("POST\n/\n\n", canonicalHeaders, "\n", signedHeaders, "\n", hashedRequestPayload) | ||
|
||
// 步骤 2:拼接待签名字符串 | ||
date := time.Unix(timestamp, 0).UTC().Format("2006-01-02") | ||
credentialScope := provider.WriteString(date, "/", service, "/tc3_request") | ||
hashedCanonicalRequest := provider.sha256hex(canonicalRequest) | ||
string2sign := provider.WriteString(algorithm, "\n", timestampStr, "\n", credentialScope, "\n", hashedCanonicalRequest) | ||
|
||
// 步骤 3:计算签名 | ||
secretDate := provider.hmacsha256(date, provider.WriteString("TC3", secretKey)) | ||
secretService := provider.hmacsha256(service, secretDate) | ||
secretSigning := provider.hmacsha256("tc3_request", secretService) | ||
signature := hex.EncodeToString([]byte(provider.hmacsha256(string2sign, secretSigning))) | ||
|
||
// 步骤 4:拼接 Authorization | ||
authorization := provider.WriteString(algorithm, " Credential=", secretId, "/", credentialScope, ", SignedHeaders=", signedHeaders, ", Signature=", signature) | ||
|
||
r.Header.Add("Authorization", authorization) | ||
r.Header.Set("Host", host) | ||
r.Header.Set("X-TC-Action", action) | ||
r.Header.Add("X-TC-Timestamp", timestampStr) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters