|
| 1 | +import { createServer, Socket, Server } from 'node:net' |
| 2 | +import {logger} from './logger' |
| 3 | +import {LayerMinus} from './layerMinus' |
| 4 | +export class ProxyServer { |
| 5 | + private server: Server |
| 6 | + private host = '0.0.0.0' |
| 7 | + |
| 8 | + constructor(private port: number, private layerMinus: LayerMinus) { |
| 9 | + this.port = port |
| 10 | + this.server = createServer(this.handleClient) |
| 11 | + this.start() |
| 12 | + } |
| 13 | + |
| 14 | + private start = () => { |
| 15 | + this.server.listen(this.port, this.host, () => { |
| 16 | + console.log(`🟢 Proxy server running at ${this.host}:${this.port}`) |
| 17 | + }) |
| 18 | + } |
| 19 | + |
| 20 | + private handleClient = (client: Socket) => { |
| 21 | + client.once('data', (data: Buffer) => { |
| 22 | + const firstByte = data[0] |
| 23 | + |
| 24 | + if (firstByte === 0x05) { |
| 25 | + this.handleSocks5(client) |
| 26 | + } else if (firstByte === 0x04) { |
| 27 | + this.handleSocks4(client, data) |
| 28 | + } else { |
| 29 | + this.handleHttp(client, data) |
| 30 | + } |
| 31 | + }) |
| 32 | + } |
| 33 | + |
| 34 | + private handleSocks5 = (client: Socket) => { |
| 35 | + client.write(Buffer.from([0x05, 0x00])) |
| 36 | + |
| 37 | + client.once('data', (req: Buffer) => { |
| 38 | + const addrType = req[3] |
| 39 | + let destAddr: string, destPort: number, offset: number |
| 40 | + |
| 41 | + if (addrType === 0x01) { |
| 42 | + destAddr = `${req[4]}.${req[5]}.${req[6]}.${req[7]}` |
| 43 | + offset = 8 |
| 44 | + } else if (addrType === 0x03) { |
| 45 | + const len = req[4] |
| 46 | + destAddr = req.slice(5, 5 + len).toString('utf8') |
| 47 | + offset = 5 + len |
| 48 | + } else { |
| 49 | + client.end() |
| 50 | + return |
| 51 | + } |
| 52 | + |
| 53 | + destPort = req.readUInt16BE(offset) |
| 54 | + this.proxyConnection(client, destAddr, destPort, () => { |
| 55 | + client.write(Buffer.from([0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0])) |
| 56 | + }) |
| 57 | + }) |
| 58 | + } |
| 59 | + |
| 60 | + private handleSocks4 = (client: Socket, data: Buffer) => { |
| 61 | + const destPort = data.readUInt16BE(2) |
| 62 | + const destIP = data.slice(4, 8) |
| 63 | + const userIdEnd = data.indexOf(0x00, 8) |
| 64 | + const isSocks4a = destIP.slice(0, 3).every(b => b === 0) && destIP[3] !== 0 |
| 65 | + |
| 66 | + let destAddr: string |
| 67 | + |
| 68 | + if (isSocks4a) { |
| 69 | + const domainStart = userIdEnd + 1 |
| 70 | + const domainEnd = data.indexOf(0x00, domainStart) |
| 71 | + destAddr = data.slice(domainStart, domainEnd).toString('utf8') |
| 72 | + } else { |
| 73 | + destAddr = `${destIP[0]}.${destIP[1]}.${destIP[2]}.${destIP[3]}` |
| 74 | + } |
| 75 | + |
| 76 | + this.proxyConnection(client, destAddr, destPort, () => { |
| 77 | + const reply = Buffer.alloc(8, 0x00) |
| 78 | + reply[1] = 0x5a |
| 79 | + client.write(reply) |
| 80 | + }, () => { |
| 81 | + const reply = Buffer.alloc(8, 0x00) |
| 82 | + reply[1] = 0x5b |
| 83 | + client.write(reply) |
| 84 | + client.end() |
| 85 | + }) |
| 86 | + } |
| 87 | + |
| 88 | + private handleHttp = (client: Socket, data: Buffer) => { |
| 89 | + const reqStr = data.toString('utf8') |
| 90 | + |
| 91 | + if (reqStr.startsWith('CONNECT')) { |
| 92 | + const [_, dest] = reqStr.split(' ') |
| 93 | + const [host, port] = dest.split(':') |
| 94 | + |
| 95 | + this.proxyConnection(client, host, parseInt(port), () => { |
| 96 | + client.write('HTTP/1.1 200 Connection Established\r\n\r\n') |
| 97 | + }) |
| 98 | + } else { |
| 99 | + const hostLine = reqStr.split('\r\n').find(line => line.startsWith('Host:')) |
| 100 | + if (!hostLine) return client.end() |
| 101 | + |
| 102 | + const host = hostLine.split(' ')[1] |
| 103 | + const port = 80 |
| 104 | + |
| 105 | + this.proxyConnection(client, host, port, (remote) => { |
| 106 | + remote.write(data) |
| 107 | + }) |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + private proxyConnection = ( |
| 112 | + client: Socket, |
| 113 | + host: string, |
| 114 | + port: number, |
| 115 | + onSuccess?: (remote: Socket) => void, |
| 116 | + onError?: () => void |
| 117 | + ) => { |
| 118 | + |
| 119 | + if (this.layerMinus) { |
| 120 | + return this.layerMinus.connectToLayerMinus(client, host, port) |
| 121 | + } |
| 122 | + const remote = new Socket() |
| 123 | + logger (`${host}:${port}`) |
| 124 | + |
| 125 | + remote.connect(port, host, () => { |
| 126 | + onSuccess?.(remote) |
| 127 | + client.pipe(remote) |
| 128 | + remote.pipe(client) |
| 129 | + |
| 130 | + }) |
| 131 | + |
| 132 | + remote.on('error', () => { |
| 133 | + onError?.() |
| 134 | + client.end() |
| 135 | + }) |
| 136 | + |
| 137 | + |
| 138 | + } |
| 139 | + |
| 140 | + private stop = () => { |
| 141 | + this.server.close(() => { |
| 142 | + console.log('🔴 Proxy server stopped') |
| 143 | + }) |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | + |
| 148 | +/** |
| 149 | + * test |
| 150 | + * curl -v -x http://127.0.0.1:3002 "https://www.google.com" |
| 151 | +// curl -v -x socks4a://localhost:3002 "https://www.google.com" |
| 152 | +// curl -v -x socks4://localhost:3002 "https://www.google.com" |
| 153 | +// curl -v -x socks5h://localhost:3002 "https://www.google.com" |
| 154 | +
|
| 155 | + * curl -v -x http://127.0.0.1:3003 "https://www.google.com" |
| 156 | + curl -v -x http://127.0.0.1:3003 "http://www.google.com" |
| 157 | +// curl -v -x socks4a://localhost:3003 "https://www.google.com" |
| 158 | +// curl -v -x socks4://localhost:3003 "https://www.google.com" |
| 159 | +// curl -v -x socks5h://localhost:3003 "https://www.google.com" |
| 160 | + * |
| 161 | + */ |
| 162 | + |
| 163 | +const test = () => { |
| 164 | + const entryNodes: nodes_info[] = [{ |
| 165 | + "region": "NW.DE", |
| 166 | + "country": "DE", |
| 167 | + "ip_addr": "217.160.189.159", |
| 168 | + "armoredPublicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxjMEZq2V5xYJKwYBBAHaRw8BAQdAhqIi6sQx/wqogD+T0Yftwsx7iBhd4Iyh\nlRCFnJKBODHNKjB4Y2JCQjEzNzE5NzNENTdlNmJENDVhQzBkZmVGRDQ5M2I1\nOUY5RDc2QsKMBBAWCgA+BYJmrZXnBAsJBwgJkArZXaLou3oNAxUICgQWAAIB\nAhkBApsDAh4BFiEExfcG2i3ma6s72VROCtldoui7eg0AAHFgAQCrT8y1Y69H\noXTHfdLuEk+XUDpq4CAvj7KkHxbPNQU+PQD/SdBbRUcvSkzzoU4tLcXxVI0Q\nST8za1hvo3RdWCglxAPOOARmrZXnEgorBgEEAZdVAQUBAQdAcLPhpj4WdcZN\nu7pP/LLYYjzg0JhyYvVpDwUoXa9WmkoDAQgHwngEGBYKACoFgmatlecJkArZ\nXaLou3oNApsMFiEExfcG2i3ma6s72VROCtldoui7eg0AADvRAQDrgO8K+hza\ntH4LTpGZ7OscC7M2ZtUV0zXshHlEnxS5NgD/ZCAHabk0Y47bANGG7KrcqsHY\n3pmfYRPFcvckAoPiagc=\n=VhCf\n-----END PGP PUBLIC KEY BLOCK-----\n", |
| 169 | + "last_online": false, |
| 170 | + "nftNumber": 100, |
| 171 | + "domain": "9977E9A45187DD80.conet.network" |
| 172 | + }] |
| 173 | + const egressNodes: nodes_info[] = [{ |
| 174 | + "region": "MD.ES", |
| 175 | + "country": "ES", |
| 176 | + "ip_addr": "93.93.112.187", |
| 177 | + "armoredPublicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxjMEZo9ITBYJKwYBBAHaRw8BAQdAtFGkXMLHSAJ3jMZAVmfMvtFF74PkpYR9\nT50s9Ndr6HnNKjB4NmJGM0FhNzI2MWUyMUJlNUZjNzgxQWMwOUY5NDc1YzhB\nMzRBZkVlYcKMBBAWCgA+BYJmj0hMBAsJBwgJkOe/gynD16TlAxUICgQWAAIB\nAhkBApsDAh4BFiEEpJqLA2EpKEPDlaCI57+DKcPXpOUAALCdAQCIFyD/LlbY\nRGWzyaS++BBNIslOoktpHxzcgS+sD7dJggEAxGvDZQiu42l7VlStvlN4J9Jr\nGWJy8opWUlghMFcZHgrOOARmj0hMEgorBgEEAZdVAQUBAQdAqtevF55R1RHW\nh3L8novWfriyXuVZJo/vwUTylQwdCggDAQgHwngEGBYKACoFgmaPSEwJkOe/\ngynD16TlApsMFiEEpJqLA2EpKEPDlaCI57+DKcPXpOUAAHHFAQCbOklWpmRw\niorLHhB99zbaNfsn9/F2uJwRs9U0/mBAhQEAg0VOc4nDfb9MD0tHTP6crD62\nFaYFiQ7vNSBo3DuXlw0=\n=XXSu\n-----END PGP PUBLIC KEY BLOCK-----\n", |
| 178 | + "last_online": false, |
| 179 | + "nftNumber": 101, |
| 180 | + "domain": "B4CB0A41352E9BDF.conet.network" |
| 181 | + }] |
| 182 | + const privateKey = '0xc3c55e163fa1ad5a08101b21eeb56756fb68605b0c8ce7b2bbbfa336f01b32c0' |
| 183 | + const layerMinus = new LayerMinus (entryNodes, egressNodes, privateKey) |
| 184 | + new ProxyServer(3002, layerMinus) |
| 185 | +} |
| 186 | + |
| 187 | +test () |
| 188 | + |
0 commit comments