1+ package ddns
2+
3+ import (
4+ "bytes"
5+ "encoding/json"
6+ "io"
7+ "log"
8+ "net/http"
9+ "crypto/hmac"
10+ "crypto/sha256"
11+ "encoding/hex"
12+ "strconv"
13+ "strings"
14+ "time"
15+ )
16+
17+ const (
18+ url = "https://dnspod.tencentcloudapi.com"
19+ )
20+
21+ type ProviderTencentCloud struct {
22+ SecretID string
23+ SecretKey string
24+ }
25+
26+ func (provider ProviderTencentCloud ) UpdateDomain (domainConfig * DomainConfig ) bool {
27+ if domainConfig == nil {
28+ return false
29+ }
30+
31+ // 当IPv4和IPv6同时成功才算作成功
32+ var resultV4 = true
33+ var resultV6 = true
34+ if domainConfig .EnableIPv4 {
35+ if ! provider .addDomainRecord (domainConfig , true ) {
36+ resultV4 = false
37+ }
38+ }
39+
40+ if domainConfig .EnableIpv6 {
41+ if ! provider .addDomainRecord (domainConfig , false ) {
42+ resultV6 = false
43+ }
44+ }
45+
46+ return resultV4 && resultV6
47+ }
48+
49+ func (provider ProviderTencentCloud ) addDomainRecord (domainConfig * DomainConfig , isIpv4 bool ) bool {
50+ record , err := provider .findDNSRecord (domainConfig .FullDomain , isIpv4 )
51+ if err != nil {
52+ log .Printf ("查找 DNS 记录时出错: %s\n " , err )
53+ return false
54+ }
55+
56+ if errResponse , ok := record ["Error" ].(map [string ]interface {}); ok {
57+ if errCode , ok := errResponse ["Code" ].(string ); ok && errCode == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录
58+ // 添加 DNS 记录
59+ return provider .createDNSRecord (domainConfig .FullDomain , domainConfig , isIpv4 )
60+ } else {
61+ log .Printf ("查询 DNS 记录时出错,错误代码为: %s\n " , errCode )
62+ }
63+ }
64+
65+ // 默认情况下更新 DNS 记录
66+ return provider .updateDNSRecord (domainConfig .FullDomain , record ["RecordList" ].([]interface {})[0 ].(map [string ]interface {})["RecordId" ].(float64 ), domainConfig , isIpv4 )
67+ }
68+
69+ func (provider ProviderTencentCloud ) findDNSRecord (domain string , isIPv4 bool ) (map [string ]interface {}, error ) {
70+ var ipType = "A"
71+ if ! isIPv4 {
72+ ipType = "AAAA"
73+ }
74+ _ , realDomain := SplitDomain (domain )
75+ prefix , _ := SplitDomain (domain )
76+ data := map [string ]interface {}{
77+ "RecordType" : ipType ,
78+ "Domain" : realDomain ,
79+ "RecordLine" : "默认" ,
80+ "Subdomain" : prefix ,
81+ }
82+ jsonData , _ := json .Marshal (data )
83+ body , err := provider .sendRequest ("DescribeRecordList" , jsonData )
84+ if err != nil {
85+ return nil , err
86+ }
87+
88+ var res map [string ]interface {}
89+ err = json .Unmarshal (body , & res )
90+ if err != nil {
91+ return nil , err
92+ }
93+
94+ result := res ["Response" ].(map [string ]interface {})
95+ return result , nil
96+ }
97+
98+ func (provider ProviderTencentCloud ) createDNSRecord (domain string , domainConfig * DomainConfig , isIPv4 bool ) bool {
99+ var ipType = "A"
100+ var ipAddr = domainConfig .Ipv4Addr
101+ if ! isIPv4 {
102+ ipType = "AAAA"
103+ ipAddr = domainConfig .Ipv6Addr
104+ }
105+ _ , realDomain := SplitDomain (domain )
106+ prefix , _ := SplitDomain (domain )
107+ data := map [string ]interface {}{
108+ "RecordType" : ipType ,
109+ "RecordLine" : "默认" ,
110+ "Domain" : realDomain ,
111+ "SubDomain" : prefix ,
112+ "Value" : ipAddr ,
113+ "TTL" : 600 ,
114+ }
115+ jsonData , _ := json .Marshal (data )
116+ _ , err := provider .sendRequest ("CreateRecord" , jsonData )
117+ return err == nil
118+ }
119+
120+ func (provider ProviderTencentCloud ) updateDNSRecord (domain string , recordID float64 , domainConfig * DomainConfig , isIPv4 bool ) bool {
121+ var ipType = "A"
122+ var ipAddr = domainConfig .Ipv4Addr
123+ if ! isIPv4 {
124+ ipType = "AAAA"
125+ ipAddr = domainConfig .Ipv6Addr
126+ }
127+ _ , realDomain := SplitDomain (domain )
128+ prefix , _ := SplitDomain (domain )
129+ data := map [string ]interface {}{
130+ "RecordType" : ipType ,
131+ "RecordLine" : "默认" ,
132+ "Domain" : realDomain ,
133+ "SubDomain" : prefix ,
134+ "Value" : ipAddr ,
135+ "TTL" : 600 ,
136+ "RecordId" : recordID ,
137+ }
138+ jsonData , _ := json .Marshal (data )
139+ _ , err := provider .sendRequest ("ModifyRecord" , jsonData )
140+ return err == nil
141+ }
142+
143+ // 以下为辅助方法,如发送 HTTP 请求等
144+ func (provider ProviderTencentCloud ) sendRequest (action string , data []byte ) ([]byte , error ) {
145+ client := & http.Client {}
146+ req , err := http .NewRequest ("POST" , url , bytes .NewBuffer (data ))
147+ if err != nil {
148+ return nil , err
149+ }
150+
151+ req .Header .Set ("Content-Type" , "application/json" )
152+ req .Header .Set ("X-TC-Version" , "2021-03-23" )
153+
154+ provider .signRequest (provider .SecretID , provider .SecretKey , req , action , string (data ))
155+ resp , err := client .Do (req )
156+ if err != nil {
157+ return nil , err
158+ }
159+ defer func (Body io.ReadCloser ) {
160+ err := Body .Close ()
161+ if err != nil {
162+ log .Printf ("NEZHA>> 无法关闭HTTP响应体流: %s\n " , err .Error ())
163+ }
164+ }(resp .Body )
165+
166+ body , err := io .ReadAll (resp .Body )
167+ if err != nil {
168+ return nil , err
169+ }
170+
171+ return body , nil
172+ }
173+
174+ // https://github.com/jeessy2/ddns-go/blob/master/util/tencent_cloud_signer.go
175+
176+ func (provider ProviderTencentCloud ) sha256hex (s string ) string {
177+ b := sha256 .Sum256 ([]byte (s ))
178+ return hex .EncodeToString (b [:])
179+ }
180+
181+ func (provider ProviderTencentCloud ) hmacsha256 (s , key string ) string {
182+ hashed := hmac .New (sha256 .New , []byte (key ))
183+ hashed .Write ([]byte (s ))
184+ return string (hashed .Sum (nil ))
185+ }
186+
187+ func (provider ProviderTencentCloud ) WriteString (strs ... string ) string {
188+ var b strings.Builder
189+ for _ , str := range strs {
190+ b .WriteString (str )
191+ }
192+
193+ return b .String ()
194+ }
195+
196+ func (provider ProviderTencentCloud ) signRequest (secretId string , secretKey string , r * http.Request , action string , payload string ) {
197+ algorithm := "TC3-HMAC-SHA256"
198+ service := "dnspod"
199+ host := provider .WriteString (service , ".tencentcloudapi.com" )
200+ timestamp := time .Now ().Unix ()
201+ timestampStr := strconv .FormatInt (timestamp , 10 )
202+
203+ // 步骤 1:拼接规范请求串
204+ canonicalHeaders := provider .WriteString ("content-type:application/json\n host:" , host , "\n x-tc-action:" , strings .ToLower (action ), "\n " )
205+ signedHeaders := "content-type;host;x-tc-action"
206+ hashedRequestPayload := provider .sha256hex (payload )
207+ canonicalRequest := provider .WriteString ("POST\n /\n \n " , canonicalHeaders , "\n " , signedHeaders , "\n " , hashedRequestPayload )
208+
209+ // 步骤 2:拼接待签名字符串
210+ date := time .Unix (timestamp , 0 ).UTC ().Format ("2006-01-02" )
211+ credentialScope := provider .WriteString (date , "/" , service , "/tc3_request" )
212+ hashedCanonicalRequest := provider .sha256hex (canonicalRequest )
213+ string2sign := provider .WriteString (algorithm , "\n " , timestampStr , "\n " , credentialScope , "\n " , hashedCanonicalRequest )
214+
215+ // 步骤 3:计算签名
216+ secretDate := provider .hmacsha256 (date , provider .WriteString ("TC3" , secretKey ))
217+ secretService := provider .hmacsha256 (service , secretDate )
218+ secretSigning := provider .hmacsha256 ("tc3_request" , secretService )
219+ signature := hex .EncodeToString ([]byte (provider .hmacsha256 (string2sign , secretSigning )))
220+
221+ // 步骤 4:拼接 Authorization
222+ authorization := provider .WriteString (algorithm , " Credential=" , secretId , "/" , credentialScope , ", SignedHeaders=" , signedHeaders , ", Signature=" , signature )
223+
224+ r .Header .Add ("Authorization" , authorization )
225+ r .Header .Set ("Host" , host )
226+ r .Header .Set ("X-TC-Action" , action )
227+ r .Header .Add ("X-TC-Timestamp" , timestampStr )
228+ }
0 commit comments