ginuerzh/gost · error · net.OpError

read not supported

Error message

read not supported

What it means

sshNopConn is a write-only SSH connection wrapper used internally by the tunnel library (e.g. for connector/dialer plumbing). Its Read method is deliberately unimplemented and returns a *net.OpError wrapping "read not supported" with Net "ssh". If any code path tries to read from this nop connection, it means the connection is being used in the wrong direction.

Source

Thrown at ssh.go:906

	return func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
		if keys[string(pubKey.Marshal())] {
			return &ssh.Permissions{
				// Record the public key used for authentication.
				Extensions: map[string]string{
					"pubkey-fp": ssh.FingerprintSHA256(pubKey),
				},
			}, nil
		}
		return nil, fmt.Errorf("unknown public key for %q", c.User())
	}
}

type sshNopConn struct {
	session *sshSession
}

func (c *sshNopConn) Read(b []byte) (n int, err error) {
	return 0, &net.OpError{Op: "read", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("read not supported")}
}

func (c *sshNopConn) Write(b []byte) (n int, err error) {
	return 0, &net.OpError{Op: "write", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("write not supported")}
}

func (c *sshNopConn) Close() error {
	return nil
}

func (c *sshNopConn) LocalAddr() net.Addr {
	return &net.TCPAddr{
		IP:   net.IPv4zero,
		Port: 0,
	}
}

func (c *sshNopConn) RemoteAddr() net.Addr {

View on GitHub (pinned to a33fdbf4c9)

Solutions

  1. Locate the call site reading from the sshNopConn and use the real underlying ssh channel/connection for reads instead
  2. Ensure the nop conn is only used where a write-only conn is expected; swap in sshConn (channel-backed) if bidirectional I/O is needed
  3. If you own the caller, check for net.OpError with Op "read" and Net "ssh" early and fail fast with a clearer message

Example fix

// before
io.Copy(stdout, nopConn) // read not supported
// after
io.Copy(stdout, realSshConn) // use the channel-backed sshConn
Defensive patterns

Strategy: type-guard

Validate before calling

func isSSHNopConn(c net.Conn) bool { _, ok := c.(*sshNopConn); return ok }
// before reading:
if isSSHNopConn(conn) { return errors.New("read unsupported on this ssh conn") }

Type guard

func asReaderConn(c net.Conn) (io.Reader, bool) {
    if _, nop := c.(*sshNopConn); nop { return nil, false }
    return c, true
}

Try / catch

n, err := conn.Read(buf)
if err != nil {
    var opErr *net.OpError
    if errors.As(err, &opErr) && opErr.Net == "ssh" && opErr.Op == "read" {
        return fmt.Errorf("ssh conn is write-only: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Read on a net.Conn that is actually a *sshNopConn, e.g. wrapping the sshSession's connection in an API that requires a bidirectional net.Conn and then attempting io.Read / io.Copy(dst, conn) from it.

Common situations: Passing the nop conn to libraries that expect a full-duplex net.Conn (TLS handshake, HTTP client transports, io.Copy loops); version changes where a code path started reading on a connection that was only meant for writing.

Related errors


AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02). Data as JSON: /api/errors/5c6d48164560f72f. Report an issue: GitHub.