teqo-backup/scripts/egress-proxy.mjs

95 lines
2.6 KiB
JavaScript

import http from 'node:http'
import net from 'node:net'
const HOST = process.env.PROXY_HOST ?? '100.94.122.26'
const PORT = Number(process.env.PROXY_PORT ?? 3128)
const ALLOWED_CLIENTS = new Set(
(process.env.PROXY_ALLOWED_CLIENTS ?? '100.119.220.31,127.0.0.1,::1')
.split(',')
.map((value) => value.trim())
.filter(Boolean),
)
const ALLOWED_PORTS = new Set(
(process.env.PROXY_ALLOWED_PORTS ?? '443')
.split(',')
.map((value) => Number(value.trim()))
.filter((value) => Number.isInteger(value) && value > 0),
)
const log = (...args) => console.log(new Date().toISOString(), ...args)
const clientAllowed = (socket) => {
const ip = (socket.remoteAddress ?? '').replace(/^::ffff:/, '')
return ALLOWED_CLIENTS.has(ip)
}
const server = http.createServer((req, res) => {
let target
try {
target = new URL(req.url ?? '')
} catch {
res.writeHead(400)
res.end('bad request')
return
}
const port = Number(target.port || 80)
if (target.protocol !== 'http:' || !ALLOWED_PORTS.has(port)) {
res.writeHead(403)
res.end('forbidden')
return
}
const upstream = http.request(
{
host: target.hostname,
port,
path: `${target.pathname}${target.search}`,
method: req.method,
headers: req.headers,
},
(upstreamRes) => {
res.writeHead(upstreamRes.statusCode ?? 502, upstreamRes.headers)
upstreamRes.pipe(res)
},
)
upstream.on('error', () => {
if (!res.headersSent) res.writeHead(502)
res.end('bad gateway')
})
req.pipe(upstream)
})
server.on('connect', (req, clientSocket, head) => {
const [rawHost, rawPort] = (req.url ?? '').split(':')
const host = rawHost?.trim()
const port = Number(rawPort ?? 443)
if (!host || !ALLOWED_PORTS.has(port)) {
log('denied connect', req.url)
clientSocket.end('HTTP/1.1 403 Forbidden\r\n\r\n')
return
}
const upstream = net.connect(port, host, () => {
clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n')
if (head.length > 0) upstream.write(head)
upstream.pipe(clientSocket)
clientSocket.pipe(upstream)
})
upstream.setTimeout(30000, () => upstream.destroy())
upstream.on('error', (error) => {
log('upstream error', host, error.code)
clientSocket.destroy()
})
clientSocket.on('error', () => upstream.destroy())
})
server.on('connection', (socket) => {
if (!clientAllowed(socket)) {
log('denied client', socket.remoteAddress)
socket.destroy()
}
})
server.listen(PORT, HOST, () => {
log(
`egress proxy on ${HOST}:${PORT} — clients: ${[...ALLOWED_CLIENTS].join(',')} — ports: ${[...ALLOWED_PORTS].join(',')}`,
)
})