Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
damonYuan committed Jun 24, 2021
0 parents commit 9a0be46
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
*.map
.idea/*
compile/*
node_modules/
27 changes: 27 additions & 0 deletions README.md
@@ -0,0 +1,27 @@
Boring
====

Bore a tunnel through WebSocket, Socks5 or HTTP Connection.

# Usage

Install through `npm install tboring -g`, check the help through `boring -h`,

```
Help:
To run server: boring -s localip:localport [remoteip:remoteport]
To run client: boring -t [localip:]localport[:remoteip:remoteport] ws[s]://wshost:wsport
Options:
-s, --server run as server, listen on localip:localport
-t, --tunnel run as tunnel client, specify localip:localport
-c, --cert path to the https server's cert, optional
-k, --key path to the https server's key, optional
```

# TODO

- [ ] Support Socks5
- [ ] Support HTTP Connection
- [ ] Support secured WebSocket client connection
64 changes: 64 additions & 0 deletions bin/cli.js
@@ -0,0 +1,64 @@
#!/usr/bin/env node
const { Server, Client } = require("../lib/boring");
const Help = `Help:
To run server: boring -s localip:localport [remoteip:remoteport]
To run client: boring -t [localip:]localport[:remoteip:remoteport] ws[s]://wshost:wsport
`;

(() => {
const optimist = require("optimist");
const argv = optimist
.usage(Help)
.string("s")
.string("t")
.string("c")
.string("k")
.alias("s", "server")
.alias("t", "tunnel")
.alias("c", "cert")
.alias("k", "key")
.describe("s", "run as server, listen on localip:localport")
.describe("t", "run as tunnel client, specify localip:localport")
.describe("c", "path to the https server's cert, optional")
.describe("k", "path to the https server's key, optional").argv;
if (argv.s) {
const server = new Server();
const remoteAddr = argv._[0];
let [cert, key] = [null, null];
if (argv.c && argv.k) {
cert = argv.c;
key = argv.k;
}
server.start(argv.s, remoteAddr, cert, key, (err) =>
err ? console.log(err) : console.log(`Server is listening on ${argv.s}`)
);
} else if (argv.t) {
const client = new Client();
let localHost = "127.0.0.1",
localPort,
remoteAddr;
const toks = argv.t.split(":");
if (toks.length === 4) {
[localHost, localPort] = toks;
remoteAddr = `${toks[2]}:${toks[3]}`;
} else if (toks.length === 3) {
remoteAddr = `${toks[1]}:${toks[2]}`;
localPort = toks[0];
} else if (toks.length === 2) {
[localHost, localPort] = toks;
} else if (toks.length === 1) {
localPort = toks[0];
} else {
console.log("Invalid tunnel option " + argv.t);
console.log(optimist.help());
process.exit();
}
localPort = parseInt(localPort);
const wsHostUrl = argv._[0];
client.start(`${localHost}:${localPort}`, remoteAddr, wsHostUrl, (err) =>
err ? console.log(err) : console.log(`Server is listening on ${argv.t}`)
);
} else {
console.log(optimist.help());
}
})();
4 changes: 4 additions & 0 deletions lib/boring.js
@@ -0,0 +1,4 @@
module.exports = {
Server: require("./server"),
Client: require("./client"),
};
50 changes: 50 additions & 0 deletions lib/client.js
@@ -0,0 +1,50 @@
const utils = require("./utils");
const net = require("net");
const WebSocket = require("ws");

module.exports = BoringClient = class BoringClient {
constructor() {
this.tcpServer = net.createServer();
}

start(localAddr, remoteAddr, wsHostUrl, cb) {
const [localHost, localPort] = Array.from(utils.parseAddr(localAddr));
console.log(`localHost: ${localHost}; localPort: ${localPort}`);
if (remoteAddr) console.log(`remoteAddr: ${remoteAddr}`);
console.log(`wsHostUrl: ${wsHostUrl}`);
if (remoteAddr) {
this.wsHostUrl = `${wsHostUrl}/?dst=${remoteAddr}`;
} else {
this.wsHostUrl = wsHostUrl;
}
this.tcpServer.listen(localPort, localHost, cb);
this.tcpServer.on("connection", (socket) => {
this._connect(this.wsHostUrl, (err, wsStream) => {
if (err) console.log(err);
else {
socket.pipe(wsStream);
wsStream.pipe(socket);
}
});
});
}

_connect(host, cb) {
try {
const re = new RegExp(
"^(?:wss?:)?(?://)?(?:[^@\n]+@)?(?:www.)?([^:/\n]+)",
"im"
);
const ws = new WebSocket(host, {
servername: host.match(re)[1],
perMessageDeflate: false,
rejectUnauthorized: false,
strictSSL: false,
});
const duplex = WebSocket.createWebSocketStream(ws);
cb(null, duplex);
} catch (e) {
cb(e, null);
}
}
};
122 changes: 122 additions & 0 deletions lib/server.js
@@ -0,0 +1,122 @@
const utils = require("./utils");
const WebSocket = require("ws");
const net = require("net");
const url = require("url");

module.exports = BoringServer = class BoringServer {
start(localAddr, remoteAddr, cert, key, cb) {
const [localHost, localPort] = Array.from(utils.parseAddr(localAddr));
if (remoteAddr) {
const [remoteHost, remotePort] = Array.from(utils.parseAddr(remoteAddr));
this.remoteHost = remoteHost;
this.remotePort = remotePort;
console.log(`Traffic is forwarded to ${remoteAddr}`);
} else {
console.log(
`The remote where the traffic is forwarded to will be defined by clients`
);
}
cb();
let server;
if (cert && key) {
const https = require("https");
server = https.createServer({
cert: fs.readFileSync(cert),
key: fs.readFileSync(key),
});
} else {
const http = require("http");
server = http.createServer();
}

const wss = new WebSocket.Server({
server: server,
path: "/",
});

wss.on("connection", (ws, req) => {
console.log(
`ws connected, headers: ${JSON.stringify(req.headers)}; url: ${
req.url
}; ip: ${req.socket.remoteAddress}`
);
ws.isAlive = true;
ws.on("pong", () => {
ws.isAlive = true;
});
const uri = url.parse(req.url, true);
let dst = uri.query.dst ? uri.query.dst : null;
setupTcpSocket(ws, dst);
ws.on("close", (code, reason) => {
console.log(`ws closed - code: ${code}; reason: ${reason}`);
});
ws.on("error", (err) => {
console.log(`ws error: `, err);
});
});

const setupTcpSocket = (ws, target) => {
if (!ws.tcpSocket) {
const duplex = WebSocket.createWebSocketStream(ws);
let host, port;
if (this.remoteHost && this.remotePort) {
[host, port] = [this.remoteHost, this.remotePort];
} else if (target) {
[host, port] = Array.from(utils.parseAddr(target));
} else {
console.log("Remote address unknown");
closeWs(ws);
return;
}

const tcpSocket = net.connect(
{ host, port, allowHalfOpen: true },
() => {
console.log(`tcpSocket connected`);
duplex.pipe(tcpSocket);
tcpSocket.pipe(duplex);
}
);
tcpSocket.on("end", () => {
console.log("tcpSocket end");
ws.tcpSocket = null;
});
tcpSocket.on("error", (err) => {
console.log("tcpSocket error: ", err);
});
ws.tcpSocket = tcpSocket;
}
};

const closeWs = (ws) => {
ws.terminate();
if (ws.tcpSocket) {
ws.tcpSocket.end();
ws.tcpSocket = null;
}
};

const noop = () => {};
const cleanup = () => {
try {
wss.clients.forEach((ws) => {
if (!ws.isAlive) {
closeWs(ws);
return;
}
ws.isAlive = false;
ws.ping(noop);
});
} catch (e) {
console.log(e);
} finally {
setTimeout(() => {
cleanup();
}, 300000);
}
};
cleanup();

server.listen(localPort, localHost);
}
};
21 changes: 21 additions & 0 deletions lib/utils.js
@@ -0,0 +1,21 @@
parseAddr = (addr) => {
let host, port;
if (typeof addr === "number") {
port = addr;
} else {
[host, port] = Array.from(addr.split(":"));
if (/^\d+$/.test(host)) {
port = host;
host = null;
}
port = parseInt(port);
}
if (host == null) {
host = "127.0.0.1";
}
return [host, port];
};

module.exports = {
parseAddr: parseAddr,
};
38 changes: 38 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions package.json
@@ -0,0 +1,31 @@
{
"name": "tboring",
"version": "1.0.0",
"description": "Bore a tunnel through WebSocket, Socks5 or HTTP Connection.",
"scripts": {
"prestart": "npx prettier --write '**/*.js'"
},
"keywords": [
"tunnel",
"websocket"
],
"author": {
"name": "Damon Yuan",
"email": "damon.yuan.dev@gmail.com"
},
"repository": {
"type": "git",
"url": "git://github.com:damonYuan/boring.git"
},
"license": "MIT",
"dependencies": {
"optimist": "^0.6.1",
"ws": "^7.5.0"
},
"devDependencies": {
"prettier": "^2.3.1"
},
"bin": {
"boring": "./bin/cli.js"
}
}

0 comments on commit 9a0be46

Please sign in to comment.