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

Go语言实现SSH X11转发遇OpenChannel失败问题求助

SSH X11 Forwarding in Go: Fixing OpenChannel Failure After Successful x11-req

Problem Context

I was implementing SSH X11 forwarding in Go by referencing Paramiko's source code, but ran into a frustrating issue: the x11-req request seemed to succeed (confirmed by sshd debug logs), but calling client.OpenChannel("x11", ...) always failed. The sshd logs showed this critical error line:

sshd[1811]: debug1: server_input_channel_open: failure x11

Root Cause

The initial approach had a fundamental misunderstanding of how X11 forwarding works over SSH: you don't actively initiate an x11 channel via client.OpenChannel(). Instead, after sending the x11-req to enable forwarding, you need to listen for incoming channel open requests from the SSH server. The server initiates this channel when an X11 application on the remote side tries to connect to the display.

Fixed Implementation

Here's the corrected core code that properly handles X11 forwarding, aligned with RFC 4254 Section 6.3.2:

package main

import (
	"errors"
	"fmt"
	"net"
	"os"

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

type x11Request struct {
	SingleConnection bool
	AuthProtocol     string
	AuthCookie       string
	ScreenNumber     uint32
}

func main() {
	user := "your_username"
	pass := "your_password"
	host := "your_host"
	port := "22"

	// Build SSH client config
	sshConfig := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{
			ssh.Password(pass),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	// Establish SSH connection
	client, err := ssh.Dial("tcp", net.JoinHostPort(host, port), sshConfig)
	if err != nil {
		fmt.Printf("Failed to dial SSH server: %v\n", err)
		os.Exit(1)
	}
	defer client.Close()

	// Create a new SSH session
	session, err := client.NewSession()
	if err != nil {
		fmt.Printf("Failed to create session: %v\n", err)
		os.Exit(1)
	}
	defer session.Close()

	// Send x11-req to enable forwarding
	payload := x11Request{
		SingleConnection: false,
		AuthProtocol:     "MIT-MAGIC-COOKIE-1",
		AuthCookie:       "d92c30482cc3d2de61888961deb74c08", // Replace with your actual X cookie
		ScreenNumber:     0,
	}

	ok, err := session.SendRequest("x11-req", true, ssh.Marshal(payload))
	if err != nil || !ok {
		fmt.Printf("x11-req failed: %v\n", err)
		os.Exit(1)
	}
	fmt.Println("x11-req succeeded - waiting for X11 channel requests")

	// Goroutine to listen for incoming X11 channels from the server
	go func() {
		for {
			channel, reqs, err := client.Accept()
			if err != nil {
				fmt.Printf("Failed to accept channel: %v\n", err)
				return
			}

			// Reject non-X11 channels
			if channel.ChannelType() != "x11" {
				channel.Reject(ssh.UnknownChannelType, "unsupported channel type")
				continue
			}

			fmt.Printf("Accepted new X11 channel\n")

			// Discard unused channel requests
			go ssh.DiscardRequests(reqs)

			// Connect to local X server (port matches screen number: 6000 + screen)
			xConn, err := net.Dial("tcp", "localhost:6000")
			if err != nil {
				fmt.Printf("Failed to connect to local X server: %v\n", err)
				channel.Close()
				continue
			}

			// Forward data between SSH channel and local X server
			go func() {
				defer channel.Close()
				defer xConn.Close()
				buf := make([]byte, 4096)
				for {
					n, err := channel.Read(buf)
					if err != nil {
						return
					}
					xConn.Write(buf[:n])
				}
			}()

			go func() {
				defer channel.Close()
				defer xConn.Close()
				buf := make([]byte, 4096)
				for {
					n, err := xConn.Read(buf)
					if err != nil {
						return
					}
					channel.Write(buf[:n])
				}
			}()
		}
	}()

	// Keep the SSH session alive by starting a shell
	if err := session.Shell(); err != nil {
		fmt.Printf("Failed to start shell: %v\n", err)
		os.Exit(1)
	}
	session.Wait()
}

Key Fixes Explained

  1. Listen for Incoming Channels: We use client.Accept() in a goroutine to wait for the SSH server to initiate x11 channels when remote X11 apps try to connect.
  2. Local X Server Forwarding: When an X11 channel is accepted, we connect to the local X server (typically localhost:6000 for screen 0) and bidirectionally forward data between the SSH channel and the local display.
  3. Session Lifecycle: Starting a shell with session.Shell() and calling session.Wait() ensures the SSH session stays active, which is required to maintain the X11 forwarding connection.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:03:26