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

Go实现SSH代理时出现Bad Packet Length错误求助

Go实现SSH代理时出现Bad Packet Length错误的解决方法

错误信息

debug1: SSH2_MSG_KEXINIT sent
Bad packet length 1231976033.
ssh_dispatch_run_fatal: Connection to ::1 port 8080: message authentication code incorrect

问题代码

func handleSSH(conn net.Conn, r *bufio.Reader, protocol string) {

    target, err := url.Parse("ssh://localhost:3333")
    if err != nil {
        fmt.Println("Error parsing target", err)
        conn.Close()
        return
    }

    targetConn, err := net.Dial("tcp", target.Host)
    if err != nil {
        fmt.Println("error dialing SSH target:", err)
        conn.Close()
        return
    }

    defer targetConn.Close()

    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        _, err := io.Copy(targetConn, conn)
        if err != nil {
            fmt.Println("error copying data to target:", err)
        }
        wg.Done()
    }()

    go func() {
        _, err := io.Copy(conn, targetConn)
        if err != nil {
            fmt.Println("error copying data from target:", err)
        }
        wg.Done()
    }()

    wg.Wait()
    conn.Close()
}

// EDIT

func connection(conn net.Conn) {

    r := bufio.NewReader(conn)
    protocol, err := r.ReadString('\n')
    if err != nil {
        fmt.Println("Error reading first line", err)
        conn.Close()
        return
    }

if protocol[0:3] == "SSH" {
        handleSSH(conn, r, protocol)
    }
}

func main() {
   ln, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
    }
    defer ln.Close()

    for {
        conn, err := ln.Accept()
        if err != nil {
            panic(err)
        }
        go connection(conn)
    }
}

问题分析

核心错误是判断SSH协议时,用ReadString('\n')读取了SSH握手的初始数据,但未将这部分数据转发到目标SSH服务器。

SSH初始握手会发送类似SSH-2.0-XXX\r\n的标识字符串,你用ReadString('\n')把这一行读走后,直接使用原始conn做数据转发,但这部分已读取的数据留在了bufio.Reader的缓存中,没有被发送到目标服务器。目标服务器收到的是不完整的握手数据,无法正确解析SSH包结构,从而引发Bad Packet Length错误。

修复方案

方案一:转发已读取的握手数据

修改handleSSH函数,先将读取到的protocol写入目标连接,再用bufio.Reader转发剩余数据:

func handleSSH(conn net.Conn, r *bufio.Reader, protocol string) {
    target, err := url.Parse("ssh://localhost:3333")
    if err != nil {
        fmt.Println("Error parsing target", err)
        conn.Close()
        return
    }

    targetConn, err := net.Dial("tcp", target.Host)
    if err != nil {
        fmt.Println("error dialing SSH target:", err)
        conn.Close()
        return
    }
    defer targetConn.Close()

    // 先转发已读取的SSH握手数据
    if _, err := targetConn.Write([]byte(protocol)); err != nil {
        fmt.Println("error writing handshake data:", err)
        return
    }

    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        // 使用bufio.Reader转发剩余数据,而非原始conn
        _, err := io.Copy(targetConn, r)
        if err != nil {
            fmt.Println("error copying data to target:", err)
        }
        wg.Done()
    }()

    go func() {
        _, err := io.Copy(conn, targetConn)
        if err != nil {
            fmt.Println("error copying data from target:", err)
        }
        wg.Done()
    }()

    wg.Wait()
    conn.Close()
}

方案二:避免缓存数据,直接判断协议

取消bufio.Reader的使用,直接从conn读取前4个字节判断是否为SSH协议,同时将读取的字节写回转发:

func connection(conn net.Conn) {
    // 读取前4个字节判断协议标识
    buf := make([]byte, 4)
    n, err := conn.Read(buf)
    if err != nil {
        fmt.Println("Error reading protocol header:", err)
        conn.Close()
        return
    }

    if string(buf[:n]) == "SSH-" {
        target, err := url.Parse("ssh://localhost:3333")
        if err != nil {
            fmt.Println("Error parsing target", err)
            conn.Close()
            return
        }

        targetConn, err := net.Dial("tcp", target.Host)
        if err != nil {
            fmt.Println("error dialing SSH target:", err)
            conn.Close()
            return
        }
        defer targetConn.Close()

        // 先写入已读取的协议标识字节
        if _, err := targetConn.Write(buf[:n]); err != nil {
            fmt.Println("error writing header:", err)
            return
        }

        var wg sync.WaitGroup
        wg.Add(2)
        go func() {
            _, err := io.Copy(targetConn, conn)
            if err != nil {
                fmt.Println("error copying data to target:", err)
            }
            wg.Done()
        }()

        go func() {
            _, err := io.Copy(conn, targetConn)
            if err != nil {
                fmt.Println("error copying data from target:", err)
            }
            wg.Done()
        }()

        wg.Wait()
    }
    conn.Close()
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 11:02:08