diff --git a/build.sh b/build.sh index 086a9d9..9790a72 100644 --- a/build.sh +++ b/build.sh @@ -4,8 +4,10 @@ set -x BIN="ddns-namesilo" - +# Arm 64 CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o ${BIN}-linux-arm64 -a -ldflags '-extldflags "-static"' && upx -9 ${BIN}-linux-arm64 + +# AMD 64 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BIN}-linux-amd64 -a -ldflags '-extldflags "-static"' && upx -9 ${BIN}-linux-amd64 diff --git a/ddns-namesilo-linux-amd64 b/ddns-namesilo-linux-amd64 index a1b84ab..ce4b9f1 100644 Binary files a/ddns-namesilo-linux-amd64 and b/ddns-namesilo-linux-amd64 differ diff --git a/ddns-namesilo-linux-arm64 b/ddns-namesilo-linux-arm64 index 395b704..8de5bcb 100644 Binary files a/ddns-namesilo-linux-arm64 and b/ddns-namesilo-linux-arm64 differ diff --git a/ddns-namesilo-linux-softfloat-mipsle b/ddns-namesilo-linux-softfloat-mipsle index c8c9fdc..fc63c8d 100644 Binary files a/ddns-namesilo-linux-softfloat-mipsle and b/ddns-namesilo-linux-softfloat-mipsle differ diff --git a/go.mod b/go.mod index ffdd0ed..1a264ba 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module ddns -go 1.23.5 +go 1.25.5 diff --git a/go.sum b/go.sum index 3fc1ea1..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,98 +0,0 @@ -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/weppos/publicsuffix-go v0.40.2 h1:LlnoSH0Eqbsi3ReXZWBKCK5lHyzf3sc1JEHH1cnlfho= -github.com/weppos/publicsuffix-go v0.40.2/go.mod h1:XsLZnULC3EJ1Gvk9GVjuCTZ8QUu9ufE4TZpOizDShko= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/main.go b/main.go index fd7a855..fad3d38 100644 --- a/main.go +++ b/main.go @@ -11,111 +11,144 @@ import ( "time" ) -// 判断IPv6 地址类型 +// 判断 IPv6 地址类型 func classifyIPv6(ip net.IP) string { if ip.IsLoopback() { return "Loopback" } else if ip.IsLinkLocalUnicast() { return "Link-Local" } else if ip.IsGlobalUnicast() { + // 排除 ULA (Unique Local Address, fc00::/7),通常以 fd 或 fc 开头 + if len(ip) == 16 && (ip[0]&0xfe) == 0xfc { + return "ULA" + } return "Global" } return "Unknown" } -// getIPv6Addresses 获取网络接口的 IPv6 地址。 -// 它会跳过回环接口,并且只考虑已启用的接口。 -func getIPv6Addresses() (map[string][]net.IP, error) { - // 创建一个 map 用于存储接口名称及其对应的 IPv6 地址。 - addresses := make(map[string][]net.IP) - - // 获取系统上的所有网络接口。 +// AutoGetBestIPv6 自动寻找最佳的 IPv6 地址和对应的接口名称 +// 优先级: 接口名含 "pppoe" > "wan" > 其他 +func AutoGetBestIPv6() (string, string) { interfaces, err := net.Interfaces() if err != nil { - return nil, err // 如果无法获取接口信息,则返回错误。 + log.Printf("获取接口列表失败: %v", err) + return "", "NULL" } - // 遍历所有网络接口。 + var fallbackIP string + var fallbackIface string + + // 遍历所有接口 for _, iface := range interfaces { - // 跳过未启用的接口或回环接口。 + // 跳过未启用或回环接口 if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { continue } - // 获取与该接口关联的地址。 addrs, err := iface.Addrs() if err != nil { - return nil, err // 如果无法获取地址,则返回错误。 + continue } - // 遍历该接口的所有地址。 for _, addr := range addrs { var ip net.IP switch v := addr.(type) { case *net.IPNet: - ip = v.IP // 从 IPNet 类型中提取 IP。 + ip = v.IP case *net.IPAddr: - ip = v.IP // 从 IPAddr 类型中提取 IP。 + ip = v.IP } - // 检查 IP 是否为 IPv6 地址(即不是 IPv4)。 - if ip != nil && ip.To4() == nil { - // 将 IPv6 地址添加到对应的接口名称列表中。 - addresses[iface.Name] = append(addresses[iface.Name], ip) + // 必须是 IPv6 且不是 IPv4 + if ip == nil || ip.To4() != nil { + continue + } + + // 获取地址类型 + addrType := classifyIPv6(ip) + + // 只处理公网 IP (Global Unicast),忽略 Link-Local (fe80) 和 ULA (fdxx) + if addrType == "Global" { + ipStr := ip.String() + ifaceName := iface.Name + + // 策略 1: 如果接口名包含 "pppoe",这就是最完美的 WAN 口,直接返回 + if strings.Contains(ifaceName, "pppoe") { + log.Printf("自动检测: 命中高优先级接口 (PPPoE): %s -> %s\n", ifaceName, ipStr) + return ifaceName, ipStr + } + + // 策略 2: 如果接口名包含 "wan" (如 wan6, eth0.2),暂存,如果没有 pppoe 就用它 + if strings.Contains(ifaceName, "wan") { + log.Printf("自动检测: 命中次优先级接口 (WAN): %s -> %s\n", ifaceName, ipStr) + return ifaceName, ipStr + } + + // 策略 3: 其他有公网 IP 的接口 (如 br-lan),作为保底 + if fallbackIP == "" { + fallbackIP = ipStr + fallbackIface = ifaceName + } } } } - return addresses, nil // 返回包含 IPv6 地址的映射。 + // 如果没有找到明确的 WAN 口,返回保底的接口(例如 br-lan) + if fallbackIP != "" { + log.Printf("自动检测: 未找到 WAN 接口,使用保底接口: %s -> %s\n", fallbackIface, fallbackIP) + return fallbackIface, fallbackIP + } + + return "", "NULL" } -// 获取指定接口的 IPv6 地址 -func pppoe_interface_ipv6(_interface string) string { - addresses, err := getIPv6Addresses() +// getIPv6ByInterface 获取指定接口名称的 IPv6 +func getIPv6ByInterface(ifaceName string) string { + iface, err := net.InterfaceByName(ifaceName) if err != nil { - log.Printf("获取 IPv6 地址失败: %v\n", err) + log.Printf("无法找到接口 %s: %v", ifaceName, err) return "NULL" } - //fmt.Println("IPv6 地址列表:") - for iface, ips := range addresses { - //log.Printf("Interface: %s\n", iface) - if iface == _interface { - for _, ip := range ips { - addrType := classifyIPv6(ip) - - if addrType == "Global" { - - return ip.String() - } - } - - } + addrs, err := iface.Addrs() + if err != nil { + return "NULL" } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + + if ip != nil && ip.To4() == nil && classifyIPv6(ip) == "Global" { + return ip.String() + } + } return "NULL" } // 使用指定的 DNS 服务器解析域名的 IPv6 地址 func check_domain_ipv6(domain string) string { - // 自定义解析器,使用 8.8.8.8 作为 DNS 服务器 resolver := &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { d := net.Dialer{ Timeout: time.Second * 5, } - return d.DialContext(ctx, "udp", "8.8.8.8:53") // 指定 Google DNS + return d.DialContext(ctx, "udp", "8.8.8.8:53") }, } - // 查询域名的 IP 地址 ips, err := resolver.LookupIP(context.Background(), "ip6", domain) if err != nil { return "NULL" } - // 遍历返回的 IP 地址,返回第一个 IPv6 地址 for _, ip := range ips { if ip.To4() == nil { return ip.String() @@ -125,38 +158,44 @@ func check_domain_ipv6(domain string) string { return "NULL" } -// 循环获取域名解析地址和当前地址,并判断是否需要更新域名解析地址 +// 循环逻辑 func Loop(info INFO) { - var Domain_ipv6_addr string var Now_ipv6_addr string + var currentInterface string - Domain_ipv6_addr = check_domain_ipv6(*info._Subdomain) - Now_ipv6_addr = pppoe_interface_ipv6(*info._interface) - - if Now_ipv6_addr != Domain_ipv6_addr { - log.Printf("域名解析地址: %s\n", Domain_ipv6_addr) - log.Printf("当前地址: %s\n", Now_ipv6_addr) - log.Printf("域名解析地址与现在地址不相等!!!\n") - - RecordID := FetchSubdomainRecord(*info._Key, *info._Domain, *info._Subdomain) - log.Printf("RecordID:%s\n", RecordID) - if RecordID != "NULL" { - rrid := RecordID - rrhost := strings.Split(*info._Subdomain, ".")[0] // 获取子域名前缀 v6 - rrvalue := Now_ipv6_addr - - r := ProcessDNSUpdateForDomain(*info._Key, *info._Domain, rrid, rrhost, rrvalue) - if r == 1 { - - } - } + // 判断是否自动模式 + if *info._interface == "auto" { + currentInterface, Now_ipv6_addr = AutoGetBestIPv6() } else { - log.Printf("域名解析地址: %s\n", Domain_ipv6_addr) - log.Printf("当前地址: %s\n", Now_ipv6_addr) - log.Printf("域名解析地址与现在地址相等,无需处理!!!\n") + currentInterface = *info._interface + Now_ipv6_addr = getIPv6ByInterface(currentInterface) } + if Now_ipv6_addr == "NULL" { + log.Println("警告: 无法获取本机 IPv6 地址,请检查网络连接。") + return + } + + Domain_ipv6_addr = check_domain_ipv6(*info._Subdomain) + + if Now_ipv6_addr != Domain_ipv6_addr { + log.Printf("检测到变动! 接口[%s] IP: %s | 域名解析 IP: %s\n", currentInterface, Now_ipv6_addr, Domain_ipv6_addr) + + RecordID := FetchSubdomainRecord(*info._Key, *info._Domain, *info._Subdomain) + if RecordID != "NULL" { + rrid := RecordID + rrhost := strings.Split(*info._Subdomain, ".")[0] + if strings.HasSuffix(*info._Subdomain, "."+*info._Domain) { + rrhost = strings.TrimSuffix(*info._Subdomain, "."+*info._Domain) + } + + rrvalue := Now_ipv6_addr + ProcessDNSUpdateForDomain(*info._Key, *info._Domain, rrid, rrhost, rrvalue) + } + } else { + log.Printf("IP 未变动 (接口: %s, IP: %s)。无需更新。\n", currentInterface, Now_ipv6_addr) + } } // Sleep 函数 @@ -164,15 +203,6 @@ func Sleep(m int) { time.Sleep(time.Duration(m) * time.Minute) } -func TimeZone() { - // 设置 Go 的本地时区为 Asia/Shanghai - loc, err := time.LoadLocation("Asia/Shanghai") - if err != nil { - log.Fatalf("加载时区失败: %v", err) - } - time.Local = loc -} - type INFO struct { _interface *string _Domain *string @@ -181,23 +211,21 @@ type INFO struct { } func GetDaemon(_Subdomain *string) *string { - // 使用 strings.Split 分割域名 parts := strings.Split(*_Subdomain, ".") - // 检查分割后的部分数量 if len(parts) < 2 { fmt.Println("无效的域名") return nil } - // 获取主域名 mainDomain := parts[len(parts)-2] + "." + parts[len(parts)-1] - return &mainDomain } func main() { - daemon := flag.Bool("d", false, "守护进程模式") - _interface := flag.String("i", "pppoe-wan", "网卡") + + // 修改默认值为 "auto" + _interface := flag.String("i", "auto", "网卡接口名称 (默认 auto 自动检测)") + _Subdomain := flag.String("s", "v6.aixiao.me", "子域名") _key := flag.String("k", "NULL", "NameSilo API 密钥") flag.Parse() @@ -211,24 +239,31 @@ func main() { if *info._Key == "NULL" { envKey := os.Getenv("NAMESILO") if envKey != "" { - info._Key = &envKey // 将环境变量的值赋值给 info._Key + info._Key = &envKey } else { log.Fatalf("密钥未设置,请通过 -k 参数或 NAMESILO 环境变量提供密钥") } } + fmt.Println("--------------------------------") fmt.Println("主域名:", *info._Domain) fmt.Println("子域名:", *info._Subdomain) fmt.Println("密钥:", *info._Key) + fmt.Println("接口模式:", *info._interface) + fmt.Println("--------------------------------") - // 守护进程模式 + // 守护进程模式占位 if *daemon { - Daemon() + if Daemon != nil { + Daemon() + } } - //TimeZone() + // 首次运行立即检测 + Loop(info) + for { - Loop(info) Sleep(30) + Loop(info) } } diff --git a/subdomain_dns_info_fetcher.go b/subdomain_dns_info_fetcher.go index 57f2660..f26a81e 100644 --- a/subdomain_dns_info_fetcher.go +++ b/subdomain_dns_info_fetcher.go @@ -6,9 +6,10 @@ import ( "io" "log" "net/http" + "strings" ) -// NameSilo API 响应结构 +// NameSiloAPIResponse 严格对应你提供的 JSON 返回 type NameSiloAPIResponse struct { Request struct { Operation string `json:"operation"` @@ -18,33 +19,22 @@ type NameSiloAPIResponse struct { Code int `json:"code"` Detail string `json:"detail"` ResourceRecords []struct { - RecordID string `json:"record_id"` - Type string `json:"type"` - Host string `json:"host"` - Value string `json:"value"` - TTL string `json:"ttl"` - Distance json.RawMessage `json:"distance"` // 兼容数字和字符串 + RecordID string `json:"record_id"` + Type string `json:"type"` + Host string `json:"host"` + Value string `json:"value"` + TTL int `json:"ttl"` // JSON 中显示为数字 + Distance int `json:"distance"` // JSON 中显示为数字 0 } `json:"resource_record"` } `json:"reply"` } -// 解析 distance,兼容数字和字符串 -func parseDistance(raw json.RawMessage) string { - var str string - if err := json.Unmarshal(raw, &str); err == nil { - return str - } - var num int - if err := json.Unmarshal(raw, &num); err == nil { - return fmt.Sprintf("%d", num) - } - return "0" -} - // RetrieveDNSRecords 使用 Namesilo API 获取指定域名的所有 DNS 记录 func RetrieveDNSRecords(apiKey, domain string) (*NameSiloAPIResponse, error) { url := fmt.Sprintf("https://www.namesilo.com/api/dnsListRecords?version=1&type=json&key=%s&domain=%s", apiKey, domain) + // log.Println("Requesting URL:", url) // 调试用,如果需要隐藏 Key 请注释 + resp, err := http.Get(url) if err != nil { return nil, err @@ -58,38 +48,51 @@ func RetrieveDNSRecords(apiKey, domain string) (*NameSiloAPIResponse, error) { var apiResponse NameSiloAPIResponse if err := json.Unmarshal(body, &apiResponse); err != nil { + log.Printf("JSON 解析错误: %v\n原始数据: %s\n", err, string(body)) return nil, err } return &apiResponse, nil } -// FetchSubdomainRecord 获取指定子域名的 DNS 记录信息 +// FetchSubdomainRecord 获取指定子域名的 DNS 记录 ID +// Subdomain 参数通常是完整域名,如 "v6.aixiao.me" func FetchSubdomainRecord(key string, domain string, Subdomain string) string { - // 获取 DNS 记录 + // 获取 DNS 记录列表 response, err := RetrieveDNSRecords(key, domain) if err != nil { - log.Println("获取 DNS 记录失败:", err) + log.Println("获取 DNS 记录网络请求失败:", err) return "NULL" } - // 打印完整 JSON 数据 - _, err = json.MarshalIndent(response, "", " ") - if err != nil { - log.Println("JSON 格式化失败:", err) + // 检查 API 返回码 + if response.Reply.Code != 300 { + log.Printf("NameSilo API 错误: Code %d, Detail: %s\n", response.Reply.Code, response.Reply.Detail) return "NULL" } - // 只获取指定子域名的信息 + // 计算主机头 (Host Prefix) + // 如果 Subdomain 是 "v6.aixiao.me",Domain 是 "aixiao.me" + // 那么我们需要匹配的 Host 应该是 "v6" + targetHost := Subdomain + if strings.HasSuffix(Subdomain, "."+domain) { + targetHost = strings.TrimSuffix(Subdomain, "."+domain) + } + + // 遍历查找 for _, record := range response.Reply.ResourceRecords { - if record.Host == Subdomain { - log.Printf("RecordID: %s, Host: %s, Type: %s, Value: %s, TTL: %s, Distance: %s\n", - record.RecordID, record.Host, record.Type, record.Value, record.TTL, parseDistance(record.Distance)) - + // 1. 匹配主机名 (Host) + // 2. 匹配记录类型 (Type) 必须为 AAAA (IPv6) + if record.Host == targetHost && record.Type == "AAAA" { + log.Printf("匹配成功! RecordID: %s | Host: %s | Type: %s | Current Value: %s\n", + record.RecordID, record.Host, record.Type, record.Value) return record.RecordID } } + log.Printf("未在 NameSilo 找到主机头为 [%s] 的 AAAA 记录。\n", targetHost) + log.Println("请检查 NameSilo 后台是否已手动创建了该域名的 AAAA 记录。") + return "NULL" }