-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
116 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
#!/usr/bin/env -S deno run --allow-net --allow-env=HOST,PORT,CONFIG | ||
|
||
import { initialize, environment, api, respond } from './lib.js' | ||
|
||
if (import.meta.main) main() | ||
|
||
async function main () { | ||
const t0 = initialize() | ||
const { HOST, PORT, CONFIG } = environment({ | ||
HOST: '0.0.0.0', | ||
PORT: '25552', | ||
CONFIG: [ | ||
['26666', '65.108.193.224:26656' ].join('='), | ||
['26667', '74.50.93.254:26656' ].join('='), | ||
['26668', '64.118.250.82:46656' ].join('='), | ||
['26669', '138.197.133.118:26656'].join('='), | ||
].join(',') | ||
}) | ||
run(HOST, PORT, parseConfig(CONFIG)) | ||
} | ||
|
||
async function run (localHost, controlPort, proxyConfig) { | ||
// Flag to allow/disallow connections | ||
let connected = true | ||
// List of currently open connections to close | ||
let connections = [] | ||
// Print the proxy config that is in use | ||
console.log('Proxy config:', JSON.stringify(proxyConfig, null, 2)) | ||
// Launch TCP proxy listeners for each remote peer | ||
await Promise.all(Object.entries(config).map(([localPort, { remoteHost, remotePort }])=>{ | ||
tcpProxy(localPort, remoteHost, remotePort) | ||
})) | ||
// Launch control api | ||
api('MultiSync', localHost, controlPort, { | ||
// Report status | ||
['/'] () { | ||
respond(200, { connected, connections: connections.length }) | ||
}, | ||
// Enable connecting | ||
['/start'] () { | ||
connected = true | ||
respond(200, { connected, connections: connections.length }) | ||
}, | ||
// Disable connecting | ||
['/pause'] () { | ||
connections = connections.filter(connection=>{ | ||
connection.clone() | ||
return false | ||
}) | ||
connected = false | ||
respond(200, { connected, connections: connections.length }) | ||
}, | ||
}, { | ||
onMessage: async ({ event }) => { | ||
const data = JSON.parse(event.data) | ||
if (data.resume) { | ||
console.log('🟢 Resuming sync...') | ||
await service.start() | ||
} | ||
} | ||
}) | ||
// Implementation of TCP proxy | ||
async function tcpProxy (localPort, remoteHost, remotePort) { | ||
const listener = Deno.listen({ transport: 'tcp', hostname: localHost, port: localPort }) | ||
for await (const connection of listener) { | ||
if (connected) { | ||
console.log(`Establishing connection at ${localPort} for ${remoteHost}:${remotePort}`) | ||
connections.push(connection) | ||
const remote = await Deno.connect({ host: remoteHost, port: remotePort }) | ||
await Promise.all([ | ||
connection.readable.pipeTo(remote.writable), | ||
remote.readable.pipeTo(connection.writable), | ||
]) | ||
} else { | ||
console.log(`Rejecting connection at ${localPort} for ${remoteHost}:${remotePort}`) | ||
} | ||
} | ||
} | ||
} | ||
|
||
function parseConfig (text) { | ||
const configError1 = (config) => { | ||
console.error({config}) | ||
return new Error(`Invalid config; format: LPORT=RHOST:RPORT[,LPORT=RHOST:RPORT]*`) | ||
} | ||
const configError2 = (port) => new Error(`Invalid local port in config: ${port}`) | ||
const configError3 = (host) => new Error(`Empty remote host in config: ${host}`) | ||
const configError4 = (port) => new Error(`Invalid remote port in config: ${port}`) | ||
const configError5 = (port) => new Error(`Duplicate local port in config: ${port}`) | ||
const config = {} | ||
// Config is comma-separated clauses: LocalPort=RemoteHost:RemotePort | ||
for (let line in text.split(',')) { | ||
// Ignore whitespace and empty lines | ||
line = line.trim() | ||
if (line.length === 0) continue | ||
// Split into left and right parts | ||
let split = line.split('=') | ||
if (split.length !== 2) throw configError1() | ||
// Validate port number | ||
const localPort = Number(split[0]) | ||
if (!(localPort > 0 && localPort < 65535)) throw configError2(split[0]) | ||
// Parse remote connection details | ||
split = split[1].split(':') | ||
// Validate remote host | ||
const remoteHost = split[0].trim() | ||
if (remoteHost.length === 0) throw configError3(split[0]) | ||
// Validate remote port | ||
const remotePort = Number(split[1]) | ||
if (!(remotePort > 0 && remotePort < 65535)) throw configError4(split[1]) | ||
// Prevent duplicates | ||
if (config[localPort]) throw configError5(localPort) | ||
config[localPort] = { remoteHost, remotePort } | ||
} | ||
return config | ||
} |