如何实现支持远程端口转发的Node SSH2服务器?
我来帮你解决这个远程端口转发的问题!你之前的代码里踩了一个常见的坑——client.forwardIn是SSH客户端用来请求本地转发(-L参数)的方法,服务器端根本不该这么用。咱们换个正确的思路来实现远程端口转发(-R参数)的功能:
核心逻辑梳理
远程端口转发的本质是:客户端让你的Node SSH服务器监听指定端口,当有外部连接打到这个端口时,服务器通过SSH通道把连接数据流传给客户端,客户端再把数据转发到它指定的目标服务(比如你的localsite.tld:80)。所以服务器需要自己监听端口,再把新连接和SSH通道做双向桥接。
修正后的完整代码
var fs = require('fs'); var inspect = require('util').inspect; var ssh2 = require('ssh2'); var net = require('net'); // 新增引入net模块,用来创建TCP监听服务器 new ssh2.Server({ hostKeys: [fs.readFileSync('/etc/ssh/ssh_host_rsa_key')] }, function(client) { console.log('Client connected!'); client.on('authentication', function(ctx) { if (ctx.method === 'password' && ctx.username === 'foo' && ctx.password === 'bar') ctx.accept(); else ctx.reject(); }).on('ready', function() { console.log('Client authenticated!'); // 保留你原来的session/exec处理逻辑(如果需要的话) client.on('session', function(accept, reject) { var session = accept(); session.once('exec', function(accept, reject, info) { console.log('Client wants to execute: ' + inspect(info.command)); var stream = accept(); stream.stderr.write('Oh no, the dreaded errors!\n'); stream.write('Just kidding about the errors!\n'); stream.exit(0); stream.end(); }); }); // 处理远程端口转发请求 client.on('request', function(accept, reject, name, info) { console.log(`Received request: ${name}`, info); if (name === 'tcpip-forward') { accept(); // 同意客户端的远程转发请求 // 创建TCP服务器,监听客户端要求的端口 const forwardServer = net.createServer((externalConn) => { console.log(`Incoming connection to forwarded port ${info.bindPort}`); // 向客户端发起direct-tcpip通道请求,把外部连接的信息传给客户端 client.openChannel('direct-tcpip', { originAddr: externalConn.remoteAddress, originPort: externalConn.remotePort, remoteAddr: info.originAddr, // 客户端指定的目标地址(localsite.tld) remotePort: info.originPort // 客户端指定的目标端口(80) }, (err, channel) => { if (err) { console.error('Failed to open SSH channel:', err); externalConn.destroy(); return; } // 双向桥接外部连接和SSH通道的数据流 externalConn.pipe(channel).pipe(externalConn); // 处理关闭/错误事件,避免资源泄漏 channel.on('close', () => externalConn.destroy()); externalConn.on('close', () => channel.close()); channel.on('error', (err) => { console.error('Channel error:', err); externalConn.destroy(); }); externalConn.on('error', (err) => { console.error('Connection error:', err); channel.close(); }); }); }); // 启动监听 forwardServer.listen(info.bindPort, info.bindAddr, () => { console.log(`Forwarding server running on ${info.bindAddr}:${info.bindPort}`); }); // 客户端断开时,自动关闭转发服务器 client.once('close', () => { console.log('Client disconnected, shutting down forward server'); forwardServer.close(); }); // 监听失败时的错误处理 forwardServer.on('error', (err) => { console.error('Forward server failed to start:', err); reject(); }); } else { reject(); // 拒绝其他未知请求 } }); }).on('close', function() { console.log('Client disconnected'); }).on('error', function(err) { console.error('Client error:', err); }); }).listen(21, '0.0.0.0', function() { console.log('SSH server listening on port ' + this.address().port); });
关键修改点说明
- 新增
net模块:用来创建自己的TCP服务器,监听客户端要求转发的端口(比如8100)。 - 正确处理
tcpip-forward请求:不再调用错误的client.forwardIn,而是同意请求后启动本地TCP监听。 - 桥接数据流:当有外部连接进来时,通过
client.openChannel发起direct-tcpip通道请求,把外部连接和SSH通道双向绑定,让数据能在两者之间流转。 - 资源清理:添加了客户端断开时关闭转发服务器的逻辑,以及各类错误/关闭事件的处理,避免资源泄漏。
测试步骤
- 确保你的服务器上有
/etc/ssh/ssh_host_rsa_key密钥文件,如果没有可以用命令生成:ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N "" - 运行上述Node.js服务器。
- 在客户端执行你想要的命令:
ssh -R 8100:localsite.tld:80 foo@sub.distantserver.com -p 21 - 输入密码
bar后,尝试访问sub.distantserver.com:8100,流量就会被转发到localsite.tld:80了。
注意事项
- 确保服务器的21端口和8100端口没有被其他服务占用,且防火墙允许这两个端口的入站连接。
- 如果你的服务器上有FTP服务(默认用21端口),建议把SSH服务器改成其他端口,比如2221。
内容的提问来源于stack exchange,提问作者Doubidou
相关产品推荐
相关产品推荐

