diff --git a/AUTHORS b/AUTHORS index 89d6bec..cf163f9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,2 +1,3 @@ Gert Van Gool John Cant +sunnyboy00 diff --git a/README.rst b/README.rst index b3af383..f91fbff 100644 --- a/README.rst +++ b/README.rst @@ -2,11 +2,16 @@ SOCKS implementation in node.js =============================== A simple SOCKS implementation and demo proxy in `node.js `_. - + +It supports both socks5 and socks4. You can run it easily as:: - ./socks - -This will create a proxy at ``127.0.0.1`` on port ``8888``. + ./socksproxy5.js + ./socksproxy4.js + ./http.js +under windows you can run run.vbs +This will create a proxy socks5 at ``127.0.0.1`` on port ``8888``. +This will create a proxy socks4 socks4a at ``127.0.0.1`` on port ``9999``. +This will create a proxy http https at ``127.0.0.1`` on port ``8080``. You can use this as a good starting point for writing a proxy or a tunnel! diff --git a/chains.js b/chains.js new file mode 100644 index 0000000..286e0a2 --- /dev/null +++ b/chains.js @@ -0,0 +1,38 @@ +var net = require('net'); +var proxyhost="127.0.0.1";//ķIP +var proxyport=9999;//Ķ˿ +var listenport=8124;//˿ +net.createServer(function (socket) { +socket.on("connect",function(){ +console.log('connected'); +try{ +var db=net.createConnection(proxyport,proxyhost); +db.on("connect",function(){ +console.log("server connected"); +socket.on("data", function (data) { +db.write(data); +}); +db.on("data",function(data){ +//console.log(data.toString('utf8',0,data.legnth)); +//console.log(data); +socket.write(data); +}); +socket.on("close",function(){ +console.log("server closed"); +db.end(); +}); +}); +db.on("error",function(data){ +console.log("error:\r\n"+data); +db.end(); +socket.end(); +}); +db.on("end",function(){ +console.log("server closed"); +socket.end(); +}); +}catch(err){ +console.log(err); +} +}); +}).listen(listenport, "0.0.0.0"); \ No newline at end of file diff --git a/http.js b/http.js new file mode 100644 index 0000000..329354d --- /dev/null +++ b/http.js @@ -0,0 +1,197 @@ +var net=require('net'); +var local_port=8080; + +//�ڱ��ش���һ��server��������local_port�˿� +var sockserver=net.createServer(function (client) +{ + +//���ȼ��������������ݷ����¼���ֱ���յ������ݰ���������http����ͷ +var buffer=new Buffer(0); +client.on('data',function (data) +{ +buffer=buffer_add(buffer,data); +if(buffer_find_body(buffer)==-1)return ; +var req=parse_request(buffer); +if(req===false)return ; +client.removeAllListeners('data'); +relay_connection(req); +}); + +//��http����ͷ��ȡ��������Ϣ�󣬼��������������������ݣ�ͬʱ����Ŀ��������������Ŀ�������������ݴ��������� +function relay_connection(req) +{ +console.log(req.method+' '+req.host+':'+req.port); + +//������������CONNECT������GET, POST������ô�滻��ͷ����һЩ���� +if(req.method!='CONNECT') +{ +//�ȴ�buffer��ȡ��ͷ�� +var _body_pos=buffer_find_body(buffer); +if(_body_pos<0)_body_pos=buffer.length; +var header=buffer.slice(0,_body_pos).toString('utf8'); +//�滻connectionͷ +header=header.replace(/(proxy\-)connection\:.+\r\n/ig,'') +.replace(/Keep\-Alive\:.+\r\n/i,'') +.replace("\r\n",'\r\nConnection: close\r\n'); +//�滻��ַ��ʽ(ȥ����������) +if(req.httpVersion=='1.1') +{ +var url=req.path.replace(/http\:\/\/[^\/]+/,''); +if(url.path!=url)header=header.replace(req.path,url); +} +buffer=buffer_add(new Buffer(header,'utf8'),buffer.slice(_body_pos)); +} + +//������Ŀ�������������� +var server=net.createConnection(req.port,req.host); +//������������������������ +client.on("data",function (data){ +try +{server.write(data);} +catch (e) +{ } +}); +server.on("data",function (data){ +try +{client.write(data);} +catch (e) +{ } +}); +server.on("end",function (e){ +console.log("server close"); +console.log(e); +try +{client.end();} +catch (e) +{ } +}); +server.on("error",function (e){ +console.log("server close"); +console.log(e); +try +{client.destroy();} +catch (e) +{ } +}); + +client.on("end",function (e){ +try +{server.end();} +catch (e) +{ } +}); +client.on("error",function (e){ +try +{server.destroy();} +catch (e) +{ } +}); +if(req.method=='CONNECT') +client.write(new Buffer("HTTP/1.1 200 Connection established\r\nConnection: close\r\n\r\n")); +else +server.write(buffer); +} +}); + +//�������ִ��� +process.on('uncaughtException',function (err) +{ +console.log("\nError!!!!"); +console.log(err); +}); + +sockserver.on('listening', function() { + var address = sockserver.address(); + console.log('LISTENING %s:%s', address.address, address.port); + }); + + +/** +* ������ͷ��ȡ��������ϸ��Ϣ +* ������ CONNECT ��������ô�᷵�� { method,host,port,httpVersion} +* ������ GET/POST ��������ô���� { metod,host,port,path,httpVersion} +*/ +function parse_request(buffer) +{ +var s=buffer.toString('utf8'); +var method=s.split('\n')[0].match(/^([A-Z]+)\s/)[1]; +if(method=='CONNECT') +{ +var arr=s.match(/^([A-Z]+)\s([^\:\s]+)\:(\d+)\sHTTP\/(\d\.\d)/); +if(arr&&arr[1]&&arr[2]&&arr[3]&&arr[4]) +return {method:arr[1],host:arr[2],port:arr[3],httpVersion:arr[4]}; +} +else +{ +var arr=s.match(/^([A-Z]+)\s([^\s]+)\sHTTP\/(\d\.\d)/); +if(arr&&arr[1]&&arr[2]&&arr[3]) +{ +var host=s.match(/Host\:\s+([^\n\s\r]+)/)[1]; +if(host) +{ +var _p=host.split(':',2); +return {method:arr[1],host:_p[0],port:_p[1] ? _p[1]:80,path:arr[2],httpVersion:arr[3]}; +} +} +} +return false; +} + + + + +/** +* ����buffer���������� +*/ +function buffer_add(buf1,buf2) +{ +var re=new Buffer(buf1.length+buf2.length); +buf1.copy(re); +buf2.copy(re,buf1.length); +return re; +} + +/** +* �ӻ������ҵ�ͷ����������("\r\n\r\n")��λ�� +*/ +function buffer_find_body(b) +{ +for(var i=0,len=b.length-3;i -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); - } -} - -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); - - log('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; - } -} - -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)); - - -} - -module.exports = { - createServer: createSocksServer -}; +var net = require('net'), + dgram = require('dgram'), + dns = require('dns'), + util = require('util'), + log = function(args) { + console.log(args); + }, + info = console.info, + errorLog = console.error, + clients = [], + ips=[], + 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 = { + NOAUTH: 0x00, + GSSAPI: 0x01, + USERPASS: 0x02, + NONE: 0xFF + }, +/* + * o CMD + * o CONNECT X'01' + * o BIND X'02' + * o UDP ASSOCIATE X'03' + */ + REQUEST_CMD = { + 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' + */ + ATYP = { + 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]+1; + } else if (buffer[offset] == ATYP.IP_V6) { + return 16; + } + } + }; +var fs = require("fs"); + +function createSocksServer(cb) { + var socksServer = net.createServer(); + + initIplist(); + fs.watch('./ip.txt',function(event,filename){ +if(event=="change") //如果文件变动了 +{ + initIplist(); +} +}); + + 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); + + var idx = ips.indexOf( socket.remoteAddress); + if (idx != -1) { + socket.end(); + log('ip pass failed '); + }else { + + initSocksConnection.bind(socket)(cb); + } + + socket.on('error', function(e) { + errorLog('error: %j', e); + this.end(); + }); + }); + return socksServer; +} +// +//iplist +function initIplist() { + fs.readFile('./ip.txt',function(err,data){ + if(err) throw err; +ips = data.toString('utf8',0,data.length).split(";"); +}); +} + +// socket is available as this +function initSocksConnection(on_accept) { + // keep log 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('initSocksConnection error', function(e) { + errorLog('initSocksConnection error'); + }); + + // 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); + + 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]; + + 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); + + 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); + } +} + +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]); + log(chunk); + 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 = 4 + Address.sizeOf(chunk, 3); + port = chunk.readUInt16BE(offset); + + log('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 if(cmd == REQUEST_CMD.UDP_ASSOCIATE){ + this.request = chunk; + var client = this; + this.udpclient = dgram.createSocket("udp4"); + this.udpclient.bind(0,function(){ + var udpaddress = client.udpclient.address(); + var resp = new Buffer(chunk.length); + chunk.copy(resp); + // rewrite response header + resp[0] = SOCKS_VERSION; + resp[1] = 0x00; + resp[2] = 0x00; + var ad = udpaddress.address.split("."); + resp[4]=Number(ad[0]); + resp[5]=Number(ad[1]); + resp[6]=Number(ad[2]); + resp[7]=Number(ad[3]); + resp.writeUInt16BE(udpaddress.port,8); + client.write(resp); + client.clientaddress=address; + client.clientport=port; + client.udphandshake = udphandshake.bind(client); + client.udpclient.on('message', client.udphandshake); + }); + +//应答 + + } + 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)); + + +} +function udphandshake(msg, rinfo) { + this.removeListener('message', this.udphandshake); +//get the udp head +if(rinfo.address==this.clientaddress){//转发 +if(msg[3]==1){ +var address=Address.read(msg, 3), + offset = 4 + Address.sizeOf(msg, 3), + port = chunk.readUInt16BE(offset); + this.udpclient.send(msg,offset+2,msg.length-offset-2,port,address); +} +else if(msg[3]==3){ +var dnsaddress=Address.read(msg, 3), + offset = 4 + Address.sizeOf(msg, 3), + port = chunk.readUInt16BE(offset); + dns.lookup(dnsaddress,4,function(err, address, family){ +if (err) throw err; +dnsaddress=address; +this.udpclient.send(msg,offset+2,msg.length-offset-2,port,dnsaddress);}); + + +} +}else {//收到其它信息 +var resp = new Buffer(10+msg.length); + msg.copy(resp,10); + // rewrite response header + resp[0] = 0x00; + resp[1] = 0x00; + resp[2] = 0x00; + resp[3] = 0x01; + var ad = rinfo.address.split("."); + resp[4]=Number(ad[0]); + resp[5]=Number(ad[1]); + resp[6]=Number(ad[2]); + resp[7]=Number(ad[3]); + resp.writeUInt16BE(rinfo.port,8); + this.udpclient.send(resp,0,resp.length,this.clientport,this.clientaddress); +} +this.udpclient.on('error',function(){}); +this.on('close',function(had_error){ +this.end(); +this.udpclient.close(); +}.bind(this)); +this.on('error',function(had_error){ +this.end(); +this.udpclient.close(); +}.bind(this)); + +} + +module.exports = { + createServer: createSocksServer +}; diff --git a/socksproxy4.js b/socksproxy4.js new file mode 100644 index 0000000..0b248d0 --- /dev/null +++ b/socksproxy4.js @@ -0,0 +1,60 @@ + +var net = require('net'), + socks4 = require('./socks4.js'); +var exec = require('child_process').exec; +var d = require('domain').create(); +var cluster = require('cluster'); + +var numCPUs = require('os').cpus().length; + +d.run(function() { + +if (cluster.isMaster) { + + // Fork workers. + for (var i = 0; i < numCPUs; i++) { + cluster.fork(); + } +if(1==numCPUs) cluster.fork();//make sure it is more than 2 + + cluster.on('exit', function(worker, code, signal) { + if (worker.suicide !== true) { + exec('taskkill /pid '+worker.process.pid +' /T /F'); + } + var exitCode = worker.process.exitCode; + console.log('worker ' + worker.process.pid + ' died ('+exitCode+'). restarting...'); + +cluster.fork(); + + }); +} else { + // Workers can share any TCP connection + // In this case its a proxy server + +// Create server +// The server accepts SOCKS connections. This particular server acts as a proxy. +var PORT4='9999', +server4 = socks4.createServer(); +server4.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',PORT); + server.close(); + server.listen(PORT4); + }, 10000); + } +}); +server4.listen(PORT4); + +} + }); + +d.on('error', function(er) { + // an error occurred somewhere. + // if we throw it now, it will crash the program + // with the normal line number and stack message. + console.log('ERROR!: %s ',er); + +}); diff --git a/socksproxy5.js b/socksproxy5.js new file mode 100644 index 0000000..f6ac222 --- /dev/null +++ b/socksproxy5.js @@ -0,0 +1,132 @@ +var net = require('net'), + socks5 = require('./socks5.js'); +var exec = require('child_process').exec; +var d = require('domain').create(); +var cluster = require('cluster'); + +var numCPUs = require('os').cpus().length; + +d.run(function() { + +if (cluster.isMaster) { + + // Fork workers. + for (var i = 0; i < numCPUs; i++) { + cluster.fork(); + } +if(1==numCPUs) cluster.fork();//make sure it is more than 2 + + cluster.on('exit', function(worker, code, signal) { + if (worker.suicide !== true) { + exec('taskkill /pid '+worker.process.pid +' /T /F'); + } +var exitCode = worker.process.exitCode; + console.log('worker ' + worker.process.pid + ' died ('+exitCode+'). restarting...'); + +// +cluster.fork(); + + }); +} else { + // Workers can share any TCP connection + // In this case its a proxy server + +// Create server +// The server accepts SOCKS connections. This particular server acts as a proxy. +var PORT5='8888', + server5 = socks5.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); +// d.add(proxy); + 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('end', function(had_error) { + socket.end(); + console.error('The proxy closed'); + }.bind(this)); + socket.on('end', function(had_error) { + if (this.proxy !== undefined) { + proxy.removeAllListeners('data'); + proxy.end(); + } + console.error('The application closed'); + + }.bind(this)); + socket.on('error', function(had_error) { + if (this.proxy !== undefined) { + proxy.removeAllListeners('data'); + proxy.destroy(); + } + console.error('The application error'); + + }.bind(this)); + + proxy.on('error', function(had_error) { + socket.destroy(); + console.error('The proxy error'); + }.bind(this)); + +socket.setTimeout(60000, function(error){ +if (this.proxy !== undefined) { +proxy.removeAllListeners('data'); +proxy.end(); + +} +this.end(); +console.error('socket timeout 60000ms'); + +}.bind(this)); + +proxy.setTimeout(60000, function(error){ +proxy.removeAllListeners('data'); +proxy.end(); +this.end(); +console.error('proxy socket timeout 60000ms'); + +}.bind(this)); + + }); + + +server5.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',PORT); + server.close(); + server.listen(PORT5); + }, 10000); + } +}); +server5.listen(PORT5); + +} + }); + +d.on('error', function(er) { + // an error occurred somewhere. + // if we throw it now, it will crash the program + // with the normal line number and stack message. + console.log('ERROR!: %s ',er); + +}); diff --git "a/\346\226\260\345\273\272\346\226\207\346\234\254\346\226\207\346\241\243.bat" "b/\346\226\260\345\273\272\346\226\207\346\234\254\346\226\207\346\241\243.bat" new file mode 100644 index 0000000..c514645 --- /dev/null +++ "b/\346\226\260\345\273\272\346\226\207\346\234\254\346\226\207\346\241\243.bat" @@ -0,0 +1,2 @@ +node socksproxy5.js +pause