upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/tests/integration/test-dns-firewall.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration/test-dns-firewall.mjs')
-rw-r--r--tests/integration/test-dns-firewall.mjs123
1 files changed, 123 insertions, 0 deletions
diff --git a/tests/integration/test-dns-firewall.mjs b/tests/integration/test-dns-firewall.mjs
new file mode 100644
index 0000000..b69b524
--- /dev/null
+++ b/tests/integration/test-dns-firewall.mjs
@@ -0,0 +1,123 @@
1import { execSync } from 'child_process';
2
3const IP = process.env.TOLLGATE_IP || '10.192.45.1';
4const API = `http://${IP}:2121`;
5let passed = 0, failed = 0;
6
7function assert(cond, msg) {
8 if (cond) { console.log(` ✓ ${msg}`); passed++; }
9 else { console.log(` ✗ ${msg}`); failed++; }
10}
11
12function run(cmd) {
13 try { return execSync(cmd, { encoding: 'utf8', timeout: 15000 }); }
14 catch { return null; }
15}
16
17function runJson(cmd) {
18 const out = run(cmd);
19 try { return out ? JSON.parse(out) : null; }
20 catch { return null; }
21}
22
23function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
24
25function mintToken(amount = 21) {
26 run('cashu -h https://testnut.cashu.space invoice ' + amount + ' 2>&1');
27 const out = run('cashu -h https://testnut.cashu.space send --legacy ' + amount + ' 2>&1');
28 const match = out && out.match(/cashuA[a-zA-Z0-9_-]+/);
29 return match ? match[0] : null;
30}
31
32function dnsResolves(domain, server) {
33 const result = run(`nslookup -timeout=3 ${domain} ${server} 2>&1`);
34 return result && result.includes('Address') && !result.includes('NXDOMAIN');
35}
36
37function dnsResolvesToSelf(domain) {
38 try {
39 const result = run(`nslookup ${domain} ${IP} 2>&1`);
40 return result && result.includes(IP);
41 } catch {
42 return false;
43 }
44}
45
46function canPing(host = '8.8.8.8') {
47 const result = run(`ping -c 1 -W 2 -I wlp59s0 ${host} 2>/dev/null`);
48 return result && !result.includes('100% packet loss');
49}
50
51console.log(`\n=== DNS + Firewall Integration Test (target: ${IP}) ===\n`);
52
53console.log('--- Part 1: Before Authentication ---\n');
54
55console.log('1. DNS hijack: resolves to ESP32 AP IP');
56assert(dnsResolvesToSelf('google.com'), 'google.com resolves to AP IP');
57assert(dnsResolvesToSelf('random-test.example.com'), 'random domain resolves to AP IP');
58
59console.log('\n2. DNS hijack: upstream DNS not reachable');
60const upstreamResolve = run(`nslookup -timeout=3 google.com 8.8.8.8 2>&1`);
61assert(!upstreamResolve || upstreamResolve.includes('connection timed out') || upstreamResolve.includes('no servers'), 'Upstream DNS unreachable before auth');
62
63console.log('\n3. Per-client NAT filter: ping blocked');
64assert(!canPing(), 'Ping to 8.8.8.8 blocked by NAT filter');
65
66console.log('\n4. Per-client NAT filter: HTTP blocked');
67const httpBefore = run(`curl -s --connect-timeout 5 -m 5 --interface wlp59s0 http://1.1.1.1/ 2>/dev/null`);
68assert(!httpBefore || httpBefore.length === 0, 'HTTP blocked before auth');
69
70console.log('\n5. Captive portal and API still accessible');
71const portal = run(`curl -s --connect-timeout 5 http://${IP}/`);
72assert(portal && portal.includes('TollGate'), 'Portal HTML accessible');
73const apiDisc = runJson(`curl -s --connect-timeout 5 ${API}/`);
74assert(apiDisc && apiDisc.kind === 10021, 'API discovery accessible');
75
76console.log('\n--- Part 2: After Authentication ---\n');
77
78console.log('6. Reset + Pay');
79run(`curl -s --connect-timeout 10 http://${IP}/reset_authentication`);
80await sleep(1000);
81
82const token = mintToken(21);
83assert(token !== null, 'Token generated');
84if (token) {
85 const payResult = runJson(`curl -s --connect-timeout 20 -X POST --data-binary '${token}' -H "Content-Type: application/cashu" ${API}/`);
86 assert(payResult && payResult.kind === 1022, 'Payment accepted');
87}
88
89await sleep(1000);
90
91console.log('\n7. DNS now forwards to upstream');
92assert(dnsResolveWorks('google.com'), 'DNS resolves to real IPs after auth');
93
94console.log('\n8. Per-client NAT filter: ping allowed');
95assert(canPing(), 'Ping to 8.8.8.8 allowed after auth');
96
97console.log('\n9. Per-client NAT filter: HTTP allowed');
98const httpAfter = run(`curl -s --connect-timeout 10 -m 10 --interface wlp59s0 http://1.1.1.1/ 2>/dev/null`);
99assert(httpAfter && httpAfter.length > 0, 'HTTP allowed after auth');
100
101console.log('\n--- Part 3: After Revocation ---\n');
102
103console.log('10. Reset auth');
104run(`curl -s --connect-timeout 10 http://${IP}/reset_authentication`);
105await sleep(1000);
106
107console.log('\n11. DNS goes back to hijack');
108assert(dnsResolvesToSelf('google.com'), 'DNS hijack restored after revoke');
109
110console.log('\n12. Per-client NAT filter: ping blocked again');
111assert(!canPing(), 'Ping blocked after revoke');
112
113console.log('\n13. Per-client NAT filter: HTTP blocked again');
114const httpRevoke = run(`curl -s --connect-timeout 5 -m 5 --interface wlp59s0 http://1.1.1.1/ 2>/dev/null`);
115assert(!httpRevoke || httpRevoke.length === 0, 'HTTP blocked after revoke');
116
117function dnsResolveWorks(domain) {
118 const result = run(`nslookup -timeout=3 ${domain} 2>&1`);
119 return result && result.includes('Address') && !result.includes(IP) && !result.includes('NXDOMAIN');
120}
121
122console.log(`\n=== Results: ${passed} passed, ${failed} failed ===\n`);
123process.exit(failed > 0 ? 1 : 0);