golang实现请求cloudflare修改域名A记录解析

现在有些DNS解析要收费,国内的几个厂商需要实名制。下面给出golang请求cloudflare修改域名A记录解析的代码。

准备工作:

  1. 在域名购买服务商处,将dns解析服务器改为cloudflare的dns服务器地址
  2. 将域名添加到cloudflare后台,非cloudflare购买的域名也可以添加!
  3. cloudflare后台获取API KEY;获取区域ID,即代码中用到的zone id ; 代码中需要用到
  4. 在后台先为域名设置一条A记录,设置“仅限DNS”

如果准备工作不知道操作的,搜索其他博客,或者在B站搜索,都有教程。

示例代码实现的功能:从wan口获取公网ip地址,与当前dns记录比较,如果相同则不请求接口修改A记录,不同则请求接口。wan获取ip用正则实现,可以根据实际情况修改代码。

package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
)

// 响应体的json中有其他字段,此处只定义需要的字段
type GetDnsRecords struct {
	Result []Result `json:"result"`
	Success bool `json:"success"`
}

type PutDnsRecordResponse struct {
	Result Result `json:"result"`
	Errors interface{} `json:"errors"`
	Success bool `json:"success"`
}

// 单条dns记录
type Result struct {
	// dns 记录后台的ID
	Id string `json:"id"`
	// 一级域名,例如:fuck.com
	ZoneName string `json:"zone_name"`
	// 具体被解析的域名, 例如:y.fuck.com
	Name string `json:"name"`
	// 解析到的内容,可能是IP
	Content string `json:"content"`
	// 解析类型,例如:A
	Type string `json:"type"`
}

type PutDns struct {
	Type string `json:"type"`
	Name string `json:"name"`
	Content string `json:"content"`
	Proxied bool `json:"proxied"`
}

func main() {
	// 需要解析的域名、cloudflare api key、zone id 写到环境变量
	domain := os.Getenv("CLOUDFLARE_DOMAIN")
	apiKey := os.Getenv("CLOUDFLARE_API_KEY")
	zoneId := os.Getenv("CLOUDFLARE_ZONE_ID")
	recordId, lastIp, err := getDnsRecordId(apiKey, zoneId, domain)
	if err != nil {
		fmt.Println("getDnsRecordId err", err)
		return
	}
	dnsType := "A"
	proxied := false
	// 从网口获取IP
	nowIp := getLocalIP()
	if nowIp != lastIp {
		_, er := putDnsRecord(zoneId, apiKey, recordId, dnsType, domain, nowIp, proxied)
		if er == nil {
			fmt.Println("putDnsRecord success ", "lastIp: ", lastIp,  "nowIp: ", nowIp)
			return
		} else {
			fmt.Println("putDnsRecord err", er)
			return
		}
	} else {
		fmt.Println("ip no change ", "lastIp: ", lastIp,  "nowIp: ", nowIp)
		return
	}


}

// 执行shell,获取wan口信息正则匹配ip
func getLocalIP() (ip string) {
	commandStr := "ifconfig pppoe-wan"
	cmd := exec.Command("/bin/bash", "-c", commandStr)
	stdout, _ := cmd.StdoutPipe()
	if err := cmd.Start(); err != nil {
		return ""
	}
	outBytes, _ := ioutil.ReadAll(stdout)
	stdout.Close()

	if err := cmd.Wait(); err != nil {
		fmt.Println("Execute failed when Wait:" + err.Error())
		return ""
	}
	ifconfig := string(outBytes)
	pattern := `inet addr:(\d+\.\d+\.\d+\.\d+)`
	reg := regexp.MustCompile(pattern)
	match := reg.FindString(ifconfig)
	if match != "" {
		pattern = `\d+\.\d+\.\d+\.\d+`
		reg = regexp.MustCompile(pattern)
		return reg.FindString(match)
	}
	return

}

// 获取DNS记录ID
func getDnsRecordId(apiKey string, zoneId string, domain string ) (recordId string, lastContent string, e error) {
	api := "https://api.cloudflare.com/client/v4/zones/%s/dns_records"
	api = fmt.Sprintf(api, zoneId)
	requestHeader := map[string]string{
		// Bearer后有1空格
		"Authorization" : "Bearer " + apiKey,
	}
	code, resBody, _, er := JsonCurl("GET", api, nil, requestHeader)
	if er != nil {
		e = er
		return
	}
	var dnsRecords GetDnsRecords
	err := json.Unmarshal(resBody, &dnsRecords)
	if code!= 200 || err != nil || dnsRecords.Success != true {
		e = err
		return
	}

	for _, value := range dnsRecords.Result {
		if value.Name == domain {
			recordId = value.Id
			lastContent = value.Content
			return
		}
	}

	return

}

/*
 * @Description: 修改dns
 * @param dnsType string A记录传"A"
 * @param name string 具体域名
 * @param content string 解析到内容例如IP
 * @param proxied bool 是否代理传
 * @author ""
 * @date 2022-05-04 15:30:00
*/
func putDnsRecord(zoneId string, apiKey string, recordId string, dnsType string, name string, content string, proxied bool) (bool,error) {
	putDns := PutDns{
		Type: dnsType,
		Name: name,
		Content: content,
		Proxied: proxied,
	}
	api := `https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s`
	api = fmt.Sprintf(api, zoneId, recordId)
	requestHeader := map[string]string{
		// Bearer后有1空格
		"Authorization" : "Bearer " + apiKey,
	}
	_, resBody, _, er := JsonCurl("PUT", api, putDns, requestHeader)
	if er != nil {
		return false, er
	}
	var dnsRecords PutDnsRecordResponse
	err := json.Unmarshal(resBody, &dnsRecords)
	if err != nil {
		// 记录日志
		return false, err
	}
	if dnsRecords.Success != true {
		return false,errors.New( fmt.Sprintf("resBody: %s, url: %s, apiKey: %s", string(resBody) , api, apiKey))
	}
	return true, nil
}


func JsonCurl(method, url string, body interface{}, headers map[string]string) (code int, data []byte, respHeader http.Header, error error) {
	jsonStr, _ := json.Marshal(body)
	buffer := bytes.NewBuffer(jsonStr)
	request, err := http.NewRequest(method, url, buffer)
	if err != nil {
		error = err
		return
	}
	request.Header.Set("Content-Type", "application/json;charset=UTF-8")
	for key, val := range headers {
		request.Header.Set(key, val)
	}

	resp, err := http.DefaultClient.Do(request)
	defer resp.Body.Close()
	if err != nil {
		error = err
		return
	}
	code = resp.StatusCode
	data, _ = ioutil.ReadAll(resp.Body)
	respHeader = resp.Header
	return
}


func writeIpToFile(fileName string, content string) error {
	f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
	if err != nil {
		return err
	} else {
		n, _ := f.Seek(0, os.SEEK_END)
		_, e := f.WriteAt([]byte(content), n)
		defer f.Close()
		if e != nil {
			return e
		}
	}
	return nil
}


posted @ 2022-05-04 21:46  熊先生不开玩笑  阅读(783)  评论(0)    收藏  举报