commit c51c98aacd4425f608ca3aed958f690d536ffe1a Author: MCHost Date: Fri Mar 29 02:43:46 2024 -0400 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/404.txt b/404.txt new file mode 100644 index 0000000..05cb7fc --- /dev/null +++ b/404.txt @@ -0,0 +1,221 @@ + + + + + + My-MC.LINK - Peer Not Found! + + + + + + +

Look's like we did not get that quite right...

+

Routing Error: Invalid hash or connection timeout.
Please reset your link and try again.

+
+ 4 + 0 + 4 +
+ + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..029b960 --- /dev/null +++ b/README.md @@ -0,0 +1,132 @@ + +# hyperMC-Web-Relay + +This relays Hyper Relay connection hashes over wildcard DNS and wildcard virtualhost reverse proxy to allow for automatic URLs via connection topics. + +Set up instructions: + +This will require a server listening to a connection topic using our version of hyper-relay: + + +https://git.ssh.surf/hypermc/hyper-relay + +`git clone git@git.ssh.surf:hypermc/hyper-relay.git` + +`cd hyper-relay` + +`npm i` + +Now listen to a TCP port over the Hyper Network: +``` +root@server:/hyper-relay# node server.js 80 hellothere +w7zpwusw6ljrgbffxtklak23rkhaxwhs2kskozteme6ofefevlia +``` + +The result above shows the connection hash for your hyper port. + +This will be the hash used as your subdomain later in this process. + +NOTE: The "hellothere" above is the connection "topic" this is used to generate the hash and it can be any string or key phrase you wish. + + +# The Reverse proxy + +We will proxy all of the traffic to their specific peers using a reverse proxy method, in this example, we will utilize httpd/apache below you will find the standard configuration that will work for our purposes. + +``` + + SSLProxyEngine On + ServerAlias *.your-tld.here + ServerAdmin webmaster@your-tld.here + DocumentRoot /var/www/html + + ErrorLog /var/log/error.log + CustomLog /var/log/access.log combined + + SSLCertificateFile /path/to/fullchain.pem + SSLCertificateKeyFile /path/to/privkey.pem + + # Reverse Proxy for subdomains + + Require all granted + + + ProxyPreserveHost On + RequestHeader set X-Forwarded-Proto https + ProxyPass "/" "http://localhost:8081/" + ProxyPassReverse "/" "http://localhost:8081/" + + +``` + +NOTE 1: This requires `mod_proxy` and `mod_ssl` to work properly. + +NOTE 2: This requires the full use of the wildcard *.your-told.here you will not be able to use any subdomain within this scope unless it runs via a hyper-relay. + +We highly reccomend using SSL only for this reverse connection, however, you may configure your proxy pass as desired. + +# Wildcard DNS Entry + +In order to serve your subdomain hashes without the need to generate an A record create a wildcard subdomain record. + +Example: +`*.example.com. IN A 192.0.2.1` + +This system does work well with cloudflare proxy, feel free to proxy your traffic! + +# Running the Web Relay ServerAdmin + +`git clone git@git.ssh.surf:hypermc/hyperMC-Web-Relay.git` + +`cd hyperMC-Web-Relay` + +`npm i` + +There are two servers provided within this repo. + +One will work without the use of workers, and it can work well for static content sites. + +To deploy this server, run `node non-worker-server.js` + +The relay will begin listening on port 8081, this port may be changed in the script. +``` +[root@server hyperMC-Web-Relay]# node fullyworking_proxy.js +Creating HyperDHT server... +HyperDHT server created and ready. +HTTP server listening on port 8081 +``` + +To deploy a multi worker server, run `node worker-server.js` + +The server will start workers based off your CPU count and then start those workers listening for connections: + +``` +[root@server hyper-web-relay]# node worker-server.js +Master 703855 is running +Total Workers 4 +Worker 703885 started +Worker 703878 started +Worker 703904 started +Worker 703929 started +Worker 703878 listening on port 8081 +Worker 703965 listening on port 8081 +Worker 704044 listening on port 8081 +Worker 703885 listening on port 8081 +``` + +You now can make a request from a hyper-relay connection hash: + +``` +~ ❯ curl -IL https://w7zpwusw6ljrgbffxtklak23rkhaxwhs2kskozteme6ofefevlia.your-tld.here/ +HTTP/2 200 +date: Fri, 29 Mar 2024 06:39:38 GMT +content-type: text/html; charset=UTF-8 +last-modified: Tue, 19 Mar 2024 05:07:41 GMT +vary: Accept-Encoding +cf-cache-status: DYNAMIC +report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=mGhuDrFp%2B2lMm9iQ45CshdOmN7Sbf%2Fs1dgWKD3PtcISbcBi6uB4YZIG2qTTmfRpL1C1f3vqF01IFERjjpDspEuQ98dRfgQpelyODFjd99aeUY5uGQcPxQoAusjb%2BBxwRWkI6ZQE0EPKy4m%2BeGbY0%2FGLVndHc%2F52vEvvAIIhzR%2FqMIweFqgyAwGAaELc7sUIDayE%3D"}],"group":"cf-nel","max_age":604800} +nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800} +server: cloudflare +cf-ray: 86bddd2349cf8bbb-ATL +alt-svc: h3=":443"; ma=86400 +``` \ No newline at end of file diff --git a/non-worker-server.js b/non-worker-server.js new file mode 100644 index 0000000..f4967ba --- /dev/null +++ b/non-worker-server.js @@ -0,0 +1,141 @@ +const fs = require('fs'); +const http = require('http'); +const httpProxy = require('http-proxy'); +const HyperDHTServer = require('hyperdht'); +const libNet = require('hyper-cmd-lib-net'); +const goodbye = require('graceful-goodbye'); +const libKeys = require('hyper-cmd-lib-keys'); +const b32 = require("hi-base32"); +const net = require("net"); +const pump = require("pump"); +let invalid = false; + +let mod = 0; +const tunnels = {}; +const agent = new http.Agent({ maxSockets: Number.MAX_VALUE }); +const content = fs.readFileSync('404.txt', 'utf8'); +const DEBUG = 0; // Set DEBUG to 1 to enable debug mode +// Enabling DEBUG mode will slow down the code, please only enable this if you need +// Information about connections coming in because you need to validate connection hashes. + +const startServer = async () => { + console.log("Creating HyperDHT server..."); + const dhtServer = new HyperDHTServer(); + await dhtServer.ready(); + console.log("HyperDHT server created and ready."); + + const proxy = httpProxy.createProxyServer({ + ws: true, + agent: agent, + timeout: 360000 + }); + + const server = http.createServer(async function (req, res) { + try { + console.log("Incoming HTTP request..."); + const split = req.headers.host.split('.'); + if (DEBUG === 1) console.log("Request Headers Host Split: ", split); + const publicKey = Buffer.from(b32.decode.asBytes(split[0].toUpperCase())); + if (DEBUG === 1) { + console.log("Public Key Buffer: ", publicKey); + console.log("Length: " + publicKey.length); + } + if (!(publicKey.length >= 32)) { + console.log("Invalid Connection!") + res.writeHead(404, { 'Content-Type': 'text/html' }); + res.end(content); + invalid = true + } else { + invalid = false + } + if (invalid == true) return + if (!tunnels[publicKey]) { + console.log("No tunnel found for public key, creating new tunnel..."); + const port = 1337 + ((mod++) % 1000); + const server = net.createServer(function (servsock) { + console.log('Incoming Connection, Connecting to:', publicKey.toString('hex')); + if (DEBUG === 1) console.log("Public Key Length: ", publicKey.toString('hex').length); + const socket = dhtServer.connect(publicKey); + + const local = servsock; + let open = { local: true, remote: true }; + local.on('data', (d) => { + if (DEBUG === 1) console.log("Local Data: ", d); + socket.write(d); + }); + socket.on('data', (d) => { + if (DEBUG === 1) console.log("Socket Data: ", d); + local.write(d); + }); + + const remoteend = (type) => { + if (DEBUG === 1) console.log('Local Connection Ended, Ending Remote Connection'); + if (open.remote) socket.end(); + open.remote = false; + }; + + const localend = (type) => { + if (DEBUG === 1) console.log('Remote Connection Ended, Ending Local Connection'); + if (open.local) local.end(); + open.local = false; + }; + + local.on('error', remoteend); + local.on('finish', remoteend); + local.on('end', remoteend); + socket.on('finish', localend); + socket.on('error', localend); + socket.on('end', localend); + }); + + server.listen(port, "127.0.0.1"); + tunnels[publicKey] = port; + console.log("New tunnel created. Public Key:", publicKey, "Port:", port); + proxy.web(req, res, { + target: 'http://127.0.0.1:' + port + }, function (e) { + console.log("Proxy Web Error: ", e); + res.writeHead(404, { 'Content-Type': 'text/html' }); + res.end(content); + }); + return; + } else { + console.log('Tunnel exists for public key:', publicKey); + proxy.web(req, res, { + target: 'http://127.0.0.1:' + tunnels[publicKey] + }, function (e) { + console.log("Proxy Web Error: ", e); + res.writeHead(404, { 'Content-Type': 'text/html' }); + res.end(content); + }); + } + } catch (e) { + console.error("Error Occurred: ", e); + } + }); + + server.on('upgrade', function (req, socket, head) { + console.log('Upgrade Request'); + const split = req.headers.host.split('.'); + const publicKey = Buffer.from(b32.decode.asBytes(split[0].toUpperCase())); + proxy.ws(req, socket, { + target: 'http://127.0.0.1:' + tunnels[publicKey] + }, function (e) { + console.error("Proxy WS Error: ", e); + socket.end(); + }); + }); + + server.listen(8081); + console.log("HTTP server listening on port 8081"); + + process.on('SIGINT', function () { + console.log("Shutting down..."); + server.close(); + dhtServer.destroy(); + console.log("Shutdown complete."); + process.exit(); + }); +}; + +startServer().catch(console.error); diff --git a/package.json b/package.json new file mode 100644 index 0000000..2de5c8b --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "raven", + "version": "1.0.0", + "description": "", + "main": "proxy.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@hyperswarm/dht": "^6.5.1", + "bun": "^1.0.35", + "graceful-goodbye": "^1.3.0", + "hi-base32": "^0.5.1", + "http": "^0.0.1-security", + "http-proxy": "^1.18.1", + "http2-proxy": "^5.0.53", + "hyper-cmd-lib-keys": "^0.1.0", + "hyper-cmd-lib-net": "^0.1.0", + "hyperdht": "^6.13.1", + "pump": "^3.0.0" + } +} diff --git a/relay.js b/relay.js new file mode 100644 index 0000000..d127e91 --- /dev/null +++ b/relay.js @@ -0,0 +1,35 @@ +const DHT = require("@hyperswarm/dht"); +const net = require("net"); +const pump = require("pump"); +const node = new DHT({}); +module.exports = () => { + return { + serve: (keyPair, host, port) => { + const server = node.createServer(); + server.on("connection", function (servsock) { + const socket = net.connect(port, host); + socket.on('serve error', console.error); + let open = { servsock: true, remote: true }; + servsock.on('data', (d) => { socket.write(d) }); + socket.on('data', (d) => { servsock.write(d) }); + + const remoteend = () => { + if (open.remote) socket.end(); + open.remote = false; + } + const servsockend = () => { + if (open.servsock) servsock.end(); + open.servsock = false; + } + servsock.on('error', remoteend) + servsock.on('finish', remoteend) + servsock.on('end', remoteend) + socket.on('finish', servsockend) + socket.on('error', servsockend) + socket.on('end', servsockend) + }); + server.listen(keyPair); + return keyPair.publicKey; + } + }; +}; diff --git a/server.js b/server.js new file mode 100644 index 0000000..3b77c25 --- /dev/null +++ b/server.js @@ -0,0 +1,34 @@ +const net = require('net'); +const b32 = require("hi-base32"); +const DHT = require("@hyperswarm/dht"); +const node = new DHT({}); +const pump = require("pump"); +const { pipeline } = require('stream'); +const server = net.createServer(clientSocket => { + clientSocket.on('end', () => { + console.log('Client disconnected'); + }); + + clientSocket.on('error', err => { + console.error('Client socket error:', err); + }); + + clientSocket.once("data",(firstData) => { + const firstLine = firstData.toString() + if (firstLine !== '') { + console.log(firstLine) + const [hostname] = firstLine.split("\n").filter(a=>a.toLowerCase().startsWith('host:')); + const split = hostname.split(":")[1].trim().split('.'); + console.log(split); + const publicKey = Buffer.from(b32.decode.asBytes(split[0].toUpperCase()), 'hex'); + console.log(publicKey.toString('hex')) + const socket = node.connect(publicKey); + socket.on('client error', console.error); + pump(socket,clientSocket,socket) + socket.write(firstLine); + } + }); +}); +server.listen(8081, () => { + console.log('Proxy server listening'); +}); diff --git a/worker-server.js b/worker-server.js new file mode 100644 index 0000000..090909c --- /dev/null +++ b/worker-server.js @@ -0,0 +1,154 @@ +const cluster = require('cluster'); +const numCPUs = require('os').cpus().length; + +if (cluster.isMaster) { + console.log(`Master ${process.pid} is running`); + console.log(`Total Workers ${numCPUs*8}`) + // Fork workers + for (let i = 0; i < numCPUs; i++) { + cluster.fork(); + } + + cluster.on('exit', (worker, code, signal) => { + console.log(`Worker ${worker.process.pid} died`); + }); +} else { + const fs = require('fs'); + const http = require('http'); + const httpProxy = require('http-proxy'); + const HyperDHTServer = require('hyperdht'); + const b32 = require("hi-base32"); + const net = require("net"); + let invalid = false; + + const tunnels = {}; + const agent = new http.Agent({ maxSockets: Number.MAX_VALUE }); + const content = fs.readFileSync('404.txt.new', 'utf8'); + const DEBUG = 0; // Set DEBUG to 1 to enable debug mode + const CONINFO = 0; // Set CONINFO to 1 for a smaller breakdown of connections. + const dhtServer = new HyperDHTServer(); + + const startServer = async () => { + console.log(`Worker ${process.pid} started`); + await dhtServer.ready(); + + const proxy = httpProxy.createProxyServer({ + ws: true, + agent: agent, + timeout: 360000 + }); + + const server = http.createServer(async function (req, res) { + try { + if (DEBUG === 1 || CONINFO === 1) console.log("Incoming HTTP request..."); + const split = req.headers.host.split('.'); + if (DEBUG === 1) console.log("Request Headers Host Split: ", split); + const publicKey = Buffer.from(b32.decode.asBytes(split[0].toUpperCase())); + if (DEBUG === 1) { + console.log("Public Key Buffer: ", publicKey); + console.log("Length: " + publicKey.length); + } + if (!(publicKey.length >= 32)) { + console.log("Invalid Connection!") + res.writeHead(418, { 'Content-Type': 'text/html' }); + res.end(content); + invalid = true + } else { + invalid = false + } + if (invalid == true) return + if (!tunnels[publicKey]) { + if (DEBUG === 1 || CONINFO === 1) console.log("No tunnel found for public key, creating new tunnel..."); + const port = await getAvailablePort(); // Get an available port + const server = net.createServer(function (servsock) { + if (DEBUG === 1 || CONINFO === 1) console.log('Incoming Connection, Connecting to:', publicKey.toString('hex')); + if (DEBUG === 1) console.log("Public Key Length: ", publicKey.toString('hex').length); + const socket = dhtServer.connect(publicKey); + + const local = servsock; + let open = { local: true, remote: true }; + local.on('data', (d) => { + if (DEBUG === 1) console.log("Local Data: ", d); + socket.write(d); + }); + socket.on('data', (d) => { + if (DEBUG === 1) console.log("Socket Data: ", d); + local.write(d); + }); + + const remoteend = (type) => { + if (DEBUG === 1) console.log('Local Connection Ended, Ending Remote Connection'); + if (open.remote) socket.end(); + open.remote = false; + }; + + const localend = (type) => { + if (DEBUG === 1) console.log('Remote Connection Ended, Ending Local Connection'); + if (open.local) local.end(); + open.local = false; + }; + + local.on('error', remoteend); + local.on('finish', remoteend); + local.on('end', remoteend); + socket.on('finish', localend); + socket.on('error', localend); + socket.on('end', localend); + }); + + // Check if the port is available + server.listen(port, "127.0.0.1", () => { + if (DEBUG === 1 | CONINFO === 1) console.log(`Tunnel server listening on port ${port}`); + tunnels[publicKey] = port; + if (DEBUG === 1 || CONINFO === 1) console.log("New tunnel created. Public Key:", publicKey, "Port:", port); + proxy.web(req, res, { + target: 'http://127.0.0.1:' + port + }, function (e) { + console.log("Proxy Web Error: ", e); + res.writeHead(404, { 'Content-Type': 'text/html' }); + res.end(content); + }); + }); + return; + } else { + if (DEBUG === 1) console.log('Tunnel exists for public key:', publicKey); + proxy.web(req, res, { + target: 'http://127.0.0.1:' + tunnels[publicKey] + }, function (e) { + console.log("Proxy Web Error: ", e); + res.writeHead(418, { 'Content-Type': 'text/html' }); + res.end(content); + }); + } + } catch (e) { + console.error("Error Occurred: ", e); + } + }); + + server.on('upgrade', function (req, socket, head) { + if (DEBUG === 1) console.log('Upgrade Request'); + const split = req.headers.host.split('.'); + const publicKey = Buffer.from(b32.decode.asBytes(split[0].toUpperCase())); + proxy.ws(req, socket, { + target: 'http://127.0.0.1:' + tunnels[publicKey] + }, function (e) { + console.error("Proxy WS Error: ", e); + socket.end(); + }); + }); + + server.listen(8081, () => { + console.log(`Worker ${process.pid} listening on port 8081`); + }); + }; + + startServer().catch(console.error); + + async function getAvailablePort() { + let port; + do { + port = 1337 + Math.floor(Math.random() * 1000); // Generate a random port between 1337 and 2336 + } while (Object.values(tunnels).includes(port)); // Check if the port is already used + return port; + } +}