@serve.zone/dcrouter 11.23.5 → 12.1.0
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/dist_serve/bundle.js +1052 -939
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/classes.cert-provision-scheduler.d.ts +6 -8
- package/dist_ts/classes.cert-provision-scheduler.js +37 -17
- package/dist_ts/classes.dcrouter.d.ts +26 -29
- package/dist_ts/classes.dcrouter.js +101 -91
- package/dist_ts/classes.storage-cert-manager.d.ts +3 -6
- package/dist_ts/classes.storage-cert-manager.js +35 -25
- package/dist_ts/config/classes.api-token-manager.d.ts +1 -3
- package/dist_ts/config/classes.api-token-manager.js +45 -15
- package/dist_ts/config/classes.route-config-manager.d.ts +1 -3
- package/dist_ts/config/classes.route-config-manager.js +62 -24
- package/dist_ts/{cache → db}/classes.cache.cleaner.d.ts +3 -3
- package/dist_ts/db/classes.cache.cleaner.js +130 -0
- package/dist_ts/{cache → db}/classes.cached.document.js +1 -1
- package/dist_ts/db/classes.dcrouter-db.d.ts +70 -0
- package/dist_ts/db/classes.dcrouter-db.js +146 -0
- package/dist_ts/db/documents/classes.accounting-session.doc.d.ts +32 -0
- package/dist_ts/db/documents/classes.accounting-session.doc.js +214 -0
- package/dist_ts/db/documents/classes.acme-cert.doc.d.ts +13 -0
- package/dist_ts/db/documents/classes.acme-cert.doc.js +109 -0
- package/dist_ts/db/documents/classes.api-token.doc.d.ts +18 -0
- package/dist_ts/db/documents/classes.api-token.doc.js +127 -0
- package/dist_ts/{cache → db}/documents/classes.cached.email.js +3 -3
- package/dist_ts/{cache → db}/documents/classes.cached.ip.reputation.js +3 -3
- package/dist_ts/db/documents/classes.cert-backoff.doc.d.ts +11 -0
- package/dist_ts/db/documents/classes.cert-backoff.doc.js +97 -0
- package/dist_ts/db/documents/classes.proxy-cert.doc.d.ts +12 -0
- package/dist_ts/db/documents/classes.proxy-cert.doc.js +103 -0
- package/dist_ts/db/documents/classes.remote-ingress-edge.doc.d.ts +17 -0
- package/dist_ts/db/documents/classes.remote-ingress-edge.doc.js +130 -0
- package/dist_ts/db/documents/classes.route-override.doc.d.ts +10 -0
- package/dist_ts/db/documents/classes.route-override.doc.js +91 -0
- package/dist_ts/db/documents/classes.stored-route.doc.d.ts +12 -0
- package/dist_ts/db/documents/classes.stored-route.doc.js +103 -0
- package/dist_ts/db/documents/classes.vlan-mappings.doc.d.ts +15 -0
- package/dist_ts/db/documents/classes.vlan-mappings.doc.js +77 -0
- package/dist_ts/db/documents/classes.vpn-client.doc.d.ts +26 -0
- package/dist_ts/db/documents/classes.vpn-client.doc.js +184 -0
- package/dist_ts/db/documents/classes.vpn-server-keys.doc.d.ts +10 -0
- package/dist_ts/db/documents/classes.vpn-server-keys.doc.js +94 -0
- package/dist_ts/db/documents/index.d.ts +13 -0
- package/dist_ts/db/documents/index.js +20 -0
- package/dist_ts/{cache → db}/index.d.ts +1 -1
- package/dist_ts/db/index.js +9 -0
- package/dist_ts/opsserver/handlers/certificate.handler.js +66 -66
- package/dist_ts/opsserver/handlers/config.handler.js +14 -15
- package/dist_ts/opsserver/handlers/vpn.handler.js +35 -1
- package/dist_ts/paths.d.ts +0 -1
- package/dist_ts/paths.js +1 -2
- package/dist_ts/radius/classes.accounting.manager.d.ts +4 -12
- package/dist_ts/radius/classes.accounting.manager.js +80 -93
- package/dist_ts/radius/classes.radius.server.d.ts +1 -3
- package/dist_ts/radius/classes.radius.server.js +4 -6
- package/dist_ts/radius/classes.vlan.manager.d.ts +3 -7
- package/dist_ts/radius/classes.vlan.manager.js +21 -28
- package/dist_ts/radius/index.d.ts +1 -1
- package/dist_ts/radius/index.js +1 -1
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +3 -5
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +41 -21
- package/dist_ts/security/classes.ipreputationchecker.d.ts +6 -21
- package/dist_ts/security/classes.ipreputationchecker.js +59 -138
- package/dist_ts/vpn/classes.vpn-manager.d.ts +37 -22
- package/dist_ts/vpn/classes.vpn-manager.js +161 -51
- package/dist_ts_interfaces/data/vpn.d.ts +8 -0
- package/dist_ts_interfaces/requests/vpn.d.ts +16 -0
- package/dist_ts_oci_container/index.js +4 -4
- package/dist_ts_web/00_commitinfo_data.js +2 -2
- package/dist_ts_web/appstate.d.ts +16 -0
- package/dist_ts_web/appstate.js +17 -1
- package/dist_ts_web/elements/ops-view-vpn.js +155 -3
- package/package.json +3 -3
- package/readme.storage.md +55 -91
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.cert-provision-scheduler.ts +35 -17
- package/ts/classes.dcrouter.ts +129 -125
- package/ts/classes.storage-cert-manager.ts +34 -22
- package/ts/config/classes.api-token-manager.ts +42 -11
- package/ts/config/classes.route-config-manager.ts +56 -21
- package/ts/{cache → db}/classes.cache.cleaner.ts +6 -6
- package/ts/db/classes.dcrouter-db.ts +179 -0
- package/ts/db/documents/classes.accounting-session.doc.ts +106 -0
- package/ts/db/documents/classes.acme-cert.doc.ts +41 -0
- package/ts/db/documents/classes.api-token.doc.ts +56 -0
- package/ts/{cache → db}/documents/classes.cached.email.ts +2 -2
- package/ts/{cache → db}/documents/classes.cached.ip.reputation.ts +2 -2
- package/ts/db/documents/classes.cert-backoff.doc.ts +35 -0
- package/ts/db/documents/classes.proxy-cert.doc.ts +38 -0
- package/ts/db/documents/classes.remote-ingress-edge.doc.ts +54 -0
- package/ts/db/documents/classes.route-override.doc.ts +32 -0
- package/ts/db/documents/classes.stored-route.doc.ts +38 -0
- package/ts/db/documents/classes.vlan-mappings.doc.ts +32 -0
- package/ts/db/documents/classes.vpn-client.doc.ts +81 -0
- package/ts/db/documents/classes.vpn-server-keys.doc.ts +31 -0
- package/ts/db/documents/index.ts +24 -0
- package/ts/{cache → db}/index.ts +6 -2
- package/ts/opsserver/handlers/certificate.handler.ts +67 -65
- package/ts/opsserver/handlers/config.handler.ts +13 -14
- package/ts/opsserver/handlers/vpn.handler.ts +37 -0
- package/ts/paths.ts +0 -1
- package/ts/radius/classes.accounting.manager.ts +81 -103
- package/ts/radius/classes.radius.server.ts +3 -6
- package/ts/radius/classes.vlan.manager.ts +20 -32
- package/ts/radius/index.ts +1 -1
- package/ts/remoteingress/classes.remoteingress-manager.ts +40 -22
- package/ts/security/classes.ipreputationchecker.ts +103 -196
- package/ts/vpn/classes.vpn-manager.ts +187 -81
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +32 -0
- package/ts_web/elements/ops-view-vpn.ts +153 -2
- package/dist_ts/cache/classes.cache.cleaner.js +0 -130
- package/dist_ts/cache/classes.cachedb.d.ts +0 -60
- package/dist_ts/cache/classes.cachedb.js +0 -126
- package/dist_ts/cache/documents/index.d.ts +0 -2
- package/dist_ts/cache/documents/index.js +0 -3
- package/dist_ts/cache/index.js +0 -7
- package/dist_ts/storage/classes.storagemanager.d.ts +0 -83
- package/dist_ts/storage/classes.storagemanager.js +0 -348
- package/dist_ts/storage/index.d.ts +0 -1
- package/dist_ts/storage/index.js +0 -3
- package/ts/cache/classes.cachedb.ts +0 -155
- package/ts/cache/documents/index.ts +0 -2
- package/ts/storage/classes.storagemanager.ts +0 -404
- package/ts/storage/index.ts +0 -2
- /package/dist_ts/{cache → db}/classes.cached.document.d.ts +0 -0
- /package/dist_ts/{cache → db}/documents/classes.cached.email.d.ts +0 -0
- /package/dist_ts/{cache → db}/documents/classes.cached.ip.reputation.d.ts +0 -0
- /package/ts/{cache → db}/classes.cached.document.ts +0 -0
|
@@ -31,6 +31,14 @@ export class VpnHandler {
|
|
|
31
31
|
createdAt: c.createdAt,
|
|
32
32
|
updatedAt: c.updatedAt,
|
|
33
33
|
expiresAt: c.expiresAt,
|
|
34
|
+
forceDestinationSmartproxy: c.forceDestinationSmartproxy ?? true,
|
|
35
|
+
destinationAllowList: c.destinationAllowList,
|
|
36
|
+
destinationBlockList: c.destinationBlockList,
|
|
37
|
+
useHostIp: c.useHostIp,
|
|
38
|
+
useDhcp: c.useDhcp,
|
|
39
|
+
staticIp: c.staticIp,
|
|
40
|
+
forceVlan: c.forceVlan,
|
|
41
|
+
vlanId: c.vlanId,
|
|
34
42
|
}));
|
|
35
43
|
return { clients };
|
|
36
44
|
},
|
|
@@ -114,8 +122,21 @@ export class VpnHandler {
|
|
|
114
122
|
clientId: dataArg.clientId,
|
|
115
123
|
serverDefinedClientTags: dataArg.serverDefinedClientTags,
|
|
116
124
|
description: dataArg.description,
|
|
125
|
+
forceDestinationSmartproxy: dataArg.forceDestinationSmartproxy,
|
|
126
|
+
destinationAllowList: dataArg.destinationAllowList,
|
|
127
|
+
destinationBlockList: dataArg.destinationBlockList,
|
|
128
|
+
useHostIp: dataArg.useHostIp,
|
|
129
|
+
useDhcp: dataArg.useDhcp,
|
|
130
|
+
staticIp: dataArg.staticIp,
|
|
131
|
+
forceVlan: dataArg.forceVlan,
|
|
132
|
+
vlanId: dataArg.vlanId,
|
|
117
133
|
});
|
|
118
134
|
|
|
135
|
+
// Retrieve the persisted doc to get dcrouter-level fields
|
|
136
|
+
const persistedClient = manager.listClients().find(
|
|
137
|
+
(c) => c.clientId === bundle.entry.clientId,
|
|
138
|
+
);
|
|
139
|
+
|
|
119
140
|
return {
|
|
120
141
|
success: true,
|
|
121
142
|
client: {
|
|
@@ -127,6 +148,14 @@ export class VpnHandler {
|
|
|
127
148
|
createdAt: Date.now(),
|
|
128
149
|
updatedAt: Date.now(),
|
|
129
150
|
expiresAt: bundle.entry.expiresAt,
|
|
151
|
+
forceDestinationSmartproxy: persistedClient?.forceDestinationSmartproxy ?? true,
|
|
152
|
+
destinationAllowList: persistedClient?.destinationAllowList,
|
|
153
|
+
destinationBlockList: persistedClient?.destinationBlockList,
|
|
154
|
+
useHostIp: persistedClient?.useHostIp,
|
|
155
|
+
useDhcp: persistedClient?.useDhcp,
|
|
156
|
+
staticIp: persistedClient?.staticIp,
|
|
157
|
+
forceVlan: persistedClient?.forceVlan,
|
|
158
|
+
vlanId: persistedClient?.vlanId,
|
|
130
159
|
},
|
|
131
160
|
wireguardConfig: bundle.wireguardConfig,
|
|
132
161
|
};
|
|
@@ -151,6 +180,14 @@ export class VpnHandler {
|
|
|
151
180
|
await manager.updateClient(dataArg.clientId, {
|
|
152
181
|
description: dataArg.description,
|
|
153
182
|
serverDefinedClientTags: dataArg.serverDefinedClientTags,
|
|
183
|
+
forceDestinationSmartproxy: dataArg.forceDestinationSmartproxy,
|
|
184
|
+
destinationAllowList: dataArg.destinationAllowList,
|
|
185
|
+
destinationBlockList: dataArg.destinationBlockList,
|
|
186
|
+
useHostIp: dataArg.useHostIp,
|
|
187
|
+
useDhcp: dataArg.useDhcp,
|
|
188
|
+
staticIp: dataArg.staticIp,
|
|
189
|
+
forceVlan: dataArg.forceVlan,
|
|
190
|
+
vlanId: dataArg.vlanId,
|
|
154
191
|
});
|
|
155
192
|
return { success: true };
|
|
156
193
|
} catch (err: unknown) {
|
package/ts/paths.ts
CHANGED
|
@@ -34,7 +34,6 @@ export function resolvePaths(baseDir?: string) {
|
|
|
34
34
|
dcrouterHomeDir: root,
|
|
35
35
|
dataDir: resolvedDataDir,
|
|
36
36
|
defaultTsmDbPath: plugins.path.join(root, 'tsmdb'),
|
|
37
|
-
defaultStoragePath: plugins.path.join(root, 'storage'),
|
|
38
37
|
dnsRecordsDir: plugins.path.join(resolvedDataDir, 'dns'),
|
|
39
38
|
};
|
|
40
39
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
2
|
import { logger } from '../logger.js';
|
|
3
|
-
import
|
|
3
|
+
import { AccountingSessionDoc } from '../db/index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* RADIUS accounting session
|
|
@@ -84,8 +84,6 @@ export interface IAccountingSummary {
|
|
|
84
84
|
* Accounting manager configuration
|
|
85
85
|
*/
|
|
86
86
|
export interface IAccountingManagerConfig {
|
|
87
|
-
/** Storage key prefix */
|
|
88
|
-
storagePrefix?: string;
|
|
89
87
|
/** Session retention period in days (default: 30) */
|
|
90
88
|
retentionDays?: number;
|
|
91
89
|
/** Enable detailed session logging */
|
|
@@ -106,7 +104,6 @@ export interface IAccountingManagerConfig {
|
|
|
106
104
|
export class AccountingManager {
|
|
107
105
|
private activeSessions: Map<string, IAccountingSession> = new Map();
|
|
108
106
|
private config: Required<IAccountingManagerConfig>;
|
|
109
|
-
private storageManager?: StorageManager;
|
|
110
107
|
private staleSessionSweepTimer?: ReturnType<typeof setInterval>;
|
|
111
108
|
|
|
112
109
|
// Counters for statistics
|
|
@@ -118,24 +115,20 @@ export class AccountingManager {
|
|
|
118
115
|
interimUpdatesReceived: 0,
|
|
119
116
|
};
|
|
120
117
|
|
|
121
|
-
constructor(config?: IAccountingManagerConfig
|
|
118
|
+
constructor(config?: IAccountingManagerConfig) {
|
|
122
119
|
this.config = {
|
|
123
|
-
storagePrefix: config?.storagePrefix ?? '/radius/accounting',
|
|
124
120
|
retentionDays: config?.retentionDays ?? 30,
|
|
125
121
|
detailedLogging: config?.detailedLogging ?? false,
|
|
126
122
|
maxActiveSessions: config?.maxActiveSessions ?? 10000,
|
|
127
123
|
staleSessionTimeoutHours: config?.staleSessionTimeoutHours ?? 24,
|
|
128
124
|
};
|
|
129
|
-
this.storageManager = storageManager;
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
/**
|
|
133
128
|
* Initialize the accounting manager
|
|
134
129
|
*/
|
|
135
130
|
async initialize(): Promise<void> {
|
|
136
|
-
|
|
137
|
-
await this.loadActiveSessions();
|
|
138
|
-
}
|
|
131
|
+
await this.loadActiveSessions();
|
|
139
132
|
|
|
140
133
|
// Start periodic sweep to evict stale sessions (every 15 minutes)
|
|
141
134
|
this.staleSessionSweepTimer = setInterval(() => {
|
|
@@ -176,9 +169,7 @@ export class AccountingManager {
|
|
|
176
169
|
session.endTime = Date.now();
|
|
177
170
|
session.sessionTime = Math.floor((session.endTime - session.startTime) / 1000);
|
|
178
171
|
|
|
179
|
-
|
|
180
|
-
this.archiveSession(session).catch(() => {});
|
|
181
|
-
}
|
|
172
|
+
this.persistSession(session).catch(() => {});
|
|
182
173
|
|
|
183
174
|
this.activeSessions.delete(sessionId);
|
|
184
175
|
swept++;
|
|
@@ -250,9 +241,7 @@ export class AccountingManager {
|
|
|
250
241
|
}
|
|
251
242
|
|
|
252
243
|
// Persist session
|
|
253
|
-
|
|
254
|
-
await this.persistSession(session);
|
|
255
|
-
}
|
|
244
|
+
await this.persistSession(session);
|
|
256
245
|
}
|
|
257
246
|
|
|
258
247
|
/**
|
|
@@ -298,9 +287,7 @@ export class AccountingManager {
|
|
|
298
287
|
}
|
|
299
288
|
|
|
300
289
|
// Update persisted session
|
|
301
|
-
|
|
302
|
-
await this.persistSession(session);
|
|
303
|
-
}
|
|
290
|
+
await this.persistSession(session);
|
|
304
291
|
}
|
|
305
292
|
|
|
306
293
|
/**
|
|
@@ -353,10 +340,8 @@ export class AccountingManager {
|
|
|
353
340
|
logger.log('info', `Accounting Stop: session=${data.sessionId}, duration=${session.sessionTime}s, in=${session.inputOctets}, out=${session.outputOctets}`);
|
|
354
341
|
}
|
|
355
342
|
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
await this.archiveSession(session);
|
|
359
|
-
}
|
|
343
|
+
// Update status in the database (single collection, no active->archive move needed)
|
|
344
|
+
await this.persistSession(session);
|
|
360
345
|
|
|
361
346
|
// Remove from active sessions
|
|
362
347
|
this.activeSessions.delete(data.sessionId);
|
|
@@ -493,23 +478,16 @@ export class AccountingManager {
|
|
|
493
478
|
* Clean up old archived sessions based on retention policy
|
|
494
479
|
*/
|
|
495
480
|
async cleanupOldSessions(): Promise<number> {
|
|
496
|
-
if (!this.storageManager) {
|
|
497
|
-
return 0;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
481
|
const cutoffTime = Date.now() - this.config.retentionDays * 24 * 60 * 60 * 1000;
|
|
501
482
|
let deletedCount = 0;
|
|
502
483
|
|
|
503
484
|
try {
|
|
504
|
-
const
|
|
485
|
+
const oldDocs = await AccountingSessionDoc.findStoppedBefore(cutoffTime);
|
|
505
486
|
|
|
506
|
-
for (const
|
|
487
|
+
for (const doc of oldDocs) {
|
|
507
488
|
try {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
await this.storageManager.delete(key);
|
|
511
|
-
deletedCount++;
|
|
512
|
-
}
|
|
489
|
+
await doc.delete();
|
|
490
|
+
deletedCount++;
|
|
513
491
|
} catch (error) {
|
|
514
492
|
// Ignore individual errors
|
|
515
493
|
}
|
|
@@ -552,9 +530,7 @@ export class AccountingManager {
|
|
|
552
530
|
session.terminateCause = 'SessionEvicted';
|
|
553
531
|
session.endTime = Date.now();
|
|
554
532
|
|
|
555
|
-
|
|
556
|
-
await this.archiveSession(session);
|
|
557
|
-
}
|
|
533
|
+
await this.persistSession(session);
|
|
558
534
|
|
|
559
535
|
this.activeSessions.delete(sessionId);
|
|
560
536
|
logger.log('warn', `Evicted session ${sessionId} due to capacity limit`);
|
|
@@ -562,25 +538,38 @@ export class AccountingManager {
|
|
|
562
538
|
}
|
|
563
539
|
|
|
564
540
|
/**
|
|
565
|
-
* Load active sessions from
|
|
541
|
+
* Load active sessions from database
|
|
566
542
|
*/
|
|
567
543
|
private async loadActiveSessions(): Promise<void> {
|
|
568
|
-
if (!this.storageManager) {
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
544
|
try {
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
for (const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
545
|
+
const docs = await AccountingSessionDoc.findActive();
|
|
546
|
+
|
|
547
|
+
for (const doc of docs) {
|
|
548
|
+
const session: IAccountingSession = {
|
|
549
|
+
sessionId: doc.sessionId,
|
|
550
|
+
username: doc.username,
|
|
551
|
+
macAddress: doc.macAddress,
|
|
552
|
+
nasIpAddress: doc.nasIpAddress,
|
|
553
|
+
nasPort: doc.nasPort,
|
|
554
|
+
nasPortType: doc.nasPortType,
|
|
555
|
+
nasIdentifier: doc.nasIdentifier,
|
|
556
|
+
vlanId: doc.vlanId,
|
|
557
|
+
framedIpAddress: doc.framedIpAddress,
|
|
558
|
+
calledStationId: doc.calledStationId,
|
|
559
|
+
callingStationId: doc.callingStationId,
|
|
560
|
+
startTime: doc.startTime,
|
|
561
|
+
endTime: doc.endTime,
|
|
562
|
+
lastUpdateTime: doc.lastUpdateTime,
|
|
563
|
+
status: doc.status,
|
|
564
|
+
terminateCause: doc.terminateCause,
|
|
565
|
+
inputOctets: doc.inputOctets,
|
|
566
|
+
outputOctets: doc.outputOctets,
|
|
567
|
+
inputPackets: doc.inputPackets,
|
|
568
|
+
outputPackets: doc.outputPackets,
|
|
569
|
+
sessionTime: doc.sessionTime,
|
|
570
|
+
serviceType: doc.serviceType,
|
|
571
|
+
};
|
|
572
|
+
this.activeSessions.set(session.sessionId, session);
|
|
584
573
|
}
|
|
585
574
|
} catch (error: unknown) {
|
|
586
575
|
logger.log('warn', `Failed to load active sessions: ${(error as Error).message}`);
|
|
@@ -588,70 +577,59 @@ export class AccountingManager {
|
|
|
588
577
|
}
|
|
589
578
|
|
|
590
579
|
/**
|
|
591
|
-
* Persist a session to
|
|
580
|
+
* Persist a session to the database (create or update)
|
|
592
581
|
*/
|
|
593
582
|
private async persistSession(session: IAccountingSession): Promise<void> {
|
|
594
|
-
if (!this.storageManager) {
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
const key = `${this.config.storagePrefix}/active/${session.sessionId}.json`;
|
|
599
583
|
try {
|
|
600
|
-
await
|
|
584
|
+
let doc = await AccountingSessionDoc.findBySessionId(session.sessionId);
|
|
585
|
+
if (!doc) {
|
|
586
|
+
doc = new AccountingSessionDoc();
|
|
587
|
+
}
|
|
588
|
+
Object.assign(doc, session);
|
|
589
|
+
await doc.save();
|
|
601
590
|
} catch (error: unknown) {
|
|
602
591
|
logger.log('error', `Failed to persist session ${session.sessionId}: ${(error as Error).message}`);
|
|
603
592
|
}
|
|
604
593
|
}
|
|
605
594
|
|
|
606
595
|
/**
|
|
607
|
-
*
|
|
608
|
-
*/
|
|
609
|
-
private async archiveSession(session: IAccountingSession): Promise<void> {
|
|
610
|
-
if (!this.storageManager) {
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
try {
|
|
615
|
-
// Remove from active
|
|
616
|
-
const activeKey = `${this.config.storagePrefix}/active/${session.sessionId}.json`;
|
|
617
|
-
await this.storageManager.delete(activeKey);
|
|
618
|
-
|
|
619
|
-
// Add to archive with date-based path
|
|
620
|
-
const date = new Date(session.endTime);
|
|
621
|
-
const archiveKey = `${this.config.storagePrefix}/archive/${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}/${session.sessionId}.json`;
|
|
622
|
-
await this.storageManager.setJSON(archiveKey, session);
|
|
623
|
-
} catch (error: unknown) {
|
|
624
|
-
logger.log('error', `Failed to archive session ${session.sessionId}: ${(error as Error).message}`);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/**
|
|
629
|
-
* Get archived sessions for a time period
|
|
596
|
+
* Get archived (stopped/terminated) sessions for a time period
|
|
630
597
|
*/
|
|
631
598
|
private async getArchivedSessions(startTime: number, endTime: number): Promise<IAccountingSession[]> {
|
|
632
|
-
if (!this.storageManager) {
|
|
633
|
-
return [];
|
|
634
|
-
}
|
|
635
|
-
|
|
636
599
|
const sessions: IAccountingSession[] = [];
|
|
637
600
|
|
|
638
601
|
try {
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
602
|
+
const docs = await AccountingSessionDoc.getInstances({
|
|
603
|
+
status: { $in: ['stopped', 'terminated'] } as any,
|
|
604
|
+
endTime: { $gt: 0, $gte: startTime } as any,
|
|
605
|
+
startTime: { $lte: endTime } as any,
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
for (const doc of docs) {
|
|
609
|
+
sessions.push({
|
|
610
|
+
sessionId: doc.sessionId,
|
|
611
|
+
username: doc.username,
|
|
612
|
+
macAddress: doc.macAddress,
|
|
613
|
+
nasIpAddress: doc.nasIpAddress,
|
|
614
|
+
nasPort: doc.nasPort,
|
|
615
|
+
nasPortType: doc.nasPortType,
|
|
616
|
+
nasIdentifier: doc.nasIdentifier,
|
|
617
|
+
vlanId: doc.vlanId,
|
|
618
|
+
framedIpAddress: doc.framedIpAddress,
|
|
619
|
+
calledStationId: doc.calledStationId,
|
|
620
|
+
callingStationId: doc.callingStationId,
|
|
621
|
+
startTime: doc.startTime,
|
|
622
|
+
endTime: doc.endTime,
|
|
623
|
+
lastUpdateTime: doc.lastUpdateTime,
|
|
624
|
+
status: doc.status,
|
|
625
|
+
terminateCause: doc.terminateCause,
|
|
626
|
+
inputOctets: doc.inputOctets,
|
|
627
|
+
outputOctets: doc.outputOctets,
|
|
628
|
+
inputPackets: doc.inputPackets,
|
|
629
|
+
outputPackets: doc.outputPackets,
|
|
630
|
+
sessionTime: doc.sessionTime,
|
|
631
|
+
serviceType: doc.serviceType,
|
|
632
|
+
});
|
|
655
633
|
}
|
|
656
634
|
} catch (error: unknown) {
|
|
657
635
|
logger.log('warn', `Failed to get archived sessions: ${(error as Error).message}`);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
2
|
import { logger } from '../logger.js';
|
|
3
|
-
import type { StorageManager } from '../storage/index.js';
|
|
4
3
|
import { VlanManager, type IMacVlanMapping, type IVlanManagerConfig } from './classes.vlan.manager.js';
|
|
5
4
|
import { AccountingManager, type IAccountingSession, type IAccountingManagerConfig } from './classes.accounting.manager.js';
|
|
6
5
|
|
|
@@ -92,7 +91,6 @@ export class RadiusServer {
|
|
|
92
91
|
private vlanManager: VlanManager;
|
|
93
92
|
private accountingManager: AccountingManager;
|
|
94
93
|
private config: IRadiusServerConfig;
|
|
95
|
-
private storageManager?: StorageManager;
|
|
96
94
|
private clientSecrets: Map<string, string> = new Map();
|
|
97
95
|
private running: boolean = false;
|
|
98
96
|
|
|
@@ -105,20 +103,19 @@ export class RadiusServer {
|
|
|
105
103
|
startTime: 0,
|
|
106
104
|
};
|
|
107
105
|
|
|
108
|
-
constructor(config: IRadiusServerConfig
|
|
106
|
+
constructor(config: IRadiusServerConfig) {
|
|
109
107
|
this.config = {
|
|
110
108
|
authPort: config.authPort ?? 1812,
|
|
111
109
|
acctPort: config.acctPort ?? 1813,
|
|
112
110
|
bindAddress: config.bindAddress ?? '0.0.0.0',
|
|
113
111
|
...config,
|
|
114
112
|
};
|
|
115
|
-
this.storageManager = storageManager;
|
|
116
113
|
|
|
117
114
|
// Initialize VLAN manager
|
|
118
|
-
this.vlanManager = new VlanManager(config.vlanAssignment
|
|
115
|
+
this.vlanManager = new VlanManager(config.vlanAssignment);
|
|
119
116
|
|
|
120
117
|
// Initialize accounting manager
|
|
121
|
-
this.accountingManager = new AccountingManager(config.accounting
|
|
118
|
+
this.accountingManager = new AccountingManager(config.accounting);
|
|
122
119
|
}
|
|
123
120
|
|
|
124
121
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
2
|
import { logger } from '../logger.js';
|
|
3
|
-
import
|
|
3
|
+
import { VlanMappingsDoc } from '../db/index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* MAC address to VLAN mapping
|
|
@@ -42,8 +42,6 @@ export interface IVlanManagerConfig {
|
|
|
42
42
|
defaultVlan?: number;
|
|
43
43
|
/** Whether to allow unknown MACs (assign default VLAN) or reject */
|
|
44
44
|
allowUnknownMacs?: boolean;
|
|
45
|
-
/** Storage key prefix for persistence */
|
|
46
|
-
storagePrefix?: string;
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
/**
|
|
@@ -56,27 +54,22 @@ export interface IVlanManagerConfig {
|
|
|
56
54
|
export class VlanManager {
|
|
57
55
|
private mappings: Map<string, IMacVlanMapping> = new Map();
|
|
58
56
|
private config: Required<IVlanManagerConfig>;
|
|
59
|
-
private storageManager?: StorageManager;
|
|
60
57
|
|
|
61
58
|
// Cache for normalized MAC lookups
|
|
62
59
|
private normalizedMacCache: Map<string, string> = new Map();
|
|
63
60
|
|
|
64
|
-
constructor(config?: IVlanManagerConfig
|
|
61
|
+
constructor(config?: IVlanManagerConfig) {
|
|
65
62
|
this.config = {
|
|
66
63
|
defaultVlan: config?.defaultVlan ?? 1,
|
|
67
64
|
allowUnknownMacs: config?.allowUnknownMacs ?? true,
|
|
68
|
-
storagePrefix: config?.storagePrefix ?? '/radius/vlan-mappings',
|
|
69
65
|
};
|
|
70
|
-
this.storageManager = storageManager;
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
/**
|
|
74
69
|
* Initialize the VLAN manager and load persisted mappings
|
|
75
70
|
*/
|
|
76
71
|
async initialize(): Promise<void> {
|
|
77
|
-
|
|
78
|
-
await this.loadMappings();
|
|
79
|
-
}
|
|
72
|
+
await this.loadMappings();
|
|
80
73
|
logger.log('info', `VlanManager initialized with ${this.mappings.size} mappings, default VLAN: ${this.config.defaultVlan}`);
|
|
81
74
|
}
|
|
82
75
|
|
|
@@ -157,10 +150,8 @@ export class VlanManager {
|
|
|
157
150
|
|
|
158
151
|
this.mappings.set(normalizedMac, fullMapping);
|
|
159
152
|
|
|
160
|
-
// Persist to
|
|
161
|
-
|
|
162
|
-
await this.saveMappings();
|
|
163
|
-
}
|
|
153
|
+
// Persist to database
|
|
154
|
+
await this.saveMappings();
|
|
164
155
|
|
|
165
156
|
logger.log('info', `VLAN mapping ${existingMapping ? 'updated' : 'added'}: ${normalizedMac} -> VLAN ${mapping.vlan}`);
|
|
166
157
|
return fullMapping;
|
|
@@ -173,7 +164,7 @@ export class VlanManager {
|
|
|
173
164
|
const normalizedMac = this.normalizeMac(mac);
|
|
174
165
|
const removed = this.mappings.delete(normalizedMac);
|
|
175
166
|
|
|
176
|
-
if (removed
|
|
167
|
+
if (removed) {
|
|
177
168
|
await this.saveMappings();
|
|
178
169
|
logger.log('info', `VLAN mapping removed: ${normalizedMac}`);
|
|
179
170
|
}
|
|
@@ -333,39 +324,36 @@ export class VlanManager {
|
|
|
333
324
|
}
|
|
334
325
|
|
|
335
326
|
/**
|
|
336
|
-
* Load mappings from
|
|
327
|
+
* Load mappings from database
|
|
337
328
|
*/
|
|
338
329
|
private async loadMappings(): Promise<void> {
|
|
339
|
-
if (!this.storageManager) {
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
330
|
try {
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
346
|
-
for (const mapping of
|
|
331
|
+
const doc = await VlanMappingsDoc.load();
|
|
332
|
+
if (doc && Array.isArray(doc.mappings)) {
|
|
333
|
+
for (const mapping of doc.mappings) {
|
|
347
334
|
this.mappings.set(this.normalizeMac(mapping.mac), mapping);
|
|
348
335
|
}
|
|
349
|
-
logger.log('info', `Loaded ${
|
|
336
|
+
logger.log('info', `Loaded ${doc.mappings.length} VLAN mappings from database`);
|
|
350
337
|
}
|
|
351
338
|
} catch (error: unknown) {
|
|
352
|
-
logger.log('warn', `Failed to load VLAN mappings from
|
|
339
|
+
logger.log('warn', `Failed to load VLAN mappings from database: ${(error as Error).message}`);
|
|
353
340
|
}
|
|
354
341
|
}
|
|
355
342
|
|
|
356
343
|
/**
|
|
357
|
-
* Save mappings to
|
|
344
|
+
* Save mappings to database
|
|
358
345
|
*/
|
|
359
346
|
private async saveMappings(): Promise<void> {
|
|
360
|
-
if (!this.storageManager) {
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
347
|
try {
|
|
365
348
|
const mappings = Array.from(this.mappings.values());
|
|
366
|
-
await
|
|
349
|
+
let doc = await VlanMappingsDoc.load();
|
|
350
|
+
if (!doc) {
|
|
351
|
+
doc = new VlanMappingsDoc();
|
|
352
|
+
}
|
|
353
|
+
doc.mappings = mappings;
|
|
354
|
+
await doc.save();
|
|
367
355
|
} catch (error: unknown) {
|
|
368
|
-
logger.log('error', `Failed to save VLAN mappings to
|
|
356
|
+
logger.log('error', `Failed to save VLAN mappings to database: ${(error as Error).message}`);
|
|
369
357
|
}
|
|
370
358
|
}
|
|
371
359
|
}
|
package/ts/radius/index.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - VLAN assignment based on MAC addresses
|
|
7
7
|
* - OUI (vendor prefix) pattern matching for device categorization
|
|
8
8
|
* - RADIUS accounting for session tracking and billing
|
|
9
|
-
* - Integration with
|
|
9
|
+
* - Integration with smartdata document classes for persistence
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
export * from './classes.radius.server.js';
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
|
-
import type { StorageManager } from '../storage/classes.storagemanager.js';
|
|
3
2
|
import type { IRemoteIngress, IDcRouterRouteConfig } from '../../ts_interfaces/data/remoteingress.js';
|
|
4
|
-
|
|
5
|
-
const STORAGE_PREFIX = '/remote-ingress/';
|
|
3
|
+
import { RemoteIngressEdgeDoc } from '../db/index.js';
|
|
6
4
|
|
|
7
5
|
/**
|
|
8
6
|
* Flatten a port range (number | number[] | Array<{from, to}>) to a sorted unique number array.
|
|
@@ -27,33 +25,40 @@ function extractPorts(portRange: number | Array<number | { from: number; to: num
|
|
|
27
25
|
|
|
28
26
|
/**
|
|
29
27
|
* Manages CRUD for remote ingress edge registrations.
|
|
30
|
-
* Persists edge configs via
|
|
28
|
+
* Persists edge configs via smartdata document classes and provides
|
|
31
29
|
* the allowed edges list for the Rust hub.
|
|
32
30
|
*/
|
|
33
31
|
export class RemoteIngressManager {
|
|
34
|
-
private storageManager: StorageManager;
|
|
35
32
|
private edges: Map<string, IRemoteIngress> = new Map();
|
|
36
33
|
private routes: IDcRouterRouteConfig[] = [];
|
|
37
34
|
|
|
38
|
-
constructor(
|
|
39
|
-
this.storageManager = storageManager;
|
|
35
|
+
constructor() {
|
|
40
36
|
}
|
|
41
37
|
|
|
42
38
|
/**
|
|
43
|
-
* Load all edge registrations from
|
|
39
|
+
* Load all edge registrations from the database into memory.
|
|
44
40
|
*/
|
|
45
41
|
public async initialize(): Promise<void> {
|
|
46
|
-
const
|
|
47
|
-
for (const
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
edge.autoDerivePorts = true;
|
|
53
|
-
await this.storageManager.setJSON(key, edge);
|
|
54
|
-
}
|
|
55
|
-
this.edges.set(edge.id, edge);
|
|
42
|
+
const docs = await RemoteIngressEdgeDoc.findAll();
|
|
43
|
+
for (const doc of docs) {
|
|
44
|
+
// Migration: old edges without autoDerivePorts default to true
|
|
45
|
+
if ((doc as any).autoDerivePorts === undefined) {
|
|
46
|
+
doc.autoDerivePorts = true;
|
|
47
|
+
await doc.save();
|
|
56
48
|
}
|
|
49
|
+
const edge: IRemoteIngress = {
|
|
50
|
+
id: doc.id,
|
|
51
|
+
name: doc.name,
|
|
52
|
+
secret: doc.secret,
|
|
53
|
+
listenPorts: doc.listenPorts,
|
|
54
|
+
listenPortsUdp: doc.listenPortsUdp,
|
|
55
|
+
enabled: doc.enabled,
|
|
56
|
+
autoDerivePorts: doc.autoDerivePorts,
|
|
57
|
+
tags: doc.tags,
|
|
58
|
+
createdAt: doc.createdAt,
|
|
59
|
+
updatedAt: doc.updatedAt,
|
|
60
|
+
};
|
|
61
|
+
this.edges.set(edge.id, edge);
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
64
|
|
|
@@ -189,7 +194,9 @@ export class RemoteIngressManager {
|
|
|
189
194
|
updatedAt: now,
|
|
190
195
|
};
|
|
191
196
|
|
|
192
|
-
|
|
197
|
+
const doc = new RemoteIngressEdgeDoc();
|
|
198
|
+
Object.assign(doc, edge);
|
|
199
|
+
await doc.save();
|
|
193
200
|
this.edges.set(id, edge);
|
|
194
201
|
return edge;
|
|
195
202
|
}
|
|
@@ -233,7 +240,11 @@ export class RemoteIngressManager {
|
|
|
233
240
|
if (updates.tags !== undefined) edge.tags = updates.tags;
|
|
234
241
|
edge.updatedAt = Date.now();
|
|
235
242
|
|
|
236
|
-
await
|
|
243
|
+
const doc = await RemoteIngressEdgeDoc.findById(id);
|
|
244
|
+
if (doc) {
|
|
245
|
+
Object.assign(doc, edge);
|
|
246
|
+
await doc.save();
|
|
247
|
+
}
|
|
237
248
|
this.edges.set(id, edge);
|
|
238
249
|
return edge;
|
|
239
250
|
}
|
|
@@ -245,7 +256,10 @@ export class RemoteIngressManager {
|
|
|
245
256
|
if (!this.edges.has(id)) {
|
|
246
257
|
return false;
|
|
247
258
|
}
|
|
248
|
-
await
|
|
259
|
+
const doc = await RemoteIngressEdgeDoc.findById(id);
|
|
260
|
+
if (doc) {
|
|
261
|
+
await doc.delete();
|
|
262
|
+
}
|
|
249
263
|
this.edges.delete(id);
|
|
250
264
|
return true;
|
|
251
265
|
}
|
|
@@ -262,7 +276,11 @@ export class RemoteIngressManager {
|
|
|
262
276
|
edge.secret = plugins.crypto.randomBytes(32).toString('hex');
|
|
263
277
|
edge.updatedAt = Date.now();
|
|
264
278
|
|
|
265
|
-
await
|
|
279
|
+
const doc = await RemoteIngressEdgeDoc.findById(id);
|
|
280
|
+
if (doc) {
|
|
281
|
+
Object.assign(doc, edge);
|
|
282
|
+
await doc.save();
|
|
283
|
+
}
|
|
266
284
|
this.edges.set(id, edge);
|
|
267
285
|
return edge.secret;
|
|
268
286
|
}
|