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

cmd/vscp: initial commit, more information on VM sockets and usage #8

Merged
merged 1 commit into from
Mar 10, 2017
Merged
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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
89 changes: 89 additions & 0 deletions cmd/vscp/README.md
Original file line number Diff line number Diff line change
@@ -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
```
169 changes: 169 additions & 0 deletions cmd/vscp/main.go
Original file line number Diff line number Diff line change
@@ -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...)
}