You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于JSch实现双网关SSH隧道连接的问题求助

解决双网关SSH隧道连接目标主机的问题

我来帮你搞定这个双网关隧道的问题!先看你遇到的报错:PortForwardingL: local port 127.0.0.1:2233 cannot be bound,原因很直接——你重复使用了同一个本地端口2233做两次本地端口转发,第一次绑定后第二次再用就会触发端口占用的冲突。

问题根源分析

你的代码里:

  1. 第一个会话(连接Gateway1)把本地2233端口转发到Gateway2的22端口:session.setPortForwardingL(2233, Gateway2, 22)
  2. 第二个会话(通过本地2233连Gateway2)又试图把同一个本地2233端口转发到endpoint的22端口:secondSession.setPortForwardingL(2233, endpoint, 22)

同一台机器上的本地端口不能被多个进程(或同一个进程的多个转发)重复绑定,这就是报错的核心原因。另外还有个小问题:你最后执行命令用的是secondSession的channel,但实际上你要连接的是finalSession对应的endpoint主机,这也会导致命令执行到错误的机器上。

修复后的代码

下面是修正后的完整代码,我标注了关键修改点:

import com.jcraft.jsch.*;
import java.io.InputStream;

public class Main {
    public static void main(String[] args){
        Main t=new Main();
        try{
            t.go();
        } catch(Exception ex){
            ex.printStackTrace();
        }
    }

    public void go() throws Exception{
        StringBuilder outputBuffer = new StringBuilder();
        String Gateway1="192.168.0.101"; // First level gateway
        String user1="root";
        String password="12qwaszx";
        String Gateway2="192.168.0.102"; // Second level gateway
        String user2 = "root";
        String secondPassword="12qwaszx";
        String endpoint = "10.81.77.52"; // Target host
        String finaluser="admin";
        String finaluserpass="admin";

        JSch jsch=new JSch();

        // 第一步:连接第一个网关Gateway1,并转发本地2233到Gateway2的22端口
        Session session=jsch.getSession(user1, Gateway1, 22);
        session.setPassword(password);
        localUserInfo lui=new localUserInfo();
        session.setUserInfo(lui);
        session.setConfig("StrictHostKeyChecking", "no");
        session.setPortForwardingL(2233, Gateway2, 22);
        session.connect();
        System.out.println("Connected to Gateway1, port 2233 forwarded to Gateway2:22");

        // 第二步:通过本地2233连接Gateway2,并转发本地2234到endpoint的22端口(换了新端口!)
        Session secondSession = jsch.getSession(user2, "localhost", 2233);
        secondSession.setPassword(secondPassword);
        secondSession.setUserInfo(lui);
        secondSession.setConfig("StrictHostKeyChecking", "no");
        secondSession.setPortForwardingL(2234, endpoint, 22); // 修改:用2234代替2233
        secondSession.connect();
        System.out.println("Connected to Gateway2, port 2234 forwarded to endpoint:22");

        // 第三步:通过本地2234连接目标主机endpoint
        Session finalSession = jsch.getSession(finaluser, "localhost", 2234); // 修改:连接2234端口
        finalSession.setPassword(finaluserpass);
        finalSession.setUserInfo(lui);
        finalSession.setConfig("StrictHostKeyChecking", "no");
        finalSession.connect();
        System.out.println("Connected to target endpoint: " + endpoint);

        // 关键修改:用finalSession打开通道,执行目标主机上的命令
        Channel channel=finalSession.openChannel("exec");
        ((ChannelExec)channel).setCommand("show system information | match \"System Name\"");
        channel.setInputStream(null);
        InputStream stdout=channel.getInputStream();
        channel.connect();

        if (channel.isConnected()){
            System.out.println("Command channel connected");
        }

        while (true) {
            byte[] tmpArray=new byte[1024];
            while(stdout.available() > 0){
                int i=stdout.read(tmpArray, 0, 1024);
                if(i<0)break;
                outputBuffer.append(new String(tmpArray, 0, i));
            }
            if(channel.isClosed()){
                System.out.println("exit-status: "+channel.getExitStatus());
                break;
            }
            // 加个短睡眠,避免循环占用CPU
            Thread.sleep(100);
        }

        // 按顺序关闭资源:通道 -> 最终会话 -> 第二个网关会话 -> 第一个网关会话
        stdout.close();
        channel.disconnect();
        finalSession.disconnect();
        secondSession.disconnect();
        session.disconnect();

        System.out.println("\nCommand output:");
        System.out.print(outputBuffer.toString());
    }

    class localUserInfo implements UserInfo {
        String passwd;
        public String getPassword(){ return passwd; }
        public boolean promptYesNo(String str){return true;}
        public String getPassphrase(){ return null; }
        public boolean promptPassphrase(String message){return true; }
        public boolean promptPassword(String message){return true;}
        public void showMessage(String message){}
    }
}

关键修改说明

  • 端口冲突修复:第二次端口转发改用本地端口2234,避免和第一次的2233重复绑定
  • 命令执行会话修正:使用finalSession打开执行命令的通道,确保命令在目标主机endpoint上运行
  • 资源关闭顺序:按创建的逆序关闭会话和通道,避免资源泄漏
  • 增加状态打印:方便你排查每一步的连接情况

额外注意事项

  1. 确保Gateway1能访问Gateway2,Gateway2能访问endpoint的22端口(SSH服务端口)
  2. StrictHostKeyChecking设为no虽然方便测试,但生产环境建议改为ask或yes,避免中间人攻击风险
  3. 如果本地端口2233、2234也被其他程序占用,可以换成其他未被使用的端口(比如2235、2236等)

内容的提问来源于stack exchange,提问作者Hugo Abreu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 21:12:28