Skip to content

Commit

Permalink
feat: 新增 getArpTable 方法
Browse files Browse the repository at this point in the history
  • Loading branch information
renxia committed Dec 8, 2023
1 parent 5e3d36f commit 02fac9b
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 17 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
[![GitHub forks][forks-badge]][forks-url]
[![GitHub stars][stars-badge]][stars-url]


[简体中文](./.github/README_zh-CN.md)

Try get the physical address(hardware MAC address) of the hosts network interfaces. Filter the virtual machine network card, VPN virtual network card, etc., and return the real MAC address information of the physical network card.
Expand Down Expand Up @@ -41,7 +40,7 @@ Example:
import { getNetworkIFaceOne, getMac, isVirtualMac } from '@lzwme/get-physical-address';

getNetworkIFaceOne().then(item => {
console.log(`isVirtualMac: ${isVirtualMac(item.mac, item.desc)}. the MAC address is ${item.mac}, the IP address is ${item.address}`)
console.log(`isVirtualMac: ${isVirtualMac(item.mac, item.desc)}. the MAC address is ${item.mac}, the IP address is ${item.address}`);
});

getMac().then(mac => console.log(`the MAC address is ${mac}`));
Expand All @@ -53,7 +52,6 @@ Example for some other API:
```ts
import { isMac, hasMac, isValidMac, isVirtualMac, formatMac, getAllPhysicsMac } from '@lzwme/get-physical-address';


isMac('aa-bb-cc-dd-ee-ff'); // true
hasMac('The MAC address is aa-bb-cc-dd-ee-ff'); // true
isMac('00:00:00:00:00:00'); // true
Expand All @@ -78,6 +76,12 @@ getAllPhysicsMac('IPv4').then(list => console.log(list));
- `getNetworkIFaces(iface?: string, family?: 'IPv4' | 'IPv6'): Promise<os.NetworkInterfaceInfo[]>`
- `getNetworkIFaceOne(iface?: string): Promise<os.NetworkInterfaceInfoIPv4 | os.NetworkInterfaceInfoIPv6>`

### `arpTable`

- `getArpTable(arpSstdout?: string)`
- `getArpMacByIp(ip: string)`
- `getArpIpByMac(mac: string)`

### `utils`

- `isMac(mac: string): boolean`
Expand All @@ -101,7 +105,6 @@ yarn dev

该插件由[志文工作室](https://lzw.me)开发和维护。


[stars-badge]: https://img.shields.io/github/stars/lzwme/get-physical-address.svg
[stars-url]: https://github.com/lzwme/get-physical-address/stargazers
[forks-badge]: https://img.shields.io/github/forks/lzwme/get-physical-address.svg
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
"physical",
"hardware",
"network interfaces",
"network card"
"network card",
"arp",
"arp table",
"arp lookup"
],
"bin": {
"gmac": "bin/cli.js"
Expand Down
58 changes: 58 additions & 0 deletions src/__test__/arpTable.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* @Author: renxia
* @Date: 2023-12-08 10:11:08
* @LastEditors: renxia
* @LastEditTime: 2023-12-08 11:40:29
* @Description:
*/
import { getArpTable } from '../arpTable';
// import { getAllMac, getAllPhysicsMac, getMac } from '../getMac';
// import { isValidMac } from '../utils';
import { ifacesMock, arpANStdout } from './testData.mock';

let platform: keyof typeof arpANStdout = 'win32';

jest.mock('os', () => ({
networkInterfaces: () => ifacesMock,
}));
jest.mock('child_process', () => ({
execSync() {
return arpANStdout[platform];
},
}));

jest.mock('process', () => {
return {
get platform() {
return platform;
},
};
});

describe('getArpTable.ts', () => {
beforeAll(() => {
console.debug = () => void 0;
console.error = () => void 0;
process.env.DEBUG = '*';
});

it('getArpTable', async () => {
platform = 'win32';
let info = await getArpTable();
expect(Array.isArray(info.table)).toBeTruthy();

platform = 'mac';
info = await getArpTable();
expect(Array.isArray(info.table)).toBeTruthy();
expect(info.table.every(d => d.type === 'unknown')).toBeTruthy();

platform = 'linux';
info = await getArpTable();
expect(Array.isArray(info.table)).toBeTruthy();
expect(info.table.some(d => d.mac === '<incomplete>')).toBeTruthy();
expect(info.table.every(d => d.type === 'unknown')).toBeTruthy();

info = await getArpTable(arpANStdout.mac);
expect(info.table.some(d => d.mac === '<incomplete>')).toBeFalsy();
});
});
62 changes: 61 additions & 1 deletion src/__test__/testData.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const ifacesMock = {
family: 'IPv6',
mac: '00:00:00:00:00:00',
internal: true,
cidr: '::1/128',
cidr: '::1/12',
scopeid: 0,
},
{
Expand Down Expand Up @@ -172,3 +172,63 @@ export const wmicNicStdout = [
`MACAddress=${ifacesMock.vmware[0].mac} \nName=Visual Adpter`,
`MACAddress=${ifacesMock.vmware[1].mac} \nName=Visual Vmware Adpter 0`,
].join('\n');

const arpAWinStdout = `接口: 10.10.1.108 --- 0xc
Internet 地址 物理地址 类型
10.10.1.1 dc-ef-80-37-aa-ff 动态
10.10.1.2 dc-ef-80-37-cc-ff 动态
10.10.1.3 00-00-ff-00-ff-ff 动态
10.10.1.43 00-00-ff-00-ff-ff 动态
10.10.1.112 00-00-ff-00-ff-ff 动态
10.10.1.255 ff-ff-ff-ff-ff-ff 静态
224.0.0.2 01-00-ff-00-ff-f2 静态
224.0.0.22 01-00-ff-00-ff-f6 静态
224.0.0.251 01-00-ff-00-ff-fb 静态
224.0.0.252 01-00-ff-00-ff-fc 静态
239.255.255.250 01-00-ff-7f-ff-fa 静态
255.255.255.255 ff-ff-ff-ff-ff-ff 静态
接口: 172.29.64.1 --- 0x15
Internet 地址 物理地址 类型
172.29.79.255 ff-ff-ff-ff-ff-ff 静态
224.0.0.2 01-00-ff-00-ff-f2 静态
224.0.0.22 01-00-ff-00-ff-f6 静态
224.0.0.251 01-00-ff-00-ff-fb 静态
224.0.0.252 01-00-ff-00-ff-fc 静态
239.255.255.250 01-00-ff-7f-ff-ff 静态
255.255.255.255 ff-ff-ff-ff-ff-ff 静态`;

const arpANMacStdout = `? (10.10.2.2) at dc:ef:80:37:ff:ff on en1 ifscope [ethernet]
? (10.10.2.3) at 0:0:5e:0:1:ff on en1 ifscope [ethernet]
? (10.10.2.51) at a0:80:69:96:ff:ff on en1 ifscope [ethernet]
? (10.10.2.54) at a4:cf:99:96:ff:ff on en1 ifscope [ethernet]
? (10.10.2.96) at 3c:22:fb:52:ff:ff on en1 ifscope [ethernet]
? (10.10.2.121) at 62:0:34:b8:ff:ff on en1 ifscope [ethernet]
? (10.10.2.135) at 6c:b1:33:9c:ff:ff on en1 ifscope [ethernet]
? (10.10.2.182) at 3c:6:30:0:ff:ff on en1 ifscope [ethernet]
? (10.10.2.255) at ff:ff:ff:ff:ff:ff on en1 ifscope [ethernet]
? (224.0.0.251) at 1:0:5e:0:0:ff on en1 ifscope permanent [ethernet]
? (239.255.255.250) at 1:0:5e:7f:ff:ff on en1 ifscope permanent [ethernet]`;

const arpANLinuxStdout = `? (10.12.11.2) at 70:79:b3:48:ff:ff [ether] on eno1
? (172.17.125.49) at <incomplete> on docker0
? (10.12.11.40) at 18:66:da:e8:ff:ff [ether] on eno1
? (10.12.11.54) at 1c:98:ec:27:ff:ff [ether] on eno1
? (172.17.0.2) at 02:42:ac:11:ff:ff [ether] on docker0
? (172.17.12.1) at <incomplete> on docker0
? (10.12.11.46) at 14:18:77:3c:ff:ff [ether] on eno1
? (10.12.11.3) at 00:00:0c:9f:ff:ff [ether] on eno1
? (10.12.11.41) at 18:66:da:e8:ff:ff [ether] on eno1
? (10.12.11.55) at 1c:98:ec:27:ff:ff [ether] on eno1
? (10.12.11.21) at 40:f2:e9:9d:ff:ff [ether] on eno1
? (172.17.0.3) at 02:42:ac:11:ff:ff [ether] on docker0
? (10.12.11.1) at 70:79:b3:48:ff:ff [ether] on eno1
? (10.12.11.47) at 14:18:77:37:ff:ff [ether] on eno1
? (10.12.11.59) at 94:18:82:6d:ff:ff [ether] on eno1
? (10.12.11.239) at 00:0c:29:44:ff:ff [ether] on eno1`;

export const arpANStdout = {
win32: arpAWinStdout,
mac: arpANMacStdout,
linux: arpANLinuxStdout,
};
89 changes: 89 additions & 0 deletions src/arpTable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* @Author: renxia
* @Date: 2023-12-08 09:31:39
* @LastEditors: renxia
* @LastEditTime: 2023-12-08 11:50:52
* @Description:
*/
import { execSync } from 'child_process';
import { isIP } from 'net';
import process from 'process';
import { formatMac } from './utils';
import { getNetworkIFaceOne } from './getNetworkInteraces';

export type ArpTableItem = {
/** interface ip address */
iip: string;
ip: string;
mac: string;
type: 'static' | 'dynamic' | 'unknown';
};

export async function getArpTable(stdout = '') {
const isWindows = process.platform === 'win32';
if (!stdout) {
if (isWindows) {
const { default: iconv } = await import('iconv-lite');
stdout = iconv.decode(execSync('arp -a', { windowsHide: true }), 'gbk').trim();
} else {
stdout = execSync('arp -an', { windowsHide: true }).toString('utf8').trim();
}
}

const table: ArpTableItem[] = [];
let iip = '';
let iface = '';

for (const line of stdout.split('\n').map(d => d.trim())) {
if (!line) continue;
let ip = '';
let mac = '';
let type = 'unknown';

if (isWindows) {
[ip, mac, type = 'unknown'] = line.split(/\s+/);

if (isIP(mac)) {
iip = mac;
continue;
}

if (type.includes('动态')) type = 'dynamic';
else if (type.includes('静态')) type = 'static';
} else {
const match = line.match(/\(([\d.]+)\) at (\S+) .*on ([\da-z]+)/);
if (!match) continue;

const preIface = iface;
[, ip, mac, iface] = match;

if (preIface !== iface) {
const item = await getNetworkIFaceOne(iface);
if (item) iip = item.address;
}
}

if (isIP(ip)) {
table.push({ iip, ip, mac: formatMac(mac), type: (type || 'unknown') as never });
}
}

return { stdout, table };
}

export async function getArpMacByIp(ip: string) {
if (!ip) return '';
const { table } = await getArpTable();
const item = table.find(d => d.ip === ip);
return item ? item.mac : '';
}

export async function getArpIpByMac(mac: string) {
mac = formatMac(mac);
if (!mac) return '';
const { table } = await getArpTable();
const item = table.find(d => d.mac.includes(mac));
return item ? item.ip : '';
}

// getArpTable().then(d => console.log(d.table));
9 changes: 8 additions & 1 deletion src/getIFacesByIpconfig.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/*
* @Author: renxia
* @Date: 2023-03-23 23:02:16
* @LastEditors: renxia
* @LastEditTime: 2023-12-08 09:43:26
* @Description:
*/
import { execSync } from 'child_process';
import process from 'process';
import { formatMac, isVirtualMac, logDebug } from './utils';
Expand All @@ -12,7 +19,7 @@ export async function getNetworkIFacesInfoByIpconfig() {

if (process.platform === 'win32') {
// https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/ipconfig
const iconv = await import('iconv-lite');
const { default: iconv } = await import('iconv-lite');
const cmd = 'ipconfig /all';
// const info = await execPromisfy(cmd, { encoding: 'binary' });
// stdout = iconv.decode(Buffer.from(info.stdout, 'binary'), 'gbk').trim();
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './utils';
export * from './getNetworkInteraces';
export * from './getMac';
export * from './arpTable';
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"sourceMap": false,
"noImplicitThis": true,
"noImplicitAny": false,
"module": "commonjs",
"target": "es2018",
"module": "ESNext",
"target": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"experimentalDecorators": true,
Expand Down
10 changes: 2 additions & 8 deletions tsconfig.module.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@
"declaration": true,
"declarationDir": "types",
"emitDeclarationOnly": false,
"outDir": "esm",
"target": "esnext",
"module": "esnext"
"outDir": "esm"
},
"include": ["src/**/*.ts"],
"exclude": [
"node_modules/**",
"src/**/*.mock.ts",
"src/**/*.spec.ts"
]
"exclude": ["node_modules/**", "src/**/*.mock.ts", "src/**/*.spec.ts"]
}

0 comments on commit 02fac9b

Please sign in to comment.