146 lines
2.9 KiB
Go
146 lines
2.9 KiB
Go
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)
|
||
}
|
||
}()
|
||
}
|