首页 > 解决方案 > NodeJS反向SSH隧道:无法绑定到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在哪里,并支持 https。xxxx

但是,如果我键入另一个端口,例如59000,该应用程序将通过 可用serveo.net:59000,但没有 SSL。

问题

现在,我想用 NodeJS 来做这件事,在我为我的同事和公司的合作伙伴构建的工具中实现自动化,这样他们就不必担心,也不必在他们的机器上安装 SSH 客户端. 我正在使用SSH2 节点模块

这是一个工作代码示例,使用自定义端口配置(此处为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)

我尝试了很多事情都没有成功。如果有人知道我的示例中可能有什么问题,我将不胜感激。谢谢!

标签: javascriptnode.jssshssh-tunnelssh2

解决方案


未指定时,OpenSSH 默认为远程主机的“localhost”。您还可以通过添加-vvv到命令行来检查 OpenSSH 客户端的调试输出来验证这一点。你应该看到如下一行:

debug1: Remote connections from LOCALHOST:80 forwarded to local address localhost:3000

如果您通过config.remoteHost = 'localhost'在 JS 代码中进行设置来模仿这一点,您应该会得到与 OpenSSH 客户端相同的结果。


推荐阅读