ipv6-bridge 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # IPv6 Bridge
2
+
3
+ > Local DNS64/NAT64 proxy for IPv6-only networks — access IPv4 sites seamlessly.
4
+
5
+ IPv4 addresses are exhausted globally. Many ISPs now deploy IPv6-only networks, but millions of websites still only support IPv4. IPv6 Bridge solves this by running a local proxy that translates traffic using DNS64 and NAT64 standards.
6
+
7
+ ## Features
8
+
9
+ - **Zero dependencies** — pure Node.js, nothing to install
10
+ - **Auto-detection** — starts only when needed (IPv6-only network with broken NAT64)
11
+ - **HTTP & HTTPS** — full proxy support including CONNECT tunneling
12
+ - **RFC compliant** — implements RFC 6052 (DNS64) and RFC 6146 (NAT64)
13
+ - **Cross-platform** — works on Windows, macOS, and Linux
14
+ - **Programmatic API** — use from your Node.js app or the CLI
15
+
16
+ ## Quick Start
17
+
18
+ ### CLI
19
+
20
+ ```bash
21
+ npx ipv6-bridge start
22
+ ```
23
+
24
+ The bridge auto-detects whether it's needed. To force it:
25
+
26
+ ```bash
27
+ FORCE_BRIDGE=1 npx ipv6-bridge start
28
+ ```
29
+
30
+ ### Programmatic
31
+
32
+ ```javascript
33
+ const { start, stop } = require('ipv6-bridge');
34
+
35
+ const server = await start(8080);
36
+ // → returns the server, or null if bridge isn't needed
37
+
38
+ // Later:
39
+ await stop();
40
+ ```
41
+
42
+ ### Install as a Dependency
43
+
44
+ ```bash
45
+ npm install ipv6-bridge
46
+ ```
47
+
48
+ ## How It Works
49
+
50
+ **The "Language Translator" Analogy**
51
+ > Imagine you only speak English (IPv6), but you need to call a business in Japan where they only speak Japanese (IPv4). If you call them directly, you won't understand each other.
52
+ >
53
+ > This project acts like a live, bilingual phone operator sitting right next to you. When you try to make the call, the software intercepts it, looks up the Japanese translation for the phone number (**DNS64**), and then acts as a middleman translating your English sentences into Japanese and back again in real-time (**NAT64 proxy**). The result is that you have a seamless conversation without even realizing a translation is happening.
54
+
55
+ ### Technical Flow
56
+
57
+ ```text
58
+ Your App → HTTP request → IPv6 Bridge (localhost:8080)
59
+
60
+ DNS64 resolution
61
+ example.com → 142.251.32.14 → 64:ff9b::8efb:200e
62
+
63
+ Outbound via IPv6
64
+
65
+ ISP NAT64 Gateway
66
+
67
+ IPv4 Internet (google.com)
68
+ ```
69
+
70
+ 1. **Detection** — checks if you're on an IPv6-only network
71
+ 2. **DNS64** — resolves hostnames; if only an IPv4 address exists, synthesizes an IPv6 address using the NAT64 prefix (`64:ff9b::`)
72
+ 3. **Proxy** — routes HTTP/HTTPS through IPv6; the ISP's NAT64 gateway translates to IPv4
73
+ 4. **Response** — data flows back through the same path, transparently
74
+
75
+ For a deep dive, see [ARCHITECTURE.md](ARCHITECTURE.md).
76
+
77
+ ## Configuration
78
+
79
+ | Environment Variable | Default | Description |
80
+ |---------------------|---------|-------------|
81
+ | `IPV6_BRIDGE_PORT` | `8080` | Proxy listen port |
82
+ | `FORCE_BRIDGE` | _(unset)_ | Start even if not needed |
83
+ | `NAT64_PREFIX` | `64:ff9b::` | Custom NAT64 prefix |
84
+
85
+ ## API
86
+
87
+ ### `start(port?): Promise<http.Server | null>`
88
+
89
+ Starts the proxy. Returns the server instance, or `null` if the bridge isn't needed.
90
+
91
+ ### `stop(): Promise<void>`
92
+
93
+ Stops the running bridge.
94
+
95
+ See [docs/API.md](docs/API.md) for the full API reference.
96
+
97
+ ## Testing
98
+
99
+ ```bash
100
+ npm test
101
+ ```
102
+
103
+ ### Demo Application
104
+
105
+ An interactive diagnostics tool:
106
+
107
+ ```bash
108
+ cd demo-app && npm start
109
+ # Open http://localhost:3000
110
+ ```
111
+
112
+ ### Dual-Stack Test Server
113
+
114
+ Test IPv4 and IPv6 endpoints with real-time logging:
115
+
116
+ ```bash
117
+ cd test-server && node server.js
118
+ ```
119
+
120
+ See [test-server/README.md](test-server/README.md) for details.
121
+
122
+ ## Project Structure
123
+
124
+ ```
125
+ src/
126
+ cli.js CLI entry point
127
+ config.js Configuration constants
128
+ detect.js Network detection
129
+ dns64.js DNS64 resolver
130
+ index.js Public API (start/stop)
131
+ proxy.js HTTP/HTTPS proxy
132
+ tests/ Test suite
133
+ demo-app/ Interactive demo
134
+ test-server/ Dual-stack test server
135
+ examples/ Usage examples
136
+ docs/ Extended documentation
137
+ ```
138
+
139
+ ## Standards
140
+
141
+ - [RFC 6052](https://tools.ietf.org/html/rfc6052) — IPv6 Addressing of IPv4/IPv6 Translators
142
+ - [RFC 6146](https://tools.ietf.org/html/rfc6146) — Stateful NAT64
143
+ - [RFC 6147](https://tools.ietf.org/html/rfc6147) — DNS64
144
+
145
+ ## Contributing
146
+
147
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
148
+
149
+ ## License
150
+
151
+ [MIT](LICENSE)
@@ -0,0 +1,38 @@
1
+ /**
2
+ * IPv6 Bridge — Basic Usage Example
3
+ *
4
+ * Demonstrates how to integrate IPv6 Bridge into a Node.js application.
5
+ *
6
+ * Run:
7
+ * node examples/basic-usage.js
8
+ */
9
+
10
+ const { start, stop } = require('../src/index');
11
+
12
+ async function main() {
13
+ try {
14
+ console.log('Starting IPv6 Bridge...\n');
15
+ const server = await start(8080);
16
+
17
+ if (server) {
18
+ const addr = server.address();
19
+ console.log(`Bridge started on port ${addr.port}`);
20
+ console.log(`Configure your browser/system proxy to localhost:${addr.port}\n`);
21
+ console.log('Press Ctrl+C to stop.\n');
22
+
23
+ process.on('SIGINT', async () => {
24
+ console.log('\nShutting down...');
25
+ await stop();
26
+ process.exit(0);
27
+ });
28
+ } else {
29
+ console.log('Bridge not needed — you have IPv4 connectivity or working NAT64.');
30
+ process.exit(0);
31
+ }
32
+ } catch (error) {
33
+ console.error('Error:', error.message);
34
+ process.exit(1);
35
+ }
36
+ }
37
+
38
+ main();
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "ipv6-bridge",
3
+ "version": "1.0.0",
4
+ "description": "Local DNS64/NAT64 proxy for IPv6-only networks — access IPv4 sites from IPv6-only environments",
5
+ "type": "commonjs",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "ipv6-bridge": "src/cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/cli.js start",
12
+ "test": "node --test tests/*.test.js",
13
+ "demo": "cd demo-app && npm install && npm start",
14
+ "prepublishOnly": "npm test"
15
+ },
16
+ "keywords": [
17
+ "ipv6",
18
+ "ipv4",
19
+ "nat64",
20
+ "dns64",
21
+ "proxy",
22
+ "networking",
23
+ "bridge",
24
+ "translation",
25
+ "connectivity",
26
+ "http-proxy",
27
+ "https-proxy"
28
+ ],
29
+ "author": "CS Hari Krishna <csharikrishna1806@gmail.com>",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/csharikrishna/ipv6-bridge.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/csharikrishna/ipv6-bridge/issues"
37
+ },
38
+ "homepage": "https://github.com/csharikrishna/ipv6-bridge#readme",
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "files": [
43
+ "src/",
44
+ "examples/",
45
+ "LICENSE",
46
+ "README.md",
47
+ "CHANGELOG.md"
48
+ ],
49
+ "dependencies": {}
50
+ }
package/src/cli.js ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * IPv6 Bridge - Command-Line Interface
5
+ *
6
+ * Usage:
7
+ * npx ipv6-bridge start Start the bridge (auto-detects if needed)
8
+ * npx ipv6-bridge --help Show this help message
9
+ * npx ipv6-bridge --version Show version
10
+ *
11
+ * Environment variables:
12
+ * IPV6_BRIDGE_PORT=<port> Port to listen on (default: 8080)
13
+ * FORCE_BRIDGE=1 Start even if bridge is not needed
14
+ * NAT64_PREFIX=<prefix> Custom NAT64 prefix (default: 64:ff9b::)
15
+ *
16
+ * Stop the bridge with Ctrl+C or SIGTERM.
17
+ *
18
+ * @file CLI entry point for IPv6 Bridge
19
+ */
20
+
21
+ const { start, stop } = require('./index');
22
+ const { DEFAULT_PORT } = require('./config');
23
+ const { version } = require('../package.json');
24
+
25
+ const HELP_TEXT = `
26
+ IPv6 Bridge v${version}
27
+ Local DNS64/NAT64 proxy for IPv6-only networks.
28
+
29
+ Usage:
30
+ ipv6-bridge start Start the bridge proxy
31
+ ipv6-bridge --help, -h Show this help message
32
+ ipv6-bridge --version, -v Show version number
33
+
34
+ Environment variables:
35
+ IPV6_BRIDGE_PORT Port to listen on (default: ${DEFAULT_PORT})
36
+ FORCE_BRIDGE Set to any value to force start
37
+ NAT64_PREFIX Custom NAT64 prefix (default: 64:ff9b::)
38
+
39
+ Stop the bridge with Ctrl+C or by sending SIGTERM.
40
+ `.trim();
41
+
42
+ const command = process.argv[2];
43
+ const port = process.env.IPV6_BRIDGE_PORT
44
+ ? parseInt(process.env.IPV6_BRIDGE_PORT, 10)
45
+ : DEFAULT_PORT;
46
+
47
+ if (command === '--help' || command === '-h' || !command) {
48
+ console.log(HELP_TEXT);
49
+ process.exit(0);
50
+ }
51
+
52
+ if (command === '--version' || command === '-v') {
53
+ console.log(version);
54
+ process.exit(0);
55
+ }
56
+
57
+ if (command === 'start') {
58
+ if (Number.isNaN(port) || port < 1 || port > 65535) {
59
+ console.error(`Error: Invalid port "${process.env.IPV6_BRIDGE_PORT}". Must be 1-65535.`);
60
+ process.exit(1);
61
+ }
62
+
63
+ start(port)
64
+ .then((server) => {
65
+ if (!server) {
66
+ process.exit(0);
67
+ }
68
+ console.log(`\nIPv6 Bridge running on http://localhost:${port}`);
69
+ console.log(`Configure your browser/system proxy to localhost:${port}\n`);
70
+ })
71
+ .catch((err) => {
72
+ console.error(`Error: ${err.message}`);
73
+ process.exit(1);
74
+ });
75
+
76
+ // Graceful shutdown on SIGINT (Ctrl+C) and SIGTERM (container/daemon stop)
77
+ function shutdown() {
78
+ console.log('\nStopping IPv6 Bridge...');
79
+ stop().then(() => process.exit(0));
80
+ }
81
+
82
+ process.on('SIGINT', shutdown);
83
+ process.on('SIGTERM', shutdown);
84
+
85
+ // Catch unhandled errors to prevent silent crashes
86
+ process.on('uncaughtException', (err) => {
87
+ console.error('Uncaught exception:', err.message);
88
+ stop().then(() => process.exit(1));
89
+ });
90
+
91
+ process.on('unhandledRejection', (reason) => {
92
+ console.error('Unhandled rejection:', reason);
93
+ stop().then(() => process.exit(1));
94
+ });
95
+ } else {
96
+ console.error(`Unknown command: "${command}"\n`);
97
+ console.log(HELP_TEXT);
98
+ process.exit(1);
99
+ }
package/src/config.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * IPv6 Bridge - Configuration
3
+ *
4
+ * RFC 6052 Well-Known NAT64 Prefix and network test constants.
5
+ * All values can be overridden via environment variables.
6
+ *
7
+ * @module config
8
+ */
9
+
10
+ module.exports = {
11
+ /**
12
+ * NAT64 Prefix (RFC 6052)
13
+ *
14
+ * The well-known prefix used to synthesize IPv6 addresses from IPv4.
15
+ * Override with the NAT64_PREFIX environment variable if your ISP
16
+ * uses a non-standard prefix.
17
+ *
18
+ * Example: 192.0.2.1 → 64:ff9b::c000:0201
19
+ *
20
+ * @see https://tools.ietf.org/html/rfc6052#section-2.1
21
+ */
22
+ NAT64_PREFIX: process.env.NAT64_PREFIX || '64:ff9b::',
23
+
24
+ /**
25
+ * Default port for the proxy server.
26
+ * Override with the IPV6_BRIDGE_PORT environment variable.
27
+ */
28
+ DEFAULT_PORT: 8080,
29
+
30
+ /**
31
+ * Test URL for IPv6 connectivity detection.
32
+ * Must be a server with IPv6 support.
33
+ */
34
+ IPV6_GOOGLE: 'http://ipv6.google.com',
35
+
36
+ /**
37
+ * Hostname for NAT64 availability testing.
38
+ * An IPv4-only hostname that should be reachable via NAT64
39
+ * if the ISP gateway is properly configured.
40
+ */
41
+ IPV4_GOOGLE: 'ipv4.google.com',
42
+ };
package/src/detect.js ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * IPv6 Bridge - Network Detection
3
+ *
4
+ * Detects whether the system is on an IPv6-only network and whether
5
+ * the bridge is needed to reach IPv4-only servers.
6
+ *
7
+ * @module detect
8
+ */
9
+
10
+ const http = require('http');
11
+ const { resolveIPv6 } = require('./dns64');
12
+ const { IPV6_GOOGLE, IPV4_GOOGLE } = require('./config');
13
+
14
+ /**
15
+ * Test if the network has IPv6 connectivity.
16
+ *
17
+ * Connects to an IPv6-capable server to verify that IPv6 is available.
18
+ *
19
+ * @returns {Promise<boolean>} true if IPv6 is available
20
+ */
21
+ async function hasIPv6() {
22
+ return new Promise((resolve) => {
23
+ const req = http.get(IPV6_GOOGLE, { family: 6 }, (res) => {
24
+ // Consume response body to free resources
25
+ res.resume();
26
+ resolve(res.statusCode === 200);
27
+ });
28
+ req.on('error', () => resolve(false));
29
+ req.setTimeout(5000, () => {
30
+ req.destroy();
31
+ resolve(false);
32
+ });
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Determine if the bridge is needed.
38
+ *
39
+ * The bridge is needed when:
40
+ * 1. IPv6 is available, AND
41
+ * 2. IPv4 servers are NOT reachable via the ISP's NAT64 gateway
42
+ *
43
+ * @returns {Promise<boolean>} true if bridge is needed
44
+ */
45
+ async function needsBridge() {
46
+ const hasV6 = await hasIPv6();
47
+ if (!hasV6) {
48
+ // No IPv6 means we're on IPv4 or a broken network.
49
+ // Either way, the bridge can't help.
50
+ return false;
51
+ }
52
+
53
+ try {
54
+ const ipv6 = await resolveIPv6(IPV4_GOOGLE);
55
+ if (!ipv6 || ipv6.length === 0) {
56
+ return true;
57
+ }
58
+
59
+ // Try connecting to the synthesized IPv6 address.
60
+ // If this works, the ISP has a working NAT64 gateway.
61
+ return new Promise((resolve) => {
62
+ const req = http.get(`http://[${ipv6[0]}]`, { family: 6 }, (res) => {
63
+ res.resume();
64
+ resolve(res.statusCode !== 200);
65
+ });
66
+ req.on('error', () => resolve(true));
67
+ req.setTimeout(5000, () => {
68
+ req.destroy();
69
+ resolve(true);
70
+ });
71
+ });
72
+ } catch {
73
+ return true;
74
+ }
75
+ }
76
+
77
+ module.exports = { hasIPv6, needsBridge };
package/src/dns64.js ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * IPv6 Bridge - DNS64 Resolver
3
+ *
4
+ * Implements DNS64 (RFC 6052) for synthesizing IPv6 addresses from IPv4.
5
+ *
6
+ * @module dns64
7
+ */
8
+
9
+ const dns = require('dns').promises;
10
+ const { NAT64_PREFIX } = require('./config');
11
+
12
+ /**
13
+ * Detect the IP version of an address string.
14
+ *
15
+ * @param {string} addr - Address or hostname to check
16
+ * @returns {'ipv4'|'ipv6'|'hostname'|null} Address type
17
+ */
18
+ function detectIPVersion(addr) {
19
+ if (!addr) return null;
20
+
21
+ // IPv4: dotted decimal (0-255 per octet)
22
+ if (/^(\d{1,3}\.){3}\d{1,3}$/.test(addr)) {
23
+ const parts = addr.split('.').map(Number);
24
+ if (parts.every((p) => p >= 0 && p <= 255)) {
25
+ return 'ipv4';
26
+ }
27
+ }
28
+
29
+ // IPv6: colon-delimited hex (includes :: shorthand)
30
+ if (/^[a-f0-9:]+$/i.test(addr) && addr.includes(':')) {
31
+ return 'ipv6';
32
+ }
33
+
34
+ // Everything else is a hostname
35
+ return 'hostname';
36
+ }
37
+
38
+ /**
39
+ * Convert an IPv4 address to IPv6 using the NAT64 prefix (RFC 6052).
40
+ *
41
+ * The IPv4 address is embedded in the lower 32 bits of the IPv6 address:
42
+ * 192.0.2.1 → 64:ff9b::c000:0201
43
+ *
44
+ * @param {string} ipv4 - IPv4 address (e.g., '192.0.2.1')
45
+ * @returns {string} IPv6 address with NAT64 prefix
46
+ * @throws {Error} If the input is not a valid IPv4 address
47
+ */
48
+ function ipv4ToIPv6(ipv4) {
49
+ if (!ipv4 || typeof ipv4 !== 'string') {
50
+ throw new Error('ipv4ToIPv6: address must be a non-empty string');
51
+ }
52
+
53
+ const parts = ipv4.split('.').map(Number);
54
+ if (parts.length !== 4 || parts.some((p) => !Number.isInteger(p) || p < 0 || p > 255)) {
55
+ throw new Error(`ipv4ToIPv6: invalid IPv4 address "${ipv4}"`);
56
+ }
57
+
58
+ const hex1 = ((parts[0] << 8) | parts[1]).toString(16).padStart(4, '0');
59
+ const hex2 = ((parts[2] << 8) | parts[3]).toString(16).padStart(4, '0');
60
+ return `${NAT64_PREFIX}${hex1}:${hex2}`;
61
+ }
62
+
63
+ /**
64
+ * Resolve a hostname to IPv6 addresses using DNS64 (RFC 6052).
65
+ *
66
+ * 1. Try native AAAA resolution first.
67
+ * 2. Fall back to A resolution and synthesize IPv6 via NAT64 prefix.
68
+ *
69
+ * @param {string} hostname - Domain name to resolve
70
+ * @returns {Promise<string[]>} Array of IPv6 addresses
71
+ * @throws {Error} If DNS resolution fails completely
72
+ */
73
+ async function resolveIPv6(hostname) {
74
+ try {
75
+ const resolver = dns.resolve6(hostname);
76
+ const timeout = new Promise((_, reject) =>
77
+ setTimeout(() => reject(new Error('DNS timeout')), 5000)
78
+ );
79
+ return await Promise.race([resolver, timeout]);
80
+ } catch {
81
+ try {
82
+ const ipv4 = await dns.resolve4(hostname);
83
+ return ipv4.map(ipv4ToIPv6);
84
+ } catch (err) {
85
+ throw new Error(`DNS resolution failed for ${hostname}: ${err.message}`);
86
+ }
87
+ }
88
+ }
89
+
90
+ module.exports = { ipv4ToIPv6, resolveIPv6, detectIPVersion };
package/src/index.js ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * IPv6 Bridge - Main Entry Point
3
+ *
4
+ * Coordinates bridge startup and shutdown. The bridge consists of:
5
+ *
6
+ * 1. Detection (detect.js): Checks if the bridge is needed.
7
+ * 2. DNS64 Resolver (dns64.js): Synthesizes IPv6 addresses from IPv4.
8
+ * 3. Proxy (proxy.js): HTTP/HTTPS proxy with NAT64 routing.
9
+ *
10
+ * @module ipv6-bridge
11
+ */
12
+
13
+ const { createProxy } = require('./proxy');
14
+ const { needsBridge } = require('./detect');
15
+
16
+ let activeServer = null;
17
+
18
+ /**
19
+ * Start the IPv6 bridge.
20
+ *
21
+ * @param {number} [port=8080] - Port to listen on
22
+ * @returns {Promise<http.Server|null>} Server instance if started, null if not needed
23
+ * @throws {Error} If already running or startup fails
24
+ */
25
+ async function start(port = 8080) {
26
+ if (activeServer) {
27
+ throw new Error('IPv6 Bridge is already running');
28
+ }
29
+
30
+ // Step 1: Detect if bridge is needed
31
+ const needed = await needsBridge();
32
+ if (!needed && !process.env.FORCE_BRIDGE) {
33
+ console.log('IPv6 bridge not needed — you have dual-stack or working NAT64.');
34
+ return null;
35
+ }
36
+
37
+ if (!needed) {
38
+ console.log('IPv6 bridge not needed, but FORCE_BRIDGE is set — starting anyway.');
39
+ }
40
+
41
+ // Step 2: Start the proxy server
42
+ activeServer = await createProxy(port);
43
+ return activeServer;
44
+ }
45
+
46
+ /**
47
+ * Stop the IPv6 bridge.
48
+ *
49
+ * @returns {Promise<void>} Resolves when the server has closed
50
+ */
51
+ function stop() {
52
+ if (!activeServer) return Promise.resolve();
53
+
54
+ return new Promise((resolve) => {
55
+ activeServer.close(() => {
56
+ activeServer = null;
57
+ resolve();
58
+ });
59
+ });
60
+ }
61
+
62
+ module.exports = { start, stop };
package/src/proxy.js ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * IPv6 Bridge - HTTP/HTTPS Proxy
3
+ *
4
+ * Implements application-level NAT64 (RFC 6146) by intercepting HTTP/HTTPS
5
+ * requests, resolving hostnames via DNS64, and routing through IPv6.
6
+ *
7
+ * @module proxy
8
+ */
9
+
10
+ const http = require('http');
11
+ const net = require('net');
12
+ const { resolveIPv6, detectIPVersion } = require('./dns64');
13
+ const { DEFAULT_PORT } = require('./config');
14
+
15
+ /**
16
+ * Resolve a target host to an IPv6 address if needed.
17
+ *
18
+ * @param {string} hostname - The hostname or IP to resolve
19
+ * @returns {Promise<{host: string, family: number}>} Resolved host and IP family
20
+ */
21
+ async function resolveTarget(hostname) {
22
+ const ipVersion = detectIPVersion(hostname);
23
+
24
+ if (ipVersion === 'ipv6') {
25
+ return { host: hostname, family: 6 };
26
+ }
27
+
28
+ // For both IPv4 addresses and hostnames, use DNS64 resolution
29
+ // to get an IPv6 address with the NAT64 prefix.
30
+ try {
31
+ const ipv6Addresses = await resolveIPv6(hostname);
32
+ if (ipv6Addresses && ipv6Addresses.length > 0) {
33
+ return { host: ipv6Addresses[0], family: 6 };
34
+ }
35
+ } catch {
36
+ // Fall through to direct connection
37
+ }
38
+
39
+ return { host: hostname, family: 4 };
40
+ }
41
+
42
+ /**
43
+ * Create an HTTP/HTTPS proxy server with NAT64 support.
44
+ *
45
+ * @param {number} port - Port to listen on (default: 8080)
46
+ * @returns {Promise<http.Server>} Resolves with the server once it's listening
47
+ */
48
+ function createProxy(port = DEFAULT_PORT) {
49
+ return new Promise((resolve, reject) => {
50
+ const server = http.createServer(async (req, res) => {
51
+ try {
52
+ const url = new URL(`http://${req.headers.host}${req.url}`);
53
+ const hostname = url.hostname;
54
+ const { host: targetHost, family: ipFamily } = await resolveTarget(hostname);
55
+
56
+ const options = {
57
+ hostname: targetHost,
58
+ port: url.port || 80,
59
+ path: url.pathname + url.search,
60
+ method: req.method,
61
+ headers: req.headers,
62
+ family: ipFamily,
63
+ timeout: 10000,
64
+ };
65
+
66
+ const proxy = http.request(options, (proxyRes) => {
67
+ res.writeHead(proxyRes.statusCode, proxyRes.headers);
68
+ proxyRes.pipe(res);
69
+ proxyRes.on('error', () => {
70
+ if (!res.headersSent) {
71
+ res.writeHead(502).end('Bad Gateway');
72
+ }
73
+ });
74
+ });
75
+
76
+ proxy.on('error', () => {
77
+ if (!res.headersSent) {
78
+ res.writeHead(502).end('Bad Gateway');
79
+ }
80
+ });
81
+
82
+ proxy.on('timeout', () => {
83
+ proxy.destroy();
84
+ if (!res.headersSent) {
85
+ res.writeHead(504).end('Gateway Timeout');
86
+ }
87
+ });
88
+
89
+ req.pipe(proxy);
90
+ req.on('error', () => proxy.destroy());
91
+ } catch {
92
+ if (!res.headersSent) {
93
+ res.writeHead(500).end('Internal Server Error');
94
+ }
95
+ }
96
+ });
97
+
98
+ // HTTPS CONNECT tunnel handler
99
+ server.on('connect', async (req, socket, head) => {
100
+ try {
101
+ const [hostname, rawPort] = req.url.split(':');
102
+ const targetPort = parseInt(rawPort, 10) || 443;
103
+
104
+ if (targetPort < 1 || targetPort > 65535) {
105
+ socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
106
+ return;
107
+ }
108
+
109
+ const { host: targetHost, family: ipFamily } = await resolveTarget(hostname);
110
+
111
+ const conn = net.connect(
112
+ { port: targetPort, host: targetHost, family: ipFamily },
113
+ () => {
114
+ socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
115
+ conn.write(head);
116
+ conn.pipe(socket).pipe(conn);
117
+ }
118
+ );
119
+
120
+ conn.on('error', () => socket.end());
121
+ socket.on('error', () => conn.end());
122
+ conn.setTimeout(10000, () => {
123
+ conn.destroy();
124
+ socket.end();
125
+ });
126
+ } catch {
127
+ socket.end();
128
+ }
129
+ });
130
+
131
+ server.on('error', (err) => {
132
+ reject(new Error(`Failed to start proxy: ${err.message}`));
133
+ });
134
+
135
+ server.listen(port, () => {
136
+ resolve(server);
137
+ });
138
+ });
139
+ }
140
+
141
+ module.exports = { createProxy };