277 lines
7.1 KiB
Go
277 lines
7.1 KiB
Go
|
|
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()
|
|||
|
|
}
|