Mirror: Best-effort discovery of the machine's local network using just Node.js dgram sockets
1import os from 'node:os';
2import { vi, describe, it, expect } from 'vitest';
3import {
4 parseMacStr,
5 parseIpStr,
6 toIpStr,
7 interfaceAssignments,
8 matchAssignment,
9 isSameSubnet,
10} from '../network';
11
12describe(parseMacStr, () => {
13 it('parses valid MAC addresses', () => {
14 expect(parseMacStr('11:22:33:44:55:66')).toEqual([17, 34, 51, 68, 85, 102]);
15 });
16});
17
18describe(parseIpStr, () => {
19 it('parses valid IP addresses', () => {
20 expect(parseIpStr('0.0.0.0').toString(16)).toBe('0');
21 expect(parseIpStr('1.1.1.1').toString(16)).toBe('1010101');
22 expect(parseIpStr('255.255.255.255').toString(16)).toBe('-1');
23 expect(parseIpStr('100.1.2.3').toString(16)).toBe('64010203');
24 });
25});
26
27describe(toIpStr, () => {
28 it.each([['0.0.0.0'], ['1.1.1.1'], ['255.255.255.255'], ['100.1.2.3']])(
29 'stringifies parsed IP (%s)',
30 addr => {
31 expect(toIpStr(parseIpStr(addr))).toBe(addr);
32 }
33 );
34});
35
36describe(isSameSubnet, () => {
37 it('returns true for same subnet', () => {
38 expect(isSameSubnet('192.168.1.1', '192.168.1.2', '255.255.255.0')).toBe(
39 true
40 );
41 });
42 it('returns false for different subnet', () => {
43 expect(isSameSubnet('192.168.1.1', '192.168.2.1', '255.255.255.0')).toBe(
44 false
45 );
46 });
47});
48
49describe(interfaceAssignments, () => {
50 const networkInterfaces = vi
51 .spyOn(os, 'networkInterfaces')
52 .mockReturnValue([] as any);
53
54 it('returns sorted list of assignments', () => {
55 networkInterfaces.mockReturnValueOnce({
56 lo0: [
57 {
58 address: '127.0.0.1',
59 netmask: '255.0.0.0',
60 family: 'IPv4',
61 mac: '00:00:00:00:00:00',
62 internal: true,
63 cidr: '',
64 },
65 ],
66 en1: [
67 {
68 address: '10.0.0.10',
69 netmask: '255.255.255.0',
70 family: 'IPv4',
71 mac: '00:00:00:00:00:00',
72 internal: false,
73 cidr: '',
74 },
75 ],
76 tun2: [
77 {
78 address: '100.0.0.11',
79 netmask: '255.255.255.0',
80 family: 'IPv4',
81 mac: '00:00:00:00:00:00',
82 internal: true,
83 cidr: '',
84 },
85 ],
86 });
87 expect(interfaceAssignments()).toMatchInlineSnapshot(`
88 [
89 {
90 "address": "10.0.0.10",
91 "cidr": "",
92 "family": "IPv4",
93 "iname": "en1",
94 "internal": false,
95 "mac": "00:00:00:00:00:00",
96 "netmask": "255.255.255.0",
97 },
98 {
99 "address": "100.0.0.11",
100 "cidr": "",
101 "family": "IPv4",
102 "iname": "tun2",
103 "internal": true,
104 "mac": "00:00:00:00:00:00",
105 "netmask": "255.255.255.0",
106 },
107 {
108 "address": "127.0.0.1",
109 "cidr": "",
110 "family": "IPv4",
111 "iname": "lo0",
112 "internal": true,
113 "mac": "00:00:00:00:00:00",
114 "netmask": "255.0.0.0",
115 },
116 ]
117 `);
118 });
119
120 it('deprioritizes bridge interfaces', () => {
121 networkInterfaces.mockReturnValueOnce({
122 bridge0: [
123 {
124 address: '100.0.0.11',
125 netmask: '255.255.255.0',
126 family: 'IPv4',
127 mac: '10:00:00:00:00:00',
128 internal: false,
129 cidr: '',
130 },
131 ],
132 en1: [
133 {
134 address: '10.0.0.10',
135 netmask: '255.255.255.0',
136 family: 'IPv4',
137 mac: '10:00:00:00:00:00',
138 internal: false,
139 cidr: '',
140 },
141 ],
142 });
143 expect(interfaceAssignments()).toMatchInlineSnapshot(`
144 [
145 {
146 "address": "10.0.0.10",
147 "cidr": "",
148 "family": "IPv4",
149 "iname": "en1",
150 "internal": false,
151 "mac": "10:00:00:00:00:00",
152 "netmask": "255.255.255.0",
153 },
154 {
155 "address": "100.0.0.11",
156 "cidr": "",
157 "family": "IPv4",
158 "iname": "bridge0",
159 "internal": false,
160 "mac": "10:00:00:00:00:00",
161 "netmask": "255.255.255.0",
162 },
163 ]
164 `);
165 });
166});
167
168describe(matchAssignment, () => {
169 it('returns matching assignment by address', () => {
170 const assignment = {
171 iname: 'en0',
172 address: '100.0.0.11',
173 netmask: '255.255.255.0',
174 family: 'IPv4',
175 mac: '00:00:00:00:00:00',
176 internal: true,
177 cidr: '',
178 } as const;
179 expect(matchAssignment([assignment], '100.0.0.11')).toEqual({
180 ...assignment,
181 gateway: null,
182 });
183 });
184
185 it('returns matching assignment by gateway', () => {
186 const assignment = {
187 iname: 'en0',
188 address: '100.0.0.11',
189 netmask: '255.255.255.0',
190 family: 'IPv4',
191 mac: '00:00:00:00:00:00',
192 internal: true,
193 cidr: '',
194 } as const;
195 expect(matchAssignment([assignment], '100.0.0.1')).toEqual({
196 ...assignment,
197 gateway: '100.0.0.1',
198 });
199 });
200
201 it('returns null otherwise', () => {
202 const assignment = {
203 iname: 'en0',
204 address: '10.0.0.1',
205 netmask: '255.255.255.0',
206 family: 'IPv4',
207 mac: '00:00:00:00:00:00',
208 internal: true,
209 cidr: '',
210 } as const;
211 expect(matchAssignment([assignment], '100.0.0.1')).toBe(null);
212 });
213});