DDNS for Cloudflare using GO
Updating your dynamic IP with Cloudflare DNS using Golang
Here is a simple program is designed to update DNS records for multiple domain names on Cloudflare every minute. It can be run as a self-contained daemon, detaching from the terminal to operate in the background, ensuring continuous operation. This will update your public IP address every minute allowing Cloudflare DNS to have your latest dynamic IP issued by your ISP.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"syscall"
"time"
)
const (
apiKey = "****" // Cloudflare API key
zoneID = "****" // Cloudflare zone ID
recordNames = "davidivashenko.com,*.davidivashenko.com" // Comma-separated list of DNS record names
cloudflareAPI = "https://api.cloudflare.com/client/v4" // Cloudflare API base URL
updateInterval = 1 * time.Minute // Interval for updating DNS records
ttl = auto // Time-to-live for DNS records
proxied = true // Whether the DNS records are proxied
)
type DNSRecord struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Content string `json:"content"`
TTL int `json:"ttl"`
Proxied bool `json:"proxied"`
}
type DNSResponse struct {
Success bool `json:"success"`
Result []DNSRecord `json:"result"`
}
type UpdateResponse struct {
Success bool `json:"success"`
}
func getCurrentIP() (string, error) {
resp, err := http.Get("https://ipinfo.io/ip")
if err != nil {
return "", err
}
defer resp.Body.Close()
ip, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return strings.TrimSpace(string(ip)), nil
}
func getDNSRecord(recordName string) (DNSRecord, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", fmt.Sprintf("%s/zones/%s/dns_records?type=A&name=%s", cloudflareAPI, zoneID, recordName), nil)
if err != nil {
return DNSRecord{}, err
}
req.Header.Add("Authorization", "Bearer "+apiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return DNSRecord{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return DNSRecord{}, err
}
var dnsResp DNSResponse
err = json.Unmarshal(body, &dnsResp)
if err != nil {
return DNSRecord{}, err
}
if len(dnsResp.Result) == 0 {
return DNSRecord{}, fmt.Errorf("no DNS record found for %s", recordName)
}
return dnsResp.Result[0], nil
}
func updateDNSRecord(recordName, recordID, ip string) (bool, error) {
client := &http.Client{}
dnsRecord := DNSRecord{
Type: "A",
Name: recordName,
Content: ip,
TTL: ttl,
Proxied: proxied,
}
jsonData, err := json.Marshal(dnsRecord)
if err != nil {
return false, err
}
req, err := http.NewRequest("PUT", fmt.Sprintf("%s/zones/%s/dns_records/%s", cloudflareAPI, zoneID, recordID), bytes.NewBuffer(jsonData))
if err != nil {
return false, err
}
req.Header.Add("Authorization", "Bearer "+apiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, err
}
var updateResp UpdateResponse
err = json.Unmarshal(body, &updateResp)
if err != nil {
return false, err
}
return updateResp.Success, nil
}
func main() {
if os.Getppid() != 1 {
// Detach from the parent process and run in the background
cmd := exec.Command(os.Args[0], os.Args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true,
}
err := cmd.Start()
if err != nil {
fmt.Printf("Failed to start daemon: %v\n", err)
os.Exit(1)
}
fmt.Printf("Daemon started with PID %d\n", cmd.Process.Pid)
os.Exit(0)
}
records := strings.Split(recordNames, ",")
for {
ip, err := getCurrentIP()
if err != nil {
fmt.Printf("Failed to get current IP: %v\n", err)
time.Sleep(updateInterval)
continue
}
for _, recordName := range records {
recordName = strings.TrimSpace(recordName)
dnsRecord, err := getDNSRecord(recordName)
if err != nil {
fmt.Printf("Failed to get DNS record for %s: %v\n", recordName, err)
continue
}
success, err := updateDNSRecord(recordName, dnsRecord.ID, ip)
if err != nil {
fmt.Printf("Failed to update DNS record for %s: %v\n", recordName, err)
continue
}
if success {
fmt.Printf("DNS record for %s updated successfully.\n", recordName)
} else {
fmt.Printf("Failed to update DNS record for %s.\n", recordName)
}
}
time.Sleep(updateInterval)
}
}
- Compile the Program:
go build -o update_dns update_dns.go
- Run the Program:
./update_dns
Ensure Startup on Boot
To ensure the program starts on boot, you can still use systemd on Linux or Task Scheduler on Windows.
For Linux using systemd:
- Create a systemd service file
/etc/systemd/system/update_dns.service
- Enable and start the service:
sudo systemctl enable update_dns.service
sudo systemctl start update_dns.service
This setup ensures the Go program runs as a daemon and updates the specified DNS records every minute.