1 | import os from 'node:os';
|
2 | import fs from 'node:fs/promises';
|
3 | import childProcess from 'node:child_process';
|
4 |
|
5 | const DEFAULT_RESOLV_FILE = '/etc/resolv.conf';
|
6 |
|
7 |
|
8 |
|
9 | const MAC_OSX_START_LINE = /^(\w+)\:\s+flags=/;
|
10 | const MAC_LINUX_START_LINE = /^(\w+)\s{2,}link encap:\w+/i;
|
11 |
|
12 |
|
13 |
|
14 | export const MAC_RE = /(?:ether|HWaddr)\s+((?:[a-z0-9]{2}\:){5}[a-z0-9]{2})/i;
|
15 |
|
16 |
|
17 |
|
18 | export const MAC_IP_RE = /inet\s(?:addr\:)?(\d+\.\d+\.\d+\.\d+)/;
|
19 |
|
20 | export interface Address {
|
21 | ip?: string;
|
22 | ipv6?: string;
|
23 | mac?: string;
|
24 | }
|
25 |
|
26 | export type AddressCallback = (err: Error | null, addr: Address) => void;
|
27 | export type MacCallback = (err?: Error | null, addr?: string | null) => void;
|
28 | export type DnsCallback = (err?: Error | null, servers?: string[]) => void;
|
29 |
|
30 | function getDefaultInterfaceName() {
|
31 | let val: string | undefined = 'eth';
|
32 | const platform = os.platform();
|
33 | if (platform === 'darwin') {
|
34 | val = 'en';
|
35 | } else if (platform === 'win32') {
|
36 | val = undefined;
|
37 | }
|
38 | return val;
|
39 | }
|
40 |
|
41 | function getIfconfigCMD() {
|
42 | if (os.platform() === 'win32') {
|
43 | return 'ipconfig/all';
|
44 | }
|
45 | return '/sbin/ifconfig';
|
46 | }
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | function matchName(actualFamily: string | number, expectedFamily: string | number) {
|
52 | if (expectedFamily === 'IPv4') {
|
53 | return actualFamily === 'IPv4' || actualFamily === 4;
|
54 | }
|
55 | if (expectedFamily === 'IPv6') {
|
56 | return actualFamily === 'IPv6' || actualFamily === 6;
|
57 | }
|
58 | return actualFamily === expectedFamily;
|
59 | }
|
60 |
|
61 | export function getInterfaceAddress(family?: string, name?: string) {
|
62 | const interfaces = os.networkInterfaces();
|
63 | const noName = !name;
|
64 | name = name || getDefaultInterfaceName();
|
65 | family = family || 'IPv4';
|
66 | if (name) {
|
67 | for (let i = -1; i < 8; i++) {
|
68 | const interfaceName = name + (i >= 0 ? i : '');
|
69 | const items = interfaces[interfaceName];
|
70 | if (items) {
|
71 | for (const item of items) {
|
72 | if (matchName(item.family, family)) {
|
73 | return item;
|
74 | }
|
75 | }
|
76 | }
|
77 | }
|
78 | }
|
79 |
|
80 | if (noName) {
|
81 |
|
82 | for (const k in interfaces) {
|
83 | const items = interfaces[k];
|
84 | if (items) {
|
85 | for (const item of items) {
|
86 |
|
87 | if (matchName(item.family, family) && !item.address.startsWith('127.')) {
|
88 | return item;
|
89 | }
|
90 | }
|
91 | }
|
92 | }
|
93 | }
|
94 | return;
|
95 | }
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 | export function ip(interfaceName?: string) {
|
103 | const item = getInterfaceAddress('IPv4', interfaceName);
|
104 | return item?.address;
|
105 | }
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 | export function ipv6(interfaceName?: string) {
|
113 | const item = getInterfaceAddress('IPv6', interfaceName);
|
114 | return item?.address;
|
115 | }
|
116 |
|
117 | function getMAC(content: string, interfaceName: string, matchIP: string) {
|
118 | const lines = content.split('\n');
|
119 | for (let i = 0; i < lines.length; i++) {
|
120 | let line = lines[i].trimEnd();
|
121 | const m = MAC_OSX_START_LINE.exec(line) || MAC_LINUX_START_LINE.exec(line);
|
122 | if (!m) {
|
123 | continue;
|
124 | }
|
125 |
|
126 |
|
127 | const name = m[1];
|
128 | if (name.indexOf(interfaceName) !== 0) {
|
129 | continue;
|
130 | }
|
131 |
|
132 | let ip: string | null = null;
|
133 | let mac: string | null = null;
|
134 | let match = MAC_RE.exec(line);
|
135 | if (match) {
|
136 | mac = match[1];
|
137 | }
|
138 |
|
139 | i++;
|
140 |
|
141 | while (true) {
|
142 | line = lines[i];
|
143 | if (!line || MAC_OSX_START_LINE.exec(line) || MAC_LINUX_START_LINE.exec(line)) {
|
144 | i--;
|
145 | break;
|
146 | }
|
147 | if (!mac) {
|
148 | match = MAC_RE.exec(line);
|
149 | if (match) {
|
150 | mac = match[1];
|
151 | }
|
152 | }
|
153 |
|
154 | if (!ip) {
|
155 | match = MAC_IP_RE.exec(line);
|
156 | if (match) {
|
157 | ip = match[1];
|
158 | }
|
159 | }
|
160 |
|
161 | i++;
|
162 | }
|
163 |
|
164 | if (ip === matchIP) {
|
165 | return mac;
|
166 | }
|
167 | }
|
168 | return null;
|
169 | }
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 | export function mac(callback: MacCallback): void;
|
177 | export function mac(interfaceName: string, callback: MacCallback): void;
|
178 | export function mac(interfaceNameOrCallback: string | MacCallback, callback?: MacCallback) {
|
179 | let interfaceName: string | undefined;
|
180 | if (typeof interfaceNameOrCallback === 'function') {
|
181 | callback = interfaceNameOrCallback;
|
182 | } else {
|
183 | interfaceName = interfaceNameOrCallback;
|
184 | }
|
185 | interfaceName = interfaceName || getDefaultInterfaceName();
|
186 | const item = getInterfaceAddress('IPv4', interfaceName);
|
187 | if (!item) {
|
188 | return callback!();
|
189 | }
|
190 |
|
191 |
|
192 |
|
193 | if (!process.env.CI && (item.mac === 'ff:00:00:00:00:00' || item.mac === '00:00:00:00:00:00')) {
|
194 |
|
195 | item.mac = '';
|
196 | }
|
197 |
|
198 | if (item.mac) {
|
199 | return callback!(null, item.mac);
|
200 | }
|
201 |
|
202 | childProcess.exec(getIfconfigCMD(), { timeout: 5000 }, (err, stdout) => {
|
203 | if (err || !stdout) {
|
204 | return callback!(err);
|
205 | }
|
206 |
|
207 | if (!interfaceName) {
|
208 | return callback!();
|
209 | }
|
210 | const mac = getMAC(stdout || '', interfaceName, item.address);
|
211 | callback!(null, mac);
|
212 | });
|
213 | }
|
214 |
|
215 |
|
216 | const DNS_SERVER_RE = /^nameserver\s+(\d+\.\d+\.\d+\.\d+)$/i;
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | export function dns(callback: DnsCallback): void;
|
224 | export function dns(filepath: string, callback: DnsCallback): void;
|
225 | export function dns(filepathOrCallback: string | DnsCallback, callback?: DnsCallback) {
|
226 | let filepath: string | undefined;
|
227 | if (typeof filepathOrCallback === 'function') {
|
228 | callback = filepathOrCallback;
|
229 | } else {
|
230 | filepath = filepathOrCallback;
|
231 | }
|
232 | filepath = filepath || DEFAULT_RESOLV_FILE;
|
233 | fs.readFile(filepath, 'utf8')
|
234 | .then(content => {
|
235 | const servers: string[] = [];
|
236 | content = content || '';
|
237 | const lines = content.split('\n');
|
238 | for (const line of lines) {
|
239 | const m = DNS_SERVER_RE.exec(line.trim());
|
240 | if (m) {
|
241 | servers.push(m[1]);
|
242 | }
|
243 | }
|
244 | callback!(null, servers);
|
245 | })
|
246 | .catch(err => {
|
247 | callback!(err);
|
248 | });
|
249 | }
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 | export function address(callback: AddressCallback): void;
|
257 | export function address(interfaceName: string, callback: AddressCallback): void;
|
258 | export function address(interfaceNameOrCallback: string | AddressCallback, callback?: AddressCallback) {
|
259 | let interfaceName: string | undefined;
|
260 | if (typeof interfaceNameOrCallback === 'function') {
|
261 | callback = interfaceNameOrCallback;
|
262 | } else {
|
263 | interfaceName = interfaceNameOrCallback;
|
264 | }
|
265 |
|
266 | const addr: Address = {
|
267 | ip: ip(interfaceName),
|
268 | ipv6: ipv6(interfaceName),
|
269 | mac: undefined,
|
270 | };
|
271 | mac(interfaceName || '', (err?: Error | null, mac?: string | null) => {
|
272 | if (mac) {
|
273 | addr.mac = mac;
|
274 | }
|
275 | callback!(err || null, addr);
|
276 | });
|
277 | }
|