diff --git a/cmd/gvproxy/main.go b/cmd/gvproxy/main.go index 7245bd020..fa5a26f92 100644 --- a/cmd/gvproxy/main.go +++ b/cmd/gvproxy/main.go @@ -36,6 +36,7 @@ var ( qemuSocket string bessSocket string stdioSocket string + vfkitSocket string forwardSocket arrayFlags forwardDest arrayFlags forwardUser arrayFlags @@ -59,6 +60,7 @@ func main() { 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.StringVar(&vfkitSocket, "listen-vfkit", "", "unixgram socket to be used by vfkit-compatible applications") 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") @@ -101,6 +103,18 @@ func main() { exitWithError(errors.Errorf("%q already exists", uri.Path)) } } + if len(vfkitSocket) > 0 { + uri, err := url.Parse(vfkitSocket) + if err != nil || uri == nil { + exitWithError(errors.Wrapf(err, "invalid value for listen-vfkit")) + } + if uri.Scheme != "unixgram" { + exitWithError(errors.New("listen-vfkit must be unixgram:// address")) + } + if _, err := os.Stat(uri.Path); err == nil { + exitWithError(errors.Errorf("%q already exists", uri.Path)) + } + } if vpnkitSocket != "" && qemuSocket != "" { exitWithError(errors.New("cannot use qemu and vpnkit protocol at the same time")) @@ -124,6 +138,9 @@ func main() { if bessSocket != "" { protocol = types.BessProtocol } + if vfkitSocket != "" { + protocol = types.VfkitProtocol + } if c := len(forwardSocket); c != len(forwardDest) || c != len(forwardUser) || c != len(forwardIdentify) { exitWithError(errors.New("-forward-sock, --forward-dest, --forward-user, and --forward-identity must all be specified together, " + @@ -363,6 +380,29 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat }) } + if vfkitSocket != "" { + conn, err := transport.ListenUnixgram(vfkitSocket) + if err != nil { + return err + } + + g.Go(func() error { + <-ctx.Done() + if err := conn.Close(); err != nil { + log.Errorf("error closing %s: %q", vfkitSocket, err) + } + return os.Remove(vfkitSocket) + }) + + g.Go(func() error { + vfkitConn, err := transport.AcceptVfkit(conn) + if err != nil { + return err + } + return vn.AcceptVfkit(ctx, vfkitConn) + }) + } + if stdioSocket != "" { g.Go(func() error { conn := stdio.GetStdioConn() diff --git a/pkg/tap/protocol.go b/pkg/tap/protocols.go similarity index 91% rename from pkg/tap/protocol.go rename to pkg/tap/protocols.go index 4fe09fefe..6402abf2d 100644 --- a/pkg/tap/protocol.go +++ b/pkg/tap/protocols.go @@ -59,3 +59,10 @@ type bessProtocol struct { func (s *bessProtocol) Stream() bool { return false } + +type vfkitProtocol struct { +} + +func (s *vfkitProtocol) Stream() bool { + return false +} diff --git a/pkg/tap/switch.go b/pkg/tap/switch.go index a05b9ccbf..8bde528c3 100644 --- a/pkg/tap/switch.go +++ b/pkg/tap/switch.go @@ -287,6 +287,8 @@ func protocolImplementation(protocol types.Protocol) protocol { return &qemuProtocol{} case types.BessProtocol: return &bessProtocol{} + case types.VfkitProtocol: + return &vfkitProtocol{} default: return &hyperkitProtocol{} } diff --git a/pkg/transport/listen_darwin.go b/pkg/transport/listen_darwin.go index 0245db5cb..3fa9d00c5 100644 --- a/pkg/transport/listen_darwin.go +++ b/pkg/transport/listen_darwin.go @@ -39,3 +39,17 @@ func Listen(endpoint string) (net.Listener, error) { return nil, errors.New("unexpected scheme") } } + +func ListenUnixgram(endpoint string) (*net.UnixConn, error) { + parsed, err := url.Parse(endpoint) + if err != nil { + return nil, err + } + if parsed.Scheme != "unixgram" { + return nil, errors.New("unexpected scheme") + } + return net.ListenUnixgram("unixgram", &net.UnixAddr{ + Name: parsed.Path, + Net: "unixgram", + }) +} diff --git a/pkg/transport/listen_linux.go b/pkg/transport/listen_linux.go index 18dc831ef..9f9ce8540 100644 --- a/pkg/transport/listen_linux.go +++ b/pkg/transport/listen_linux.go @@ -31,3 +31,17 @@ func Listen(endpoint string) (net.Listener, error) { return nil, errors.New("unexpected scheme") } } + +func ListenUnixgram(endpoint string) (*net.UnixConn, error) { + parsed, err := url.Parse(endpoint) + if err != nil { + return nil, err + } + if parsed.Scheme != "unixgram" { + return nil, errors.New("unexpected scheme") + } + return net.ListenUnixgram("unixgram", &net.UnixAddr{ + Name: parsed.Path, + Net: "unixgram", + }) +} diff --git a/pkg/transport/listen_windows.go b/pkg/transport/listen_windows.go index 423f887e2..cafb24735 100644 --- a/pkg/transport/listen_windows.go +++ b/pkg/transport/listen_windows.go @@ -33,3 +33,11 @@ func Listen(endpoint string) (net.Listener, error) { return nil, errors.New("unexpected scheme") } } + +func ListenUnixgram(endpoint string) (net.Conn, error) { + return nil, errors.New("unsupported 'unixgram' scheme") +} + +func AcceptVfkit(listeningConn net.Conn) (net.Conn, error) { + return nil, errors.New("vfkit is unsupported on Windows") +} diff --git a/pkg/transport/unixgram_unix.go b/pkg/transport/unixgram_unix.go new file mode 100644 index 000000000..c5e33ad08 --- /dev/null +++ b/pkg/transport/unixgram_unix.go @@ -0,0 +1,67 @@ +//go:build !windows +// +build !windows + +package transport + +import ( + "bytes" + "encoding/hex" + "fmt" + "net" + "syscall" +) + +type connectedUnixgramConn struct { + *net.UnixConn + remoteAddr *net.UnixAddr +} + +func connectListeningUnixgramConn(conn *net.UnixConn, remoteAddr *net.UnixAddr) (*connectedUnixgramConn, error) { + rawConn, err := conn.SyscallConn() + if err != nil { + return nil, err + } + err = rawConn.Control(func(fd uintptr) { + if err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_SNDBUF, 1*1024*1024); err != nil { + return + } + if err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_RCVBUF, 4*1024*1024); err != nil { + return + } + }) + if err != nil { + return nil, err + } + + return &connectedUnixgramConn{ + UnixConn: conn, + remoteAddr: remoteAddr, + }, nil +} + +func (conn *connectedUnixgramConn) RemoteAddr() net.Addr { + return conn.remoteAddr +} + +func (conn *connectedUnixgramConn) Write(b []byte) (int, error) { + return conn.WriteTo(b, conn.remoteAddr) +} + +func AcceptVfkit(listeningConn *net.UnixConn) (net.Conn, error) { + vfkitMagic := make([]byte, 4) + // the main reason for this magic check is to get the address to use to send data to the vfkit VM + bytesRead, vfkitAddr, err := listeningConn.ReadFrom(vfkitMagic) + if bytesRead != len(vfkitMagic) { + return nil, fmt.Errorf("invalid magic length: %d", len(vfkitMagic)) + } + if err != nil { + return nil, err + } + if _, ok := vfkitAddr.(*net.UnixAddr); !ok { + return nil, fmt.Errorf("unexpected type for vfkit unix sockaddr: %t", vfkitAddr) + } + if !bytes.Equal(vfkitMagic, []byte("VFKT")) { + return nil, fmt.Errorf("invalid magic from the vfkit process: %s", hex.EncodeToString(vfkitMagic)) + } + return connectListeningUnixgramConn(listeningConn, vfkitAddr.(*net.UnixAddr)) +} diff --git a/pkg/types/configuration.go b/pkg/types/configuration.go index ec48c844f..912fcb90e 100644 --- a/pkg/types/configuration.go +++ b/pkg/types/configuration.go @@ -63,6 +63,8 @@ const ( BessProtocol Protocol = "bess" // StdioProtocol is HyperKitProtocol without the handshake StdioProtocol Protocol = "stdio" + // VfkitProtocol transfers bare L2 packets as SOCK_DGRAM. + VfkitProtocol Protocol = "vfkit" ) type Zone struct { diff --git a/pkg/virtualnetwork/vfkit.go b/pkg/virtualnetwork/vfkit.go new file mode 100644 index 000000000..905e20035 --- /dev/null +++ b/pkg/virtualnetwork/vfkit.go @@ -0,0 +1,12 @@ +package virtualnetwork + +import ( + "context" + "net" + + "github.com/containers/gvisor-tap-vsock/pkg/types" +) + +func (n *VirtualNetwork) AcceptVfkit(ctx context.Context, conn net.Conn) error { + return n.networkSwitch.Accept(ctx, conn, types.VfkitProtocol) +}