Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for volume sharing via gvproxy for HyperV #280

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
./bin/
/bin/
capture.pcap
tmp/
test/qcon.log
22 changes: 22 additions & 0 deletions cmd/gvproxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"syscall"
"time"

"github.com/containers/gvisor-tap-vsock/pkg/fileserver"
"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"
Expand Down Expand Up @@ -44,6 +45,7 @@ var (
forwardIdentify arrayFlags
sshPort int
pidFile string
shareVolumes arrayFlags
exitCode int
)

Expand All @@ -70,6 +72,7 @@ func main() {
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.Var(&shareVolumes, "share-volume", "Share a volume to the guest virtual machine over 9p")
flag.Parse()

ctx, cancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -161,6 +164,20 @@ func main() {
}
}

// Verify syntax of requested volume shares
hvsockShares := make(map[string]string, len(shareVolumes))
for i := 0; i < len(shareVolumes); i++ {
splitPath := strings.Split(shareVolumes[i], ":")
cfergeau marked this conversation as resolved.
Show resolved Hide resolved

if len(splitPath) < 2 {
exitWithError(errors.New("Share paths passed to --share-volume must include a vsock guid"))
}

path := strings.Join(splitPath[:len(splitPath)-1], ":")

hvsockShares[path] = splitPath[len(splitPath)-1]
}

// Create a PID file if requested
if len(pidFile) > 0 {
f, err := os.Create(pidFile)
Expand All @@ -179,6 +196,11 @@ func main() {
}
}

// Start shares
if err := fileserver.StartShares(hvsockShares); err != nil {
exitWithError(err)
}

config := types.Configuration{
Debug: debug,
CaptureFile: captureFile(),
Expand Down
9 changes: 6 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ require (
github.com/coreos/stream-metadata-go v0.4.3
github.com/dustin/go-humanize v1.0.1
github.com/google/gopacket v1.1.19
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
github.com/hugelgupf/p9 v0.3.1-0.20230822151754-54f5c5530921
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c
github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2
github.com/mdlayher/vsock v1.2.1
github.com/miekg/dns v1.1.56
Expand All @@ -34,11 +35,13 @@ require (
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/pierrec/lz4/v4 v4.1.18 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/text v0.13.0 // indirect
Expand Down
79 changes: 32 additions & 47 deletions go.sum

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions pkg/fileserver/plan9/serve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package plan9

import (
"fmt"
"net"
"os"
"path/filepath"

"github.com/hugelgupf/p9/fsimpl/localfs"
"github.com/hugelgupf/p9/p9"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

type Server struct {
server *p9.Server
// TODO: Once server has a proper Close() we don't need this.
// This is basically just a short-circuit to actually close the server
// without that ability.
listener net.Listener
// Errors from the server being started will come out here.
errChan chan error
}

// Expose a single directory (and all children) via the given net.Listener.
// Directory given must be an absolute path and must exist.
func New9pServer(listener net.Listener, exposeDir string) (*Server, error) {
// Verify that exposeDir makes sense.
if !filepath.IsAbs(exposeDir) {
return nil, fmt.Errorf("path to expose to machine must be absolute: %s", exposeDir)
}
stat, err := os.Stat(exposeDir)
if err != nil {
return nil, errors.Wrapf(err, "cannot stat path to expose to machine")
}
if !stat.IsDir() {
return nil, fmt.Errorf("path to expose to machine must be a directory: %s", exposeDir)
}

server := p9.NewServer(localfs.Attacher(exposeDir), []p9.ServerOpt{}...)
if server == nil {
return nil, fmt.Errorf("p9.NewServer returned nil")
}

errChan := make(chan error)

// TODO: Use a channel to pass back this if it occurs within a
// reasonable timeframe.
go func() {
errChan <- server.Serve(listener)
close(errChan)
}()

toReturn := new(Server)
toReturn.listener = listener
toReturn.server = server
toReturn.errChan = errChan

// Just before returning, check to see if we got an error off server
// startup.
select {
case err := <-errChan:
return nil, errors.Wrapf(err, "starting 9p server")
default:
logrus.Infof("Successfully started 9p server for directory %s", exposeDir)
}

return toReturn, nil
}

// Stop a running server.
// Please note that this does *BAD THINGS* to clients if they are still running
// when the server stops. Processes get stuck in I/O deep sleep and zombify, and
// nothing I do save restarting the VM can remove the zombies.
func (s *Server) Stop() error {
if s.server != nil {
if err := s.listener.Close(); err != nil {
return err
}
s.server = nil
}

return nil
}

// Wait for an error from a running server.
func (s *Server) WaitForError() error {
if s.server != nil {
err := <-s.errChan
return err
}

// Server already down, return nil
return nil
}
16 changes: 16 additions & 0 deletions pkg/fileserver/server_unsupported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build !windows
// +build !windows

package fileserver

import (
"fmt"
)

func StartShares(mounts map[string]string) error {
if len(mounts) == 0 {
return nil
}

return fmt.Errorf("this platform does not support sharing directories")
}
61 changes: 61 additions & 0 deletions pkg/fileserver/server_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package fileserver

import (
"github.com/containers/gvisor-tap-vsock/pkg/fileserver/plan9"
"github.com/linuxkit/virtsock/pkg/hvsock"
"github.com/sirupsen/logrus"
"github.com/pkg/errors"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw, this is obsolete https://github.com/pkg/errors " This repository has been archived by the owner on Dec 1, 2021. It is now read-only. "

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used throughout gvproxy right now, and I don't think mixing legacy pkg/errors and the new Golang fmt errors is a good idea. I can convert the entire project over to it, if you'd like? Shouldn't be that difficult.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I did not check the existing code for usage of pkg/errors. I only noticed it's deprecated when looking for the doc/source code for errors.Wrapf, I'm fine if you keep the pkg/errors usage in this file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about a follow-up/refactor to address this?

)

// Start serving the given shares on Windows HVSocks for use by a Hyper-V VM.
// Mounts is formatted as a map of directory to be shared to vsock GUID.
// The vsocks used must already be defined before StartShares is called; it's
// expected that the vsocks will be created and torn down by the program calling
// gvproxy.
// TODO: The map here probably doesn't make sense.
// In the future, possibly accept a struct instead, so we can accept things
// other than a vsock and support non-Windows OSes.
func StartShares(mounts map[string]string) (defErr error) {
for path, guid := range mounts {
service, err := hvsock.GUIDFromString(guid)
if err != nil {
return errors.Wrapf(err, "parsing vsock guid %s", guid)
}

listener, err := hvsock.Listen(hvsock.Addr{
VMID: hvsock.GUIDWildcard,
ServiceID: service,
})
if err != nil {
return errors.Wrapf(err, "retrieving listener for vsock %s", guid)
}

logrus.Debugf("Going to serve directory %s on vsock %s", path, guid)

server, err := plan9.New9pServer(listener, path)
if err != nil {
return errors.Wrapf(err, "serving directory %s on vsock %s", path, guid)
}
defer func() {
if defErr != nil {
if err := server.Stop(); err != nil {
logrus.Errorf("Error stopping 9p server: %v", err)
}
}
}()

serverDir := path

go func() {
if err := server.WaitForError(); err != nil {
logrus.Errorf("Error from 9p server for %s: %v", serverDir, err)
} else {
// We do not expect server exits - this should
// run until the program exits.
logrus.Warnf("9p server for %s exited without error", serverDir)
}
}()
}

return nil
}
Loading