From 09aefa80737aaf0872b1e90a6bf3d00c21cc6bac Mon Sep 17 00:00:00 2001 From: Moon Flower Date: Mon, 2 Mar 2026 16:04:48 +0330 Subject: plain http layer on 10001 --- layer/broadlayer.ts | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) diff --git a/layer/broadlayer.ts b/layer/broadlayer.ts index 8d57556..6237f6b 100644 --- a/layer/broadlayer.ts +++ b/layer/broadlayer.ts @@ -42,6 +42,7 @@ ------ websocket: ---------- the websocket connection behaves exactly like a plain connection to the server would. ---------- only difference is a special command "ping", which returns "pong" and nothing is relayed to the target server. +---------- websocket returns `ok_` as your client id. you can downgrade from websocket and switch to http if you want. */ const hardcode_target = true @@ -264,6 +265,203 @@ setInterval(() => { if (hasEvicted) console.log('clients:' + Object.keys(sessions).length) }, eviction_timeout) +Bun.serve({ + port: '10001', + hostname: server_ip, + routes: { + '/': new Response('this is a broadlayer server.'), + '/motd': new Response('default message-of-the-day!'), + '/timeout': new Response(eviction_timeout.toString()), + '/tcp/connect/:ip/:port': async req => { + const port = parseInt(req.params.port) + if (isNaN(port)) return new Response('err_badport') + if (port > 65535 || port < 0) return new Response('err_badport') + + // ratelimit + const clientIp = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || 'unknown' + let count = connectionsPerIp.get(clientIp) || 0 + if (count >= ratelimit_count) { + return new Response('err_toomany', { status: 429 }) + } + + const res = await establishTCP(req.params.ip, port, clientIp) + if (res === false) return new Response('err_timeout') + + connectionsPerIp.set(clientIp, count + 1) + console.log('clients:' + Object.keys(sessions).length) + return new Response('ok_' + res) + }, + '/udp/connect/:ip/:port': async req => { + const port = parseInt(req.params.port) + if (isNaN(port)) return new Response('err_badport') + if (port > 65535 || port < 0) return new Response('err_badport') + + // ratelimit + const clientIp = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || 'unknown' + let count = connectionsPerIp.get(clientIp) || 0 + if (count >= ratelimit_count) { + return new Response('err_toomany', { status: 429 }) + } + + const res = await establishUDP(req.params.ip, port, clientIp) + if (!res) return new Response('err_badhost') + connectionsPerIp.set(clientIp, count + 1) + console.log('clients:' + Object.keys(sessions).length) + return new Response('ok_' + res) + }, + '/alive/:id': async req => { // heartbeat + const sess = sessions[req.params.id] + if (!sess) return new Response('err_badid') + sess.lastSeen = Date.now() + return new Response('me too') + }, + '/poll/:id': async req => { // read and consume queue + const sess = sessions[req.params.id] + if (!sess) return new Response('err_badid') + const resp = JSON.stringify(sess.queue) + sess.queue = [] + sess.lastSeen = Date.now() + return new Response(resp) + }, + '/peek/:id': async req => { // read queue without consuming + const sess = sessions[req.params.id] + if (!sess) return new Response('err_badid') + const resp = JSON.stringify(sess.queue) + sess.lastSeen = Date.now() + return new Response(resp) + }, + '/close/:id': async req => { // closes a connection + const sess = sessions[req.params.id] + if (!sess) return new Response('err_badid') + if (sess.mode === 'tcp') sess.socket?.close() + else { + sess.die() + delete sessions[req.params.id] + } + + console.log('clients:' + Object.keys(sessions).length) + return new Response('ok') + }, + '/send/:id': { + POST: async req => { + const sess = sessions[req.params.id] + if (!sess) return new Response('err_badid') + + if (!req.body) return new Response('err_empty') + const buf = await (await req.body.blob())!.arrayBuffer() + if (sess.mode === 'tcp') sess.socket?.write(buf) + else udp.send(buf, sess.port, sess.ip) + + console.log('sendto') + return new Response('ok') + } + }, + // websocket upgrades + '/tcp/ws/:ip/:port': async (req, server) => { + const port = parseInt(req.params.port) + if (isNaN(port)) return new Response('err_badport') + if (port > 65535 || port < 0) return new Response('err_badport') + + // ratelimit + const clientIp = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || 'unknown' + let count = connectionsPerIp.get(clientIp) || 0 + if (count >= ratelimit_count) { + return new Response('err_toomany', { status: 429 }) + } + + const res = await establishTCP(req.params.ip, port, clientIp) + if (res === false) return new Response('err_timeout') + + connectionsPerIp.set(clientIp, count + 1) + + server.upgrade(req, { + data: { sessionId: res }, + headers: { + 'Set-Cookie': `SessionID=${res}` + } + }) + + console.log('clients:' + Object.keys(sessions).length) + return new Response('ok_' + res) + }, + '/udp/ws/:ip/:port': async (req, server) => { + const port = parseInt(req.params.port) + if (isNaN(port)) return new Response('err_badport') + if (port > 65535 || port < 0) return new Response('err_badport') + + // ratelimit + const clientIp = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || 'unknown' + let count = connectionsPerIp.get(clientIp) || 0 + if (count >= ratelimit_count) { + return new Response('err_toomany', { status: 429 }) + } + connectionsPerIp.set(clientIp, count + 1) + + const res = await establishUDP(req.params.ip, port, clientIp) + if (!res) return new Response('err_badhost') + + server.upgrade(req, { + data: { sessionId: res }, + headers: { + 'Set-Cookie': `SessionID=${res}` + } + }) + + console.log('clients:' + Object.keys(sessions).length) + return new Response('ok_' + res) + } + }, + websocket: { + data: { sessionId: '' }, + perMessageDeflate: true, + open(ws) { + const sessionId = ws.data.sessionId + if (!sessionId || !sessions[sessionId]) { + ws.close() + return + } + sessions[sessionId].ws = ws + sessions[sessionId].lastSeen = Date.now() + ws.send('ok_' + sessionId) + }, + message(ws, message) { + const sessionId = ws.data.sessionId + const sess = sessions[sessionId] + if (!sess) { + ws.close() + return + } + sess.lastSeen = Date.now() + + if (typeof message === 'string') { + // ping + if (message === 'ping') { + ws.send('pong') + return + } + // send + const buf = Buffer.from(message) + if (sess.mode === 'tcp') sess.socket?.write(buf) + else udp.send(buf, sess.port, sess.ip) + } else { + // binary + if (sess.mode === 'tcp') sess.socket?.write(message) + else udp.send(message, sess.port, sess.ip) + } + }, + close(ws) { + const sessionId = ws.data.sessionId + if (sessionId && sessions[sessionId]) { + // close websocket but keepalive connection, in case user wants to downgrade to polling + const sess = sessions[sessionId] + sess.ws = undefined + sess.lastSeen = Date.now() + console.log('connclose') + } + } + } +}) + Bun.serve({ port: server_port, hostname: server_ip, -- cgit v1.2.3