package main // network_info.go:采集本机网卡信息(Windows 跑 ipconfig /all 并结构化解析,其它平台用 Go net)。 // 自动识别主局域网 IPv4,供启动台一键复制。 import ( "net" "os" "os/exec" "runtime" "strings" ) // NetAdapter 单块网卡的摘要信息。 type NetAdapter struct { Name string `json:"name"` Description string `json:"description,omitempty"` MAC string `json:"mac,omitempty"` IPv4 []string `json:"ipv4,omitempty"` IPv6 []string `json:"ipv6,omitempty"` Gateway []string `json:"gateway,omitempty"` DNS []string `json:"dns,omitempty"` DHCP bool `json:"dhcp"` Primary bool `json:"primary"` Virtual bool `json:"virtual"` } // NetworkInfo 本机网络总览。 type NetworkInfo struct { Hostname string `json:"hostname"` PrimaryIPv4 string `json:"primaryIPv4"` Adapters []NetAdapter `json:"adapters"` Raw string `json:"raw"` } // GetNetworkInfo 一键采集本机网卡信息。 func (a *App) GetNetworkInfo() (NetworkInfo, error) { out := NetworkInfo{Adapters: []NetAdapter{}} out.Hostname, _ = os.Hostname() if runtime.GOOS == "windows" { raw, err := exec.Command("powershell", "-NoProfile", "-Command", "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ipconfig /all").CombinedOutput() if err != nil || len(raw) == 0 { raw, err = exec.Command("ipconfig", "/all").CombinedOutput() } out.Raw = string(raw) if err == nil && len(raw) > 0 { out.Adapters = parseIPConfig(out.Raw) } } if len(out.Adapters) == 0 { out.Adapters = listAdaptersGo() if out.Raw == "" { out.Raw = formatAdaptersRaw(out.Hostname, out.Adapters) } } markPrimary(&out) return out, nil } func isVirtualName(s string) bool { low := strings.ToLower(s) for _, k := range []string{ "virtual", "vmware", "vbox", "virtualbox", "hyper-v", "vethernet", "wsl", "docker", "vpn", "tap-windows", "zerotier", "tailscale", "loopback", "蓝牙", "bluetooth", "isatap", "teredo", "microsoft wi-fi direct", } { if strings.Contains(low, k) { return true } } return false } func isPrivateIPv4(ip string) bool { p := net.ParseIP(strings.Split(ip, "/")[0]) if p == nil { return false } v4 := p.To4() if v4 == nil { return false } return v4[0] == 10 || (v4[0] == 172 && v4[1] >= 16 && v4[1] <= 31) || (v4[0] == 192 && v4[1] == 168) } func markPrimary(info *NetworkInfo) { best := -1 bestScore := -1 for i := range info.Adapters { info.Adapters[i].Virtual = isVirtualName(info.Adapters[i].Name + " " + info.Adapters[i].Description) score := 0 if info.Adapters[i].Virtual { score -= 50 } for _, ip := range info.Adapters[i].IPv4 { if strings.HasPrefix(ip, "127.") { continue } score += 10 if isPrivateIPv4(ip) { score += 30 } if len(info.Adapters[i].Gateway) > 0 { score += 20 } break } if score > bestScore { bestScore = score best = i } } if best >= 0 && bestScore > 0 { info.Adapters[best].Primary = true for _, ip := range info.Adapters[best].IPv4 { if !strings.HasPrefix(ip, "127.") { info.PrimaryIPv4 = strings.Split(ip, "/")[0] break } } } } // parseIPConfig 解析 Windows `ipconfig /all` 文本(兼容中英文关键字)。 func parseIPConfig(raw string) []NetAdapter { lines := strings.Split(strings.ReplaceAll(raw, "\r\n", "\n"), "\n") var list []NetAdapter var cur *NetAdapter flush := func() { if cur == nil { return } if cur.Name != "" || len(cur.IPv4) > 0 || len(cur.IPv6) > 0 { list = append(list, *cur) } cur = nil } for _, line := range lines { trim := strings.TrimSpace(line) if trim == "" { continue } // 适配器标题行:不以空格开头,且以冒号结束或含“适配器/adapter” if len(line) > 0 && line[0] != ' ' && line[0] != '\t' { flush() name := strings.TrimRight(trim, ":") cur = &NetAdapter{Name: name} continue } if cur == nil { continue } key, val, ok := splitKV(trim) if !ok { continue } lk := strings.ToLower(key) switch { case strings.Contains(lk, "description") || strings.Contains(key, "描述"): cur.Description = val case strings.Contains(lk, "physical address") || strings.Contains(key, "物理地址"): cur.MAC = val case strings.Contains(lk, "dhcp enabled") || strings.Contains(key, "dhcp 已启用") || strings.Contains(key, "DHCP 已启用"): cur.DHCP = strings.EqualFold(val, "yes") || val == "是" case strings.Contains(lk, "ipv4") || strings.Contains(key, "IPv4"): cur.IPv4 = append(cur.IPv4, cleanIP(val)) case strings.Contains(lk, "ipv6") || strings.Contains(key, "IPv6"): if !strings.HasPrefix(strings.ToLower(val), "fe80") { cur.IPv6 = append(cur.IPv6, cleanIP(val)) } case strings.Contains(lk, "default gateway") || strings.Contains(key, "默认网关"): if v := cleanIP(val); v != "" { cur.Gateway = append(cur.Gateway, v) } case strings.Contains(lk, "dns servers") || strings.Contains(key, "dns 服务器") || strings.Contains(key, "DNS 服务器"): if v := cleanIP(val); v != "" { cur.DNS = append(cur.DNS, v) } } } flush() return list } func splitKV(line string) (key, val string, ok bool) { // ipconfig 用 ". . . . :" 或直接 ":" 分隔 idx := strings.LastIndex(line, ":") if idx <= 0 { return "", "", false } key = strings.TrimSpace(strings.TrimRight(line[:idx], ". \t")) val = strings.TrimSpace(line[idx+1:]) if key == "" { return "", "", false } return key, val, true } func cleanIP(v string) string { v = strings.TrimSpace(v) // 去掉 "(首选)" / "(Preferred)" 等后缀 if i := strings.IndexAny(v, " ("); i > 0 { v = strings.TrimSpace(v[:i]) } return v } func listAdaptersGo() []NetAdapter { ifaces, err := net.Interfaces() if err != nil { return nil } var list []NetAdapter for _, iface := range ifaces { if iface.Flags&net.FlagLoopback != 0 { continue } addrs, _ := iface.Addrs() a := NetAdapter{Name: iface.Name, MAC: iface.HardwareAddr.String()} for _, addr := range addrs { s := addr.String() ip, _, _ := net.ParseCIDR(s) if ip == nil { ip = net.ParseIP(s) } if ip == nil { continue } if v4 := ip.To4(); v4 != nil { a.IPv4 = append(a.IPv4, v4.String()) } else if !ip.IsLinkLocalUnicast() { a.IPv6 = append(a.IPv6, ip.String()) } } if len(a.IPv4) == 0 && len(a.IPv6) == 0 { continue } list = append(list, a) } return list } func formatAdaptersRaw(host string, adapters []NetAdapter) string { var b strings.Builder b.WriteString("Hostname: " + host + "\n\n") for _, a := range adapters { b.WriteString(a.Name) if a.Description != "" { b.WriteString(" (" + a.Description + ")") } b.WriteString(":\n") if a.MAC != "" { b.WriteString(" MAC: " + a.MAC + "\n") } for _, ip := range a.IPv4 { b.WriteString(" IPv4: " + ip + "\n") } for _, ip := range a.IPv6 { b.WriteString(" IPv6: " + ip + "\n") } for _, g := range a.Gateway { b.WriteString(" Gateway: " + g + "\n") } for _, d := range a.DNS { b.WriteString(" DNS: " + d + "\n") } b.WriteString("\n") } return b.String() }