├── AUTHORS ├── package.json ├── README.rst ├── LICENSE ├── socks └── socks.js /AUTHORS: -------------------------------------------------------------------------------- 1 | Gert Van Gool 2 | John Cant 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-socks" 3 | , "version": "0.1.0" 4 | , "description": "A simple SOCKS implementation and demo proxy" 5 | , "author": "Gert Van Gool " 6 | , "dependencies": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | SOCKS implementation in node.js 2 | =============================== 3 | 4 | A simple SOCKS implementation and demo proxy in `node.js `_. 5 | 6 | You can run it easily as:: 7 | 8 | ./socks 9 | 10 | This will create a proxy at ``127.0.0.1`` on port ``8888``. 11 | 12 | You can use this as a good starting point for writing a proxy or a tunnel! 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2013 Gert Van Gool 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /socks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var net = require('net'), 3 | socks = require('./socks.js'); 4 | 5 | // Create server 6 | // The server accepts SOCKS connections. This particular server acts as a proxy. 7 | var HOST='127.0.0.1', 8 | PORT='8888', 9 | server = socks.createServer(function(socket, port, address, proxy_ready) { 10 | 11 | // Implement your own proxy here! Do encryption, tunnelling, whatever! Go flippin' mental! 12 | // I plan to tunnel everything including SSH over an HTTP tunnel. For now, though, here is the plain proxy: 13 | 14 | console.log('Got through the first part of the SOCKS protocol.') 15 | var proxy = net.createConnection(port, address, proxy_ready); 16 | 17 | proxy.on('data', function(d) { 18 | try { 19 | console.log('receiving ' + d.length + ' bytes from proxy'); 20 | socket.write(d); 21 | } catch(err) { 22 | } 23 | }); 24 | socket.on('data', function(d) { 25 | // If the application tries to send data before the proxy is ready, then that is it's own problem. 26 | try { 27 | console.log('sending ' + d.length + ' bytes to proxy'); 28 | proxy.write(d); 29 | } catch(err) { 30 | } 31 | }); 32 | 33 | proxy.on('close', function(had_error) { 34 | socket.end(); 35 | console.error('The proxy closed'); 36 | }.bind(this)); 37 | socket.on('close', function(had_error) { 38 | if (this.proxy !== undefined) { 39 | proxy.removeAllListeners('data'); 40 | proxy.end(); 41 | } 42 | console.error('The application closed'); 43 | }.bind(this)); 44 | 45 | }); 46 | 47 | server.on('error', function (e) { 48 | console.error('SERVER ERROR: %j', e); 49 | if (e.code == 'EADDRINUSE') { 50 | console.log('Address in use, retrying in 10 seconds...'); 51 | setTimeout(function () { 52 | console.log('Reconnecting to %s:%s', HOST, PORT); 53 | server.close(); 54 | server.listen(PORT, HOST); 55 | }, 10000); 56 | } 57 | }); 58 | server.listen(PORT, HOST); 59 | 60 | // vim: set filetype=javascript syntax=javascript : 61 | -------------------------------------------------------------------------------- /socks.js: -------------------------------------------------------------------------------- 1 | var net = require('net'), 2 | util = require('util'), 3 | log = function(args) { 4 | //console.log(args); 5 | }, 6 | info = console.info, 7 | errorLog = console.error, 8 | clients = [], 9 | SOCKS_VERSION = 5, 10 | /* 11 | * Authentication methods 12 | ************************ 13 | * o X'00' NO AUTHENTICATION REQUIRED 14 | * o X'01' GSSAPI 15 | * o X'02' USERNAME/PASSWORD 16 | * o X'03' to X'7F' IANA ASSIGNED 17 | * o X'80' to X'FE' RESERVED FOR PRIVATE METHODS 18 | * o X'FF' NO ACCEPTABLE METHODS 19 | */ 20 | AUTHENTICATION = { 21 | NOAUTH: 0x00, 22 | GSSAPI: 0x01, 23 | USERPASS: 0x02, 24 | NONE: 0xFF 25 | }, 26 | /* 27 | * o CMD 28 | * o CONNECT X'01' 29 | * o BIND X'02' 30 | * o UDP ASSOCIATE X'03' 31 | */ 32 | REQUEST_CMD = { 33 | CONNECT: 0x01, 34 | BIND: 0x02, 35 | UDP_ASSOCIATE: 0x03 36 | }, 37 | /* 38 | * o ATYP address type of following address 39 | * o IP V4 address: X'01' 40 | * o DOMAINNAME: X'03' 41 | * o IP V6 address: X'04' 42 | */ 43 | ATYP = { 44 | IP_V4: 0x01, 45 | DNS: 0x03, 46 | IP_V6: 0x04 47 | }, 48 | Address = { 49 | read: function (buffer, offset) { 50 | if (buffer[offset] == ATYP.IP_V4) { 51 | return util.format('%s.%s.%s.%s', buffer[offset+1], buffer[offset+2], buffer[offset+3], buffer[offset+4]); 52 | } else if (buffer[offset] == ATYP.DNS) { 53 | return buffer.toString('utf8', offset+2, offset+2+buffer[offset+1]); 54 | } else if (buffer[offset] == ATYP.IP_V6) { 55 | return buffer.slice(buffer[offset+1], buffer[offset+1+16]); 56 | } 57 | }, 58 | sizeOf: function(buffer, offset) { 59 | if (buffer[offset] == ATYP.IP_V4) { 60 | return 4; 61 | } else if (buffer[offset] == ATYP.DNS) { 62 | return buffer[offset+1]; 63 | } else if (buffer[offset] == ATYP.IP_V6) { 64 | return 16; 65 | } 66 | } 67 | }; 68 | 69 | function createSocksServer(cb) { 70 | var socksServer = net.createServer(); 71 | socksServer.on('listening', function() { 72 | var address = socksServer.address(); 73 | info('LISTENING %s:%s', address.address, address.port); 74 | }); 75 | socksServer.on('connection', function(socket) { 76 | info('CONNECTED %s:%s', socket.remoteAddress, socket.remotePort); 77 | initSocksConnection.bind(socket)(cb); 78 | }); 79 | return socksServer; 80 | } 81 | // 82 | // socket is available as this 83 | function initSocksConnection(on_accept) { 84 | // keep log of connected clients 85 | clients.push(this); 86 | 87 | // remove from clients on disconnect 88 | this.on('end', function() { 89 | var idx = clients.indexOf(this); 90 | if (idx != -1) { 91 | clients.splice(idx, 1); 92 | } 93 | }); 94 | this.on('error', function(e) { 95 | errorLog('%j', e); 96 | }); 97 | 98 | // do a handshake 99 | this.handshake = handshake.bind(this); 100 | this.on_accept = on_accept; // No bind. We want 'this' to be the server, like it would be for net.createServer 101 | this.on('data', this.handshake); 102 | } 103 | 104 | function handshake(chunk) { 105 | this.removeListener('data', this.handshake); 106 | 107 | var method_count = 0; 108 | 109 | // SOCKS Version 5 is the only support version 110 | if (chunk[0] != SOCKS_VERSION) { 111 | errorLog('handshake: wrong socks version: %d', chunk[0]); 112 | this.end(); 113 | } 114 | // Number of authentication methods 115 | method_count = chunk[1]; 116 | 117 | this.auth_methods = []; 118 | // i starts on 1, since we've read chunk 0 & 1 already 119 | for (var i=2; i < method_count + 2; i++) { 120 | this.auth_methods.push(chunk[i]); 121 | } 122 | log('Supported auth methods: %j', this.auth_methods); 123 | 124 | var resp = new Buffer(2); 125 | resp[0] = 0x05; 126 | if (this.auth_methods.indexOf(AUTHENTICATION.NOAUTH) > -1) { 127 | log('Handing off to handleRequest'); 128 | this.handleRequest = handleRequest.bind(this); 129 | this.on('data', this.handleRequest); 130 | resp[1] = AUTHENTICATION.NOAUTH; 131 | this.write(resp); 132 | } else { 133 | errorLog('Unsuported authentication method -- disconnecting'); 134 | resp[1] = 0xFF; 135 | this.end(resp); 136 | } 137 | } 138 | 139 | function handleRequest(chunk) { 140 | this.removeListener('data', this.handleRequest); 141 | var cmd=chunk[1], 142 | address, 143 | port, 144 | offset=3; 145 | // Wrong version! 146 | if (chunk[0] !== SOCKS_VERSION) { 147 | this.end('%d%d', 0x05, 0x01); 148 | errorLog('handleRequest: wrong socks version: %d', chunk[0]); 149 | return; 150 | } /* else if (chunk[2] == 0x00) { 151 | this.end(util.format('%d%d', 0x05, 0x01)); 152 | errorLog('handleRequest: Mangled request. Reserved field is not null: %d', chunk[offset]); 153 | return; 154 | } */ 155 | address = Address.read(chunk, 3); 156 | offset = 3 + Address.sizeOf(chunk, 3) + 2; 157 | port = chunk.readUInt16BE(offset); 158 | 159 | log('Request: type: %d -- to: %s:%s', chunk[1], address, port); 160 | 161 | if (cmd == REQUEST_CMD.CONNECT) { 162 | this.request = chunk; 163 | this.on_accept(this, port, address, proxyReady.bind(this)); 164 | } else { 165 | this.end('%d%d', 0x05, 0x01); 166 | return; 167 | } 168 | } 169 | 170 | function proxyReady() { 171 | log('Indicating to the client that the proxy is ready'); 172 | // creating response 173 | var resp = new Buffer(this.request.length); 174 | this.request.copy(resp); 175 | // rewrite response header 176 | resp[0] = SOCKS_VERSION; 177 | resp[1] = 0x00; 178 | resp[2] = 0x00; 179 | this.write(resp); 180 | log('Connected to: %s:%d', resp.toString('utf8', 4, resp.length - 2), resp.readUInt16BE(resp.length - 2)); 181 | 182 | 183 | } 184 | 185 | module.exports = { 186 | createServer: createSocksServer 187 | }; 188 | --------------------------------------------------------------------------------