本篇文章介紹一下利用Node獲取真實物理網(wǎng)卡的 MAC 地址的方法,其中主要討論了基于實踐經(jīng)驗對虛擬網(wǎng)卡的識別處理方式,希望對大家有所幫助!
node.js極速入門課程:進入學習
在基于 Electron 的應用中,有一個業(yè)務需求是獲取物理網(wǎng)卡的 Mac 地址以用于客戶機唯一性識別。
剛接到需求時你可能會想,這還不簡單,調(diào)用 Node.js 的 os 模塊提供的 networkInterfaces
API 就行了?!鞠嚓P教程推薦:nodejs視頻教程】
于是馬上開干:
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
打印的信息一致,滿懷信心的提交代碼完工。
測試同學當天驗證了一下表示沒什么問題,然而第二天卻找上門了:同一臺電腦今昨兩天取到的值不一樣。經(jīng)過各種排查分析,最后才發(fā)現(xiàn)原來這位測試妹妹因疫情管控居家了,用著 VPN 遠程接入辦公網(wǎng)絡干活呢。
原來開 VPN 的時候使用了虛擬網(wǎng)卡,此時你才發(fā)現(xiàn)事情并沒有那么簡單。實際上,在存在 VPN、虛擬機等場景下,都可能會使用到虛擬網(wǎng)卡。
1. 根據(jù) networkInterfaces 返回值的字段值過濾
networkInterfaces
可以獲取到所有網(wǎng)卡的基本信息,可根據(jù) internal
、mac
等字段的值做一次過濾,得到有效的信息:
const isValid = (item) => item.internal === false && !isZeroMac(item.mac);
但是對于 VPN、虛擬機等存在虛擬網(wǎng)卡的場景下,僅根據(jù)該信息無法進行有效區(qū)分。
2. 根據(jù)虛擬網(wǎng)卡 Mac 特征過濾
如果能夠得到虛擬網(wǎng)卡的特征,則可基于相關特征點進行識別與過濾。
基于某內(nèi)部項目長達六年的實踐積累以及參考 vscode 中類似的實現(xiàn),我們得到了一個常見虛擬網(wǎng)卡默認 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 (微軟回環(huán)網(wǎng)卡) '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 ]);
于是可以據(jù)此實現(xiàn)一個是否為虛擬網(wǎng)卡的判斷方法 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)); }
據(jù)此可對 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. 根據(jù)描述關鍵字特征過濾
在 Windows 系統(tǒng)下,可以通過執(zhí)行 ipconfig /all
或 wmic nic get
命令得到所有網(wǎng)卡的詳情,其中包含了描述信息。
基于實踐經(jīng)驗分析,我們總結(jié)了一個常見虛擬網(wǎng)卡描述關鍵字的特征列表,參考如下:
const virtualDescList = ['virtual', ' vpn ', ' ssl ', 'tap-windows', 'hyper-v', 'km-test', 'microsoft loopback'];
若經(jīng)過前述規(guī)則過濾之后仍然存在多個網(wǎng)卡信息,則可繼續(xù)獲取網(wǎng)卡詳情,并基于 virtualDescList
列表以嘗試進一步的過濾處理:
// 執(zhí)行 wmic nic get 命令獲取所有網(wǎng)卡詳情 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
方法的具體實現(xiàn)可以參見這里:
https://github.com/lzwme/get-physical-address/blob/main/src/getIFacesByExec.ts#L121
4. 按優(yōu)先級規(guī)則排序
過濾方式會將視為無效的項排除,但是可能會因規(guī)則的誤差而導致最后得到的列表為空。為了避免這種可能現(xiàn)象的出現(xiàn),可以將過濾排除改為優(yōu)先級排序方式,最后取列表第一項視為最優(yōu)選項。
排序方法實現(xiàn)示例:
/** * 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; }); }
于是最終的邏輯大致如下:
- 獲取全部網(wǎng)卡信息
- 基于
iface
特征排序取得全部列表:en0 - mac, eth3 - linux, ethernet - windows
優(yōu)先級更高 - 基于
internal
字段、虛擬網(wǎng)卡特征(mac
)、family
字段等進行排序 - 對排序的結(jié)果進行基礎過濾:
internal=true
、isZeroMac
- 若過濾后列表多于1個,則基于虛擬網(wǎng)卡特征繼續(xù)過濾
- 若過濾結(jié)果仍多余1個,則基于描述特征繼續(xù)過濾
- 取最終結(jié)果的第一項作為最優(yōu)選擇
5. 總結(jié)與參考
實際上社區(qū)里已經(jīng)有 address、getmac和macaddress 等較為流行的相關庫,但它們都不涉及虛擬網(wǎng)卡的識別。 本文主要介紹了基于實踐經(jīng)驗對虛擬網(wǎng)卡的識別處理方式,與 vscode 中的相關實現(xiàn)邏輯較為相似,但又增加了基于描述信息過濾的規(guī)則邏輯。
- www.npmjs.com/package/add…
- www.npmjs.com/package/get…
- www.npmjs.com/package/mac…
- github.com/sebhildebra…
- github.com/microsoft/v…
- github.com/lzwme/get-p…
- lzw.me/a/nodejs-ge…