net.Interfaces() 返回网卡设备而非IP,需调用 iface.Addrs() 获取地址;Addrs() 返回多种类型地址,须类型断言为 *net.IPNet 并过滤 link-local、回环等无效地址;MAC 可能为空,需判空;Windows 下 Index 不可靠,应优先用 Name。

net.Interfaces() 返回的是网卡设备,不是 IP 地址
很多人调用 net.Interfaces() 后发现返回的 net.Interface 结构体里只有名字、索引、标志位和 MAC,压根没有 IP 字段——这很正常,因为接口信息和地址信息在 Go 的网络包里是分离的。要拿到 IP,必须对每个接口再调用 iface.Addrs()。
常见错误现象:
• 直接打印 iface 对象以为能看见 IP
• 忽略 Addr 是接口类型,没做类型断言就强转 *net.IPNet
• 遍历时没跳过非 IPv4/IPv6 地址(比如 ipx 或未知前缀)
- 先用
net.Interfaces()获取所有网卡,再对每个net.Interface调用Addrs() -
Addrs()返回的[]net.Addr里混着各种地址类型,只处理*net.IPNet类型的 - 用
addr.(*net.IPNet).IP.To4()判断是否为 IPv4;To16()判断 IPv6,避免把 IPv4 映射地址当原生 IPv6 处理
MAC 地址可能为空或被禁用
net.Interface.HardwareAddr 不一定有值:虚拟网卡(如 veth、docker0)、未启用的物理接口、或某些容器网络环境下会返回 nil 或零长度切片。直接取 String() 可能 panic 或输出 <nil>。
使用场景:
• 采集机器指纹时依赖 MAC,但有些云主机(如 AWS EC2)的主网卡 MAC 在重启后会变
• 容器内看到的是 veth peer 的 MAC,不是宿主机物理网卡
立即学习“go语言免费学习笔记(深入)”;
- 检查
iface.HardwareAddr != nil && len(iface.HardwareAddr) > 0再格式化 - 用
fmt.Sprintf("%x", iface.HardwareAddr)替代String(),避免空指针 - 注意:Loopback 接口(如
lo)通常没有 MAC,别把它当成有效网卡参与逻辑
遍历 Addr 时容易漏掉 secondary IP 或 link-local 地址
iface.Addrs() 返回的地址列表包含主 IP、secondary IP(通过 ip addr add 添加的多个 IP)、以及 IPv6 link-local(fe80::/10)。默认逻辑如果只取第一个 *net.IPNet,大概率拿到的是 link-local 地址,而不是你期望的全局可路由 IP。
性能 / 兼容性影响:
• fe80::/10 地址不能用于跨子网通信,拿它做服务绑定会失败
• 某些系统(如 macOS)会给 en0 自动配多个 IPv6 地址,顺序不固定
- 过滤掉
fe80::/10和::1(IPv6 loopback) - IPv4 中跳过
0.0.0.0和127.0.0.0/8(除非你明确需要本地回环) - 如果业务需要“首选 IP”,建议按地址族优先级排序:IPv4 全局地址 > IPv6 全局地址 > IPv4 link-local(
169.254.0.0/16)
Windows 下 net.Interface.Index 不可靠,别用它索引路由表
在 Linux/macOS 上,iface.Index 基本对应 /sys/class/net/ 下的设备序号,也能跟 net.RouteTable() 的 Interface 字段对上。但在 Windows 上,这个值是 Win32 API 返回的 LUID 低 32 位,重启可能变化,且无法直接映射到注册表里的网卡实例 ID。
错误现象:
• 在 Windows 上用 Index 查路由表,匹配不到任何条目
• 误以为 Index == 1 就是第一个网卡,实际可能是虚拟交换机
- 跨平台代码中,避免用
Index做唯一标识,改用Name(但注意 Docker/K8s 环境下Name可能动态生成) - Windows 上真要查路由,得用
golang.org/x/sys/windows调GetIpForwardTable2,再比对Luid - 如果只是采集 IP/MAC,
Index无实际用途,可以忽略
真正麻烦的是地址生命周期管理:网卡热插拔、DHCP 租约更新、IPv6 SLAAC 地址自动增删——这些都不会触发 Go 程序里的任何回调。靠单次遍历拿到的快照,过几秒就可能失效。










