Skip to content

Commit

Permalink
fix: 更新通过 wmic 获取描述信息的处理逻辑
Browse files Browse the repository at this point in the history
  • Loading branch information
renxia committed May 11, 2022
1 parent 5223967 commit 1ec85a2
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 47 deletions.
4 changes: 4 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,9 @@ module.exports = {
'unicorn/prefer-module': 'off',
'unicorn/prefer-node-protocol': 'off',
'unicorn/filename-case': 'off',
"prefer-const": ["error", {
"destructuring": "all",
// "ignoreReadBeforeAssign": true,
}]
},
};
9 changes: 4 additions & 5 deletions src/__test__/testData.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,8 @@ export const ipconfigStdout = [
].join('\n');

export const wmicNicStdout = [
`MACAddress Name `,
`${ifacesMock.en0[1].mac} Realtek PCIe FE Family Controller `,
`1C:1B:B5:9B:FF:CC Intel(R) Wireless-AC 9462 `,
`${ifacesMock.vmware[0].mac} Visual Adpter`,
`${ifacesMock.vmware[1].mac} Visual Vmware Adpter 0`,
`MACAddress=${ifacesMock.en0[1].mac} \nName=Realtek PCIe FE Family Controller `,
`MACAddress=1C:1B:B5:9B:FF:CC \nName=Intel(R) Wireless-AC 9462 `,
`MACAddress=${ifacesMock.vmware[0].mac} \nName=Visual Adpter`,
`MACAddress=${ifacesMock.vmware[1].mac} \nName=Visual Vmware Adpter 0`,
].join('\n');
11 changes: 9 additions & 2 deletions src/__test__/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatMac, hasMac, hasMutiMac, isDebug, isMac, isValidMac, isZeroMac, logDebug } from '../utils';
import { formatMac, hasMac, hasMutiMac, isDebug, isMac, isValidMac, isVirtualMac, isZeroMac, logDebug } from '../utils';
import { ifacesMock } from './testData.mock';

describe('utils', () => {
Expand Down Expand Up @@ -45,9 +45,16 @@ describe('utils', () => {
for (const mac of ['', undefined, void 0, 'AB:34:56:78:90:01', '12-34-56-78-90-01']) expect(isZeroMac(mac)).toBeFalsy();
});

it('isVirtualMac', () => {
for (const mac of ['08:00:27:78:90:01', '00-1C-14-78-90-01']) expect(isVirtualMac(mac)).toBeTruthy();
for (const mac of ['', undefined, '08:00:27:78', 'a0:01', 'AB:34:56:78:90:01', '00-00-00-00-00-00']) {
expect(isVirtualMac(mac)).toBeFalsy();
}
});

it('isValidMac', () => {
for (const mac of ['AB:34:56:78:90:01', '12-34-56-78-90-01']) expect(isValidMac(mac)).toBeTruthy();
for (const mac of ['', undefined, void 0, 'a0:01', '00:00:00:00:00:00', '00-00-00-00-00-00']) expect(isValidMac(mac)).toBeFalsy();
for (const mac of ['', undefined, 'a0:01', '00:00:00:00:00:00', '00-00-00-00-00-00']) expect(isValidMac(mac)).toBeFalsy();
});

it('formatMac', () => {
Expand Down
44 changes: 27 additions & 17 deletions src/getIFacesByExec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { exec, execSync, type ExecException, type ExecOptions } from 'child_process';
import { ObjectEncodingOptions } from 'fs';
import process from 'process';
import { formatMac, hasMac, logDebug } from './utils';
import { formatMac, logDebug } from './utils';

export interface IpconfigNIFItem {
desc?: string;
Expand All @@ -19,11 +20,13 @@ export interface IpconfigNIFItem {
isMain?: boolean;
}

function execPromisfy(cmd: string, options: ExecOptions = {}) {
function execPromisfy(cmd: string, options: ObjectEncodingOptions & ExecOptions = {}, trimEmptyLine = false) {
return new Promise<{ error: ExecException; stdout: string; stderr: string }>(resolve => {
exec(cmd, { windowsHide: true, ...options }, (error, stdout, stderr) => {
if (error) console.error('exec error:', `cmd: ${cmd}\n`, error.message || error);
resolve({ error, stdout: stdout.replace(/\n\n/g, '\n'), stderr });
stdout = stdout.replace(/\r+\n/g, '\n').trim();
if (trimEmptyLine) stdout = stdout.replace(/\n{2,}/g, '\n');
resolve({ error, stdout, stderr });
});
});
}
Expand All @@ -36,7 +39,8 @@ export async function getNetworkIFacesInfoByIpconfig() {
// https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/ipconfig
const iconv = await import('iconv-lite');
const cmd = 'ipconfig /all';
// stdout = iconv.decode((await execPromisfy(cmd)).stdout, 'gbk').trim();
// const info = await execPromisfy(cmd, { encoding: 'binary' });
// stdout = iconv.decode(Buffer.from(info.stdout, 'binary'), 'gbk').trim();
stdout = iconv.decode(execSync(cmd, { encoding: 'binary', windowsHide: true }) as never, 'gbk').trim();

const keyMap = {
Expand Down Expand Up @@ -83,8 +87,6 @@ export async function getNetworkIFacesInfoByIpconfig() {
item = {};
};

logDebug(`[exec]cmd: ${cmd}. stdout:\n${stdout}`);

for (let line of lines) {
if (!line) continue;
if (line.startsWith(' ')) {
Expand All @@ -110,33 +112,41 @@ export async function getNetworkIFacesInfoByIpconfig() {
}
setToConfig();

logDebug(`[exec]cmd: ${cmd}. getNetworkIFacesInfo:`, config);
logDebug(`[exec]cmd: ${cmd}. getNetworkIFacesInfo:`, stdout, config);
}

return { config, stdout };
return { stdout, config };
}

// todo
export async function getNetworkIFacesInfoByWmic() {
const config: { [mac: string]: IpconfigNIFItem } = {};
let stdout = '';

if (process.platform === 'win32') {
const cmd = `wmic nic get Caption MACAddress`;
const keyMap = { MACAddress: 'mac', Description: 'desc' };
const cmd = `wmic nic get MACAddress,Description /format:list`;
const info = await execPromisfy(cmd);
const lines = info.stdout.split('\n').filter(d => d.includes('='));

stdout = info.stdout;
if (stdout) {
for (const line of stdout.split('\n')) {
if (hasMac(line)) {
const mac = formatMac(line.slice(0, 18).trim());
const desc = line.slice(18).trim();
config[mac] = { mac, desc };
if (lines[0]) {
let item: Record<string, string> = {};

for (const line of lines) {
let [key, value] = line.split('=').map(d => d.trim());
key = keyMap[key] || key.toLowerCase();

if (item[key]) {
if (item.mac) config[item.mac] = item;
item = {};
}
item[key] = value;
}
if (item.mac) config[item.mac] = item;
}

if (stdout) logDebug(`[getNetworkIFacesInfoByWmic]`, stdout, config);
}

return { config, stdout };
return { stdout, config };
}
45 changes: 23 additions & 22 deletions src/getNetworkInteraces.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
import { networkInterfaces, type NetworkInterfaceInfo } from 'os';
import { getNetworkIFacesInfoByWmic } from './getIFacesByExec';
import { isZeroMac, hasMutiMac, logDebug } from './utils';
import { isZeroMac, hasMutiMac, logDebug, isVirtualMac } from './utils';

/** sort by: !internal > !zeroMac(mac) > (todo: desc for visual) > family=IPv4 */
/** sort by: !internal > !zeroMac(mac) > desc for 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;
});
}

function getNif() {
const nif = networkInterfaces();

for (const key in nif) {
if (key.includes('以太网')) {
nif[key.replace('以太网', 'ethernet')] = nif[key];
delete nif[key];
}
}
return nif;
}

/** get all networkInterfaces and sort by some rules */
export function getAllNetworkIFaces() {
const nif = networkInterfaces();
const nif = getNif();
const list: NetworkInterfaceInfo[] = [];

// en0 - mac, eth3 - linux, Ethernet - windows
// en0 - mac, eth3 - linux, ethernet - windows
const highPriorityIfaces = /^((en|eth)\d+|ethernet)$/i;
const lowPriorityIfaces = /^((lo|vboxnet)\d+)$/i;
const ifaces = Object.keys(nif).sort((a, b) => {
Expand Down Expand Up @@ -44,7 +61,7 @@ export async function getNetworkIFaces(iface?: string, family?: 'IPv4' | 'IPv6')
let list: NetworkInterfaceInfo[] = [];

if (iface) {
const nif = networkInterfaces();
const nif = getNif();

if (nif[iface]) {
list = nif[iface];
Expand All @@ -57,23 +74,7 @@ export async function getNetworkIFaces(iface?: string, family?: 'IPv4' | 'IPv6')
list = getAllNetworkIFaces().filter(item => !item.internal && !isZeroMac(item.mac) && (!family || item.family === family));

if (hasMutiMac(list)) list = list.filter(d => d.address);

if (hasMutiMac(list)) {
// 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
]);
list = list.filter(d => !virtualMacPrefix.has(d.mac.toLowerCase().slice(0, 8)));
}
if (hasMutiMac(list)) list = list.filter(d => !isVirtualMac(d.mac));

// filter by desc for windows
if (hasMutiMac(list)) {
Expand Down
20 changes: 19 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,30 @@ export function isZeroMac(mac: string) {
return /^(0{1,2}[:-]){5}0{1,2}$/.test(mac.trim());
}

// 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
]);

export function isVirtualMac(mac: string) {
return isMac(mac) && virtualMacPrefix.has(formatMac(mac).slice(0, 8));
}

export function isValidMac(mac: string) {
return !isZeroMac(mac) && isMac(mac);
}

export function formatMac(mac: string) {
return mac.trim().toLowerCase().replace(/-/g, ':');
return String(mac).trim().toLowerCase().replace(/-/g, ':');
}

export function hasMutiMac(list: NetworkInterfaceInfo[], filter?: (mac: string) => boolean) {
Expand Down

0 comments on commit 1ec85a2

Please sign in to comment.