├── package.json ├── .gitattributes ├── README.md ├── .gitignore └── index.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Travis Collins (http://tec27.com)", 3 | "name": "argyle", 4 | "description": "Basic SOCKS5 server libary", 5 | "version": "0.0.2", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/tec27/node-argyle.git" 9 | }, 10 | "keywords": ["socks"], 11 | "main": "index.js", 12 | "dependencies": {}, 13 | "devDependencies": {}, 14 | "optionalDependencies": {}, 15 | "engines": { 16 | "node": "*" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # argyle 2 | A basic SOCKS5 server library written for node.js. 3 | 4 | ## Features/Limitations 5 | argyle supports the most basic features of SOCKS and not a whole lot more, namely: 6 | 7 | - 'No authentication' auth mode *only* 8 | - CONNECT commmand *only* 9 | 10 | In the future I may add support for more auth modes and commands, but currently this implementation works well for my main use case (sitting between a local browser and server). 11 | 12 | ## Usage 13 | ### Example: "Normal" proxy server 14 | ```javascript 15 | var argyle = require('argyle'); 16 | 17 | var server = argyle(8080, '127.0.0.1'); 18 | server.on('connected', function(req, dest) { 19 | req.pipe(dest); 20 | dest.pipe(req); 21 | }); 22 | ``` 23 | 24 | ### Example: Throttled proxy server using [node-throttled-stream](https://github.com/tec27/node-throttled-stream) 25 | ```javascript 26 | var argyle = require('argyle'), 27 | throttle = require('throttled-stream'), 28 | kbpsUp = 32, 29 | kbpsDown = 128; 30 | 31 | var server = argyle(8080, '127.0.0.1'); 32 | server.on('connected', function(req, dest) { 33 | var tReq = throttle(req, kbpsUp * 1024), 34 | tDest = throttle(dest, kbpsDown * 1024); 35 | 36 | dest.once('error', function(err) { req.end(); }) 37 | .on('close', function() { req.end(); }); 38 | 39 | tReq.on('data', function(chunk) { 40 | dest.write(chunk); 41 | }); 42 | tDest.on('data', function(chunk) { 43 | req.write(chunk); 44 | }); 45 | }); 46 | ``` 47 | 48 | ## Methods 49 | ### argyle([port = 8080], [host = 127.0.0.1], [debug = false]) 50 | Sets up a new SOCKS server on the specified port and host. If debug is specified, the server will output messages about the status of connections. 51 | 52 | ## Events 53 | ### 'connected' 54 | A new client connected to the server and the socket to their requested destination is now open. Handlers for this event are passed a `request` socket, corresponding to the client that made the request from the server, and a `destination` socket, corresponding to the server that they requested to connect to. 55 | 56 | ## Installation 57 | With npm: 58 | 59 | ``` 60 | npm install argyle 61 | ``` 62 | 63 | ## License 64 | WTFPL 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | *_i.c 48 | *_p.c 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.vspscc 63 | .builds 64 | *.dotCover 65 | 66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 67 | #packages/ 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | 80 | # ReSharper is a .NET coding add-in 81 | _ReSharper* 82 | 83 | # Installshield output folder 84 | [Ee]xpress 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish 98 | 99 | # Others 100 | [Bb]in 101 | [Oo]bj 102 | sql 103 | TestResults 104 | *.Cache 105 | ClientBin 106 | stylecop.* 107 | ~$* 108 | *.dbmdl 109 | Generated_Code #added for RIA/Silverlight projects 110 | 111 | # Backup & report files from converting an old project file to a newer 112 | # Visual Studio version. Backup files are not needed, because we have git ;-) 113 | _UpgradeReport_Files/ 114 | Backup*/ 115 | UpgradeLog*.XML 116 | 117 | 118 | 119 | ############ 120 | ## Windows 121 | ############ 122 | 123 | # Windows image file caches 124 | Thumbs.db 125 | 126 | # Folder config file 127 | Desktop.ini 128 | 129 | 130 | ############# 131 | ## Python 132 | ############# 133 | 134 | *.py[co] 135 | 136 | # Packages 137 | *.egg 138 | *.egg-info 139 | dist 140 | build 141 | eggs 142 | parts 143 | bin 144 | var 145 | sdist 146 | develop-eggs 147 | .installed.cfg 148 | 149 | # Installer logs 150 | pip-log.txt 151 | 152 | # Unit test / coverage reports 153 | .coverage 154 | .tox 155 | 156 | #Translations 157 | *.mo 158 | 159 | #Mr Developer 160 | .mr.developer.cfg 161 | 162 | # Mac crap 163 | .DS_Store 164 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*jshint laxcomma:true asi:true */ 2 | var net = require('net') 3 | , util = require('util') 4 | , EventEmitter = require('events').EventEmitter 5 | 6 | var debugOut = console.log.bind(console) 7 | 8 | module.exports = function(port, host, backlog, debug) { 9 | if(!port) port = 8080 10 | if(!host) host = '127.0.0.1' 11 | if(typeof backlog === 'boolean') { 12 | debug = backlog 13 | backlog = undefined 14 | } 15 | 16 | return new Argyle(port, host, backlog, debug) 17 | } 18 | 19 | function Argyle(port, host, backlog, debug) { 20 | Argyle.super_.call(this) 21 | var self = this 22 | 23 | if(!!debug) this._debug = debugOut 24 | else this._debug = function() {} 25 | 26 | this.serverSock = net.createServer() 27 | this.serverSock.on('listening', function() { 28 | var addr = self.serverSock.address() 29 | self._debug('socks server listening on %s:%s', addr.address, addr.port) 30 | }).on('connection', function(client) { 31 | self.handleConnection(client) 32 | }) 33 | 34 | if(backlog) { 35 | this.serverSock.listen(port, host, backlog) 36 | } else { 37 | this.serverSock.listen(port, host) 38 | } 39 | } 40 | util.inherits(Argyle, EventEmitter); 41 | 42 | Argyle.socksVersion = 5 43 | 44 | var STATES = { handshake: 0 45 | , request: 1 46 | , forwarding: 2 47 | } 48 | Argyle.prototype.handleConnection = function(client) { 49 | var curState = STATES.handshake 50 | , handlers = {} 51 | , self = this 52 | 53 | function onClientData(chunk) { 54 | handlers[curState](chunk) 55 | } 56 | 57 | client.on('end', function() { 58 | }).on('error', function(err) { 59 | }).on('data', onClientData) 60 | 61 | var buffer = null 62 | handlers[STATES.handshake] = function(chunk) { 63 | buffer = expandAndCopy(buffer, chunk) 64 | if(buffer.length < 2) return 65 | 66 | var socksVersion = buffer[0] 67 | if(socksVersion != Argyle.socksVersion) { 68 | self._debug('unsupported client version: %d', socksVersion) 69 | return client.end() 70 | } 71 | 72 | var nMethods = buffer[1]; 73 | if(buffer.length < nMethods + 2) return; 74 | for(var i = 0; i < nMethods; i++) { 75 | // try to find the no-auth method type, and if found, choose it 76 | if(buffer[i+2] === 0) { 77 | client.write(new Buffer([0x05, 0x00])) 78 | curState++ 79 | if(buffer.length > nMethods + 2) { 80 | var newChunk = buffer.slice(nMethods + 2) 81 | buffer = null 82 | handlers[STATES.request](newChunk) 83 | } 84 | buffer = null 85 | return 86 | } 87 | } 88 | 89 | self._debug('No supported auth methods found, disconnecting.') 90 | client.end(new Buffer([0x05, 0xff])) 91 | } 92 | 93 | var proxyBuffers = [] 94 | handlers[STATES.request] = function(chunk) { 95 | buffer = expandAndCopy(buffer, chunk) 96 | if(buffer.length < 4) return 97 | 98 | var socksVersion = buffer[0]; 99 | if(socksVersion != Argyle.socksVersion) { 100 | self._debug('unsupported client version: %d', socksVersion) 101 | return client.end() 102 | } 103 | 104 | var cmd = buffer[1]; 105 | if(cmd != 0x01) { 106 | self._debug('unsupported command: %d', cmd) 107 | return client.end(new Buffer([0x05, 0x01])) 108 | } 109 | 110 | var addressType = buffer[3] 111 | , host 112 | , port 113 | , responseBuf 114 | if(addressType == 0x01) { // ipv4 115 | if(buffer.length < 10) return // 4 for host + 2 for port 116 | host = util.format('%d.%d.%d.%d', buffer[4], buffer[5], buffer[6], buffer[7]) 117 | port = buffer.readUInt16BE(8) 118 | responseBuf = new Buffer(10) 119 | buffer.copy(responseBuf, 0, 0, 10) 120 | buffer = buffer.slice(10) 121 | } 122 | else if(addressType == 0x03) { // dns 123 | if(buffer.length < 5) return // if no length present yet 124 | var addrLength = buffer[4] 125 | if(buffer.length < 5 + addrLength + 2) return // host + port 126 | host = buffer.toString('utf8', 5, 5+addrLength) 127 | port = buffer.readUInt16BE(5+addrLength) 128 | responseBuf = new Buffer(5 + addrLength + 2) 129 | buffer.copy(responseBuf, 0, 0, 5 + addrLength + 2) 130 | buffer = buffer.slice(5 + addrLength + 2) 131 | } 132 | else if(addressType == 0x04) { // ipv6 133 | if(buffer.length < 22) return // 16 for host + 2 for port 134 | host = buffer.slice(4, 20) 135 | port = buffer.readUInt16BE(20) 136 | responseBuf = new Buffer(22) 137 | buffer.copy(responseBuf, 0, 0, 22) 138 | buffer = buffer.slice(22); 139 | } 140 | else { 141 | self._debug('unsupported address type: %d', addressType) 142 | return client.end(new Buffer([0x05, 0x01])) 143 | } 144 | 145 | self._debug('Request to %s:%s', host, port) 146 | curState++ 147 | 148 | var connected = false 149 | var dest = net.createConnection(port, host, function() { 150 | responseBuf[1] = 0 151 | responseBuf[2] = 0 152 | client.write(responseBuf) // emit success to client 153 | client.removeListener('data', onClientData) 154 | 155 | client.resume() 156 | self.emit('connected', client, dest) 157 | connected = true 158 | if(buffer && buffer.length) { 159 | client.emit(buffer) 160 | buffer = null 161 | } 162 | for(var j = 0; j < proxyBuffers.length; j++) { // re-emit any leftover data for proxy to handle 163 | client.emit('data', proxyBuffers[i]) 164 | } 165 | proxyBuffers = [] 166 | }).once('error', function(err) { 167 | if(!connected) { 168 | client.end(new Buffer([0x05, 0x01])) 169 | } 170 | }).once('close', function() { 171 | if(!connected) { 172 | client.end() 173 | } 174 | }) 175 | client.pause() 176 | } 177 | 178 | handlers[STATES.forwarding] = function (chunk) { 179 | proxyBuffers.push(chunk); 180 | } 181 | } 182 | 183 | function expandAndCopy(old, newer) { 184 | if(!old) return newer; 185 | var newBuf = new Buffer(old.length + newer.length); 186 | old.copy(newBuf); 187 | newer.copy(newBuf, old.length); 188 | 189 | return newBuf; 190 | } 191 | 192 | // vim: tabstop=2:shiftwidth=2:softtabstop=2:expandtab 193 | 194 | --------------------------------------------------------------------------------