2024-02-25 22:49:28 -05:00
|
|
|
package ddns
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
2024-08-23 23:11:06 -04:00
|
|
|
"net/url"
|
|
|
|
|
|
|
|
"github.com/naiba/nezha/pkg/utils"
|
2024-02-25 22:49:28 -05:00
|
|
|
)
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
const baseEndpoint = "https://api.cloudflare.com/client/v4/zones"
|
|
|
|
|
2024-02-25 22:49:28 -05:00
|
|
|
type ProviderCloudflare struct {
|
2024-08-23 23:11:06 -04:00
|
|
|
secret string
|
|
|
|
zoneId string
|
|
|
|
recordId string
|
|
|
|
domainConfig *DomainConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
type cfReq struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Content string `json:"content"`
|
|
|
|
TTL uint32 `json:"ttl"`
|
|
|
|
Proxied bool `json:"proxied"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type cfResp struct {
|
|
|
|
Result []struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
} `json:"result"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewProviderCloudflare(s string) *ProviderCloudflare {
|
|
|
|
return &ProviderCloudflare{
|
|
|
|
secret: s,
|
|
|
|
}
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) error {
|
2024-02-25 22:49:28 -05:00
|
|
|
if domainConfig == nil {
|
2024-08-23 23:11:06 -04:00
|
|
|
return fmt.Errorf("获取 DDNS 配置失败")
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
2024-08-23 23:11:06 -04:00
|
|
|
provider.domainConfig = domainConfig
|
2024-02-25 22:49:28 -05:00
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
err := provider.getZoneID()
|
2024-02-25 22:49:28 -05:00
|
|
|
if err != nil {
|
2024-08-23 23:11:06 -04:00
|
|
|
return fmt.Errorf("无法获取 zone ID: %s", err)
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 当IPv4和IPv6同时成功才算作成功
|
2024-08-23 23:11:06 -04:00
|
|
|
if provider.domainConfig.EnableIPv4 {
|
|
|
|
if err = provider.addDomainRecord(true); err != nil {
|
|
|
|
return err
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
if provider.domainConfig.EnableIpv6 {
|
|
|
|
if err = provider.addDomainRecord(false); err != nil {
|
|
|
|
return err
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
return nil
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
func (provider *ProviderCloudflare) addDomainRecord(isIpv4 bool) error {
|
|
|
|
err := provider.findDNSRecord(isIpv4)
|
2024-02-25 22:49:28 -05:00
|
|
|
if err != nil {
|
2024-08-23 23:11:06 -04:00
|
|
|
return fmt.Errorf("查找 DNS 记录时出错: %s", err)
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
if provider.recordId == "" {
|
2024-02-25 22:49:28 -05:00
|
|
|
// 添加 DNS 记录
|
2024-08-23 23:11:06 -04:00
|
|
|
return provider.createDNSRecord(isIpv4)
|
2024-02-25 22:49:28 -05:00
|
|
|
} else {
|
|
|
|
// 更新 DNS 记录
|
2024-08-23 23:11:06 -04:00
|
|
|
return provider.updateDNSRecord(isIpv4)
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
func (provider *ProviderCloudflare) getZoneID() error {
|
|
|
|
_, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
|
|
|
zu, _ := url.Parse(baseEndpoint)
|
|
|
|
|
|
|
|
q := zu.Query()
|
|
|
|
q.Set("name", realDomain)
|
|
|
|
zu.RawQuery = q.Encode()
|
|
|
|
|
|
|
|
body, err := provider.sendRequest("GET", zu.String(), nil)
|
2024-02-25 22:49:28 -05:00
|
|
|
if err != nil {
|
2024-08-23 23:11:06 -04:00
|
|
|
return err
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
res := &cfResp{}
|
|
|
|
err = utils.Json.Unmarshal(body, res)
|
2024-02-25 22:49:28 -05:00
|
|
|
if err != nil {
|
2024-08-23 23:11:06 -04:00
|
|
|
return err
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
result := res.Result
|
2024-02-25 22:49:28 -05:00
|
|
|
if len(result) > 0 {
|
2024-08-23 23:11:06 -04:00
|
|
|
provider.zoneId = result[0].ID
|
|
|
|
return nil
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
return fmt.Errorf("找不到 Zone ID")
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
func (provider *ProviderCloudflare) findDNSRecord(isIPv4 bool) error {
|
|
|
|
var ipType string
|
|
|
|
if isIPv4 {
|
|
|
|
ipType = "A"
|
|
|
|
} else {
|
2024-02-25 22:49:28 -05:00
|
|
|
ipType = "AAAA"
|
|
|
|
}
|
2024-08-23 23:11:06 -04:00
|
|
|
|
|
|
|
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
|
|
|
|
du, _ := url.Parse(de)
|
|
|
|
|
|
|
|
q := du.Query()
|
|
|
|
q.Set("name", provider.domainConfig.FullDomain)
|
|
|
|
q.Set("type", ipType)
|
|
|
|
du.RawQuery = q.Encode()
|
|
|
|
|
|
|
|
body, err := provider.sendRequest("GET", du.String(), nil)
|
2024-02-25 22:49:28 -05:00
|
|
|
if err != nil {
|
2024-08-23 23:11:06 -04:00
|
|
|
return err
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
res := &cfResp{}
|
|
|
|
err = utils.Json.Unmarshal(body, res)
|
2024-02-25 22:49:28 -05:00
|
|
|
if err != nil {
|
2024-08-23 23:11:06 -04:00
|
|
|
return err
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
result := res.Result
|
2024-02-25 22:49:28 -05:00
|
|
|
if len(result) > 0 {
|
2024-08-23 23:11:06 -04:00
|
|
|
provider.recordId = result[0].ID
|
|
|
|
return nil
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
return nil
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
func (provider *ProviderCloudflare) createDNSRecord(isIPv4 bool) error {
|
|
|
|
var ipType, ipAddr string
|
|
|
|
if isIPv4 {
|
|
|
|
ipType = "A"
|
|
|
|
ipAddr = provider.domainConfig.Ipv4Addr
|
|
|
|
} else {
|
2024-02-25 22:49:28 -05:00
|
|
|
ipType = "AAAA"
|
2024-08-23 23:11:06 -04:00
|
|
|
ipAddr = provider.domainConfig.Ipv6Addr
|
|
|
|
}
|
|
|
|
|
|
|
|
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
|
|
|
|
data := &cfReq{
|
|
|
|
Name: provider.domainConfig.FullDomain,
|
|
|
|
Type: ipType,
|
|
|
|
Content: ipAddr,
|
|
|
|
TTL: 60,
|
|
|
|
Proxied: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonData, _ := utils.Json.Marshal(data)
|
|
|
|
_, err := provider.sendRequest("POST", de, jsonData)
|
|
|
|
return err
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
func (provider *ProviderCloudflare) updateDNSRecord(isIPv4 bool) error {
|
|
|
|
var ipType, ipAddr string
|
|
|
|
if isIPv4 {
|
|
|
|
ipType = "A"
|
|
|
|
ipAddr = provider.domainConfig.Ipv4Addr
|
|
|
|
} else {
|
2024-02-25 22:49:28 -05:00
|
|
|
ipType = "AAAA"
|
2024-08-23 23:11:06 -04:00
|
|
|
ipAddr = provider.domainConfig.Ipv6Addr
|
|
|
|
}
|
|
|
|
|
|
|
|
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records", provider.recordId)
|
|
|
|
data := &cfReq{
|
|
|
|
Name: provider.domainConfig.FullDomain,
|
|
|
|
Type: ipType,
|
|
|
|
Content: ipAddr,
|
|
|
|
TTL: 60,
|
|
|
|
Proxied: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonData, _ := utils.Json.Marshal(data)
|
|
|
|
_, err := provider.sendRequest("PATCH", de, jsonData)
|
|
|
|
return err
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 以下为辅助方法,如发送 HTTP 请求等
|
2024-07-06 22:19:58 -04:00
|
|
|
func (provider *ProviderCloudflare) sendRequest(method string, url string, data []byte) ([]byte, error) {
|
2024-02-25 22:49:28 -05:00
|
|
|
req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", provider.secret))
|
2024-02-25 22:49:28 -05:00
|
|
|
req.Header.Add("Content-Type", "application/json")
|
|
|
|
|
2024-08-23 23:11:06 -04:00
|
|
|
resp, err := utils.HttpClient.Do(req)
|
2024-02-25 22:49:28 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer func(Body io.ReadCloser) {
|
|
|
|
err := Body.Close()
|
|
|
|
if err != nil {
|
2024-08-23 23:11:06 -04:00
|
|
|
log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s", err.Error())
|
2024-02-25 22:49:28 -05:00
|
|
|
}
|
|
|
|
}(resp.Body)
|
|
|
|
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return body, nil
|
|
|
|
}
|