diff --git a/README.rst b/README.rst index b3af383..04921e0 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ A simple SOCKS implementation and demo proxy in `node.js `_. You can run it easily as:: - ./socks + ./socks or node socks This will create a proxy at ``127.0.0.1`` on port ``8888``. diff --git a/socks b/socks index 9c46f3a..9c1a901 100755 --- a/socks +++ b/socks @@ -1,60 +1,56 @@ #!/usr/bin/env node + var net = require('net'), - socks = require('./socks.js'); + socks = require('./socks.js') // Create server // The server accepts SOCKS connections. This particular server acts as a proxy. -var HOST='127.0.0.1', - PORT='8888', +var HOST = '127.0.0.1', + PORT = '8888', server = socks.createServer(function(socket, port, address, proxy_ready) { - // Implement your own proxy here! Do encryption, tunnelling, whatever! Go flippin' mental! - // I plan to tunnel everything including SSH over an HTTP tunnel. For now, though, here is the plain proxy: - - console.log('Got through the first part of the SOCKS protocol.') - var proxy = net.createConnection(port, address, proxy_ready); - - proxy.on('data', function(d) { - try { - console.log('receiving ' + d.length + ' bytes from proxy'); - socket.write(d); - } catch(err) { - } - }); - socket.on('data', function(d) { - // If the application tries to send data before the proxy is ready, then that is it's own problem. - try { - console.log('sending ' + d.length + ' bytes to proxy'); - proxy.write(d); - } catch(err) { - } - }); - - proxy.on('close', function(had_error) { - socket.end(); - console.error('The proxy closed'); - }.bind(this)); - socket.on('close', function(had_error) { - if (this.proxy !== undefined) { - proxy.removeAllListeners('data'); - proxy.end(); - } - console.error('The application closed'); - }.bind(this)); - - }); - -server.on('error', function (e) { - console.error('SERVER ERROR: %j', e); - if (e.code == 'EADDRINUSE') { - console.log('Address in use, retrying in 10 seconds...'); - setTimeout(function () { - console.log('Reconnecting to %s:%s', HOST, PORT); - server.close(); - server.listen(PORT, HOST); - }, 10000); - } -}); -server.listen(PORT, HOST); - -// vim: set filetype=javascript syntax=javascript : + // Implement your own proxy here! Do encryption, tunnelling, whatever! Go flippin' mental! + // I plan to tunnel everything including SSH over an HTTP tunnel. For now, though, here is the plain proxy: + console.log('Got through the first part of the SOCKS protocol.') + var proxy = net.createConnection(port, address, proxy_ready) + + proxy.on('data', function(d) { + try { + console.log('receiving ' + d.length + ' bytes from proxy') + socket.write(d) + } catch (err) {} + }) + socket.on('data', function(d) { + // If the application tries to send data before the proxy is ready, then that is it's own problem. + try { + console.log('sending ' + d.length + ' bytes to proxy') + proxy.write(d) + } catch (err) {} + }) + + proxy.on('close', function(had_error) { + socket.end() + console.error('The proxy closed') + }.bind(this)) + socket.on('close', function(had_error) { + if (this.proxy !== undefined) { + proxy.removeAllListeners('data') + proxy.end() + } + console.error('The application closed') + }.bind(this)) + + }) + +server.on('error', function(e) { + console.error('SERVER ERROR: %j', e) + if (e.code == 'EADDRINUSE') { + console.log('Address in use, retrying in 10 seconds...') + setTimeout(function() { + console.log('Reconnecting to %s:%s', HOST, PORT) + server.close() + server.listen(PORT, HOST) + }, 10000) + } +}) +server.listen(PORT, HOST) \ No newline at end of file diff --git a/socks.js b/socks.js index 3daeb5e..215422a 100644 --- a/socks.js +++ b/socks.js @@ -1,187 +1,191 @@ var net = require('net'), util = require('util'), - log = function(args) { - //console.log(args); - }, - info = console.info, - errorLog = console.error, + llog = console.log, + lerror = console.error, clients = [], SOCKS_VERSION = 5, -/* - * Authentication methods - ************************ - * o X'00' NO AUTHENTICATION REQUIRED - * o X'01' GSSAPI - * o X'02' USERNAME/PASSWORD - * o X'03' to X'7F' IANA ASSIGNED - * o X'80' to X'FE' RESERVED FOR PRIVATE METHODS - * o X'FF' NO ACCEPTABLE METHODS - */ + + /* + * Authentication methods + ************************ + * o X'00' NO AUTHENTICATION REQUIRED + * o X'01' GSSAPI + * o X'02' USERNAME/PASSWORD + * o X'03' to X'7F' IANA ASSIGNED + * o X'80' to X'FE' RESERVED FOR PRIVATE METHODS + * o X'FF' NO ACCEPTABLE METHODS + */ + AUTHENTICATION = { - NOAUTH: 0x00, - GSSAPI: 0x01, - USERPASS: 0x02, - NONE: 0xFF + NOAUTH: 0x00, + GSSAPI: 0x01, + USERPASS: 0x02, + NONE: 0xFF }, -/* - * o CMD - * o CONNECT X'01' - * o BIND X'02' - * o UDP ASSOCIATE X'03' - */ + + /* + * o CMD + * o CONNECT X'01' + * o BIND X'02' + * o UDP ASSOCIATE X'03' + */ + REQUEST_CMD = { - CONNECT: 0x01, - BIND: 0x02, - UDP_ASSOCIATE: 0x03 + CONNECT: 0x01, + BIND: 0x02, + UDP_ASSOCIATE: 0x03 }, -/* - * o ATYP address type of following address - * o IP V4 address: X'01' - * o DOMAINNAME: X'03' - * o IP V6 address: X'04' - */ + + /* + * o ATYP address type of following address + * o IP V4 address: X'01' + * o DOMAINNAME: X'03' + * o IP V6 address: X'04' + */ + ATYP = { - IP_V4: 0x01, - DNS: 0x03, - IP_V6: 0x04 + IP_V4: 0x01, + DNS: 0x03, + IP_V6: 0x04 }, + Address = { - read: function (buffer, offset) { - if (buffer[offset] == ATYP.IP_V4) { - return util.format('%s.%s.%s.%s', buffer[offset+1], buffer[offset+2], buffer[offset+3], buffer[offset+4]); - } else if (buffer[offset] == ATYP.DNS) { - return buffer.toString('utf8', offset+2, offset+2+buffer[offset+1]); - } else if (buffer[offset] == ATYP.IP_V6) { - return buffer.slice(buffer[offset+1], buffer[offset+1+16]); - } - }, - sizeOf: function(buffer, offset) { - if (buffer[offset] == ATYP.IP_V4) { - return 4; - } else if (buffer[offset] == ATYP.DNS) { - return buffer[offset+1]; - } else if (buffer[offset] == ATYP.IP_V6) { - return 16; - } - } - }; + read: function(buffer, offset) { + var atyp = buffer[offset] + if (atyp === ATYP.IP_V4) { + return util.format('%s.%s.%s.%s', buffer[offset+1], buffer[offset+2], buffer[offset+3], buffer[offset+4]) + } else if (atyp === ATYP.DNS) { + var dlen = buffer[offset+1] // domain's length in byte + return buffer.toString('utf8', offset+2, offset+2+dlen) + } else if (atyp === ATYP.IP_V6) { + return buffer.slice(offset+1, offset+1+16) + } + }, + + sizeOf: function(buffer, offset) { + var atyp = buffer[offset] + if (atyp === ATYP.IP_V4) { + return 4 + } else if (atyp === ATYP.DNS) { + return buffer[offset+1] + 1 // must add first byte + } else if (atyp === ATYP.IP_V6) { + return 16 + } + } + } function createSocksServer(cb) { - var socksServer = net.createServer(); - socksServer.on('listening', function() { - var address = socksServer.address(); - info('LISTENING %s:%s', address.address, address.port); - }); - socksServer.on('connection', function(socket) { - info('CONNECTED %s:%s', socket.remoteAddress, socket.remotePort); - initSocksConnection.bind(socket)(cb); - }); - return socksServer; + var socksServer = net.createServer() + socksServer.on('listening', function() { + var address = socksServer.address() + llog('LISTENING %s:%s', address.address, address.port) + }) + socksServer.on('connection', function(socket) { + llog('CONNECTED %s:%s', socket.remoteAddress, socket.remotePort) + initSocksConnection.bind(socket)(cb) + }) + return socksServer } // // socket is available as this + + function initSocksConnection(on_accept) { - // keep log of connected clients - clients.push(this); + // keep llog of connected clients + clients.push(this) - // remove from clients on disconnect - this.on('end', function() { - var idx = clients.indexOf(this); - if (idx != -1) { - clients.splice(idx, 1); - } - }); - this.on('error', function(e) { - errorLog('%j', e); - }); + // remove from clients on disconnect + this.on('end', function() { + var idx = clients.indexOf(this) + if (idx != -1) { + clients.splice(idx, 1) + } + }) + this.on('error', function(e) { + lerror('%j', e) + }) - // do a handshake - this.handshake = handshake.bind(this); - this.on_accept = on_accept; // No bind. We want 'this' to be the server, like it would be for net.createServer - this.on('data', this.handshake); + // do a handshake + this.handshake = handshake.bind(this) + this.on_accept = on_accept // No bind. We want 'this' to be the server, like it would be for net.createServer + this.on('data', this.handshake) } function handshake(chunk) { - this.removeListener('data', this.handshake); + this.removeListener('data', this.handshake) - var method_count = 0; + var method_count = 0 - // SOCKS Version 5 is the only support version - if (chunk[0] != SOCKS_VERSION) { - errorLog('handshake: wrong socks version: %d', chunk[0]); - this.end(); - } - // Number of authentication methods - method_count = chunk[1]; + // SOCKS Version 5 is the only support version + if (chunk[0] != SOCKS_VERSION) { + lerror('handshake: wrong socks version: %d', chunk[0]) + this.end() + } + // Number of authentication methods + method_count = chunk[1] - this.auth_methods = []; - // i starts on 1, since we've read chunk 0 & 1 already - for (var i=2; i < method_count + 2; i++) { - this.auth_methods.push(chunk[i]); - } - log('Supported auth methods: %j', this.auth_methods); + this.auth_methods = [] + // i starts on 1, since we've read chunk 0 & 1 already + for (var i = 2; i < method_count+2; i++) { + this.auth_methods.push(chunk[i]) + } + llog('Supported auth methods: %j', this.auth_methods) - var resp = new Buffer(2); - resp[0] = 0x05; - if (this.auth_methods.indexOf(AUTHENTICATION.NOAUTH) > -1) { - log('Handing off to handleRequest'); - this.handleRequest = handleRequest.bind(this); - this.on('data', this.handleRequest); - resp[1] = AUTHENTICATION.NOAUTH; - this.write(resp); - } else { - errorLog('Unsuported authentication method -- disconnecting'); - resp[1] = 0xFF; - this.end(resp); - } + var resp = new Buffer(2) + resp[0] = 0x05 + if (this.auth_methods.indexOf(AUTHENTICATION.NOAUTH) > -1) { + llog('Handing off to handleRequest') + this.handleRequest = handleRequest.bind(this) + this.on('data', this.handleRequest) + resp[1] = AUTHENTICATION.NOAUTH + this.write(resp) + } else { + lerror('Unsuported authentication method -- disconnecting') + resp[1] = 0xFF + this.end(resp) + } } function handleRequest(chunk) { - this.removeListener('data', this.handleRequest); - var cmd=chunk[1], - address, - port, - offset=3; - // Wrong version! - if (chunk[0] !== SOCKS_VERSION) { - this.end('%d%d', 0x05, 0x01); - errorLog('handleRequest: wrong socks version: %d', chunk[0]); - return; - } /* else if (chunk[2] == 0x00) { - this.end(util.format('%d%d', 0x05, 0x01)); - errorLog('handleRequest: Mangled request. Reserved field is not null: %d', chunk[offset]); - return; - } */ - address = Address.read(chunk, 3); - offset = 3 + Address.sizeOf(chunk, 3) + 2; - port = chunk.readUInt16BE(offset); + this.removeListener('data', this.handleRequest) + var cmd = chunk[1], + address, port, + offset = 3 + + // Wrong version! + if (chunk[0] !== SOCKS_VERSION) { + this.end('%d%d', 0x05, 0x01) + lerror('handleRequest: wrong socks version: %d', chunk[0]) + return + } + address = Address.read(chunk, offset) + offset = 3 + Address.sizeOf(chunk, 3) + 1 + port = chunk.readUInt16BE(offset) - log('Request: type: %d -- to: %s:%s', chunk[1], address, port); + llog('Request: type: %d -- to: %s:%s', chunk[1], address, port) - if (cmd == REQUEST_CMD.CONNECT) { - this.request = chunk; - this.on_accept(this, port, address, proxyReady.bind(this)); - } else { - this.end('%d%d', 0x05, 0x01); - return; - } + if (cmd == REQUEST_CMD.CONNECT) { + this.request = chunk + this.on_accept(this, port, address, proxyReady.bind(this)) + } else { + this.end('%d%d', 0x05, 0x01) + return + } } function proxyReady() { - log('Indicating to the client that the proxy is ready'); - // creating response - var resp = new Buffer(this.request.length); - this.request.copy(resp); - // rewrite response header - resp[0] = SOCKS_VERSION; - resp[1] = 0x00; - resp[2] = 0x00; - this.write(resp); - log('Connected to: %s:%d', resp.toString('utf8', 4, resp.length - 2), resp.readUInt16BE(resp.length - 2)); - - + llog('Indicating to the client that the proxy is ready') + // creating response + var resp = new Buffer(this.request.length) + this.request.copy(resp) + // rewrite response header + resp[0] = SOCKS_VERSION + resp[1] = 0x00 + resp[2] = 0x00 + this.write(resp) + llog('Connected to: %s:%d', resp.toString('utf8', 4, resp.length-2), resp.readUInt16BE(resp.length-2)) } module.exports = { - createServer: createSocksServer -}; + createServer: createSocksServer +} \ No newline at end of file