dgb-wallet/packages/net/scripts/socks5-loopback.ts
Digibyte.X d50286a9a5 DigiByte.X Wallet — recovery-first web wallet monorepo
Packages: core (BIP39/32/44/49/84/86 derivation, WIF), keystore (PBKDF2 +
AES-GCM), psbt, p2p (BIP157/158 SPV daemon, Dandelion++ broadcast with
relay-echo verification), blockbook, rpc, electrum, net, bns, web-ui
(React/Vite), mobile (Expo). ops/ holds the nginx vhost, systemd unit and
push.sh deploy script for digibyte.x/web.
2026-09-20 20:16:52 +02:00

102 lines
3.4 KiB
TypeScript

// Minimal loopback smoke test — spins up an in-process SOCKS5 server
// that answers with an echo target, then connects Socks5Net to it and
// verifies bytes round-trip. Meant as a regression check for the
// handshake, not a real network test.
//
// npm run test:socks5 -w @dgb-wallet/net
import * as net from 'node:net';
import { Socks5Net } from '../src/socks5.js';
const PROXY_PORT = 19050;
const ECHO_PORT = 19051;
// Trivial echo server on ECHO_PORT.
const echo = net.createServer((sock) => {
sock.on('data', (chunk) => sock.write(chunk));
});
// Minimal SOCKS5 server: NO-AUTH only, CONNECT to any host:port,
// implemented against 127.0.0.1 (the only place we route in this test).
const proxy = net.createServer((client) => {
let stage = 'greet' as 'greet' | 'connect' | 'piped';
let buf: Buffer = Buffer.alloc(0);
let outbound: net.Socket | null = null;
client.on('data', (chunk) => {
if (stage === 'piped') return;
buf = Buffer.concat([buf, chunk]);
if (stage === 'greet') {
if (buf.length < 2) return;
const n = buf[1];
if (buf.length < 2 + n) return;
client.write(Buffer.from([0x05, 0x00])); // NO-AUTH accepted
buf = buf.subarray(2 + n);
stage = 'connect';
}
if (stage === 'connect') {
if (buf.length < 4) return;
const atyp = buf[3];
let addrLen: number;
let addr: string;
let cursor: number;
if (atyp === 0x03) {
if (buf.length < 5) return;
addrLen = buf[4];
if (buf.length < 5 + addrLen + 2) return;
addr = buf.subarray(5, 5 + addrLen).toString('ascii');
cursor = 5 + addrLen;
} else if (atyp === 0x01) {
if (buf.length < 4 + 4 + 2) return;
addr = Array.from(buf.subarray(4, 8)).join('.');
cursor = 8;
} else {
client.end(); return;
}
const port = buf.readUInt16BE(cursor);
// Success reply — bound address 0.0.0.0:0 (Tor sends this too).
client.write(Buffer.from([0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0]));
buf = buf.subarray(cursor + 2);
stage = 'piped';
outbound = net.connect({ host: addr === 'localhost' ? '127.0.0.1' : addr, port });
outbound.on('data', (d) => client.write(d));
client.on('data', (d) => outbound?.write(d));
outbound.on('close', () => client.end());
client.on('close', () => outbound?.end());
if (buf.length > 0) outbound.write(buf);
}
});
});
async function main(): Promise<void> {
await new Promise<void>((r) => echo.listen(ECHO_PORT, '127.0.0.1', r));
await new Promise<void>((r) => proxy.listen(PROXY_PORT, '127.0.0.1', r));
const client = new Socks5Net({ proxyHost: '127.0.0.1', proxyPort: PROXY_PORT });
const sock = await client.connect({ host: '127.0.0.1', port: ECHO_PORT });
const roundtrip = await new Promise<string>((resolve, reject) => {
let chunks = '';
sock.on('data', (d) => {
chunks += d.toString('utf8');
if (chunks.length >= 5) resolve(chunks);
});
sock.on('error', reject);
sock.write('hello');
});
if (roundtrip.startsWith('hello')) {
console.log(`✅ socks5 loopback: echoed ${JSON.stringify(roundtrip)}`);
} else {
console.error(`❌ unexpected response: ${JSON.stringify(roundtrip)}`);
process.exitCode = 1;
}
sock.destroy();
proxy.close();
echo.close();
}
main().catch((e) => {
console.error('❌ test failed:', e);
process.exit(1);
});