feat(exports): export datagram handler types and align tests with updated nftables and route security APIs

This commit is contained in:
2026-04-30 09:05:24 +00:00
parent e806f7257f
commit 2933ee5257
14 changed files with 622 additions and 684 deletions
+12 -6
View File
@@ -1,7 +1,13 @@
import { IpUtils } from '../../../ts/core/utils/ip-utils.js';
import { IpMatcher } from '../../../ts/core/routing/matchers/ip.js';
const isGlobIPMatch = (ip: string, patterns: string[]): boolean =>
patterns.some((pattern) => IpMatcher.match(pattern, ip));
const isIPAuthorized = (ip: string, allowedIPs: string[], blockedIPs: string[]): boolean =>
IpMatcher.isAuthorized(ip, allowedIPs, blockedIPs);
// Test the overlap case
const result = IpUtils.isIPAuthorized('127.0.0.1', ['127.0.0.1'], ['127.0.0.1']);
const result = isIPAuthorized('127.0.0.1', ['127.0.0.1'], ['127.0.0.1']);
console.log('Result of IP that is both allowed and blocked:', result);
// Trace through the code logic
@@ -13,10 +19,10 @@ console.log('Step 1 check:', (!ip || (allowedIPs.length === 0 && blockedIPs.leng
// Check if IP is blocked - blocked IPs take precedence
console.log('blockedIPs length > 0:', blockedIPs.length > 0);
console.log('isGlobIPMatch result:', IpUtils.isGlobIPMatch(ip, blockedIPs));
console.log('Step 2 check (is blocked):', (blockedIPs.length > 0 && IpUtils.isGlobIPMatch(ip, blockedIPs)));
console.log('isGlobIPMatch result:', isGlobIPMatch(ip, blockedIPs));
console.log('Step 2 check (is blocked):', (blockedIPs.length > 0 && isGlobIPMatch(ip, blockedIPs)));
// Check if IP is allowed
console.log('allowedIPs length === 0:', allowedIPs.length === 0);
console.log('isGlobIPMatch for allowed:', IpUtils.isGlobIPMatch(ip, allowedIPs));
console.log('Step 3 (is allowed):', allowedIPs.length === 0 || IpUtils.isGlobIPMatch(ip, allowedIPs));
console.log('isGlobIPMatch for allowed:', isGlobIPMatch(ip, allowedIPs));
console.log('Step 3 (is allowed):', allowedIPs.length === 0 || isGlobIPMatch(ip, allowedIPs));
+2 -1
View File
@@ -27,7 +27,8 @@ export function loadTestCertificates(): TestCertificates {
key: privateKey
});
} catch (error) {
throw new Error(`Invalid certificates: ${error.message}`);
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Invalid certificates: ${message}`);
}
return {
+10 -5
View File
@@ -79,18 +79,23 @@ testFn('NFTables integration tests', async () => {
const status = await smartProxy.getNfTablesStatus();
console.log('NFTables status:', JSON.stringify(status, null, 2));
expect(Object.keys(status).length).toEqual(routes.length);
if (!status) {
throw new Error('Expected NFTables status after SmartProxy start');
}
for (const routeStatus of Object.values(status)) {
expect(routeStatus.active).toBeTrue();
expect(routeStatus.ruleCount.total).toBeGreaterThan(0);
expect(status.activeGroups).toEqual(routes.length);
expect(Object.keys(status.groups).length).toEqual(routes.length);
for (const routeStatus of Object.values(status.groups)) {
expect(routeStatus.ruleCount).toBeGreaterThan(0);
expect(routeStatus.createdAt).toBeGreaterThan(0);
}
await smartProxy.stop();
console.log('SmartProxy stopped');
const finalStatus = await smartProxy.getNfTablesStatus();
expect(Object.keys(finalStatus).length).toEqual(0);
expect(finalStatus).toEqual(null);
});
export default tap.start();
+14 -8
View File
@@ -150,8 +150,9 @@ tap.skip.test('setup NFTables integration test environment', async () => {
type: 'forward',
forwardingEngine: 'nftables',
targets: [{ host: 'localhost', port: TEST_TCP_PORT }],
nftables: { protocol: 'tcp', ipAllowList: ['127.0.0.1', '::1'] }
nftables: { protocol: 'tcp' }
},
security: { ipAllowList: ['127.0.0.1', '::1'] },
name: 'secure-tcp'
},
@@ -174,7 +175,7 @@ tap.skip.test('setup NFTables integration test environment', async () => {
await smartProxy.start();
console.log('SmartProxy started successfully');
const listeningPorts = smartProxy.getListeningPorts();
const listeningPorts = await smartProxy.getListeningPorts();
console.log(`SmartProxy is listening on ports: ${listeningPorts.join(', ')}`);
} catch (err) {
console.error('Failed to start SmartProxy:', err);
@@ -301,14 +302,19 @@ tap.skip.test('should respect IP allow lists in NFTables', async () => {
tap.skip.test('should get NFTables status', async () => {
const status = await smartProxy.getNfTablesStatus();
const statusKeys = Object.keys(status);
if (!status) {
throw new Error('Expected NFTables status after SmartProxy start');
}
const statusKeys = Object.keys(status.groups);
expect(statusKeys.length).toBeGreaterThan(0);
const firstStatus = status[statusKeys[0]];
expect(firstStatus).toHaveProperty('active');
expect(firstStatus).toHaveProperty('ruleCount');
expect(firstStatus.ruleCount).toHaveProperty('total');
expect(firstStatus.ruleCount).toHaveProperty('added');
const firstStatus = Object.values(status.groups)[0];
if (!firstStatus) {
throw new Error('Expected at least one NFTables rule group');
}
expect(firstStatus.ruleCount).toBeGreaterThan(0);
expect(firstStatus.createdAt).toBeGreaterThan(0);
});
tap.skip.test('cleanup NFTables integration test environment', async () => {
+8 -8
View File
@@ -327,12 +327,12 @@ tap.test('Edge Case - Wildcard Domains and Path Matching', async () => {
const bestMatch = findBestMatchingRoute(routes, { domain: 'api.example.com', path: '/api/users', port: 443 });
expect(bestMatch).not.toBeUndefined();
if (bestMatch) {
expect(bestMatch.action.targets[0].port).toEqual(3001);
expect(bestMatch.action.targets?.[0]?.port).toEqual(3001);
}
const otherMatches = findMatchingRoutes(routes, { domain: 'other.example.com', path: '/api/products', port: 443 });
expect(otherMatches.length).toEqual(1);
expect(otherMatches[0].action.targets[0].port).toEqual(3000);
expect(otherMatches[0]?.action.targets?.[0]?.port).toEqual(3000);
});
tap.test('Edge Case - Disabled Routes', async () => {
@@ -353,7 +353,7 @@ tap.test('Edge Case - Disabled Routes', async () => {
const matches = findMatchingRoutes(routes, { domain: 'example.com', port: 80 });
expect(matches.length).toEqual(1);
expect(matches[0].action.targets[0].port).toEqual(3000);
expect(matches[0]?.action.targets?.[0]?.port).toEqual(3000);
});
tap.test('Edge Case - Complex Path and Headers Matching', async () => {
@@ -487,7 +487,7 @@ tap.test('Wildcard Domain Handling', async () => {
const bestSpecificMatch = findBestMatchingRoute(routes, specificSubdomainRequest);
expect(bestSpecificMatch).not.toBeUndefined();
if (bestSpecificMatch) {
const matchedPort = bestSpecificMatch.action.targets[0].port;
const matchedPort = bestSpecificMatch.action.targets?.[0]?.port;
console.log(`Matched route with port: ${matchedPort}`);
expect(bestSpecificMatch.priority).toEqual(200);
@@ -497,7 +497,7 @@ tap.test('Wildcard Domain Handling', async () => {
const bestWildcardMatch = findBestMatchingRoute(routes, otherSubdomainRequest);
expect(bestWildcardMatch).not.toBeUndefined();
if (bestWildcardMatch) {
const matchedPort = bestWildcardMatch.action.targets[0].port;
const matchedPort = bestWildcardMatch.action.targets?.[0]?.port;
console.log(`Matched route with port: ${matchedPort}`);
expect(bestWildcardMatch.priority).toEqual(100);
@@ -573,7 +573,7 @@ tap.test('Route Integration - Combining Multiple Route Types', async () => {
expect(webServerMatch).not.toBeUndefined();
if (webServerMatch) {
expect(webServerMatch.action.type).toEqual('forward');
expect(webServerMatch.action.targets[0].host).toEqual('web-server');
expect(webServerMatch.action.targets?.[0]?.host).toEqual('web-server');
}
const webRedirectMatch = findBestMatchingRoute(routes, { domain: 'example.com', port: 80 });
@@ -590,7 +590,7 @@ tap.test('Route Integration - Combining Multiple Route Types', async () => {
expect(apiMatch).not.toBeUndefined();
if (apiMatch) {
expect(apiMatch.action.type).toEqual('forward');
expect(apiMatch.action.targets[0].host).toEqual('api-server');
expect(apiMatch.action.targets?.[0]?.host).toEqual('api-server');
}
const wsMatch = findBestMatchingRoute(routes, {
@@ -601,7 +601,7 @@ tap.test('Route Integration - Combining Multiple Route Types', async () => {
expect(wsMatch).not.toBeUndefined();
if (wsMatch) {
expect(wsMatch.action.type).toEqual('forward');
expect(wsMatch.action.targets[0].host).toEqual('websocket-server');
expect(wsMatch.action.targets?.[0]?.host).toEqual('websocket-server');
expect(wsMatch.action.websocket?.enabled).toBeTrue();
}
+3 -2
View File
@@ -148,7 +148,8 @@ tap.test('route-specific IP block list should be enforced', async () => {
try {
client.write('test data');
} catch (e) {
console.log('Write failed:', e.message);
const message = e instanceof Error ? e.message : String(e);
console.log('Write failed:', message);
}
});
@@ -272,4 +273,4 @@ tap.test('routes without security should allow all connections', async () => {
});
});
export default tap.start();
export default tap.start();