Files
code-utils/network_info.go

277 lines
7.1 KiB
Go
Raw Normal View History

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()
}