aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Moon Flower <mforoud86@gmail.com> 2026-03-02 16:04:48 +0330
committerGravatar Moon Flower <mforoud86@gmail.com> 2026-03-02 16:04:48 +0330
commit09aefa80737aaf0872b1e90a6bf3d00c21cc6bac (patch)
treedf8950e5aa7b2c71cb35e1ad599cdceba434504f
parentfonts+changes (diff)
plain http layer on 10001
-rw-r--r--layer/broadlayer.ts198
1 files changed, 198 insertions, 0 deletions
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_<uuid>` as your client id. you can downgrade from websocket and switch to http if you want.
*/
const hardcode_target = true
@@ -265,6 +266,203 @@ setInterval(() => {
}, 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,
tls: {