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

如何用Go代码实现等效于指定SSH隧道与ProxyJump的功能?

用Go实现带跳转机的SSH本地端口转发

你要实现的SSH命令等效于:通过IPv6跳转机root@[IPv6 address]建立到目标机myuser@100.1.1.100的SSH连接,并将本地9999端口的流量转发到目标机可访问的192.168.1.1:80。以下是具体实现步骤和代码:

核心逻辑拆解

  1. 先建立到跳转机的SSH客户端连接
  2. 通过跳转机的连接,建立到目标机的SSH连接
  3. 监听本地9999端口,将每个本地连接的流量通过目标机转发到192.168.1.1:80

完整代码示例

package main

import (
    "io"
    "log"
    "net"
    "sync"

    "golang.org/x/crypto/ssh"
)

// 创建SSH客户端配置(示例用密码认证,可替换为密钥认证)
func createSSHConfig(user, password string) *ssh.ClientConfig {
    return &ssh.ClientConfig{
        User: user,
        Auth: []ssh.AuthMethod{
            ssh.Password(password),
        },
        // 生产环境请替换为安全的主机密钥验证逻辑,不要忽略主机密钥
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }
}

func main() {
    // 配置参数,替换为你的实际信息
    jumpIPv6Addr := "[2001:db8::1]" // 跳转机IPv6地址,保留方括号
    jumpUser := "root"
    jumpPassword := "your_jump_password"
    targetHost := "100.1.1.100"
    targetUser := "myuser"
    targetPassword := "your_target_password"
    localPort := ":9999"
    remoteTarget := "192.168.1.1:80"

    // 1. 建立到跳转机的SSH连接
    jumpSSHAddr := net.JoinHostPort(jumpIPv6Addr, "22")
    jumpConfig := createSSHConfig(jumpUser, jumpPassword)
    jumpClient, err := ssh.Dial("tcp", jumpSSHAddr, jumpConfig)
    if err != nil {
        log.Fatalf("连接跳转机失败: %v", err)
    }
    defer jumpClient.Close()

    // 2. 通过跳转机连接目标机的SSH服务
    targetSSHAddr := net.JoinHostPort(targetHost, "22")
    targetConfig := createSSHConfig(targetUser, targetPassword)
    // 通过跳转机的TCP连接建立到目标机的SSH握手
    targetRawConn, err := jumpClient.Dial("tcp", targetSSHAddr)
    if err != nil {
        log.Fatalf("通过跳转机连接目标机失败: %v", err)
    }
    targetConn, chans, reqs, err := ssh.NewClientConn(targetRawConn, targetSSHAddr, targetConfig)
    if err != nil {
        log.Fatalf("建立目标机SSH连接失败: %v", err)
    }
    targetClient := ssh.NewClient(targetConn, chans, reqs)
    defer targetClient.Close()

    // 3. 监听本地端口,开始转发流量
    localListener, err := net.Listen("tcp", localPort)
    if err != nil {
        log.Fatalf("监听本地端口失败: %v", err)
    }
    defer localListener.Close()

    log.Printf("本地端口%s已监听,流量将转发到%s", localPort, remoteTarget)

    // 循环处理本地连接
    for {
        localConn, err := localListener.Accept()
        if err != nil {
            log.Printf("接受本地连接失败: %v", err)
            continue
        }

        // 每个连接启动单独协程处理
        go func(local net.Conn) {
            defer local.Close()

            // 通过目标机SSH连接建立到最终目标的TCP连接
            remoteConn, err := targetClient.Dial("tcp", remoteTarget)
            if err != nil {
                log.Printf("连接远程目标%s失败: %v", remoteTarget, err)
                return
            }
            defer remoteConn.Close()

            // 双向拷贝数据,完成流量转发
            var wg sync.WaitGroup
            wg.Add(2)

            go func() {
                io.Copy(local, remoteConn)
                wg.Done()
            }()

            go func() {
                io.Copy(remoteConn, local)
                wg.Done()
            }()

            wg.Wait()
        }(localConn)
    }
}

关键注意事项

  • 主机密钥验证:示例中用ssh.InsecureIgnoreHostKey()跳过验证,生产环境必须实现安全的主机密钥校验,比如从known_hosts文件加载密钥进行验证。
  • 认证方式替换:如果要用密钥认证,可将ssh.Password(password)替换为ssh.PublicKeys(signer),其中signer通过ssh.ParsePrivateKey读取私钥文件生成。
  • IPv6地址处理:跳转机的IPv6地址必须保留方括号,net.JoinHostPort会自动处理成[IPv6]:22的正确格式。
  • 错误处理:示例用log.Fatalf终止程序,实际应用可根据需求添加重试、告警等逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 17:10:35