From 7118b076a2bca3959adc878f86cc25031aadb4ba Mon Sep 17 00:00:00 2001 From: sakai135 Date: Sat, 21 Jan 2023 22:50:49 -0500 Subject: [PATCH] connect over stdio for wsl This adds stdio as a way to communicate between gvproxy and vm mainly for use with WSL2, although it should work for other cases as well. When network connections between WSL2 and the Windows host are blocked, stdio is the only reliable way to establish a channel between WSL2 and the Windows host. Hyper-V socket for WSL2 is a possibility, but it requires undocumented APIs and admin privileges. Signed-off-by: Keiichi Shimamura --- cmd/gvproxy/main.go | 13 +++++++++- cmd/vm/main_linux.go | 14 +++++----- pkg/net/stdio/dial.go | 51 +++++++++++++++++++++++++++++++++++++ pkg/net/stdio/ioaddr.go | 12 +++++++++ pkg/net/stdio/ioconn.go | 50 ++++++++++++++++++++++++++++++++++++ pkg/transport/dial_linux.go | 11 ++++++++ test/wsl.sh | 9 +++++++ 7 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 pkg/net/stdio/dial.go create mode 100644 pkg/net/stdio/ioaddr.go create mode 100644 pkg/net/stdio/ioconn.go create mode 100755 test/wsl.sh diff --git a/cmd/gvproxy/main.go b/cmd/gvproxy/main.go index ce8c896d7..aa257929f 100644 --- a/cmd/gvproxy/main.go +++ b/cmd/gvproxy/main.go @@ -17,6 +17,7 @@ import ( "syscall" "time" + "github.com/containers/gvisor-tap-vsock/pkg/net/stdio" "github.com/containers/gvisor-tap-vsock/pkg/sshclient" "github.com/containers/gvisor-tap-vsock/pkg/transport" "github.com/containers/gvisor-tap-vsock/pkg/types" @@ -34,6 +35,7 @@ var ( vpnkitSocket string qemuSocket string bessSocket string + stdioSocket string forwardSocket arrayFlags forwardDest arrayFlags forwardUser arrayFlags @@ -56,12 +58,14 @@ func main() { flag.StringVar(&vpnkitSocket, "listen-vpnkit", "", "VPNKit socket to be used by Hyperkit") flag.StringVar(&qemuSocket, "listen-qemu", "", "Socket to be used by Qemu") flag.StringVar(&bessSocket, "listen-bess", "", "unixpacket socket to be used by Bess-compatible applications") + flag.StringVar(&stdioSocket, "listen-stdio", "", "accept stdio pipe") flag.Var(&forwardSocket, "forward-sock", "Forwards a unix socket to the guest virtual machine over SSH") flag.Var(&forwardDest, "forward-dest", "Forwards a unix socket to the guest virtual machine over SSH") flag.Var(&forwardUser, "forward-user", "SSH user to use for unix socket forward") flag.Var(&forwardIdentify, "forward-identity", "Path to SSH identity key for forwarding") flag.StringVar(&pidFile, "pid-file", "", "Generate a file with the PID in it") flag.Parse() + ctx, cancel := context.WithCancel(context.Background()) // Make this the last defer statement in the stack defer os.Exit(exitCode) @@ -275,7 +279,7 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat for { select { case <-time.After(5 * time.Second): - fmt.Printf("%v sent to the VM, %v received from the VM\n", humanize.Bytes(vn.BytesSent()), humanize.Bytes(vn.BytesReceived())) + log.Debugf("%v sent to the VM, %v received from the VM\n", humanize.Bytes(vn.BytesSent()), humanize.Bytes(vn.BytesReceived())) case <-ctx.Done(): break debugLog } @@ -359,6 +363,13 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat }) } + if stdioSocket != "" { + g.Go(func() error { + conn := stdio.GetStdioConn() + return vn.AcceptQemu(ctx, conn) + }) + } + for i := 0; i < len(forwardSocket); i++ { var ( src *url.URL diff --git a/cmd/vm/main_linux.go b/cmd/vm/main_linux.go index d4d48bf84..b1c9d8053 100644 --- a/cmd/vm/main_linux.go +++ b/cmd/vm/main_linux.go @@ -79,12 +79,14 @@ func run() error { } defer conn.Close() - req, err := http.NewRequest("POST", path, nil) - if err != nil { - return err - } - if err := req.Write(conn); err != nil { - return err + if path != "" { + req, err := http.NewRequest("POST", path, nil) + if err != nil { + return err + } + if err := req.Write(conn); err != nil { + return err + } } tap, err := water.New(water.Config{ diff --git a/pkg/net/stdio/dial.go b/pkg/net/stdio/dial.go new file mode 100644 index 000000000..51b69b7fc --- /dev/null +++ b/pkg/net/stdio/dial.go @@ -0,0 +1,51 @@ +package stdio + +import ( + "net" + "os" + "os/exec" + "strconv" +) + +func Dial(endpoint string, arg ...string) (net.Conn, error) { + cmd := exec.Command(endpoint, arg...) + cmd.Stderr = os.Stderr + + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + + err = cmd.Start() + if err != nil { + return nil, err + } + + local := IoAddr{path: strconv.Itoa(os.Getpid())} + remote := IoAddr{path: strconv.Itoa(cmd.Process.Pid)} + conn := IoConn{ + reader: stdout, + writer: stdin, + local: local, + remote: remote, + close: cmd.Process.Kill, + } + return conn, nil +} + +func GetStdioConn() net.Conn { + local := IoAddr{path: strconv.Itoa(os.Getpid())} + remote := IoAddr{path: "remote"} + conn := IoConn{ + writer: os.Stdout, + reader: os.Stdin, + local: local, + remote: remote, + } + return conn +} diff --git a/pkg/net/stdio/ioaddr.go b/pkg/net/stdio/ioaddr.go new file mode 100644 index 000000000..4ed69a525 --- /dev/null +++ b/pkg/net/stdio/ioaddr.go @@ -0,0 +1,12 @@ +package stdio + +type IoAddr struct { + path string +} + +func (a IoAddr) Network() string { + return "stdio" +} +func (a IoAddr) String() string { + return a.path +} diff --git a/pkg/net/stdio/ioconn.go b/pkg/net/stdio/ioconn.go new file mode 100644 index 000000000..91d2a279b --- /dev/null +++ b/pkg/net/stdio/ioconn.go @@ -0,0 +1,50 @@ +package stdio + +import ( + "io" + "net" + "time" +) + +type IoConn struct { + writer io.Writer + reader io.Reader + local net.Addr + remote net.Addr + close func() error +} + +func (c IoConn) Read(b []byte) (n int, err error) { + return c.reader.Read(b) +} + +func (c IoConn) Write(b []byte) (n int, err error) { + return c.writer.Write(b) +} + +func (c IoConn) Close() error { + if c.close != nil { + return c.close() + } + return nil +} + +func (c IoConn) LocalAddr() net.Addr { + return c.local +} + +func (c IoConn) RemoteAddr() net.Addr { + return c.remote +} + +func (c IoConn) SetDeadline(t time.Time) error { + return nil +} + +func (c IoConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (c IoConn) SetWriteDeadline(t time.Time) error { + return nil +} diff --git a/pkg/transport/dial_linux.go b/pkg/transport/dial_linux.go index 396652a7b..6030bf043 100644 --- a/pkg/transport/dial_linux.go +++ b/pkg/transport/dial_linux.go @@ -1,10 +1,12 @@ package transport import ( + "fmt" "net" "net/url" "strconv" + "github.com/containers/gvisor-tap-vsock/pkg/net/stdio" mdlayhervsock "github.com/mdlayher/vsock" "github.com/pkg/errors" ) @@ -29,6 +31,15 @@ func Dial(endpoint string) (net.Conn, string, error) { case "unix": conn, err := net.Dial("unix", parsed.Path) return conn, "/connect", err + case "stdio": + var values []string + for k, vs := range parsed.Query() { + for _, v := range vs { + values = append(values, fmt.Sprintf("-%s=%s", k, v)) + } + } + conn, err := stdio.Dial(parsed.Path, values...) + return conn, "", err default: return nil, "", errors.New("unexpected scheme") } diff --git a/test/wsl.sh b/test/wsl.sh new file mode 100755 index 000000000..4c335dea5 --- /dev/null +++ b/test/wsl.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -x + +./bin/vm \ + -url="stdio:$(pwd)/bin/gvproxy-windows.exe?listen-stdio=accept&debug=true" \ + -iface="eth1" \ + -stop-if-exist="" \ + -debug