家里路由器,以前用的网件,它自带了noip的支持。 不过呢,可能是太老了,速度上不去,我家800M的宽带只能跑到4-500M。换成小米并刷openwrt的话,可以跑到700多M,算跑满了。不过问题就是ddns不太支持了,虽然有一堆,但我都没号也不想花钱呀,自己搓一个吧。用起来其实挺简单的,在openwrt里面的ddns那里服务商选自定义然后填好参数就可以了。
域名的话是用自己的托管在cloudflare的域名。用户名密码固定写死在代码中了,不是商业的东西,简单防刷就好。
自定义URL格式是标准的,就是 https://ddns服务器域名/updatedns?hostname=[DOMAIN]&myip=[IP]&username=[USERNAME]&password=[PASSWORD]
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
var (
apiKey = "cloudflare API KEY"
email = "cloudflare登录email"
zoneID = "域名的zone id"
username = "客户端认证的用户名"
password = "密码"
ListenPort = "监听端口"
ListenAddr = "监听ip"
)
type dnsRecord struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Content string `json:"content"`
}
func updateDNS(ip, hostname string) error {
client := &http.Client{}
reqURL := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records?type=A&name=%s", zoneID, hostname)
req, _ := http.NewRequest("GET", reqURL, nil)
req.Header.Add("X-Auth-Email", email)
req.Header.Add("X-Auth-Key", apiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result struct {
Success bool `json:"success"`
Result []dnsRecord `json:"result"`
Errors []struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"errors"`
}
if err := json.Unmarshal(body, &result); err != nil {
return fmt.Errorf("json unmarshal failed: %w", err)
}
if !result.Success {
return fmt.Errorf("API returned success=false, errors: %+v", result.Errors)
}
if len(result.Result) == 0 {
return fmt.Errorf("record not found for %s - the DNS record may not exist yet. Please create it first in Cloudflare Dashboard", hostname)
}
rec := result.Result[0]
if rec.Content == ip {
log.Printf("No change: %s already -> %s", hostname, ip)
return nil
}
data := map[string]interface{}{
"type": "A",
"name": hostname,
"content": ip,
"ttl": 120,
}
j, _ := json.Marshal(data)
putURL := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneID, rec.ID)
req2, _ := http.NewRequest("PUT", putURL, bytes.NewBuffer(j))
req2.Header.Add("X-Auth-Email", email)
req2.Header.Add("X-Auth-Key", apiKey)
req2.Header.Add("Content-Type", "application/json")
resp2, err := client.Do(req2)
if err != nil {
return fmt.Errorf("update request failed: %w", err)
}
defer resp2.Body.Close()
io.ReadAll(resp2.Body)
log.Printf("DNS updated: %s -> %s", hostname, ip)
return nil
}
func main() {
r := gin.Default()
r.GET("/updatedns", func(c *gin.Context) {
hostname := c.Query("hostname")
myip := c.Query("myip")
reqUsername := c.Query("username")
reqPassword := c.Query("password")
if hostname == "" || myip == "" || reqUsername == "" || reqPassword == "" {
c.String(http.StatusBadRequest, "missing required parameters: hostname, myip, username, password")
return
}
if reqUsername != username || reqPassword != password {
c.String(http.StatusUnauthorized, "invalid username or password")
return
}
log.Printf("Update request: %s -> %s", hostname, myip)
if err := updateDNS(myip, hostname); err != nil {
log.Printf("Error: %v", err)
c.String(http.StatusInternalServerError, err.Error())
return
}
c.String(http.StatusOK, "good %s", myip)
})
addr := fmt.Sprintf("%s:%s", ListenAddr, ListenPort)
log.Printf("DDNS server running on %s", addr)
r.Run(addr)
}