NodeJS 反向 SSH 隧道:无法绑定到 serveo.net:80
NodeJS reverse SSH tunnel: unable to bind to serveo.net:80
上下文
不久前,我发现了一项很棒的服务,叫做Serveo。它允许我使用反向 SSH 隧道将我的本地应用程序公开到 Internet。
例如https://abc.serveo.net
的连接被转发到我机器上的 http://localhost:3000
。
为此,他们不需要安装客户端,我只需在命令行中输入:
ssh -R 80:localhost:3000 serveo.net
其中 80
是我要绑定到的 serveo.net 上的远程端口,localhost:3000
是我的应用程序的本地地址。
如果我只在左侧输入 80
,Serveo 将回答 Forwarding HTTP traffic from https://xxxx.serveo.net
,其中 xxxx
是一个可用的子域,支持 https。
但是,如果我输入另一个端口,例如 59000
,应用程序将通过 serveo.net:59000
可用,但没有 SSL。
问题
现在,我想用 NodeJS 来做这件事,在我为我的同事和我公司的合作伙伴构建的工具中实现自动化,这样他们就不需要担心,也不需要他们机器上的 SSH 客户端。我正在使用 SSH2 Node module.
这是一个工作代码示例,使用自定义端口配置(此处为 59000
),应用程序侦听 http://localhost:3000
:
/**
* Want to try it out?
* Go to https://github.com/blex41/demo-ssh2-tunnel
*/
const Client = require("ssh2").Client; // To communicate with Serveo
const Socket = require("net").Socket; // To accept forwarded connections (native module)
// Create an SSH client
const conn = new Client();
// Config, just like the second example in my question
const config = {
remoteHost: "",
remotePort: 59000,
localHost: "localhost",
localPort: 3000
};
conn
.on("ready", () => {
// When the connection is ready
console.log("Connection ready");
// Start an interactive shell session
conn.shell((err, stream) => {
if (err) throw err;
// And display the shell output (so I can see how Serveo responds)
stream.on("data", data => {
console.log("SHELL OUTPUT: " + data);
});
});
// Request port forwarding from the remote server
conn.forwardIn(config.remoteHost, config.remotePort, (err, port) => {
if (err) throw err;
conn.emit("forward-in", port);
});
})
// ===== Note: this part is irrelevant to my problem, but here for the demo to work
.on("tcp connection", (info, accept, reject) => {
console.log("Incoming TCP connection", JSON.stringify(info));
let remote;
const srcSocket = new Socket();
srcSocket
.on("error", err => {
if (remote === undefined) reject();
else remote.end();
})
.connect(config.localPort, config.localPort, () => {
remote = accept()
.on("close", () => {
console.log("TCP :: CLOSED");
})
.on("data", data => {
console.log(
"TCP :: DATA: " +
data
.toString()
.split(/\n/g)
.slice(0, 2)
.join("\n")
);
});
console.log("Accept remote connection");
srcSocket.pipe(remote).pipe(srcSocket);
});
})
// ===== End Note
// Connect to Serveo
.connect({
host: "serveo.net",
username: "johndoe",
tryKeyboard: true
});
// Just for the demo, create a server listening on port 3000
// Accessible both on:
// http://localhost:3000
// https://serveo.net:59000
const http = require("http"); // native module
http
.createServer((req, res) => {
res.writeHead(200, {
"Content-Type": "text/plain"
});
res.write("Hello world!");
res.end();
})
.listen(config.localPort);
这很好用,我可以在 http://serveo.net:59000
上访问我的应用程序。但是它不支持HTTPS,这是我的要求之一。如果我想要 HTTPS,我需要将端口设置为 80
,并将远程主机留空,就像上面给出的普通 SSH 命令一样,以便 Servo 为我分配一个可用的子域:
// equivalent to `ssh -R 80:localhost:3000 serveo.net`
const config = {
remoteHost: "",
remotePort: 80,
localHost: "localhost",
localPort: 3000
};
然而,这是抛出一个错误:
Error: Unable to bind to :80
at C:\workspace\demo-ssh2-tunnel\node_modules\ssh2\lib\client.js:939:21
at SSH2Stream.<anonymous> (C:\workspace\demo-ssh2-tunnel\node_modules\ssh2\lib\client.js:628:24)
at SSH2Stream.emit (events.js:182:13)
at parsePacket (C:\workspace\demo-ssh2-tunnel\node_modules\ssh2-streams\lib\ssh.js:3851:10)
at SSH2Stream._transform (C:\workspace\demo-ssh2-tunnel\node_modules\ssh2-streams\lib\ssh.js:693:13)
at SSH2Stream.Transform._read (_stream_transform.js:190:10)
at SSH2Stream._read (C:\workspace\demo-ssh2-tunnel\node_modules\ssh2-streams\lib\ssh.js:252:15)
at SSH2Stream.Transform._write (_stream_transform.js:178:12)
at doWrite (_stream_writable.js:410:12)
at writeOrBuffer (_stream_writable.js:394:5)
我试过很多东西都没有成功。如果有人知道我的例子中可能有什么问题,我将非常感激。谢谢!
OpenSSH defaults to "localhost" for the remote host when it's not specified。您还可以通过在命令行中添加 -vvv
检查 OpenSSH 客户端的调试输出来验证这一点。您应该会看到如下一行:
debug1: Remote connections from LOCALHOST:80 forwarded to local address localhost:3000
如果您通过在 JS 代码中设置 config.remoteHost = 'localhost'
来模拟此操作,您应该会得到与 OpenSSH 客户端相同的结果。
上下文
不久前,我发现了一项很棒的服务,叫做Serveo。它允许我使用反向 SSH 隧道将我的本地应用程序公开到 Internet。
例如https://abc.serveo.net
的连接被转发到我机器上的 http://localhost:3000
。
为此,他们不需要安装客户端,我只需在命令行中输入:
ssh -R 80:localhost:3000 serveo.net
其中 80
是我要绑定到的 serveo.net 上的远程端口,localhost:3000
是我的应用程序的本地地址。
如果我只在左侧输入 80
,Serveo 将回答 Forwarding HTTP traffic from https://xxxx.serveo.net
,其中 xxxx
是一个可用的子域,支持 https。
但是,如果我输入另一个端口,例如 59000
,应用程序将通过 serveo.net:59000
可用,但没有 SSL。
问题
现在,我想用 NodeJS 来做这件事,在我为我的同事和我公司的合作伙伴构建的工具中实现自动化,这样他们就不需要担心,也不需要他们机器上的 SSH 客户端。我正在使用 SSH2 Node module.
这是一个工作代码示例,使用自定义端口配置(此处为 59000
),应用程序侦听 http://localhost:3000
:
/**
* Want to try it out?
* Go to https://github.com/blex41/demo-ssh2-tunnel
*/
const Client = require("ssh2").Client; // To communicate with Serveo
const Socket = require("net").Socket; // To accept forwarded connections (native module)
// Create an SSH client
const conn = new Client();
// Config, just like the second example in my question
const config = {
remoteHost: "",
remotePort: 59000,
localHost: "localhost",
localPort: 3000
};
conn
.on("ready", () => {
// When the connection is ready
console.log("Connection ready");
// Start an interactive shell session
conn.shell((err, stream) => {
if (err) throw err;
// And display the shell output (so I can see how Serveo responds)
stream.on("data", data => {
console.log("SHELL OUTPUT: " + data);
});
});
// Request port forwarding from the remote server
conn.forwardIn(config.remoteHost, config.remotePort, (err, port) => {
if (err) throw err;
conn.emit("forward-in", port);
});
})
// ===== Note: this part is irrelevant to my problem, but here for the demo to work
.on("tcp connection", (info, accept, reject) => {
console.log("Incoming TCP connection", JSON.stringify(info));
let remote;
const srcSocket = new Socket();
srcSocket
.on("error", err => {
if (remote === undefined) reject();
else remote.end();
})
.connect(config.localPort, config.localPort, () => {
remote = accept()
.on("close", () => {
console.log("TCP :: CLOSED");
})
.on("data", data => {
console.log(
"TCP :: DATA: " +
data
.toString()
.split(/\n/g)
.slice(0, 2)
.join("\n")
);
});
console.log("Accept remote connection");
srcSocket.pipe(remote).pipe(srcSocket);
});
})
// ===== End Note
// Connect to Serveo
.connect({
host: "serveo.net",
username: "johndoe",
tryKeyboard: true
});
// Just for the demo, create a server listening on port 3000
// Accessible both on:
// http://localhost:3000
// https://serveo.net:59000
const http = require("http"); // native module
http
.createServer((req, res) => {
res.writeHead(200, {
"Content-Type": "text/plain"
});
res.write("Hello world!");
res.end();
})
.listen(config.localPort);
这很好用,我可以在 http://serveo.net:59000
上访问我的应用程序。但是它不支持HTTPS,这是我的要求之一。如果我想要 HTTPS,我需要将端口设置为 80
,并将远程主机留空,就像上面给出的普通 SSH 命令一样,以便 Servo 为我分配一个可用的子域:
// equivalent to `ssh -R 80:localhost:3000 serveo.net`
const config = {
remoteHost: "",
remotePort: 80,
localHost: "localhost",
localPort: 3000
};
然而,这是抛出一个错误:
Error: Unable to bind to :80
at C:\workspace\demo-ssh2-tunnel\node_modules\ssh2\lib\client.js:939:21
at SSH2Stream.<anonymous> (C:\workspace\demo-ssh2-tunnel\node_modules\ssh2\lib\client.js:628:24)
at SSH2Stream.emit (events.js:182:13)
at parsePacket (C:\workspace\demo-ssh2-tunnel\node_modules\ssh2-streams\lib\ssh.js:3851:10)
at SSH2Stream._transform (C:\workspace\demo-ssh2-tunnel\node_modules\ssh2-streams\lib\ssh.js:693:13)
at SSH2Stream.Transform._read (_stream_transform.js:190:10)
at SSH2Stream._read (C:\workspace\demo-ssh2-tunnel\node_modules\ssh2-streams\lib\ssh.js:252:15)
at SSH2Stream.Transform._write (_stream_transform.js:178:12)
at doWrite (_stream_writable.js:410:12)
at writeOrBuffer (_stream_writable.js:394:5)
我试过很多东西都没有成功。如果有人知道我的例子中可能有什么问题,我将非常感激。谢谢!
OpenSSH defaults to "localhost" for the remote host when it's not specified。您还可以通过在命令行中添加 -vvv
检查 OpenSSH 客户端的调试输出来验证这一点。您应该会看到如下一行:
debug1: Remote connections from LOCALHOST:80 forwarded to local address localhost:3000
如果您通过在 JS 代码中设置 config.remoteHost = 'localhost'
来模拟此操作,您应该会得到与 OpenSSH 客户端相同的结果。