Skip to content

Commit

Permalink
Add/fix support for socks|http|https proxies.
Browse files Browse the repository at this point in the history
  • Loading branch information
mhzed committed Mar 3, 2019
1 parent 5685fe7 commit 08d046d
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 847 deletions.
91 changes: 46 additions & 45 deletions bin/wstunnel.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const globalTunnel = require('global-tunnel-ng');
const SocksProxyAgent = require('socks-proxy-agent');
const HttpProxyAgent = require('http-proxy-agent');
const HttpsProxyAgent = require('https-proxy-agent');
var urlParse = require('url').parse;

const Help = `
Expand All @@ -7,7 +9,7 @@ Run websocket tunnel server or client.
To run client: wstunnel -t localport:host:port ws[s]://wshost:wsport
Or client via proxy: wstunnel -t localport:host:port -p http://[user:pass@]host:port ws[s]://wshost:wsport
Now connecting to localhost:localport is same as connecting to host:port on wshost
Connecting to localhost:localport is the same as connecting to host:port on wshost
For security, you can "lock" the tunnel destination on server side, for eample:
wstunnel -s 0.0.0.0:8080 -t host:port
Expand Down Expand Up @@ -40,8 +42,10 @@ module.exports = (Server, Client) => {
.default('c', false)
.describe('s', 'run as server, listen on [localip:]localport, default localip is 127.0.0.1')
.describe('tunnel', 'run as tunnel client, specify [localip:]localport:host:port')
.describe("proxy", "connect via a http proxy server in client mode")
.describe("proxy", "connect via a http or socks proxy server in client mode, "
+ "format: socks://ip:port or http[s]://host:port")
.describe("c", "accept any certificates")
.describe("http", "force to use http tunnel")
.argv;

if (argv.s) {
Expand All @@ -54,20 +58,7 @@ module.exports = (Server, Client) => {
}
server.start(argv.s, (err) => err ? console.log(` Server is listening on ${argv.s}`) : null)
} else if (argv.t || argv.uuid !== undefined) {
// client mode
function tryParse(url) {
if (!url) {
return null;
}
var parsed = urlParse(url);
return {
protocol: parsed.protocol,
host: parsed.hostname,
port: parseInt(parsed.port, 10),
proxyAuth: parsed.auth
};
}

// client mode
const uuid = require("machine-uuid");
uuid((machineId) => {
if (argv.uuid === true) { // --uuid without param
Expand All @@ -76,52 +67,62 @@ module.exports = (Server, Client) => {
} else if (argv.uuid){
machineId = argv.uuid;
}
let conf = {};
if (argv.c) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
let client = new Client()
let wsHostUrl = argv._[0]

if ( argv.proxy ) {
conf = tryParse( argv.proxy );
if ( argv.c ) {
conf.proxyHttpsOptions = {rejectUnauthorized: false};
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const conf = urlParse(argv.proxy );
if (conf.protocol === "socks:") {
client.setAgentMaker((c) => new SocksProxyAgent(Object.assign({}, c, conf)));
} else if (conf.protocol === "https:" || conf.protocol === "http:") {
const p = urlParse(wsHostUrl).protocol;
if ('wss:' === p || 'https:' === p)
client.setAgentMaker((c) => new HttpsProxyAgent(Object.assign({}, c, conf)));
else if ('ws:' === p || 'http:' === p)
client.setAgentMaker((c) => new HttpProxyAgent(Object.assign({}, c, conf)));
else {
console.log("Invalid target " + wsHostUrl);
process.exit(1);
}
} else {
console.log("Invalid proxy " + argv.proxy);
process.exit(1);
}
globalTunnel.initialize(conf);
} else {
require("../lib/httpSetup").config(argv.proxy, argv.c)
}

let client = new Client()
if (argv.http) {
client.setHttpOnly(true)
}

let wsHostUrl = argv._[0]
client.verbose()

let DefaultLocalIp = "127.0.0.1"
let localAddr
let remoteAddr
let localHost = "127.0.0.1", localPort;
let remoteAddr;
let toks = argv.t.split(":")
if (toks.length === 4) {
localAddr = `${toks[0]}:${toks[1]}`
[localHost, localPort] = toks;
remoteAddr = `${toks[2]}:${toks[3]}`
} else if (toks.length === 3) {
remoteAddr = `${toks[1]}:${toks[2]}`
if (toks[0] === 'stdio') {
client.startStdio(wsHostUrl, remoteAddr, {'x-wstclient': machineId}, (err) => {
if (err) {
console.error(err)
process.exit(1)
}
})
return
localHost = toks[0];
} else {
localAddr = `${DefaultLocalIp}:${toks[0]}`
localPort = toks[0];
}
} else if (toks.length === 1) {
localAddr = `${DefaultLocalIp}:${toks[0]}`
} else {
console.log("Invalid tunnel option " + argv.t);
console.log(optimist.help());
process.exit(1);
}
localPort = parseInt(localPort);
if (localHost === "stdio") {
client.startStdio(wsHostUrl, remoteAddr, {'x-wstclient': machineId});
} else {
client.start(localHost, localPort, wsHostUrl, remoteAddr, {'x-wstclient': machineId});
}
client.start(localAddr, wsHostUrl, remoteAddr, {'x-wstclient': machineId});
})
} else {
return console.log(optimist.help());
console.log(optimist.help());
}
}
6 changes: 0 additions & 6 deletions lib/WsStream.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@

let WsStream;
const stream = require("stream");
const assert = require("assert");
const log = require("lawg");
const util = require("util");
const future = require("phuture");
const domain = require("domain");

// Stream wrapper for http://github.com/Worlize/WebSocket-Node.git version 1.0.8
module.exports = (WsStream = class WsStream extends stream.Duplex {
Expand Down
107 changes: 38 additions & 69 deletions lib/WstClient.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@

let wst_client;
const net = require("net");
const WsStream = require("./WsStream");
const url = require('url');
const log = require("lawg");
const ClientConn = require("./httptunnel/ClientConn");
const etagHeader = require("./etagHeader");
const bindStream = require("./bindStream");
const createWsClient = () => new (require('websocket').client)();

module.exports = (wst_client = class wst_client extends require('events').EventEmitter {
Expand All @@ -22,10 +21,12 @@ module.exports = (wst_client = class wst_client extends require('events').EventE
}

verbose() {
this.on('tunnel', (ws, sock) => {
this.on('tunnel', (sock, ws) => {
if (ws instanceof WsStream) {
log('Websocket tunnel established');
} else { log('Http tunnel established'); }
} else {
log('Http tunnel established');
}
return sock.on('close', () => log('Tunnel closed'));
});
this.on('connectHttpFailed', error => log(`HTTP connect error: ${error.toString()}`));
Expand All @@ -35,59 +36,23 @@ module.exports = (wst_client = class wst_client extends require('events').EventE
setHttpOnly(httpOnly) {
this.httpOnly = httpOnly;
}

// example: start(8081, "wss://ws.domain.com:454", "dst.domain.com:22")
// meaning: tunnel *:localport to remoteAddr by using websocket connection to wsHost
// or start("localhost:8081", "wss://ws.domain.com:454", "dst.domain.com:22")
// example: start("localhost", 8081, "wss://ws.domain.com:454", "dst.domain.com:22")
// meaning: tunnel localhost:8081 to remoteAddr by using websocket connection to wsHost
// @wsHostUrl: ws:// denotes standard socket, wss:// denotes ssl socket
// may be changed at any time to change websocket server info
start(localAddr, wsHostUrl, remoteAddr, optionalHeaders, cb) {
let localHost, localPort;
start(localHost, localPort, wsHostUrl, remoteAddr, optionalHeaders, cb) {
this.wsHostUrl = wsHostUrl;
if (typeof optionalHeaders === 'function') {
cb = optionalHeaders;
optionalHeaders = {};
}

if (typeof localAddr === 'number') {
localPort = localAddr;
} else {
[localHost, localPort] = Array.from(localAddr.split(':'));
if (/^\d+$/.test(localHost)) {
localPort = localHost;
localHost = null;
}
localPort = parseInt(localPort);
}
if (localHost == null) { localHost = '127.0.0.1'; }

this.tcpServer.listen(localPort, localHost, cb);
return this.tcpServer.on("connection", tcpConn => {
const bind = (s, tcp) => {
require("./bindStream")(s, tcp);
return this.emit('tunnel', s, tcp);
this.tcpServer.on("connection", tcpConn => {
const bind = (tcp, s) => {
bindStream(tcp, s);
this.emit('tunnel', tcp, s);
};

if (this.httpOnly) {
return this._httpConnect(this.wsHostUrl, remoteAddr, optionalHeaders, (err, httpConn) => {
if (!err) {
return bind(httpConn, tcpConn);
} else { return tcpConn.end(); }
});
} else {
return this._wsConnect(this.wsHostUrl, remoteAddr, optionalHeaders, (error, wsStream) => {
if (!error) {
return bind(wsStream, tcpConn);
} else {
this.emit('connectFailed', error);
return this._httpConnect(this.wsHostUrl, remoteAddr, optionalHeaders, (err, httpConn) => {
if (!err) {
return bind(httpConn, tcpConn);
} else { return tcpConn.end(); }
});
}
});
}
this._connect(this.wsHostUrl, remoteAddr, optionalHeaders, (err, stream)=>{
if (err) this.emit('connectFailed', err);
else bind(tcpConn, stream);
})
});
}

Expand All @@ -97,34 +62,37 @@ module.exports = (wst_client = class wst_client extends require('events').EventE
process.stdin.pipe(s);
s.pipe(process.stdout);
s.on('close', () => process.exit(0));
return s.on('finish', () => process.exit(0));
s.on('finish', () => process.exit(0));
};
this._connect(this.wsHostUrl, remoteAddr, optionalHeaders, (err, stream)=>{
if (err) this.emit('connectFailed', err);
else bind(stream);
if (cb) cb(err);
})
}

_connect(wsHostUrl, remoteAddr, optionalHeaders, cb) {
if (this.httpOnly) {
return this._httpConnect(this.wsHostUrl, remoteAddr, optionalHeaders, (err, httpConn) => {
if (!err) { bind(httpConn); }
return cb(err);
});
return this._httpConnect(wsHostUrl, remoteAddr, optionalHeaders, cb);
} else {
return this._wsConnect(this.wsHostUrl, remoteAddr, optionalHeaders, (error, wsStream) => {
if (!error) {
bind(wsStream);
return cb();
return this._wsConnect(wsHostUrl, remoteAddr, optionalHeaders, (err, wsStream) => {
if (!err) {
cb(err, wsStream);
} else {
this.emit('connectFailed', error);
return this._httpConnect(this.wsHostUrl, remoteAddr, optionalHeaders, (err, httpConn) => {
if (!err) { bind(httpConn); }
return cb(err);
});
this.emit('connectFailed', err);
return this._httpConnect(wsHostUrl, remoteAddr, optionalHeaders, cb);
}
});
}
}
}
setAgentMaker(maker) {
this.agentMaker = maker;
}

_httpConnect(url, remoteAddr, optionalHeaders, cb) {
let tunurl = url.replace(/^ws/, 'http');
if (remoteAddr) { tunurl += `?dst=${remoteAddr}`; }
const httpConn = new ClientConn(tunurl);
const httpConn = new ClientConn(tunurl, this.agentMaker);
return httpConn.connect(optionalHeaders, err => {
if (err) {
this.emit('connectHttpFailed', err);
Expand All @@ -136,15 +104,16 @@ module.exports = (wst_client = class wst_client extends require('events').EventE
}

_wsConnect(wsHostUrl, remoteAddr, optionalHeaders, cb) {
wsHostUrl = wsHostUrl.replace(/^http/, 'ws');
let wsurl;
if (remoteAddr) { wsurl = `${wsHostUrl}/?dst=${remoteAddr}`; } else { wsurl = `${wsHostUrl}`; }
const wsClient = createWsClient();
const urlo = url.parse(wsurl);
if (urlo.auth) {
optionalHeaders.Authorization = `Basic ${(new Buffer(urlo.auth)).toString('base64')}`;
}
wsClient.connect(wsurl, 'tunnel-protocol', undefined, optionalHeaders
, { agent: null } );
wsClient.connect(wsurl, 'tunnel-protocol', undefined, optionalHeaders,
{agent: this.agentMaker ? this.agentMaker(): null});
wsClient.on('connectFailed', error => cb(error));
return wsClient.on('connect', wsConn => {
const wsStream = new WsStream(wsConn);
Expand Down
2 changes: 1 addition & 1 deletion lib/WstServer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
let wst_server;

const WebSocketServer = require('websocket').server;
const http = require('http');
const url = require("url");
Expand Down
Loading

0 comments on commit 08d046d

Please sign in to comment.