package utils import ( "context" "encoding/json" "fmt" "net" "os/exec" "runtime" "strings" "time" "github.com/civet148/log" ) /* ---------- 获取本机同网段 IP ---------- */ func getGatewayIP() (string, error) { var cmd *exec.Cmd switch runtime.GOOS { case "linux": cmd = exec.Command("sh", "-c", "ip route | grep default | awk '{print $3}'") case "darwin": cmd = exec.Command("sh", "-c", "netstat -rn | grep default | awk '{print $2}'") default: return "", fmt.Errorf("unsupported OS") } out, err := cmd.Output() if err != nil { return "", err } return strings.TrimSpace(string(out)), nil } func ipInSameSubnet(ip1 net.IP, ip2 net.IP, mask net.IPMask) bool { if mask == nil { return false } return ip1.Mask(mask).Equal(ip2.Mask(mask)) } func GetLocalIPSameAsGW() string { gwStr, err := getGatewayIP() if err != nil { return "127.0.0.1" } gw := net.ParseIP(gwStr) ifaces, _ := net.Interfaces() for _, iface := range ifaces { if iface.Flags&net.FlagUp == 0 { continue } addrs, _ := iface.Addrs() for _, a := range addrs { var ip net.IP var mask net.IPMask switch v := a.(type) { case *net.IPNet: ip = v.IP mask = v.Mask case *net.IPAddr: ip = v.IP } if ip == nil || ip.IsLoopback() || ip.To4() == nil { continue } if ipInSameSubnet(ip, gw, mask) { return ip.String() } } } return "127.0.0.1" } func IsValidIP(ip string) bool { if ip == "" || ip == "127.0.0.1" { return false } p := net.ParseIP(ip) return p != nil && !p.IsLoopback() && !p.IsUnspecified() && !p.IsLinkLocalMulticast() && !p.IsLinkLocalUnicast() } /* ---------- UDP 广播 ---------- */ type BroadcastMessage struct { Type string `json:"type"` IP string `json:"ip"` Port int `json:"port"` Name string `json:"name"` } // StartBroadcast 启动 UDP 广播;ctx 取消时自动退出 func StartBroadcast(ctx context.Context, port, interval int, name string) { go func() { var prevIP string var conn *net.UDPConn for { select { case <-ctx.Done(): if conn != nil { conn.Close() } return default: } curIP := GetLocalIPSameAsGW() if !IsValidIP(curIP) { time.Sleep(time.Duration(interval) * time.Second) continue } if curIP != prevIP || conn == nil { if conn != nil { conn.Close() } addr := net.UDPAddr{IP: net.IPv4bcast, Port: port} c, err := net.DialUDP("udp4", nil, &addr) if err != nil { log.Warnf("UDP 连接失败: %v", err) time.Sleep(time.Duration(interval) * time.Second) continue } conn = c prevIP = curIP log.Infof("绑定新 IP 广播: %s", curIP) } msg := BroadcastMessage{ Type: "ai_server_announce", IP: curIP, Port: port, Name: name, } data, _ := json.Marshal(msg) if _, err := conn.Write(data); err != nil { log.Warnf("广播失败: %v", err) } time.Sleep(time.Duration(interval) * time.Second) } }() }