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.
102 lines
3.4 KiB
TypeScript
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);
|
|
});
|