Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df9cc3e49b | |||
| 7f3ab2499d |
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-04-26 - 13.25.0 - feat(security)
|
||||||
|
compile network ranges and CIDR arrays into edge firewall policies
|
||||||
|
|
||||||
|
- add support for storing intelligence network CIDR arrays alongside single network ranges
|
||||||
|
- convert start-end IPv4 ranges into CIDR blocks when compiling security policies
|
||||||
|
- always return an explicit remote ingress firewall snapshot with a blockedIps array
|
||||||
|
- add tests covering range normalization, ASN-derived CIDRs, and empty firewall snapshots
|
||||||
|
|
||||||
## 2026-04-26 - 13.24.0 - feat(security)
|
## 2026-04-26 - 13.24.0 - feat(security)
|
||||||
add security policy management and IP intelligence operations to the ops UI
|
add security policy management and IP intelligence operations to the ops UI
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/dcrouter",
|
"name": "@serve.zone/dcrouter",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "13.24.0",
|
"version": "13.25.0",
|
||||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"@push.rocks/smartmetrics": "^3.0.3",
|
"@push.rocks/smartmetrics": "^3.0.3",
|
||||||
"@push.rocks/smartmigration": "1.2.0",
|
"@push.rocks/smartmigration": "1.2.0",
|
||||||
"@push.rocks/smartmta": "^5.3.3",
|
"@push.rocks/smartmta": "^5.3.3",
|
||||||
"@push.rocks/smartnetwork": "^4.6.0",
|
"@push.rocks/smartnetwork": "^4.7.0",
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartproxy": "^27.9.0",
|
"@push.rocks/smartproxy": "^27.9.0",
|
||||||
|
|||||||
Generated
+8
-8
@@ -72,8 +72,8 @@ importers:
|
|||||||
specifier: ^5.3.3
|
specifier: ^5.3.3
|
||||||
version: 5.3.3
|
version: 5.3.3
|
||||||
'@push.rocks/smartnetwork':
|
'@push.rocks/smartnetwork':
|
||||||
specifier: ^4.6.0
|
specifier: ^4.7.0
|
||||||
version: 4.6.0
|
version: 4.7.0
|
||||||
'@push.rocks/smartpath':
|
'@push.rocks/smartpath':
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
@@ -1257,8 +1257,8 @@ packages:
|
|||||||
'@push.rocks/smartmustache@3.0.2':
|
'@push.rocks/smartmustache@3.0.2':
|
||||||
resolution: {integrity: sha512-G3LyRXoJhyM+iQhkvP/MR/2WYMvC9U7zc2J44JxUM5tPdkQ+o3++FbfRtnZj6rz5X/A7q03//vsxPitVQwoi2Q==}
|
resolution: {integrity: sha512-G3LyRXoJhyM+iQhkvP/MR/2WYMvC9U7zc2J44JxUM5tPdkQ+o3++FbfRtnZj6rz5X/A7q03//vsxPitVQwoi2Q==}
|
||||||
|
|
||||||
'@push.rocks/smartnetwork@4.6.0':
|
'@push.rocks/smartnetwork@4.7.0':
|
||||||
resolution: {integrity: sha512-ubaO/Qp8r30A+qwk33M/0+nQi+o8gNHEI9zq3jv1MwqiLxhiV1hnbr4CL9AUcvs4EhwUBiw0EswKjCJROwDqvQ==}
|
resolution: {integrity: sha512-WZ46pJlklDRcw1AqkyyBhmGSNSK3i7IYM9D9vcVJOUhlLmgUSai8o1NbpWlb7HvOkp1IhQ7iZeuJV2JiWLtl1g==}
|
||||||
|
|
||||||
'@push.rocks/smartnftables@1.1.0':
|
'@push.rocks/smartnftables@1.1.0':
|
||||||
resolution: {integrity: sha512-7JNzerlW20HEl2wKMBIHltwneCQRpXiD2lJkXZZc02ctnfjgFejXVDIeWomhPx6PZ0Z6zmqdF6rrFDtDHyqqfA==}
|
resolution: {integrity: sha512-7JNzerlW20HEl2wKMBIHltwneCQRpXiD2lJkXZZc02ctnfjgFejXVDIeWomhPx6PZ0Z6zmqdF6rrFDtDHyqqfA==}
|
||||||
@@ -5163,7 +5163,7 @@ snapshots:
|
|||||||
'@push.rocks/smartjson': 6.0.0
|
'@push.rocks/smartjson': 6.0.0
|
||||||
'@push.rocks/smartlog': 3.2.2
|
'@push.rocks/smartlog': 3.2.2
|
||||||
'@push.rocks/smartmongo': 5.1.1(socks@2.8.7)
|
'@push.rocks/smartmongo': 5.1.1(socks@2.8.7)
|
||||||
'@push.rocks/smartnetwork': 4.6.0
|
'@push.rocks/smartnetwork': 4.7.0
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrequest': 5.0.1
|
'@push.rocks/smartrequest': 5.0.1
|
||||||
@@ -5966,7 +5966,7 @@ snapshots:
|
|||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartdns': 7.9.0
|
'@push.rocks/smartdns': 7.9.0
|
||||||
'@push.rocks/smartlog': 3.2.2
|
'@push.rocks/smartlog': 3.2.2
|
||||||
'@push.rocks/smartnetwork': 4.6.0
|
'@push.rocks/smartnetwork': 4.7.0
|
||||||
'@push.rocks/smartstring': 4.1.0
|
'@push.rocks/smartstring': 4.1.0
|
||||||
'@push.rocks/smarttime': 4.2.3
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@push.rocks/smartunique': 3.0.9
|
'@push.rocks/smartunique': 3.0.9
|
||||||
@@ -6433,7 +6433,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
handlebars: 4.7.9
|
handlebars: 4.7.9
|
||||||
|
|
||||||
'@push.rocks/smartnetwork@4.6.0':
|
'@push.rocks/smartnetwork@4.7.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdns': 7.9.0
|
'@push.rocks/smartdns': 7.9.0
|
||||||
'@push.rocks/smartrust': 1.3.2
|
'@push.rocks/smartrust': 1.3.2
|
||||||
@@ -6499,7 +6499,7 @@ snapshots:
|
|||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartfs': 1.5.0
|
'@push.rocks/smartfs': 1.5.0
|
||||||
'@push.rocks/smartjimp': 1.2.0
|
'@push.rocks/smartjimp': 1.2.0
|
||||||
'@push.rocks/smartnetwork': 4.6.0
|
'@push.rocks/smartnetwork': 4.7.0
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartpuppeteer': 2.0.5(typescript@6.0.2)
|
'@push.rocks/smartpuppeteer': 2.0.5(typescript@6.0.2)
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import * as plugins from '../ts/plugins.js';
|
||||||
|
import { DcRouterDb, IpIntelligenceDoc, SecurityBlockRuleDoc, SecurityPolicyAuditDoc } from '../ts/db/index.js';
|
||||||
|
import { SecurityPolicyManager } from '../ts/security/index.js';
|
||||||
|
|
||||||
|
const createTestDb = async () => {
|
||||||
|
const storagePath = plugins.path.join(
|
||||||
|
plugins.os.tmpdir(),
|
||||||
|
`dcrouter-security-policy-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
DcRouterDb.resetInstance();
|
||||||
|
const db = DcRouterDb.getInstance({
|
||||||
|
storagePath,
|
||||||
|
dbName: `dcrouter-security-policy-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||||
|
});
|
||||||
|
await db.start();
|
||||||
|
await db.getDb().mongoDb.createCollection('__test_init');
|
||||||
|
|
||||||
|
return {
|
||||||
|
async cleanup() {
|
||||||
|
await db.stop();
|
||||||
|
DcRouterDb.resetInstance();
|
||||||
|
await plugins.fs.promises.rm(storagePath, { recursive: true, force: true });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const testDbPromise = createTestDb();
|
||||||
|
|
||||||
|
const clearTestState = async () => {
|
||||||
|
for (const rule of await SecurityBlockRuleDoc.findAll()) {
|
||||||
|
await rule.delete();
|
||||||
|
}
|
||||||
|
for (const record of await IpIntelligenceDoc.findAll()) {
|
||||||
|
await record.delete();
|
||||||
|
}
|
||||||
|
for (const event of await SecurityPolicyAuditDoc.findRecent(1000)) {
|
||||||
|
await event.delete();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tap.test('SecurityPolicyManager compiles start-end CIDR rules for edge firewall snapshots', async () => {
|
||||||
|
await testDbPromise;
|
||||||
|
await clearTestState();
|
||||||
|
const manager = new SecurityPolicyManager();
|
||||||
|
|
||||||
|
await manager.createBlockRule({
|
||||||
|
type: 'cidr',
|
||||||
|
value: '203.0.113.0 - 203.0.113.255',
|
||||||
|
reason: 'test range',
|
||||||
|
});
|
||||||
|
|
||||||
|
const policy = await manager.compilePolicy();
|
||||||
|
expect(policy.blockedCidrs).toEqual(['203.0.113.0/24']);
|
||||||
|
|
||||||
|
const firewall = await manager.compileRemoteIngressFirewall();
|
||||||
|
expect(firewall.blockedIps).toEqual(['203.0.113.0/24']);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('SecurityPolicyManager compiles intelligence network ranges for ASN rules', async () => {
|
||||||
|
await testDbPromise;
|
||||||
|
await clearTestState();
|
||||||
|
const manager = new SecurityPolicyManager();
|
||||||
|
|
||||||
|
const intelligenceDoc = new IpIntelligenceDoc();
|
||||||
|
intelligenceDoc.ipAddress = '198.51.100.23';
|
||||||
|
intelligenceDoc.asn = 64500;
|
||||||
|
intelligenceDoc.asnOrg = 'Example Network';
|
||||||
|
intelligenceDoc.networkRange = '198.51.100.0 - 198.51.100.127';
|
||||||
|
intelligenceDoc.firstSeenAt = Date.now();
|
||||||
|
intelligenceDoc.lastSeenAt = Date.now();
|
||||||
|
intelligenceDoc.updatedAt = Date.now();
|
||||||
|
intelligenceDoc.seenCount = 1;
|
||||||
|
await intelligenceDoc.save();
|
||||||
|
|
||||||
|
await manager.createBlockRule({
|
||||||
|
type: 'asn',
|
||||||
|
value: 'AS64500',
|
||||||
|
reason: 'test asn range',
|
||||||
|
});
|
||||||
|
|
||||||
|
const policy = await manager.compilePolicy();
|
||||||
|
expect(policy.blockedCidrs).toEqual(['198.51.100.0/25']);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('SecurityPolicyManager compiles intelligence CIDR arrays for ASN rules', async () => {
|
||||||
|
await testDbPromise;
|
||||||
|
await clearTestState();
|
||||||
|
const manager = new SecurityPolicyManager();
|
||||||
|
|
||||||
|
const intelligenceDoc = new IpIntelligenceDoc();
|
||||||
|
intelligenceDoc.ipAddress = '198.51.100.130';
|
||||||
|
intelligenceDoc.asn = 64501;
|
||||||
|
intelligenceDoc.asnOrg = 'Example Split Network';
|
||||||
|
intelligenceDoc.networkRange = null;
|
||||||
|
intelligenceDoc.networkCidrs = ['198.51.100.128/25', '198.51.101.0/24'];
|
||||||
|
intelligenceDoc.firstSeenAt = Date.now();
|
||||||
|
intelligenceDoc.lastSeenAt = Date.now();
|
||||||
|
intelligenceDoc.updatedAt = Date.now();
|
||||||
|
intelligenceDoc.seenCount = 1;
|
||||||
|
await intelligenceDoc.save();
|
||||||
|
|
||||||
|
await manager.createBlockRule({
|
||||||
|
type: 'asn',
|
||||||
|
value: 'AS64501',
|
||||||
|
reason: 'test asn cidr array',
|
||||||
|
});
|
||||||
|
|
||||||
|
const policy = await manager.compilePolicy();
|
||||||
|
expect(policy.blockedCidrs).toEqual(['198.51.100.128/25', '198.51.101.0/24']);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('SecurityPolicyManager returns an explicit empty edge firewall snapshot', async () => {
|
||||||
|
await testDbPromise;
|
||||||
|
await clearTestState();
|
||||||
|
const manager = new SecurityPolicyManager();
|
||||||
|
|
||||||
|
const firewall = await manager.compileRemoteIngressFirewall();
|
||||||
|
expect(firewall).toEqual({ blockedIps: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('cleanup security policy test db', async () => {
|
||||||
|
const dbHandle = await testDbPromise;
|
||||||
|
await clearTestState();
|
||||||
|
await dbHandle.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '13.24.0',
|
version: '13.25.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ export class IpIntelligenceDoc extends plugins.smartdata.SmartDataDbDoc<IpIntell
|
|||||||
@plugins.smartdata.svDb()
|
@plugins.smartdata.svDb()
|
||||||
public networkRange: string | null = null;
|
public networkRange: string | null = null;
|
||||||
|
|
||||||
|
@plugins.smartdata.svDb()
|
||||||
|
public networkCidrs: string[] | null = null;
|
||||||
|
|
||||||
@plugins.smartdata.svDb()
|
@plugins.smartdata.svDb()
|
||||||
public abuseContact: string | null = null;
|
public abuseContact: string | null = null;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export interface ISecurityPolicyManagerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IRemoteIngressFirewallSnapshot {
|
export interface IRemoteIngressFirewallSnapshot {
|
||||||
blockedIps?: string[];
|
blockedIps: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SecurityPolicyManager {
|
export class SecurityPolicyManager {
|
||||||
@@ -122,6 +122,7 @@ export class SecurityPolicyManager {
|
|||||||
registrantOrg: doc.registrantOrg,
|
registrantOrg: doc.registrantOrg,
|
||||||
registrantCountry: doc.registrantCountry,
|
registrantCountry: doc.registrantCountry,
|
||||||
networkRange: doc.networkRange,
|
networkRange: doc.networkRange,
|
||||||
|
networkCidrs: doc.networkCidrs,
|
||||||
abuseContact: doc.abuseContact,
|
abuseContact: doc.abuseContact,
|
||||||
country: doc.country,
|
country: doc.country,
|
||||||
countryCode: doc.countryCode,
|
countryCode: doc.countryCode,
|
||||||
@@ -205,16 +206,22 @@ export class SecurityPolicyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rule.type === 'cidr') {
|
if (rule.type === 'cidr') {
|
||||||
const cidr = this.normalizeCidr(normalizedValue);
|
for (const cidr of this.normalizeNetworkEntries(normalizedValue)) {
|
||||||
if (cidr) blockedCidrs.add(cidr);
|
blockedCidrs.add(cidr);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const doc of intelligenceDocs) {
|
for (const doc of intelligenceDocs) {
|
||||||
if (!this.ruleMatchesIntelligence(rule, doc)) continue;
|
if (!this.ruleMatchesIntelligence(rule, doc)) continue;
|
||||||
const cidr = this.normalizeCidr(doc.networkRange || '');
|
const networkEntries = this.normalizeNetworkEntryList([
|
||||||
if (cidr) {
|
...(doc.networkCidrs || []),
|
||||||
|
doc.networkRange,
|
||||||
|
]);
|
||||||
|
if (networkEntries.length > 0) {
|
||||||
|
for (const cidr of networkEntries) {
|
||||||
blockedCidrs.add(cidr);
|
blockedCidrs.add(cidr);
|
||||||
|
}
|
||||||
} else if (this.normalizeIp(doc.ipAddress)) {
|
} else if (this.normalizeIp(doc.ipAddress)) {
|
||||||
blockedIps.add(this.normalizeIp(doc.ipAddress)!);
|
blockedIps.add(this.normalizeIp(doc.ipAddress)!);
|
||||||
}
|
}
|
||||||
@@ -231,13 +238,13 @@ export class SecurityPolicyManager {
|
|||||||
return await this.compilePolicy();
|
return await this.compilePolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async compileRemoteIngressFirewall(): Promise<IRemoteIngressFirewallSnapshot | undefined> {
|
public async compileRemoteIngressFirewall(): Promise<IRemoteIngressFirewallSnapshot> {
|
||||||
const policy = await this.compilePolicy();
|
const policy = await this.compilePolicy();
|
||||||
const blockedIps = [
|
const blockedIps = [
|
||||||
...policy.blockedIps.filter((ip) => plugins.net.isIP(ip) === 4),
|
...policy.blockedIps.filter((ip) => plugins.net.isIP(ip) === 4),
|
||||||
...policy.blockedCidrs.filter((cidr) => plugins.net.isIP(cidr.split('/')[0]) === 4),
|
...policy.blockedCidrs.filter((cidr) => plugins.net.isIP(cidr.split('/')[0]) === 4),
|
||||||
];
|
];
|
||||||
return blockedIps.length > 0 ? { blockedIps } : undefined;
|
return { blockedIps };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async matchesAnyReactiveRule(doc: IpIntelligenceDoc): Promise<boolean> {
|
private async matchesAnyReactiveRule(doc: IpIntelligenceDoc): Promise<boolean> {
|
||||||
@@ -287,6 +294,81 @@ export class SecurityPolicyManager {
|
|||||||
return `${ip}/${prefix}`;
|
return `${ip}/${prefix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private normalizeNetworkEntries(value: string): string[] {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return [];
|
||||||
|
|
||||||
|
const cidr = this.normalizeCidr(trimmed);
|
||||||
|
if (cidr) return [cidr];
|
||||||
|
|
||||||
|
const rangeParts = trimmed.split(/\s+-\s+/);
|
||||||
|
if (rangeParts.length === 2) {
|
||||||
|
return this.ipv4RangeToCidrs(rangeParts[0], rangeParts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeNetworkEntryList(values: Array<string | null | undefined>): string[] {
|
||||||
|
const cidrs = new Set<string>();
|
||||||
|
for (const value of values) {
|
||||||
|
if (!value) continue;
|
||||||
|
for (const entry of value.split(',').map((part) => part.trim()).filter(Boolean)) {
|
||||||
|
for (const cidr of this.normalizeNetworkEntries(entry)) {
|
||||||
|
cidrs.add(cidr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...cidrs];
|
||||||
|
}
|
||||||
|
|
||||||
|
private ipv4RangeToCidrs(startIp: string, endIp: string): string[] {
|
||||||
|
const start = this.ipv4ToBigInt(startIp);
|
||||||
|
const end = this.ipv4ToBigInt(endIp);
|
||||||
|
if (start === undefined || end === undefined || start > end) return [];
|
||||||
|
|
||||||
|
const cidrs: string[] = [];
|
||||||
|
let current = start;
|
||||||
|
while (current <= end) {
|
||||||
|
let maxBlockSize = current === 0n ? 1n << 32n : current & -current;
|
||||||
|
const remaining = end - current + 1n;
|
||||||
|
while (maxBlockSize > remaining) {
|
||||||
|
maxBlockSize = maxBlockSize / 2n;
|
||||||
|
}
|
||||||
|
const prefixLength = 32 - this.powerOfTwoExponent(maxBlockSize);
|
||||||
|
cidrs.push(`${this.numberToIpv4(current)}/${prefixLength}`);
|
||||||
|
current += maxBlockSize;
|
||||||
|
}
|
||||||
|
return cidrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ipv4ToBigInt(ip: string): bigint | undefined {
|
||||||
|
const normalized = this.normalizeIp(ip);
|
||||||
|
if (!normalized || plugins.net.isIP(normalized) !== 4) return undefined;
|
||||||
|
return normalized
|
||||||
|
.split('.')
|
||||||
|
.reduce((sum, part) => (sum * 256n) + BigInt(Number(part)), 0n);
|
||||||
|
}
|
||||||
|
|
||||||
|
private numberToIpv4(value: bigint): string {
|
||||||
|
return [
|
||||||
|
Number((value >> 24n) & 255n),
|
||||||
|
Number((value >> 16n) & 255n),
|
||||||
|
Number((value >> 8n) & 255n),
|
||||||
|
Number(value & 255n),
|
||||||
|
].join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
private powerOfTwoExponent(value: bigint): number {
|
||||||
|
let exponent = 0;
|
||||||
|
let remaining = value;
|
||||||
|
while (remaining > 1n) {
|
||||||
|
remaining >>= 1n;
|
||||||
|
exponent++;
|
||||||
|
}
|
||||||
|
return exponent;
|
||||||
|
}
|
||||||
|
|
||||||
private isPublicIp(ip: string): boolean {
|
private isPublicIp(ip: string): boolean {
|
||||||
const family = plugins.net.isIP(ip);
|
const family = plugins.net.isIP(ip);
|
||||||
if (family === 4) {
|
if (family === 4) {
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '13.24.0',
|
version: '13.25.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user