package main import ( "context" "flag" "fmt" "log" "net" "os" "strings" "time" ) // 判断 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" } // AutoGetBestIPv6 自动寻找最佳的 IPv6 地址和对应的接口名称 // 优先级: 接口名含 "pppoe" > "wan" > 其他 func AutoGetBestIPv6() (string, string) { interfaces, err := net.Interfaces() if err != nil { 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 { continue } for _, addr := range addrs { var ip net.IP switch v := addr.(type) { case *net.IPNet: ip = v.IP case *net.IPAddr: ip = v.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 } } } } // 如果没有找到明确的 WAN 口,返回保底的接口(例如 br-lan) if fallbackIP != "" { log.Printf("自动检测: 未找到 WAN 接口,使用保底接口: %s -> %s\n", fallbackIface, fallbackIP) return fallbackIface, fallbackIP } return "", "NULL" } // getIPv6ByInterface 获取指定接口名称的 IPv6 func getIPv6ByInterface(ifaceName string) string { iface, err := net.InterfaceByName(ifaceName) if err != nil { log.Printf("无法找到接口 %s: %v", ifaceName, err) return "NULL" } 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 { 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") }, } ips, err := resolver.LookupIP(context.Background(), "ip6", domain) if err != nil { return "NULL" } for _, ip := range ips { if ip.To4() == nil { return ip.String() } } return "NULL" } // 循环逻辑 func Loop(info INFO) { var Domain_ipv6_addr string var Now_ipv6_addr string var currentInterface string // 判断是否自动模式 if *info._interface == "auto" { currentInterface, Now_ipv6_addr = AutoGetBestIPv6() } else { 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 函数 func Sleep(m int) { time.Sleep(time.Duration(m) * time.Minute) } type INFO struct { _interface *string _Domain *string _Subdomain *string _Key *string } func GetDaemon(_Subdomain *string) *string { 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, "守护进程模式") // 修改默认值为 "auto" _interface := flag.String("i", "auto", "网卡接口名称 (默认 auto 自动检测)") _Subdomain := flag.String("s", "v6.aixiao.me", "子域名") _key := flag.String("k", "NULL", "NameSilo API 密钥") flag.Parse() var info INFO info._interface = _interface info._Subdomain = _Subdomain info._Key = _key info._Domain = GetDaemon(_Subdomain) if *info._Key == "NULL" { envKey := os.Getenv("NAMESILO") if envKey != "" { 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 { if Daemon != nil { Daemon() } } // 首次运行立即检测 Loop(info) for { Sleep(30) Loop(info) } }