From be0feaee70c58e42f9f64c1386d42340c3aec02d Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Fri, 10 Mar 2017 11:11:20 -0500 Subject: [PATCH] cmd/vscp: initial commit, more information on VM sockets and usage --- README.md | 21 ++++++ cmd/vscp/README.md | 89 ++++++++++++++++++++++++ cmd/vscp/main.go | 169 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 cmd/vscp/README.md create mode 100644 cmd/vscp/main.go diff --git a/README.md b/README.md index 824c622..8cda0f0 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,24 @@ vsock [![Build Status](https://travis-ci.org/mdlayher/vsock.svg?branch=master)]( Package `vsock` provides access to Linux VM sockets (`AF_VSOCK`) for communication between a hypervisor and its virtual machines. MIT Licensed. + +Requirements +------------ + +To make use of VM sockets with QEMU and virtio-vsock, you must have: + - a Linux hypervisor with kernel 4.8+ + - a Linux virtual machine on that hypervisor with kernel 4.8+ + - QEMU 2.8+ on the hypervisor, running the virtual machine + +Check out the +[QEMU wiki page on virtio-vsock](http://wiki.qemu-project.org/Features/VirtioVsock) +for more details. More detail on setting up this environment will be provided +in the future. + +Usage +----- + +To try out VM sockets and see an example of how they work, see +[cmd/vscp](https://github.com/mdlayher/vsock/tree/master/cmd/vscp). +This command shows usage of the `vsock.ListenStream` and `vsock.DialStream` +APIs, and allows users to easily test VM sockets on their systems. diff --git a/cmd/vscp/README.md b/cmd/vscp/README.md new file mode 100644 index 0000000..ac4e43d --- /dev/null +++ b/cmd/vscp/README.md @@ -0,0 +1,89 @@ +vscp +==== + +Command `vscp` provides a `scp`-like utility for copying files over VM +sockets. It is meant to show example usage of package `vsock`, but is +also useful in scenarios where a virtual machine does not have +networking configured, but VM sockets are available. + +Usage +----- + +`vscp` has two modes of operation: receiving and sending. + +``` +$ vscp -h +Usage of vscp: + -c uint + send only: context ID of the remote VM socket + -p uint + - receive: port ID to listen on (random port by default) + - send: port ID to connect to + -r receive files from another instance of vscp + -s send files to another instance of vscp + -v enable verbose logging to stderr +``` + +For example, let's transfer the contents of `/proc/cpuinfo` from a +virtual machine to a hypervisor. + +First, start a server on the hypervisor. The following command will: + - enable verbose logging + - start `vscp` as a server to receive data + - specify port 1024 as the server's listener port + - use `cpuinfo.txt` as an output file + +``` +hv $ vscp -v -r -p 1024 cpuinfo.txt +2017/03/10 10:51:48 receive: creating file "cpuinfo.txt" for output +2017/03/10 10:51:48 receive: opening listener: 1024 +2017/03/10 10:51:48 receive: listening: host(2):1024 +# listening for client connection +``` + +Next, in the virtual machine, start a client to send a file to +the server on the hypervisor. The following command will: + - enable verbose logging + - start `vscp` as a client to send data + - specify context ID 2 (host process) as the server's context ID + - specify port 1024 as the server's port + - use `/proc/cpuinfo` as an input file + +``` +vm $ vscp -v -s -c 2 -p 1024 /proc/cpuinfo +2017/03/10 10:56:18 send: opening file "/proc/cpuinfo" for input +2017/03/10 10:56:18 send: dialing: 2.1024 +2017/03/10 10:56:18 send: client: vm(3):1077 +2017/03/10 10:56:18 send: server: host(2):1024 +2017/03/10 10:56:18 send: sending data +2017/03/10 10:56:18 send: transfer complete +vm $ +``` + +The transfer is now complete. You should see more output in the +hypervisor's terminal, and the server process should have exited. + +``` +hv $ vscp -v -r -p 1024 cpuinfo.txt +2017/03/10 10:51:48 receive: creating file "cpuinfo.txt" for output +2017/03/10 10:51:48 receive: opening listener: 1024 +2017/03/10 10:51:48 receive: listening: host(2):1024 +# listening for client connection +2017/03/10 10:55:39 receive: server: host(2):1024 +2017/03/10 10:55:39 receive: client: vm(3):1077 +2017/03/10 10:55:39 receive: receiving data +2017/03/10 10:55:39 receive: transfer complete +hv $ +``` + +To verify the transfer worked as intended, you can check the hash +of the file on both sides using a tool such as `md5sum`. + +``` +hv $ md5sum cpuinfo.txt +cda57941b11f0c82da425eec5d837c26 cpuinfo.txt +``` +``` +vm $ md5sum /proc/cpuinfo +cda57941b11f0c82da425eec5d837c26 /proc/cpuinfo +``` diff --git a/cmd/vscp/main.go b/cmd/vscp/main.go new file mode 100644 index 0000000..9a28ee8 --- /dev/null +++ b/cmd/vscp/main.go @@ -0,0 +1,169 @@ +// Command vscp provides a scp-like utility for copying files over VM +// sockets. It is meant to show example usage of package vsock, but is +// also useful in scenarios where a virtual machine does not have +// networking configured, but VM sockets are available. +package main + +import ( + "flag" + "io" + "log" + "os" + + "github.com/mdlayher/vsock" +) + +var ( + flagVerbose = flag.Bool("v", false, "enable verbose logging to stderr") +) + +func main() { + var ( + flagReceive = flag.Bool("r", false, "receive files from another instance of vscp") + flagSend = flag.Bool("s", false, "send files to another instance of vscp") + + flagContextID = flag.Uint("c", 0, "send only: context ID of the remote VM socket") + flagPort = flag.Uint("p", 0, "- receive: port ID to listen on (random port by default)\n\t- send: port ID to connect to") + ) + + flag.Parse() + log.SetOutput(os.Stderr) + + // Determine if target should be stdin/stdout or a regular file. + var target string + if t := flag.Arg(0); t != "" { + target = t + } + + switch { + case *flagReceive && *flagSend: + log.Fatalf(`vscp: specify only one of "-r" for receive or "-s" for send`) + case *flagReceive: + if *flagContextID != 0 { + log.Fatalf(`vscp: context ID flag "-c" not valid for receive operation`) + } + + receive(target, uint32(*flagPort)) + case *flagSend: + send(target, uint32(*flagContextID), uint32(*flagPort)) + default: + flag.PrintDefaults() + } +} + +// receive starts a server and receives data from a remote client using +// VM sockets. The data is written to target, which may be a file, +// or stdout, if no file is specified. +func receive(target string, port uint32) { + // Log helper functions. + logf := func(format string, a ...interface{}) { + logf("receive: "+format, a...) + } + + fatalf := func(format string, a ...interface{}) { + log.Fatalf("vscp: receive: "+format, a...) + } + + // Determine if target is stdout or a file to be created. + var w io.Writer + switch target { + case "": + logf("empty target, file will be written to stdout") + w = os.Stdout + default: + logf("creating file %q for output", target) + f, err := os.Create(target) + if err != nil { + fatalf("failed to create output file: %q", err) + } + defer f.Close() + w = f + } + + logf("opening listener: %d", port) + + l, err := vsock.ListenStream(port) + if err != nil { + fatalf("failed to listen: %v", err) + } + defer l.Close() + + // Show server's address for setting up client flags. + log.Printf("receive: listening: %s", l.Addr()) + + // Accept a single connection, and receive stream from that connection. + c, err := l.Accept() + if err != nil { + fatalf("failed to accept: %v", err) + } + defer c.Close() + + logf("server: %s", c.LocalAddr()) + logf("client: %s", c.RemoteAddr()) + logf("receiving data") + + if _, err := io.Copy(w, c); err != nil { + fatalf("failed to receive data: %v", err) + } + + logf("transfer complete") +} + +// send dials a server and sends data to it using VM sockets. The data +// is read from target, which may be a file, or stdin if no file or "-" +// is specified. +func send(target string, cid, port uint32) { + // Log helper functions. + logf := func(format string, a ...interface{}) { + logf("send: "+format, a...) + } + + fatalf := func(format string, a ...interface{}) { + log.Fatalf("vscp: send: "+format, a...) + } + + // Determine if target is stdin or a file to be read in. + var r io.Reader + switch target { + case "", "-": + logf("empty or stdin target, file will be read from stdin") + r = os.Stdin + default: + logf("opening file %q for input", target) + f, err := os.Open(target) + if err != nil { + fatalf("failed to open input file: %q", err) + } + defer f.Close() + r = f + } + + logf("dialing: %d.%d", cid, port) + + // Dial a remote server and send a stream to that server. + c, err := vsock.DialStream(cid, port) + if err != nil { + fatalf("failed to dial: %v", err) + } + defer c.Close() + + logf("client: %s", c.LocalAddr()) + logf("server: %s", c.RemoteAddr()) + + logf("sending data") + if _, err := io.Copy(c, r); err != nil { + fatalf("failed to send data: %v", err) + } + + logf("transfer complete") +} + +// logf shows verbose logging if -v is specified, or does nothing +// if it is not. +func logf(format string, a ...interface{}) { + if !*flagVerbose { + return + } + + log.Printf(format, a...) +}