@serve.zone/dcrouter 13.39.0 → 13.40.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/deno.json +3 -3
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +2 -0
- package/dist_ts/monitoring/classes.metricsmanager.js +53 -19
- package/dist_ts/opsserver/handlers/security.handler.d.ts +2 -0
- package/dist_ts/opsserver/handlers/security.handler.js +50 -73
- package/dist_ts/radius/classes.radius.server.d.ts +0 -5
- package/dist_ts/radius/classes.radius.server.js +19 -65
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +5 -5
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/monitoring/classes.metricsmanager.ts +59 -21
- package/ts/opsserver/handlers/security.handler.ts +57 -109
- package/ts/radius/classes.radius.server.ts +19 -69
- package/ts_web/00_commitinfo_data.ts +1 -1
|
@@ -143,8 +143,9 @@ export class MetricsManager {
|
|
|
143
143
|
public async getServerStats() {
|
|
144
144
|
return this.metricsCache.get('serverStats', async () => {
|
|
145
145
|
const smartMetricsData = await this.smartMetrics.getMetrics();
|
|
146
|
-
const
|
|
147
|
-
const
|
|
146
|
+
const smartProxy = this.dcRouter.smartProxy;
|
|
147
|
+
const proxyMetrics = smartProxy ? smartProxy.getMetrics() : null;
|
|
148
|
+
const proxyStats = smartProxy ? await smartProxy.getStatistics() : null;
|
|
148
149
|
const { heapUsed, heapTotal, external, rss } = process.memoryUsage();
|
|
149
150
|
|
|
150
151
|
return {
|
|
@@ -291,27 +292,44 @@ export class MetricsManager {
|
|
|
291
292
|
});
|
|
292
293
|
}
|
|
293
294
|
|
|
295
|
+
public async getActiveConnectionSnapshots(
|
|
296
|
+
options: plugins.smartproxy.IActiveConnectionSnapshotOptions = {},
|
|
297
|
+
): Promise<plugins.smartproxy.IActiveConnectionSnapshot[]> {
|
|
298
|
+
const cacheKey = `activeConnectionSnapshots:${options.limit ?? 1000}:${options.routeId ?? ''}`;
|
|
299
|
+
return await this.metricsCache.get<plugins.smartproxy.IActiveConnectionSnapshot[]>(cacheKey, async () => {
|
|
300
|
+
if (!this.dcRouter.smartProxy) {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
return this.dcRouter.smartProxy.getActiveConnectionSnapshots(options);
|
|
304
|
+
}, 500);
|
|
305
|
+
}
|
|
306
|
+
|
|
294
307
|
// Get connection info from SmartProxy
|
|
295
308
|
public async getConnectionInfo() {
|
|
296
|
-
return this.metricsCache.get('connectionInfo', () => {
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
309
|
+
return this.metricsCache.get('connectionInfo', async () => {
|
|
310
|
+
const snapshots = await this.getActiveConnectionSnapshots({ limit: 10000 });
|
|
311
|
+
const connectionsByRoute = new Map<string, { count: number; lastActivity: Date }>();
|
|
312
|
+
|
|
313
|
+
for (const snapshot of snapshots) {
|
|
314
|
+
const source = snapshot.routeId || snapshot.domain || `${snapshot.protocol || 'connection'}:${snapshot.localPort}`;
|
|
315
|
+
const existing = connectionsByRoute.get(source) || { count: 0, lastActivity: new Date(snapshot.startedAtMs) };
|
|
316
|
+
existing.count++;
|
|
317
|
+
if (snapshot.startedAtMs > existing.lastActivity.getTime()) {
|
|
318
|
+
existing.lastActivity = new Date(snapshot.startedAtMs);
|
|
319
|
+
}
|
|
320
|
+
connectionsByRoute.set(source, existing);
|
|
301
321
|
}
|
|
302
322
|
|
|
303
|
-
const connectionsByRoute = proxyMetrics.connections.byRoute();
|
|
304
323
|
const connectionInfo: Array<{ type: string; count: number; source: string; lastActivity: Date }> = [];
|
|
305
|
-
|
|
306
|
-
for (const [routeName, count] of connectionsByRoute) {
|
|
324
|
+
for (const [source, info] of connectionsByRoute) {
|
|
307
325
|
connectionInfo.push({
|
|
308
326
|
type: 'https',
|
|
309
|
-
count,
|
|
310
|
-
source
|
|
311
|
-
lastActivity:
|
|
327
|
+
count: info.count,
|
|
328
|
+
source,
|
|
329
|
+
lastActivity: info.lastActivity,
|
|
312
330
|
});
|
|
313
331
|
}
|
|
314
|
-
|
|
332
|
+
|
|
315
333
|
return connectionInfo;
|
|
316
334
|
});
|
|
317
335
|
}
|
|
@@ -547,7 +565,8 @@ export class MetricsManager {
|
|
|
547
565
|
public async getNetworkStats() {
|
|
548
566
|
// Use shorter cache TTL for network stats to ensure real-time updates
|
|
549
567
|
return this.metricsCache.get('networkStats', async () => {
|
|
550
|
-
const
|
|
568
|
+
const smartProxy = this.dcRouter.smartProxy;
|
|
569
|
+
const proxyMetrics = smartProxy ? smartProxy.getMetrics() : null;
|
|
551
570
|
|
|
552
571
|
if (!proxyMetrics) {
|
|
553
572
|
return {
|
|
@@ -568,8 +587,22 @@ export class MetricsManager {
|
|
|
568
587
|
};
|
|
569
588
|
}
|
|
570
589
|
|
|
571
|
-
|
|
572
|
-
|
|
590
|
+
const activeConnectionSnapshots = await this.getActiveConnectionSnapshots({ limit: 10000 });
|
|
591
|
+
|
|
592
|
+
const connectionsByIP = new Map<string, number>();
|
|
593
|
+
const connectionsByRoute = new Map<string, number>();
|
|
594
|
+
const activeConnectionsByDomain = new Map<string, number>();
|
|
595
|
+
|
|
596
|
+
for (const snapshot of activeConnectionSnapshots) {
|
|
597
|
+
connectionsByIP.set(snapshot.sourceIp, (connectionsByIP.get(snapshot.sourceIp) || 0) + 1);
|
|
598
|
+
if (snapshot.routeId) {
|
|
599
|
+
connectionsByRoute.set(snapshot.routeId, (connectionsByRoute.get(snapshot.routeId) || 0) + 1);
|
|
600
|
+
}
|
|
601
|
+
if (snapshot.domain) {
|
|
602
|
+
activeConnectionsByDomain.set(snapshot.domain, (activeConnectionsByDomain.get(snapshot.domain) || 0) + 1);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
573
606
|
const instantThroughput = proxyMetrics.throughput.instant();
|
|
574
607
|
|
|
575
608
|
// Get throughput rate
|
|
@@ -578,8 +611,11 @@ export class MetricsManager {
|
|
|
578
611
|
bytesOutPerSecond: instantThroughput.out
|
|
579
612
|
};
|
|
580
613
|
|
|
581
|
-
// Get top IPs by connection count
|
|
582
|
-
const topIPs =
|
|
614
|
+
// Get top IPs by active connection count
|
|
615
|
+
const topIPs = Array.from(connectionsByIP.entries())
|
|
616
|
+
.sort((a, b) => b[1] - a[1])
|
|
617
|
+
.slice(0, 10)
|
|
618
|
+
.map(([ip, count]) => ({ ip, count }));
|
|
583
619
|
|
|
584
620
|
// Get total data transferred
|
|
585
621
|
const totalDataTransferred = {
|
|
@@ -738,7 +774,6 @@ export class MetricsManager {
|
|
|
738
774
|
const topASNs = await this.buildTopASNs(observedIps, allIPData);
|
|
739
775
|
|
|
740
776
|
// Build domain activity using per-IP domain request counts from Rust engine
|
|
741
|
-
const connectionsByRoute = proxyMetrics.connections.byRoute();
|
|
742
777
|
const throughputByRoute = proxyMetrics.throughput.byRoute();
|
|
743
778
|
|
|
744
779
|
// Aggregate per-IP domain request counts into per-domain totals
|
|
@@ -773,6 +808,9 @@ export class MetricsManager {
|
|
|
773
808
|
for (const entry of protocolCache) {
|
|
774
809
|
if (entry.domain) allKnownDomains.add(entry.domain);
|
|
775
810
|
}
|
|
811
|
+
for (const snapshot of activeConnectionSnapshots) {
|
|
812
|
+
if (snapshot.domain) allKnownDomains.add(snapshot.domain);
|
|
813
|
+
}
|
|
776
814
|
|
|
777
815
|
// Build reverse map: concrete domain → canonical route key(s)
|
|
778
816
|
const domainToRoutes = new Map<string, string[]>();
|
|
@@ -844,7 +882,7 @@ export class MetricsManager {
|
|
|
844
882
|
}
|
|
845
883
|
|
|
846
884
|
domainAgg.set(domain, {
|
|
847
|
-
activeConnections: Math.round(totalConns),
|
|
885
|
+
activeConnections: activeConnectionsByDomain.get(domain) ?? Math.round(totalConns),
|
|
848
886
|
bytesInPerSec: totalIn,
|
|
849
887
|
bytesOutPerSec: totalOut,
|
|
850
888
|
routeCount: routeKeys.length,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import type { OpsServer } from '../classes.opsserver.js';
|
|
3
3
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
|
4
|
-
import { MetricsManager } from '../../monitoring/index.js';
|
|
5
4
|
import { requireOpsAuth } from '../helpers/auth.js';
|
|
6
5
|
|
|
7
6
|
export class SecurityHandler {
|
|
@@ -46,18 +45,7 @@ export class SecurityHandler {
|
|
|
46
45
|
'getActiveConnections',
|
|
47
46
|
async (dataArg, toolsArg) => {
|
|
48
47
|
await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'stats:read' });
|
|
49
|
-
const
|
|
50
|
-
const connectionInfos: interfaces.data.IConnectionInfo[] = connections.map(conn => ({
|
|
51
|
-
id: conn.id,
|
|
52
|
-
remoteAddress: conn.source.ip,
|
|
53
|
-
localAddress: conn.destination.ip,
|
|
54
|
-
startTime: conn.startTime,
|
|
55
|
-
protocol: conn.type === 'http' ? 'https' : conn.type as any,
|
|
56
|
-
state: conn.status === 'active' ? 'connected' : conn.status as any,
|
|
57
|
-
bytesReceived: (conn as any)._throughputIn || 0,
|
|
58
|
-
bytesSent: (conn as any)._throughputOut || 0,
|
|
59
|
-
connectionCount: conn.bytesTransferred || 1,
|
|
60
|
-
}));
|
|
48
|
+
const connectionInfos = await this.getActiveConnections(dataArg.protocol, dataArg.state);
|
|
61
49
|
const totalConnections = connectionInfos.reduce((sum, conn) => sum + (conn.connectionCount || 1), 0);
|
|
62
50
|
|
|
63
51
|
const summary = {
|
|
@@ -362,106 +350,66 @@ export class SecurityHandler {
|
|
|
362
350
|
private async getActiveConnections(
|
|
363
351
|
protocol?: 'http' | 'https' | 'smtp' | 'smtps',
|
|
364
352
|
state?: string
|
|
365
|
-
): Promise<
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
port: number;
|
|
371
|
-
country?: string;
|
|
372
|
-
};
|
|
373
|
-
destination: {
|
|
374
|
-
ip: string;
|
|
375
|
-
port: number;
|
|
376
|
-
service?: string;
|
|
377
|
-
};
|
|
378
|
-
startTime: number;
|
|
379
|
-
bytesTransferred: number;
|
|
380
|
-
status: 'active' | 'idle' | 'closing';
|
|
381
|
-
}>> {
|
|
382
|
-
const connections: Array<{
|
|
383
|
-
id: string;
|
|
384
|
-
type: 'http' | 'smtp' | 'dns';
|
|
385
|
-
source: {
|
|
386
|
-
ip: string;
|
|
387
|
-
port: number;
|
|
388
|
-
country?: string;
|
|
389
|
-
};
|
|
390
|
-
destination: {
|
|
391
|
-
ip: string;
|
|
392
|
-
port: number;
|
|
393
|
-
service?: string;
|
|
394
|
-
};
|
|
395
|
-
startTime: number;
|
|
396
|
-
bytesTransferred: number;
|
|
397
|
-
status: 'active' | 'idle' | 'closing';
|
|
398
|
-
}> = [];
|
|
399
|
-
|
|
400
|
-
// Get connection info and network stats from MetricsManager if available
|
|
401
|
-
if (this.opsServerRef.dcRouterRef.metricsManager) {
|
|
402
|
-
const connectionInfo = await this.opsServerRef.dcRouterRef.metricsManager.getConnectionInfo();
|
|
403
|
-
const networkStats = await this.opsServerRef.dcRouterRef.metricsManager.getNetworkStats();
|
|
404
|
-
|
|
405
|
-
// One aggregate row per IP with real throughput data
|
|
406
|
-
if (networkStats.connectionsByIP && networkStats.connectionsByIP.size > 0) {
|
|
407
|
-
let connIndex = 0;
|
|
408
|
-
const publicIp = this.opsServerRef.dcRouterRef.options.publicIp || 'server';
|
|
353
|
+
): Promise<interfaces.data.IConnectionInfo[]> {
|
|
354
|
+
const metricsManager = this.opsServerRef.dcRouterRef.metricsManager;
|
|
355
|
+
if (!metricsManager) {
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
409
358
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
// Fallback to route-based connection info if no IP data available
|
|
433
|
-
connectionInfo.forEach((info, index) => {
|
|
434
|
-
connections.push({
|
|
435
|
-
id: `conn-${index}`,
|
|
436
|
-
type: 'http',
|
|
437
|
-
source: {
|
|
438
|
-
ip: 'unknown',
|
|
439
|
-
port: 0,
|
|
440
|
-
},
|
|
441
|
-
destination: {
|
|
442
|
-
ip: this.opsServerRef.dcRouterRef.options.publicIp || 'server',
|
|
443
|
-
port: 443,
|
|
444
|
-
service: info.source,
|
|
445
|
-
},
|
|
446
|
-
startTime: info.lastActivity.getTime(),
|
|
447
|
-
bytesTransferred: 0,
|
|
448
|
-
status: 'active',
|
|
449
|
-
});
|
|
450
|
-
});
|
|
359
|
+
const snapshots = await metricsManager.getActiveConnectionSnapshots({ limit: 10000 });
|
|
360
|
+
const connections = snapshots.map((snapshot): interfaces.data.IConnectionInfo => ({
|
|
361
|
+
id: String(snapshot.id),
|
|
362
|
+
remoteAddress: snapshot.sourcePort === null
|
|
363
|
+
? snapshot.sourceIp
|
|
364
|
+
: `${snapshot.sourceIp}:${snapshot.sourcePort}`,
|
|
365
|
+
localAddress: snapshot.targetHost
|
|
366
|
+
? `${snapshot.targetHost}:${snapshot.targetPort ?? snapshot.localPort}`
|
|
367
|
+
: `${this.opsServerRef.dcRouterRef.options.publicIp || 'server'}:${snapshot.localPort}`,
|
|
368
|
+
startTime: snapshot.startedAtMs,
|
|
369
|
+
protocol: this.mapSnapshotProtocol(snapshot),
|
|
370
|
+
state: this.mapSnapshotState(snapshot.state),
|
|
371
|
+
bytesReceived: snapshot.bytesIn,
|
|
372
|
+
bytesSent: snapshot.bytesOut,
|
|
373
|
+
}));
|
|
374
|
+
|
|
375
|
+
return connections.filter((connection) => {
|
|
376
|
+
if (protocol && connection.protocol !== protocol) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
if (state && connection.state !== state) {
|
|
380
|
+
return false;
|
|
451
381
|
}
|
|
382
|
+
return true;
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private mapSnapshotProtocol(
|
|
387
|
+
snapshot: plugins.smartproxy.IActiveConnectionSnapshot,
|
|
388
|
+
): interfaces.data.IConnectionInfo['protocol'] {
|
|
389
|
+
if (snapshot.localPort === 465) {
|
|
390
|
+
return 'smtps';
|
|
452
391
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (protocol) {
|
|
456
|
-
return connections.filter(conn => {
|
|
457
|
-
if (protocol === 'https' || protocol === 'http') {
|
|
458
|
-
return conn.type === 'http';
|
|
459
|
-
}
|
|
460
|
-
return conn.type === protocol.replace('s', ''); // smtp/smtps -> smtp
|
|
461
|
-
});
|
|
392
|
+
if ([25, 587, 2525].includes(snapshot.localPort)) {
|
|
393
|
+
return 'smtp';
|
|
462
394
|
}
|
|
463
|
-
|
|
464
|
-
|
|
395
|
+
|
|
396
|
+
switch (snapshot.protocol) {
|
|
397
|
+
case 'http':
|
|
398
|
+
return 'http';
|
|
399
|
+
case 'https':
|
|
400
|
+
case 'tls':
|
|
401
|
+
case 'tls-passthrough':
|
|
402
|
+
case 'tls-reencrypt':
|
|
403
|
+
case 'tls-socket-handler':
|
|
404
|
+
case 'quic':
|
|
405
|
+
return 'https';
|
|
406
|
+
default:
|
|
407
|
+
return snapshot.localPort === 80 ? 'http' : 'https';
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private mapSnapshotState(state: string): interfaces.data.IConnectionInfo['state'] {
|
|
412
|
+
return state === 'closing' ? 'closing' : 'connected';
|
|
465
413
|
}
|
|
466
414
|
|
|
467
415
|
private async getRateLimitStatus(
|
|
@@ -91,7 +91,6 @@ export class RadiusServer {
|
|
|
91
91
|
private vlanManager: VlanManager;
|
|
92
92
|
private accountingManager: AccountingManager;
|
|
93
93
|
private config: IRadiusServerConfig;
|
|
94
|
-
private clientSecrets: Map<string, string> = new Map();
|
|
95
94
|
private running: boolean = false;
|
|
96
95
|
|
|
97
96
|
// Statistics
|
|
@@ -138,24 +137,18 @@ export class RadiusServer {
|
|
|
138
137
|
await this.vlanManager.importMappings(this.config.vlanAssignment.mappings);
|
|
139
138
|
}
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
this.buildClientSecretsMap();
|
|
140
|
+
const cidrSecrets = this.buildClientSecretsMap();
|
|
143
141
|
|
|
144
142
|
// Create the RADIUS server
|
|
145
143
|
this.radiusServer = new plugins.smartradius.RadiusServer({
|
|
146
144
|
authPort: this.config.authPort,
|
|
147
145
|
acctPort: this.config.acctPort,
|
|
148
146
|
bindAddress: this.config.bindAddress,
|
|
149
|
-
|
|
147
|
+
cidrSecrets,
|
|
150
148
|
authenticationHandler: this.handleAuthentication.bind(this),
|
|
151
149
|
accountingHandler: this.handleAccounting.bind(this),
|
|
152
150
|
});
|
|
153
151
|
|
|
154
|
-
// Configure per-client secrets
|
|
155
|
-
for (const [ip, secret] of this.clientSecrets) {
|
|
156
|
-
this.radiusServer.setClientSecret(ip, secret);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
152
|
// Start the server
|
|
160
153
|
await this.radiusServer.start();
|
|
161
154
|
|
|
@@ -204,6 +197,7 @@ export class RadiusServer {
|
|
|
204
197
|
calledStationId: request.calledStationId,
|
|
205
198
|
callingStationId: request.callingStationId,
|
|
206
199
|
serviceType: request.serviceType !== undefined ? String(request.serviceType) : undefined,
|
|
200
|
+
framedMtu: request.framedMtu,
|
|
207
201
|
};
|
|
208
202
|
|
|
209
203
|
logger.log('debug', `RADIUS Auth Request: user=${authData.username}, NAS=${authData.nasIpAddress}`);
|
|
@@ -288,6 +282,8 @@ export class RadiusServer {
|
|
|
288
282
|
outputPackets: request.outputPackets,
|
|
289
283
|
sessionTime: request.sessionTime,
|
|
290
284
|
terminateCause: request.terminateCause !== undefined ? String(request.terminateCause) : undefined,
|
|
285
|
+
framedIpAddress: request.framedIpAddress,
|
|
286
|
+
serviceType: request.serviceType !== undefined ? String(request.serviceType) : undefined,
|
|
291
287
|
};
|
|
292
288
|
|
|
293
289
|
try {
|
|
@@ -394,66 +390,18 @@ export class RadiusServer {
|
|
|
394
390
|
/**
|
|
395
391
|
* Build client secrets map from configuration
|
|
396
392
|
*/
|
|
397
|
-
private buildClientSecretsMap():
|
|
398
|
-
|
|
393
|
+
private buildClientSecretsMap(): Record<string, string> {
|
|
394
|
+
const cidrSecrets: Record<string, string> = {};
|
|
399
395
|
|
|
400
396
|
for (const client of this.config.clients) {
|
|
401
397
|
if (!client.enabled) {
|
|
402
398
|
continue;
|
|
403
399
|
}
|
|
404
400
|
|
|
405
|
-
|
|
406
|
-
this.clientSecrets.set(client.ipRange, client.secret);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
private resolveClientSecret(clientAddress: string): string | undefined {
|
|
412
|
-
const normalizedClientAddress = this.normalizeClientAddress(clientAddress);
|
|
413
|
-
if (!normalizedClientAddress) return undefined;
|
|
414
|
-
|
|
415
|
-
const exactSecret = this.clientSecrets.get(normalizedClientAddress);
|
|
416
|
-
if (exactSecret !== undefined) return exactSecret;
|
|
417
|
-
|
|
418
|
-
for (const client of this.config.clients) {
|
|
419
|
-
if (!client.enabled || !client.ipRange.includes('/')) continue;
|
|
420
|
-
if (this.clientIpMatchesCidr(normalizedClientAddress, client.ipRange)) return client.secret;
|
|
401
|
+
cidrSecrets[client.ipRange] = client.secret;
|
|
421
402
|
}
|
|
422
403
|
|
|
423
|
-
return
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
private normalizeClientAddress(clientAddress: string): string | undefined {
|
|
427
|
-
const trimmedAddress = clientAddress.trim();
|
|
428
|
-
const normalizedAddress = trimmedAddress.startsWith('::ffff:')
|
|
429
|
-
? trimmedAddress.slice('::ffff:'.length)
|
|
430
|
-
: trimmedAddress;
|
|
431
|
-
return plugins.net.isIP(normalizedAddress) ? normalizedAddress : undefined;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
private clientIpMatchesCidr(clientAddress: string, cidr: string): boolean {
|
|
435
|
-
const [networkAddress, rawPrefix] = cidr.trim().split('/');
|
|
436
|
-
const normalizedNetworkAddress = this.normalizeClientAddress(networkAddress || '');
|
|
437
|
-
if (!normalizedNetworkAddress || rawPrefix === undefined) return false;
|
|
438
|
-
|
|
439
|
-
const clientIp = this.ipv4ToNumber(clientAddress);
|
|
440
|
-
const networkIp = this.ipv4ToNumber(normalizedNetworkAddress);
|
|
441
|
-
if (clientIp === undefined || networkIp === undefined) return false;
|
|
442
|
-
|
|
443
|
-
const prefix = Number(rawPrefix);
|
|
444
|
-
if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32) return false;
|
|
445
|
-
|
|
446
|
-
const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
|
|
447
|
-
return ((clientIp & mask) >>> 0) === ((networkIp & mask) >>> 0);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
private ipv4ToNumber(ipAddress: string): number | undefined {
|
|
451
|
-
if (plugins.net.isIP(ipAddress) !== 4) return undefined;
|
|
452
|
-
const parts = ipAddress.split('.').map((part) => Number(part));
|
|
453
|
-
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
454
|
-
return undefined;
|
|
455
|
-
}
|
|
456
|
-
return (((parts[0] * 256 + parts[1]) * 256 + parts[2]) * 256 + parts[3]) >>> 0;
|
|
404
|
+
return cidrSecrets;
|
|
457
405
|
}
|
|
458
406
|
|
|
459
407
|
/**
|
|
@@ -462,16 +410,20 @@ export class RadiusServer {
|
|
|
462
410
|
async addClient(client: IRadiusClient): Promise<void> {
|
|
463
411
|
// Check if client already exists
|
|
464
412
|
const existingIndex = this.config.clients.findIndex(c => c.name === client.name);
|
|
413
|
+
const previousClient = existingIndex >= 0 ? this.config.clients[existingIndex] : undefined;
|
|
465
414
|
if (existingIndex >= 0) {
|
|
466
415
|
this.config.clients[existingIndex] = client;
|
|
467
416
|
} else {
|
|
468
417
|
this.config.clients.push(client);
|
|
469
418
|
}
|
|
470
419
|
|
|
471
|
-
this.
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
420
|
+
if (this.running && this.radiusServer) {
|
|
421
|
+
if (previousClient) {
|
|
422
|
+
this.radiusServer.removeNetworkSecret(previousClient.ipRange);
|
|
423
|
+
}
|
|
424
|
+
if (client.enabled) {
|
|
425
|
+
this.radiusServer.setNetworkSecret(client.ipRange, client.secret);
|
|
426
|
+
}
|
|
475
427
|
}
|
|
476
428
|
|
|
477
429
|
logger.log('info', `RADIUS client ${client.enabled ? 'added' : 'disabled'}: ${client.name} (${client.ipRange})`);
|
|
@@ -486,10 +438,8 @@ export class RadiusServer {
|
|
|
486
438
|
const client = this.config.clients[index];
|
|
487
439
|
this.config.clients.splice(index, 1);
|
|
488
440
|
|
|
489
|
-
this.
|
|
490
|
-
|
|
491
|
-
if (this.radiusServer && !client.ipRange.includes('/')) {
|
|
492
|
-
this.radiusServer.removeClientSecret(client.ipRange);
|
|
441
|
+
if (this.radiusServer) {
|
|
442
|
+
this.radiusServer.removeNetworkSecret(client.ipRange);
|
|
493
443
|
}
|
|
494
444
|
|
|
495
445
|
logger.log('info', `RADIUS client removed: ${name}`);
|