共计 3789 个字符,预计需要花费 10 分钟才能阅读完成。
摘要
本文介绍了Node.js 获取物理网卡 mac 地址的方法,其中主要讨论了基于实践经验对虚拟网卡的识别处理方式。
提醒:本文最后更新于2025-07-07 14:39,文中所关联的信息可能已发生改变,请知悉!
在基于 Electron 的应用中,有一个业务需求是获取物理网卡的 Mac 地址以用于客户机唯一性识别。
刚接到需求时你可能会想,这还不简单,调用 Node.js 的 os 模块提供的 networkInterfaces
API 就行了。于是马上开干:
import { networkInterfaces } from 'os'; | |
function isZeroMac(mac) { | |
return /^(0{1,2}[:-]){5}0{1,2}$/.test(mac); | |
} | |
function getMac(family = 'IPv4') { | |
const nif = networkInterfaces(); | |
for (const list of Object.values(nif)) { | |
const item = list.find(d => !d.internal && !isZeroMac(d.mac) && (!d.family ||d.family === family)); | |
if (item) return item.mac; | |
} | |
return ''; | |
} |
两分钟就写完了,测试一下返回值也与 ipconfig/ifconfig
打印的信息一致,满怀信心的提交代码完工。
测试同学当天验证了一下表示没什么问题,然而第二天却找上门了:同一台电脑今昨两天取到的值不一样。经过各种排查分析,最后才发现原来这位测试妹妹因疫情管控居家了,用着 VPN 远程接入办公网络干活呢。
原来开 VPN 的时候使用了虚拟网卡,此时你才发现事情并没有那么简单。实际上,在存在 VPN、虚拟机等场景下,都可能会使用到虚拟网卡。
1. 根据 networkInterfaces 返回值的字段值过滤
networkInterfaces
可以获取到所有网卡的基本信息,可根据 internal
、mac
等字段的值做一次过滤,得到有效的信息:
const isValid = (item) => item.internal === false && !isZeroMac(item.mac);
但是对于 VPN、虚拟机等存在虚拟网卡的场景下,仅根据该信息无法进行有效区分。
2. 根据虚拟网卡 Mac 特征过滤
如果能够得到虚拟网卡的特征,则可基于相关特征点进行识别与过滤。
基于某内部项目长达六年的实践积累以及参考 vscode 中类似的实现,我们得到了一个常见虚拟网卡默认 Mac 地址特征的列表,参考如下:
// see https://standards-oui.ieee.org/oui/oui.txt | |
const virtualMacPrefix = new Set([ | |
'00:05:69', // vmware1 | |
'00:0c:29', // vmware2 | |
'00:50:56', // vmware3 | |
'00:1c:14', // vmware | |
'00:1c:42', // parallels1 | |
'02:00:4c', // Microsoft Loopback Adapter (微软回环网卡) | |
'00:03:ff', // microsoft virtual pc | |
'00:0f:4b', // virtual iron 4 | |
'00:16:3e', // red hat xen , oracle vm , xen source, novell xen | |
'08:00:27', // virtualbox | |
]); |
于是可以据此实现一个是否为虚拟网卡的判断方法 isVirtualMac
:
export function isMac(mac: string) { | |
return /^([\da-f]{1,2}[:-]){5}([\da-f]{1,2})$/i.test(mac); | |
} | |
export function formatMac(mac: string) { | |
return String(mac).trim().toLowerCase().replace(/-/g, ':'); | |
} | |
export function isVirtualMac(mac: string) { | |
return isMac(mac) && virtualMacPrefix.has(formatMac(mac).slice(0, 8)); | |
} |
据此可对 getMac
方法改进如下:
function getMac(family = 'IPv4') { | |
const nif = networkInterfaces(); | |
for (const list of Object.values(nif)) { | |
const item = list.find(d => !d.internal && !isZeroMac(d.mac) && (!d.family ||d.family === family) && !isVirtualMac(d.mac)); | |
if (item) return item.mac; | |
} | |
return ''; | |
} |
3. 根据描述关键字特征过滤
在 Windows 系统下,可以通过执行 ipconfig /all
或 wmic nic get
命令得到所有网卡的详情,其中包含了描述信息。
基于实践经验分析,我们总结了一个常见虚拟网卡描述关键字的特征列表,参考如下:
const virtualDescList = ['virtual', ' vpn ', ' ssl ', 'tap-windows', 'hyper-v', 'km-test', 'microsoft loopback'];
若经过前述规则过滤之后仍然存在多个网卡信息,则可继续获取网卡详情,并基于 virtualDescList
列表以尝试进一步的过滤处理:
// 执行 wmic nic get 命令获取所有网卡详情 | |
function getNetworkIFacesInfoByWmic() { | |
// 略 | |
} | |
if (hasMutiMac(list)) { | |
const info = await getNetworkIFacesInfoByWmic(); | |
list = list.filter(item => { | |
if (!info.config[item.mac]) return true; | |
const desc = String(info.config[item.mac].desc).toLowerCase(); | |
return !virtualDescList.some(d => desc.includes(d)); | |
}); | |
} |
getNetworkIFacesInfoByWmic
方法的具体实现可以参见这里:
https://github.com/lzwme/get-physical-address/blob/main/src/getIFacesByExec.ts#L121
4. 按优先级规则排序
过滤方式会将视为无效的项排除,但是可能会因规则的误差而导致最后得到的列表为空。为了避免这种可能现象的出现,可以将过滤排除改为优先级排序方式,最后取列表第一项视为最优选项。
排序方法实现示例:
/** | |
* sort by: !internal > !zeroMac(mac) > visual > family=IPv4 | |
*/ | |
function ifacesSort(list: NetworkInterfaceInfo[]) { | |
return list.sort((a, b) => { | |
if (a.internal !== b.internal) return a.internal ? 1 : -1; | |
if (isZeroMac(a.mac) !== isZeroMac(b.mac)) return isZeroMac(a.mac) ? 1 : -1; | |
const isVirtualA = isVirtualMac(a.mac); | |
const isVirtualB = isVirtualMac(b.mac); | |
if (isVirtualA !== isVirtualB) return isVirtualA ? 1 : -1; | |
if (a.family !== b.family) return a.family === 'IPv6' ? 1 : -1; | |
}); | |
} |
于是最终的逻辑大致如下:
- 获取全部网卡信息
- 基于
iface
特征排序取得全部列表:en0 - mac, eth3 - linux, ethernet - windows
优先级更高 - 基于
internal
字段、虚拟网卡特征(mac
)、family
字段等进行排序 - 对排序的结果进行基础过滤:
internal=true
、isZeroMac
- 若过滤后列表多于1个,则基于虚拟网卡特征继续过滤
- 若过滤结果仍多余1个,则基于描述特征继续过滤
- 取最终结果的第一项作为最优选择
5. 总结与参考
实际上社区里已经有 address、getmac和macaddress 等较为流行的相关库,但它们都不涉及虚拟网卡的识别。
本文主要介绍了基于实践经验对虚拟网卡的识别处理方式,与 vscode 中的相关实现逻辑较为相似,但又增加了基于描述信息过滤的规则逻辑。
- https://www.npmjs.com/package/address
- https://www.npmjs.com/package/getmac
- https://www.npmjs.com/package/macaddress
- https://github.com/sebhildebrandt/systeminformation
- https://github.com/microsoft/vscode/blob/main/src/vs/base/node/id.ts
- https://github.com/lzwme/get-physical-address
- https://lzw.me/a/nodejs-get-physical-mac-address.html