mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-09-11 10:07:53 +00:00
161 lines
3.8 KiB
Go
161 lines
3.8 KiB
Go
package bindings
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"net/url"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
commonssh "go.podman.io/common/pkg/ssh"
|
|
"go.podman.io/storage/pkg/stringutils"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
func isFallbackableChannelErr(err error) bool {
|
|
var openChannelErr *ssh.OpenChannelError
|
|
if !errors.As(err, &openChannelErr) {
|
|
return false
|
|
}
|
|
|
|
return openChannelErr.Reason == ssh.UnknownChannelType || openChannelErr.Reason == ssh.ConnectionFailed
|
|
}
|
|
|
|
func newSSHDialContext(client *ssh.Client, _url *url.URL) func(context.Context, string, string) (net.Conn, error) {
|
|
var useFallback atomic.Bool
|
|
return func(ctx context.Context, _, _ string) (net.Conn, error) {
|
|
if useFallback.Load() {
|
|
return dialSSHStdio(client, _url.Path)
|
|
}
|
|
|
|
conn, err := commonssh.DialNetContext(ctx, client, "unix", _url)
|
|
if err == nil || !isFallbackableChannelErr(err) {
|
|
return conn, err
|
|
}
|
|
|
|
logrus.Debugf("direct-streamlocal channel refused (%v), trying dial-stdio fallback", err)
|
|
fallbackConn, fallbackErr := dialSSHStdio(client, _url.Path)
|
|
if fallbackErr != nil {
|
|
return nil, errors.Join(err, fallbackErr)
|
|
}
|
|
|
|
useFallback.Store(true)
|
|
return fallbackConn, nil
|
|
}
|
|
}
|
|
|
|
func dialSSHStdio(client *ssh.Client, path string) (net.Conn, error) {
|
|
session, err := client.NewSession()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stdin, err := session.StdinPipe()
|
|
if err != nil {
|
|
session.Close()
|
|
return nil, err
|
|
}
|
|
|
|
stdout, err := session.StdoutPipe()
|
|
if err != nil {
|
|
session.Close()
|
|
return nil, err
|
|
}
|
|
|
|
var stderr bytes.Buffer
|
|
session.Stderr = &stderr
|
|
|
|
cmd := stringutils.ShellQuoteArguments([]string{"podman", "--url", "unix://" + path, "system", "dial-stdio"})
|
|
if err := session.Start(cmd); err != nil {
|
|
session.Close()
|
|
return nil, err
|
|
}
|
|
|
|
conn := &sshStdioConn{
|
|
path: path,
|
|
session: session,
|
|
writer: stdin,
|
|
reader: stdout,
|
|
sessionDone: make(chan struct{}),
|
|
closeTimeout: 5 * time.Second,
|
|
}
|
|
|
|
go func() {
|
|
defer close(conn.sessionDone)
|
|
waitErr := session.Wait()
|
|
stderrOut := strings.TrimRight(stderr.String(), "\n")
|
|
switch {
|
|
case waitErr != nil && stderrOut != "":
|
|
logrus.Errorf("ssh session error: %v: %s", waitErr, stderrOut)
|
|
case waitErr != nil:
|
|
logrus.Errorf("ssh session error: %v", waitErr)
|
|
case stderrOut != "":
|
|
logrus.Debugf("dial-stdio stderr: %s", stderrOut)
|
|
}
|
|
}()
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
type sshStdioConn struct {
|
|
path string
|
|
session io.Closer
|
|
writer io.WriteCloser
|
|
reader io.Reader
|
|
sessionDone chan struct{}
|
|
closeTimeout time.Duration
|
|
}
|
|
|
|
func (c *sshStdioConn) Close() error {
|
|
err := c.writer.Close()
|
|
|
|
select {
|
|
case <-c.sessionDone:
|
|
case <-time.After(c.closeTimeout):
|
|
logrus.Debugf("timed out waiting for dial-stdio session to exit")
|
|
}
|
|
|
|
if sessionErr := c.session.Close(); sessionErr != nil && !errors.Is(sessionErr, io.EOF) {
|
|
err = errors.Join(err, sessionErr)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (c *sshStdioConn) LocalAddr() net.Addr {
|
|
return &net.UnixAddr{Name: "@", Net: "unix"}
|
|
}
|
|
|
|
func (c *sshStdioConn) RemoteAddr() net.Addr {
|
|
return &net.UnixAddr{Name: c.path, Net: "unix"}
|
|
}
|
|
|
|
func (c *sshStdioConn) Read(b []byte) (n int, err error) {
|
|
return c.reader.Read(b)
|
|
}
|
|
|
|
func (c *sshStdioConn) Write(b []byte) (n int, err error) {
|
|
return c.writer.Write(b)
|
|
}
|
|
|
|
// http.Transport wraps this connection and manages its own timeouts, so the deadline methods are no-ops.
|
|
|
|
func (c *sshStdioConn) SetDeadline(_ time.Time) error {
|
|
logrus.Debugf("SetDeadline not implemented for sshStdioConn")
|
|
return nil
|
|
}
|
|
|
|
func (c *sshStdioConn) SetReadDeadline(_ time.Time) error {
|
|
logrus.Debugf("SetReadDeadline not implemented for sshStdioConn")
|
|
return nil
|
|
}
|
|
|
|
func (c *sshStdioConn) SetWriteDeadline(_ time.Time) error {
|
|
logrus.Debugf("SetWriteDeadline not implemented for sshStdioConn")
|
|
return nil
|
|
}
|