@coffeerunhobby/mcp-ha-connect 2.4.8 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +16 -1
- package/README.md +31 -5
- package/dist/config.d.ts +5 -0
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/net/adapters/omada.d.ts +10 -1
- package/dist/net/adapters/omada.js +40 -2
- package/dist/net/adapters/omada.js.map +1 -1
- package/dist/net/adapters/ruijie.d.ts +82 -0
- package/dist/net/adapters/ruijie.js +491 -0
- package/dist/net/adapters/ruijie.js.map +1 -0
- package/dist/net/model.d.ts +30 -2
- package/dist/net/model.js.map +1 -1
- package/dist/ruijieClient/crypto.d.ts +20 -0
- package/dist/ruijieClient/crypto.js +57 -0
- package/dist/ruijieClient/crypto.js.map +1 -0
- package/dist/ruijieClient/index.d.ts +74 -0
- package/dist/ruijieClient/index.js +240 -0
- package/dist/ruijieClient/index.js.map +1 -0
- package/dist/server/common.d.ts +2 -0
- package/dist/server/common.js +2 -1
- package/dist/server/common.js.map +1 -1
- package/dist/server/http.d.ts +11 -0
- package/dist/server/http.js +25 -10
- package/dist/server/http.js.map +1 -1
- package/dist/server/stdio.d.ts +2 -0
- package/dist/server/stdio.js.map +1 -1
- package/dist/server/stream.d.ts +2 -0
- package/dist/server/stream.js +2 -1
- package/dist/server/stream.js.map +1 -1
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +9 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/net/index.d.ts +14 -2
- package/dist/tools/net/index.js +84 -3
- package/dist/tools/net/index.js.map +1 -1
- package/dist/upstreams.d.ts +7 -2
- package/dist/upstreams.js +54 -2
- package/dist/upstreams.js.map +1 -1
- package/dist/utils/redact.js +1 -1
- package/dist/utils/redact.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ruijie / Reyee adapter for the vendor-neutral network model (src/net/model.ts), through the
|
|
3
|
+
* router's local eWeb API (src/ruijieClient). Mapped on a RG-EG105G-V3, ReyeeOS 2.324
|
|
4
|
+
* (2026-10-24). It reads; the one change it makes (2.5.1) is a firmware upgrade to the version
|
|
5
|
+
* the router itself is offered (system getVersion / upgradeConfirm, the page's "Online Upgrade").
|
|
6
|
+
* Other changes are refused (UNSUPPORTED) until their write phases are built and live-tested.
|
|
7
|
+
*
|
|
8
|
+
* What is read, and how it maps:
|
|
9
|
+
* - networks: devConfig.get network, `lan[]` (one entry per LAN interface: address, netmask,
|
|
10
|
+
* VLAN id, DHCP). The default LAN carries Ruijie's internal VLAN 233, untagged on the LAN
|
|
11
|
+
* ports; the id of a network is its interface name, "vlan<id>", which rules refer to.
|
|
12
|
+
* - clients: the devices the router sees on its LAN right now, from its ARP table (devSta.get
|
|
13
|
+
* arp, interfaces "br-lan*", resolved MACs only), named by their address lease (devSta.get
|
|
14
|
+
* dhcp_lease) when they have one. A lease alone is no online device (a switched-off laptop
|
|
15
|
+
* keeps it until it expires); WAN-side ARP entries are the upstream network.
|
|
16
|
+
* - reservations: devConfig.get dhcp_static.
|
|
17
|
+
* - ports: devSta.get port_status. The router doesn't say which device is on which port, so
|
|
18
|
+
* `seen` stays empty; the WAN port is the uplink and protected.
|
|
19
|
+
* - rules: devConfig.get access_ctrl (evaluated in list order). Anything the model can't say
|
|
20
|
+
* faithfully (a source "anywhere", source ports, IPv6, user groups, schedules, a rule that
|
|
21
|
+
* matches only some connection states) is listed as `foreign` with the reason.
|
|
22
|
+
* No Wi-Fi radio on this model, no switch port profiles, no mDNS repeater; groups (Ruijie's
|
|
23
|
+
* user groups are address ranges and users, not the model's address groups) come later.
|
|
24
|
+
*/
|
|
25
|
+
import { createHash } from 'node:crypto';
|
|
26
|
+
import { formatMac, normalizeMac } from '../../omadaClient/client.js';
|
|
27
|
+
import { RuijieError } from '../../ruijieClient/index.js';
|
|
28
|
+
import { inSubnet, ipToNumber, parseCidr, subnetsOverlap } from '../../utils/ipv4.js';
|
|
29
|
+
import { NetError, } from '../model.js';
|
|
30
|
+
/** Ruijie's name for "all the time" in a rule's time setting (the default time group). */
|
|
31
|
+
const ALL_TIME = new Set(['所有时段', 'everytime', 'All Time', '']);
|
|
32
|
+
const READ_ONLY = 'The Ruijie adapter makes no changes but firmware upgrades in this version; reservations and rules come next, then networks.';
|
|
33
|
+
/**
|
|
34
|
+
* Firmware is only installed from Ruijie's own download servers (e.g. devicecdn-eu.ruijienetworks.com),
|
|
35
|
+
* at the address the router itself was offered. The router fetches it and checks it itself; the
|
|
36
|
+
* address Ruijie hands out is plain http (live, 2026-10-24), exactly what the page's Online Upgrade uses.
|
|
37
|
+
*/
|
|
38
|
+
const RUIJIE_DOWNLOADS = /(^|\.)ruijienetworks\.com$/i;
|
|
39
|
+
/** The getVersion answer as an offer: only a version other than the running one counts as new. */
|
|
40
|
+
export function toOffer(answer) {
|
|
41
|
+
const current = text(answer.cur).trim();
|
|
42
|
+
const offered = list(answer.new?.firmwareList)[0];
|
|
43
|
+
const version = offered ? text(offered.versionCode).trim() : '';
|
|
44
|
+
if (!offered || !version || version === current)
|
|
45
|
+
return { current };
|
|
46
|
+
const size = Number(text(offered.fileSize));
|
|
47
|
+
return {
|
|
48
|
+
current,
|
|
49
|
+
version,
|
|
50
|
+
...(text(offered.releaseNotes).trim() ? { notes: text(offered.releaseNotes).trim() } : {}),
|
|
51
|
+
...(size > 0 ? { sizeBytes: size } : {}),
|
|
52
|
+
url: text(offered.downloadUrlTar),
|
|
53
|
+
...(text(offered.md5) ? { md5: text(offered.md5) } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Whether a download address is on Ruijie's own servers (nothing else is ever handed to the router). */
|
|
57
|
+
export function isRuijieDownload(url) {
|
|
58
|
+
if (!url)
|
|
59
|
+
return false;
|
|
60
|
+
try {
|
|
61
|
+
const parsed = new URL(url);
|
|
62
|
+
return (parsed.protocol === 'https:' || parsed.protocol === 'http:') && !parsed.username && !parsed.password && RUIJIE_DOWNLOADS.test(parsed.hostname);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const text = (value) => (typeof value === 'string' ? value : value === undefined || value === null ? '' : String(value));
|
|
69
|
+
const list = (value) => (Array.isArray(value) ? value.filter((v) => v && typeof v === 'object') : []);
|
|
70
|
+
/** "255.255.255.0" -> 24; undefined for a mask that isn't contiguous ones. */
|
|
71
|
+
export function maskToPrefix(mask) {
|
|
72
|
+
let n;
|
|
73
|
+
try {
|
|
74
|
+
n = ipToNumber(mask);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
let prefix = 0;
|
|
80
|
+
for (let bit = 31; bit >= 0; bit--) {
|
|
81
|
+
if (Math.floor(n / 2 ** bit) % 2 === 1)
|
|
82
|
+
prefix++;
|
|
83
|
+
else
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
return n === (prefix === 0 ? 0 : (2 ** 32 - 2 ** (32 - prefix))) ? prefix : undefined;
|
|
87
|
+
}
|
|
88
|
+
const numberToIp = (n) => [24, 16, 8, 0].map((s) => Math.floor(n / 2 ** s) % 256).join('.');
|
|
89
|
+
/** One `lan[]` entry as a model network (undefined when its address or mask can't be read). */
|
|
90
|
+
export function toNetwork(raw, index, routerHost) {
|
|
91
|
+
const ip = text(raw.ipaddr);
|
|
92
|
+
const prefix = maskToPrefix(text(raw.netmask));
|
|
93
|
+
try {
|
|
94
|
+
ipToNumber(ip);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
if (prefix === undefined)
|
|
100
|
+
return undefined;
|
|
101
|
+
const vlan = Number(text(raw.vlanid));
|
|
102
|
+
const gateway = `${ip}/${prefix}`;
|
|
103
|
+
const start = text(raw.ipstart);
|
|
104
|
+
const limit = Number(text(raw.limit));
|
|
105
|
+
let range;
|
|
106
|
+
try {
|
|
107
|
+
if (start && Number.isInteger(limit) && limit > 0)
|
|
108
|
+
range = { from: start, to: numberToIp(ipToNumber(start) + limit - 1) };
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
range = undefined;
|
|
112
|
+
}
|
|
113
|
+
const lease = Number(text(raw.leasetime));
|
|
114
|
+
let routerAddress;
|
|
115
|
+
try {
|
|
116
|
+
routerAddress = ipToNumber(routerHost);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
routerAddress = undefined;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
id: `vlan${text(raw.vlanid)}`,
|
|
123
|
+
name: text(raw.desc) || text(raw.name) || (index === 0 ? 'LAN' : `VLAN ${text(raw.vlanid)}`),
|
|
124
|
+
purpose: 'normal',
|
|
125
|
+
vlan: Number.isInteger(vlan) ? vlan : 0,
|
|
126
|
+
gateway,
|
|
127
|
+
dhcp: {
|
|
128
|
+
enabled: text(raw.ignore) !== '1',
|
|
129
|
+
...(range ? { range } : {}),
|
|
130
|
+
...(Number.isInteger(lease) && lease > 0 ? { leaseMinutes: lease } : {}),
|
|
131
|
+
},
|
|
132
|
+
ipv6: false,
|
|
133
|
+
isolated: false,
|
|
134
|
+
// The first LAN is the router's own (its management address), and so is any LAN the
|
|
135
|
+
// router is reached through; neither may be cut off.
|
|
136
|
+
protected: index === 0 || (routerAddress !== undefined && inSubnet(routerAddress, gateway)),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const networkOf = (ip, networks) => {
|
|
140
|
+
let n;
|
|
141
|
+
try {
|
|
142
|
+
n = ipToNumber(ip);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
return networks.find((net) => net.gateway && inSubnet(n, net.gateway));
|
|
148
|
+
};
|
|
149
|
+
const macOrUndefined = (value) => {
|
|
150
|
+
try {
|
|
151
|
+
return formatMac(text(value));
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
/** The devices the router sees on its LAN (ARP), once each, named by their lease (same MAC and address) when they have one. */
|
|
158
|
+
export function toClients(leases, arp, networks) {
|
|
159
|
+
const names = new Map();
|
|
160
|
+
for (const l of leases) {
|
|
161
|
+
const mac = macOrUndefined(l.mac);
|
|
162
|
+
const name = text(l.hostname);
|
|
163
|
+
const key = `${mac}|${text(l.ip)}`;
|
|
164
|
+
if (mac && name && name !== '*' && !names.has(key))
|
|
165
|
+
names.set(key, name);
|
|
166
|
+
}
|
|
167
|
+
const clients = [];
|
|
168
|
+
const seen = new Set();
|
|
169
|
+
for (const a of arp) {
|
|
170
|
+
if (!text(a.intf).startsWith('br-lan'))
|
|
171
|
+
continue;
|
|
172
|
+
const mac = macOrUndefined(a.hardware);
|
|
173
|
+
// 00:00:00:00:00:00 = an address nobody answered for.
|
|
174
|
+
if (!mac || mac === '00-00-00-00-00-00' || seen.has(mac))
|
|
175
|
+
continue;
|
|
176
|
+
seen.add(mac);
|
|
177
|
+
const ip = text(a.address);
|
|
178
|
+
const network = networkOf(ip, networks);
|
|
179
|
+
const name = names.get(`${mac}|${ip}`);
|
|
180
|
+
clients.push({ mac, ...(name ? { name } : {}), ...(ip ? { ip } : {}), ...(network ? { network: { id: network.id } } : {}), online: true });
|
|
181
|
+
}
|
|
182
|
+
return clients;
|
|
183
|
+
}
|
|
184
|
+
export function toReservations(entries, networks) {
|
|
185
|
+
const out = [];
|
|
186
|
+
for (const e of entries) {
|
|
187
|
+
const mac = macOrUndefined(e.macaddr);
|
|
188
|
+
const ip = text(e.ipaddr);
|
|
189
|
+
if (!mac || !ip)
|
|
190
|
+
continue;
|
|
191
|
+
const network = networkOf(ip, networks);
|
|
192
|
+
out.push({ mac, ip, network: network ? { id: network.id } : { name: 'outside the router\'s networks' } });
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
export function toPorts(entries, routerMac) {
|
|
197
|
+
return entries.map((p) => {
|
|
198
|
+
const wan = text(p.name).toUpperCase() === 'WAN';
|
|
199
|
+
return {
|
|
200
|
+
device: { mac: routerMac },
|
|
201
|
+
port: Number(text(p.portId)),
|
|
202
|
+
...(text(p.panel_name) ? { name: text(p.panel_name) } : {}),
|
|
203
|
+
seen: [],
|
|
204
|
+
uplink: wan,
|
|
205
|
+
...(wan ? { protected: "it is the router's internet (WAN) port" } : {}),
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* A rule side (src or dest, with its addresses) as model selectors, or the reason it can't be one.
|
|
211
|
+
* The model's address selector carries no interface, so addresses are kept only where they imply
|
|
212
|
+
* the interface the rule names: inside the LANs (lan) or that VLAN (vlanX), outside every LAN
|
|
213
|
+
* (wan), or any interface (all). Anything else would overstate or misplace what the rule matches.
|
|
214
|
+
*/
|
|
215
|
+
function side(where, addresses, networks, isSource) {
|
|
216
|
+
if (addresses) {
|
|
217
|
+
const parts = addresses.split(',').map((a) => a.trim()).filter(Boolean);
|
|
218
|
+
if (parts.some((a) => a.includes('-')))
|
|
219
|
+
return `an address range (${addresses})`;
|
|
220
|
+
let ranges;
|
|
221
|
+
try {
|
|
222
|
+
ranges = parts.map((a) => parseCidr(a));
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return `an address it can't read (${addresses})`;
|
|
226
|
+
}
|
|
227
|
+
const inside = (nets) => ranges.every((r) => nets.some((n) => n.gateway && r.network >= parseCidr(n.gateway).network && r.broadcast <= parseCidr(n.gateway).broadcast));
|
|
228
|
+
const lans = networks.filter((n) => n.gateway);
|
|
229
|
+
const implied = where === 'all' ||
|
|
230
|
+
(where === 'lan' && inside(lans)) ||
|
|
231
|
+
(where === 'wan' && parts.every((a) => !lans.some((n) => subnetsOverlap(a, n.gateway)))) ||
|
|
232
|
+
(where.startsWith('vlan') && inside(networks.filter((n) => n.id === where)));
|
|
233
|
+
if (!implied)
|
|
234
|
+
return `addresses (${addresses}) limited to the interface '${where}'`;
|
|
235
|
+
return [{ addresses: parts }];
|
|
236
|
+
}
|
|
237
|
+
if (where === 'wan')
|
|
238
|
+
return [{ internet: true }];
|
|
239
|
+
if (where === 'lan')
|
|
240
|
+
return networks.map((n) => ({ network: { id: n.id } }));
|
|
241
|
+
if (where === 'all')
|
|
242
|
+
return isSource ? 'traffic from anywhere, the internet included' : 'traffic to anywhere';
|
|
243
|
+
const network = networks.find((n) => n.id === where);
|
|
244
|
+
if (network)
|
|
245
|
+
return [{ network: { id: network.id } }];
|
|
246
|
+
return `the interface '${where}'`;
|
|
247
|
+
}
|
|
248
|
+
export function toRule(raw, index, networks) {
|
|
249
|
+
const reasons = [];
|
|
250
|
+
const target = text(raw.target).toUpperCase();
|
|
251
|
+
const action = target === 'ACCEPT' ? 'allow' : 'block';
|
|
252
|
+
if (!['ACCEPT', 'DROP', 'REJECT'].includes(target))
|
|
253
|
+
reasons.push(`an unknown action '${text(raw.target)}'`);
|
|
254
|
+
let from = [];
|
|
255
|
+
if (text(raw.by) === 'mac') {
|
|
256
|
+
const mac = macOrUndefined(raw.mac);
|
|
257
|
+
if (mac)
|
|
258
|
+
from = [{ device: mac }];
|
|
259
|
+
else
|
|
260
|
+
reasons.push('a device match without a readable MAC');
|
|
261
|
+
// The model's device match has no interface: only "any LAN" or "anywhere" say the same.
|
|
262
|
+
const where = text(raw.src);
|
|
263
|
+
if (where && where !== 'lan' && where !== 'all')
|
|
264
|
+
reasons.push(`a device match limited to the interface '${where}'`);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
const s = side(text(raw.src), text(raw.srcIP), networks, true);
|
|
268
|
+
if (typeof s === 'string')
|
|
269
|
+
reasons.push(s);
|
|
270
|
+
else
|
|
271
|
+
from = s;
|
|
272
|
+
}
|
|
273
|
+
let to = [];
|
|
274
|
+
const d = side(text(raw.dest), text(raw.destIP), networks, false);
|
|
275
|
+
if (typeof d === 'string')
|
|
276
|
+
reasons.push(d);
|
|
277
|
+
else
|
|
278
|
+
to = d;
|
|
279
|
+
const proto = text(raw.proto).toLowerCase();
|
|
280
|
+
let protocol = 'any';
|
|
281
|
+
if (proto === 'tcp' || proto === 'udp' || proto === 'icmp')
|
|
282
|
+
protocol = proto;
|
|
283
|
+
else if (proto === 'tcpudp' && !text(raw.destPort))
|
|
284
|
+
reasons.push('TCP and UDP together without ports');
|
|
285
|
+
else if (proto !== 'all' && proto !== '' && proto !== 'tcpudp')
|
|
286
|
+
reasons.push(`the protocol '${text(raw.proto)}'`);
|
|
287
|
+
if (text(raw.destPort)) {
|
|
288
|
+
// The model's ports mean TCP and UDP together; a rule for one of them only would be overstated.
|
|
289
|
+
if (proto === 'tcpudp')
|
|
290
|
+
protocol = { tcpUdpPorts: text(raw.destPort) };
|
|
291
|
+
else if (proto === 'tcp' || proto === 'udp')
|
|
292
|
+
reasons.push(`destination ports for ${proto} only (the model's ports mean TCP and UDP)`);
|
|
293
|
+
else
|
|
294
|
+
reasons.push('destination ports without TCP or UDP');
|
|
295
|
+
}
|
|
296
|
+
if (text(raw.srcPort))
|
|
297
|
+
reasons.push('source ports');
|
|
298
|
+
if (text(raw.netProto) && text(raw.netProto) !== 'ipv4')
|
|
299
|
+
reasons.push(`an ${text(raw.netProto)} rule`);
|
|
300
|
+
if (list(raw.user).length || list(raw.user_group_list).length || (Array.isArray(raw.userGroupList) && raw.userGroupList.length))
|
|
301
|
+
reasons.push('user groups');
|
|
302
|
+
if (!ALL_TIME.has(text(raw.tmngtName)))
|
|
303
|
+
reasons.push(`a schedule ('${text(raw.tmngtName)}')`);
|
|
304
|
+
// "0" = every connection state, replies included (the rule is not stateful).
|
|
305
|
+
const state = text(raw.state) || '0';
|
|
306
|
+
if (state !== '0')
|
|
307
|
+
reasons.push(`only some connection states (${state})`);
|
|
308
|
+
return {
|
|
309
|
+
id: text(raw.uuid) || text(raw.ruleid) || `rule-${index + 1}`,
|
|
310
|
+
name: text(raw.ruleName) || `rule ${index + 1}`,
|
|
311
|
+
action,
|
|
312
|
+
from,
|
|
313
|
+
to,
|
|
314
|
+
protocol,
|
|
315
|
+
replies: 'match',
|
|
316
|
+
schedule: { kind: 'always' },
|
|
317
|
+
enabled: text(raw.enable) === '1',
|
|
318
|
+
position: index + 1,
|
|
319
|
+
...(reasons.length ? { foreign: { reason: `Read-only here: it uses ${reasons.join(', ')}.` } } : {}),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
export class RuijieNetworkController {
|
|
323
|
+
client;
|
|
324
|
+
vendor = 'ruijie';
|
|
325
|
+
id;
|
|
326
|
+
executors = new WeakMap();
|
|
327
|
+
/** One change at a time. */
|
|
328
|
+
queue = Promise.resolve();
|
|
329
|
+
constructor(client, options = {}) {
|
|
330
|
+
this.client = client;
|
|
331
|
+
this.id = options.id ?? `ruijie:${client.host}`;
|
|
332
|
+
}
|
|
333
|
+
async capabilities() {
|
|
334
|
+
return {
|
|
335
|
+
vendor: 'ruijie',
|
|
336
|
+
networks: { create: 'unsupported', isolation: 'unsupported', ipv6: 'unsupported', guestPurpose: 'unsupported' },
|
|
337
|
+
wifi: { create: 'unsupported', bands: [], security: [], pmf: 'unsupported', fastRoaming: 'unsupported', bandSteering: 'unsupported' },
|
|
338
|
+
ports: { profiles: 'unsupported', assign: 'unsupported' },
|
|
339
|
+
rules: { between: 'unsupported', toInternet: 'unsupported', deviceMatch: 'unsupported', wifiMatch: 'unsupported', stateful: 'unsupported', schedule: 'unsupported' },
|
|
340
|
+
groups: { address: 'unsupported', mac: 'unsupported' },
|
|
341
|
+
reservations: 'unsupported',
|
|
342
|
+
mdnsRepeater: 'unsupported',
|
|
343
|
+
firmware: 'native',
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
async protectedResources() {
|
|
347
|
+
const networks = (await this.listNetworks()).filter((n) => n.protected);
|
|
348
|
+
const ports = (await this.listPorts()).filter((p) => p.protected);
|
|
349
|
+
return { networks: networks.map((n) => ({ id: n.id })), ports: ports.map((p) => ({ device: p.device, port: p.port })), devices: [] };
|
|
350
|
+
}
|
|
351
|
+
async listNetworks() {
|
|
352
|
+
const config = await this.client.config('network');
|
|
353
|
+
return list(config.lan)
|
|
354
|
+
.map((raw, i) => toNetwork(raw, i, this.client.host))
|
|
355
|
+
.filter((n) => n !== undefined);
|
|
356
|
+
}
|
|
357
|
+
async listWifi() {
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
360
|
+
async listPortProfiles() {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
/** The router's identity; a device given must be the router (by MAC, model or "router"). */
|
|
364
|
+
async router(device) {
|
|
365
|
+
const info = await this.client.status('sysinfo');
|
|
366
|
+
const mac = macOrUndefined(info.sys_mac) ?? 'unknown';
|
|
367
|
+
if (device) {
|
|
368
|
+
const wanted = 'mac' in device ? device.mac : 'id' in device ? device.id : device.name;
|
|
369
|
+
const names = [mac, text(info.model), text(info.product_class), 'router'].map((v) => v.toLowerCase());
|
|
370
|
+
if (normalizeMac(wanted) !== normalizeMac(mac) && !names.includes(wanted.trim().toLowerCase())) {
|
|
371
|
+
throw new NetError('NOT_FOUND', `This Ruijie router (${text(info.model) || 'router'}, ${mac}) is the only device here; '${wanted}' is not it`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return { info, mac };
|
|
375
|
+
}
|
|
376
|
+
async listPorts(device) {
|
|
377
|
+
const { mac } = await this.router(device);
|
|
378
|
+
const status = await this.client.status('port_status');
|
|
379
|
+
return toPorts(list(status.List), mac);
|
|
380
|
+
}
|
|
381
|
+
async offer() {
|
|
382
|
+
return toOffer(await this.client.system('getVersion', { deviceType: 'self' }));
|
|
383
|
+
}
|
|
384
|
+
async listFirmware() {
|
|
385
|
+
const { info, mac } = await this.router();
|
|
386
|
+
const offer = await this.offer();
|
|
387
|
+
return [
|
|
388
|
+
{
|
|
389
|
+
device: { mac },
|
|
390
|
+
...(text(info.model) ? { model: text(info.product_class) || text(info.model) } : {}),
|
|
391
|
+
current: offer.current || text(info.software_version) || 'unknown',
|
|
392
|
+
...(offer.version ? { available: { version: offer.version, ...(offer.notes ? { notes: offer.notes } : {}), ...(offer.sizeBytes ? { sizeBytes: offer.sizeBytes } : {}) } } : {}),
|
|
393
|
+
upgradable: offer.version !== undefined && isRuijieDownload(offer.url),
|
|
394
|
+
},
|
|
395
|
+
];
|
|
396
|
+
}
|
|
397
|
+
async listRules() {
|
|
398
|
+
const networks = await this.listNetworks();
|
|
399
|
+
const acl = await this.client.config('access_ctrl');
|
|
400
|
+
return list(acl.list).map((raw, i) => toRule(raw, i, networks));
|
|
401
|
+
}
|
|
402
|
+
async listGroups() {
|
|
403
|
+
return [];
|
|
404
|
+
}
|
|
405
|
+
async listReservations() {
|
|
406
|
+
const networks = await this.listNetworks();
|
|
407
|
+
const config = await this.client.config('dhcp_static');
|
|
408
|
+
return toReservations(list(config.dhcp_static), networks);
|
|
409
|
+
}
|
|
410
|
+
async listClients() {
|
|
411
|
+
const networks = await this.listNetworks();
|
|
412
|
+
const leases = await this.client.status('dhcp_lease');
|
|
413
|
+
const arp = await this.client.status('arp');
|
|
414
|
+
return toClients(list(leases.List), list(arp.arpList), networks);
|
|
415
|
+
}
|
|
416
|
+
async getMdnsRepeater() {
|
|
417
|
+
return { enabled: false, networks: [], foreign: { reason: 'This Ruijie router has no mDNS repeater the tools manage.' } };
|
|
418
|
+
}
|
|
419
|
+
async plan(change) {
|
|
420
|
+
if (change.kind !== 'firmware')
|
|
421
|
+
throw new NetError('UNSUPPORTED', READ_ONLY, { capability: change.kind });
|
|
422
|
+
const { info, mac } = await this.router(change.device);
|
|
423
|
+
// The basis names the router; without its MAC a dry run could vouch for another one.
|
|
424
|
+
if (mac === 'unknown')
|
|
425
|
+
throw new NetError('INVALID', "The router doesn't report its MAC address, so it can't be told which router a dry run was for; not upgraded");
|
|
426
|
+
const offer = await this.offer();
|
|
427
|
+
if (!offer.version)
|
|
428
|
+
throw new NetError('INVALID', `The router already runs the newest firmware it is offered (${offer.current || 'unknown'})`);
|
|
429
|
+
if (!isRuijieDownload(offer.url)) {
|
|
430
|
+
throw new NetError('UNSUPPORTED', "The offered firmware doesn't come from Ruijie's download server; it is not installed through the tools", { capability: 'firmware' });
|
|
431
|
+
}
|
|
432
|
+
// Every device on the router's LAN loses its connection while it restarts.
|
|
433
|
+
const clients = await this.listClients();
|
|
434
|
+
const model = text(info.product_class) || text(info.model) || 'router';
|
|
435
|
+
const size = offer.sizeBytes ? ` (${Math.round(offer.sizeBytes / 1e6)} MB)` : '';
|
|
436
|
+
const url = offer.url;
|
|
437
|
+
const plan = {
|
|
438
|
+
change,
|
|
439
|
+
vendor: 'ruijie',
|
|
440
|
+
steps: [{ action: 'update', object: 'ruijie.firmware', name: `${model} ${mac}`, detail: `${offer.current} → ${offer.version}` }],
|
|
441
|
+
effects: [
|
|
442
|
+
`The router downloads ${offer.version} from Ruijie${size} and installs it, keeping its settings.`,
|
|
443
|
+
'It restarts: for several minutes every device on its LAN is offline and the router can\'t be reached.',
|
|
444
|
+
...(offer.notes ? [`Release notes: ${offer.notes.slice(0, 600)}`] : []),
|
|
445
|
+
],
|
|
446
|
+
wouldLockOut: clients,
|
|
447
|
+
basis: createHash('sha256')
|
|
448
|
+
.update(JSON.stringify({ change, controller: this.id, router: mac, current: offer.current, version: offer.version, url, md5: offer.md5 ?? null, lockOut: clients.map((c) => c.mac).sort() }))
|
|
449
|
+
.digest('hex')
|
|
450
|
+
.slice(0, 16),
|
|
451
|
+
};
|
|
452
|
+
this.executors.set(plan, async () => {
|
|
453
|
+
try {
|
|
454
|
+
await this.client.system('upgradeConfirm', { path: url, isPersist: true });
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
// Sent but unanswered: it may be restarting into the upgrade, or the request may not have arrived.
|
|
458
|
+
if (error instanceof RuijieError && error.kind === 'unreachable' && !error.notSent) {
|
|
459
|
+
throw new RuijieError('The router stopped answering while the upgrade request was being sent: it may be restarting into the upgrade, or the request may not have arrived. Check net_listFirmware in about five minutes before trying again.', 'unreachable');
|
|
460
|
+
}
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
applied: true,
|
|
465
|
+
object: { device: { mac }, model, current: offer.current, available: { version: offer.version }, upgradable: true },
|
|
466
|
+
owned: [],
|
|
467
|
+
warnings: ['The router is downloading and installing the firmware and will restart; check net_listFirmware in about five minutes.'],
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
return plan;
|
|
471
|
+
}
|
|
472
|
+
async apply(plan, options = {}) {
|
|
473
|
+
const run = this.queue.then(async () => {
|
|
474
|
+
// Planned again right before writing: anything changed since is a CONFLICT.
|
|
475
|
+
const fresh = await this.plan(plan.change);
|
|
476
|
+
if (fresh.basis !== plan.basis)
|
|
477
|
+
throw new NetError('CONFLICT', 'The router changed after this change was planned; nothing was written. Plan it again.');
|
|
478
|
+
// The router restarts whoever is online, so an upgrade always needs allowLockout (and so the dry run's basis).
|
|
479
|
+
if (!options.allowLockout) {
|
|
480
|
+
const who = fresh.wouldLockOut.length ? `cut off ${fresh.wouldLockOut.length} online device(s) and ` : '';
|
|
481
|
+
throw new NetError('LOCKOUT', `The upgrade would ${who}make the router unreachable while it restarts. Nothing was changed; repeat with allowLockout and the dry run's basis if that is intended.`, {
|
|
482
|
+
clients: fresh.wouldLockOut,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
return await this.executors.get(fresh)();
|
|
486
|
+
});
|
|
487
|
+
this.queue = run.catch(() => undefined);
|
|
488
|
+
return await run;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
//# sourceMappingURL=ruijie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ruijie.js","sourceRoot":"","sources":["../../../src/net/adapters/ruijie.ts"],"names":[],"mappings":"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"}
|
package/dist/net/model.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type Ipv4 = string;
|
|
|
26
26
|
export type Clock = string;
|
|
27
27
|
export declare const DAYS: readonly ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
|
|
28
28
|
export type Day = (typeof DAYS)[number];
|
|
29
|
-
export type Vendor = 'omada' | 'unifi' | 'mikrotik';
|
|
29
|
+
export type Vendor = 'omada' | 'unifi' | 'mikrotik' | 'ruijie';
|
|
30
30
|
export interface TimeWindow {
|
|
31
31
|
days: Day[];
|
|
32
32
|
from: Clock;
|
|
@@ -177,6 +177,23 @@ export interface MdnsRepeater {
|
|
|
177
177
|
reason: string;
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
|
+
/** A network device's firmware: what runs now and, when the vendor tells, what is offered. */
|
|
181
|
+
export interface Firmware {
|
|
182
|
+
device: Ref | {
|
|
183
|
+
mac: Mac;
|
|
184
|
+
};
|
|
185
|
+
name?: string;
|
|
186
|
+
model?: string;
|
|
187
|
+
current: string;
|
|
188
|
+
/** A newer version the vendor offers; absent when there is none or the vendor doesn't say. */
|
|
189
|
+
available?: {
|
|
190
|
+
version: string;
|
|
191
|
+
notes?: string;
|
|
192
|
+
sizeBytes?: number;
|
|
193
|
+
};
|
|
194
|
+
/** Whether `available` can be installed through the tools (net_upgradeFirmware). */
|
|
195
|
+
upgradable: boolean;
|
|
196
|
+
}
|
|
180
197
|
export interface Group {
|
|
181
198
|
id: string;
|
|
182
199
|
name: string;
|
|
@@ -269,6 +286,8 @@ export interface Capabilities {
|
|
|
269
286
|
};
|
|
270
287
|
reservations: Support;
|
|
271
288
|
mdnsRepeater: Support;
|
|
289
|
+
/** Installing the firmware the vendor offers (net_upgradeFirmware). */
|
|
290
|
+
firmware: Support;
|
|
272
291
|
}
|
|
273
292
|
/** Things no change may cut off: where the controller, this server and their uplinks live. */
|
|
274
293
|
export interface ProtectedResources {
|
|
@@ -360,6 +379,14 @@ export type Change = {
|
|
|
360
379
|
kind: 'mdnsRepeater';
|
|
361
380
|
enabled: boolean;
|
|
362
381
|
networks?: Ref[];
|
|
382
|
+
}
|
|
383
|
+
/** Installs the version the vendor offers for one device (it restarts). */
|
|
384
|
+
| {
|
|
385
|
+
op: 'upgrade';
|
|
386
|
+
kind: 'firmware';
|
|
387
|
+
device?: Ref | {
|
|
388
|
+
mac: Mac;
|
|
389
|
+
};
|
|
363
390
|
};
|
|
364
391
|
/** One vendor-level step of a plan, in the order it will run. Never contains secrets. */
|
|
365
392
|
export interface Step {
|
|
@@ -383,7 +410,7 @@ export interface Plan {
|
|
|
383
410
|
export interface ApplyResult {
|
|
384
411
|
applied: true;
|
|
385
412
|
/** The neutral object after the change (redacted), when there is one. */
|
|
386
|
-
object?: Network | Wifi | PortProfile | Port | Rule | Group | Reservation | MdnsRepeater;
|
|
413
|
+
object?: Network | Wifi | PortProfile | Port | Rule | Group | Reservation | MdnsRepeater | Firmware;
|
|
387
414
|
/** Vendor objects created for this change; tagged so delete() removes them all again. */
|
|
388
415
|
owned: Array<{
|
|
389
416
|
object: string;
|
|
@@ -432,6 +459,7 @@ export interface NetworkController {
|
|
|
432
459
|
listReservations(): Promise<Reservation[]>;
|
|
433
460
|
listClients(): Promise<Client[]>;
|
|
434
461
|
getMdnsRepeater(): Promise<MdnsRepeater>;
|
|
462
|
+
listFirmware(): Promise<Firmware[]>;
|
|
435
463
|
/** Read-only: validates, resolves refs, checks protection, in-use and lock-out. Throws NetError. */
|
|
436
464
|
plan(change: Change): Promise<Plan>;
|
|
437
465
|
/** Runs a plan; re-checks `basis` first (CONFLICT) and lock-out (LOCKOUT unless allowLockout). */
|
package/dist/net/model.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/net/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAcH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/net/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAcH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AA0T/E,MAAM,OAAO,QAAS,SAAQ,KAAK;IAElB;IAEA;IAHb,YACa,IAAkB,EAC3B,OAAe,EACN,UAAyE,EAAE;QAEpF,KAAK,CAAC,OAAO,CAAC,CAAC;QAJN,SAAI,GAAJ,IAAI,CAAc;QAElB,YAAO,GAAP,OAAO,CAAoE;QAGpF,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IAC3B,CAAC;CACJ"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The small amount of crypto Ruijie's eWeb login and API calls need, done the way the
|
|
3
|
+
* router's own web page does it (verified against a RG-EG105G-V3, ReyeeOS 2.324, 2026-10-24):
|
|
4
|
+
*
|
|
5
|
+
* - The page encrypts the login password with GibberishAES: OpenSSL's "Salted__" format,
|
|
6
|
+
* AES-256-CBC with key and IV from EVP_BytesToKey (MD5). Its key is itself shipped in the
|
|
7
|
+
* page, encrypted with the passphrase "eweb". This hides the password from a casual look at
|
|
8
|
+
* the traffic; it is not a secret, so the transport should still be HTTPS.
|
|
9
|
+
* - Every API request carries two checksum headers, MD5 of a fixed string plus the body's
|
|
10
|
+
* byte length (Content-Accept) and plus the body itself (Contents-Accept).
|
|
11
|
+
*/
|
|
12
|
+
/** GibberishAES / `openssl enc -aes-256-cbc -md md5` encryption, base64 with the "Salted__" header. */
|
|
13
|
+
export declare function gibberishEncrypt(text: string, passphrase: string, salt?: Buffer): string;
|
|
14
|
+
export declare function gibberishDecrypt(blob: string, passphrase: string): string;
|
|
15
|
+
/** The key the login page encrypts the password with. */
|
|
16
|
+
export declare function pageKey(): string;
|
|
17
|
+
/** The password as the login page sends it. */
|
|
18
|
+
export declare function encryptPassword(password: string): string;
|
|
19
|
+
/** The two checksum headers the web page sends with every API request. */
|
|
20
|
+
export declare function checksumHeaders(body: string): Record<string, string>;
|