pax_global_header00006660000000000000000000000064144232235540014516gustar00rootroot0000000000000052 comment=8569e301791e164b669ff36b51035bb1d7c9b3a3 vsock-1.2.1/000077500000000000000000000000001442322355400126445ustar00rootroot00000000000000vsock-1.2.1/.github/000077500000000000000000000000001442322355400142045ustar00rootroot00000000000000vsock-1.2.1/.github/workflows/000077500000000000000000000000001442322355400162415ustar00rootroot00000000000000vsock-1.2.1/.github/workflows/linux-integration-test.yml000066400000000000000000000013551442322355400234250ustar00rootroot00000000000000name: Linux Integration Test on: push: branches: - "*" pull_request: branches: - "*" jobs: build: strategy: matrix: go-version: [1.18, 1.19] runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: Load the vhost_vsock kernel module run: sudo modprobe vhost_vsock - name: Change the permissions of /dev/vsock for integration tests run: sudo chmod 666 /dev/vsock - name: Run integration tests run: go test -v -race -run TestIntegration ./... vsock-1.2.1/.github/workflows/linux-test.yml000066400000000000000000000013261442322355400211020ustar00rootroot00000000000000name: Linux Test on: push: branches: - "*" pull_request: branches: - "*" jobs: build: strategy: matrix: go-version: [1.18, 1.19] runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: Run tests run: go test -v -race ./... # Although this package doesn't support Windows, we want to verify that # everything builds properly. - name: Verify build for non-UNIX platforms run: go build env: GOOS: windows vsock-1.2.1/.github/workflows/macos-test.yml000066400000000000000000000007611442322355400210470ustar00rootroot00000000000000name: macOS Test on: push: branches: - "*" pull_request: branches: - "*" jobs: build: strategy: matrix: go-version: ["1.20"] runs-on: macos-latest steps: - name: Set up Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: Run tests run: go test -v -race ./... vsock-1.2.1/.github/workflows/static-analysis.yml000066400000000000000000000013341442322355400220750ustar00rootroot00000000000000name: Static Analysis on: push: branches: - "*" pull_request: branches: - "*" jobs: build: strategy: matrix: go-version: ["1.20"] runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: Install staticcheck run: go install honnef.co/go/tools/cmd/staticcheck@latest - name: Print staticcheck version run: staticcheck -version - name: Run staticcheck run: staticcheck ./... - name: Run go vet run: go vet ./... vsock-1.2.1/.gitignore000066400000000000000000000000731442322355400146340ustar00rootroot00000000000000cover.out vsock.test cmd/vscp/vscp cmd/vsockhttp/vsockhttp vsock-1.2.1/CHANGELOG.md000066400000000000000000000043341442322355400144610ustar00rootroot00000000000000# CHANGELOG # v1.2.1 - [Improvement]: updated dependencies, test with Go 1.20. # v1.2.0 **This is the first release of package vsock that only supports Go 1.18+. Users on older versions of Go must use v1.1.1.** - [Improvement]: drop support for older versions of Go so we can begin using modern versions of `x/sys` and other dependencies. ## v1.1.1 **This is the last release of package vsock that supports Go 1.17 and below.** - [Bug Fix] [commit](https://github.com/mdlayher/vsock/commit/ead86435c244d5d6baad549a6df0557ada3f4401): fix build on non-UNIX platforms such as Windows. This is a no-op change on Linux but provides a friendlier experience for non-Linux users. ## v1.1.0 - [New API] [commit](https://github.com/mdlayher/vsock/commit/44cd82dc5f7de644436f22236b111ab97fa9a14f): `vsock.FileListener` can be used to create a `vsock.Listener` from an existing `os.File`, which may be provided by systemd socket activation or another external mechanism. ## v1.0.1 - [Bug Fix] [commit](https://github.com/mdlayher/vsock/commit/99a6dccdebad21d1fa5f757d228d677ccb1412dc): upgrade `github.com/mdlayher/socket` to handle non-blocking `connect(2)` errors (called in `vsock.Dial`) properly by checking the `SO_ERROR` socket option. Lock in this behavior with a new test. - [Improvement] [commit](https://github.com/mdlayher/vsock/commit/375f3bbcc363500daf367ec511638a4655471719): downgrade the version of `golang.org/x/net` in use to support Go 1.12. We don't need the latest version for this package. ## v1.0.0 **This is the first release of package vsock that only supports Go 1.12+. Users on older versions of Go must use an unstable release.** - Initial stable commit! - [API change]: the `vsock.Dial` and `vsock.Listen` constructors now accept an optional `*vsock.Config` parameter to enable future expansion in v1.x.x without prompting further breaking API changes. Because `vsock.Config` has no options as of this release, `nil` may be passed in all call sites to fix existing code upon upgrading to v1.0.0. - [New API]: the `vsock.ListenContextID` function can be used to create a `*vsock.Listener` which is bound to an explicit context ID address, rather than inferring one automatically as `vsock.Listen` does. vsock-1.2.1/LICENSE.md000066400000000000000000000020631442322355400142510ustar00rootroot00000000000000# MIT License Copyright (C) 2017-2022 Matt Layher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. vsock-1.2.1/README.md000066400000000000000000000023761442322355400141330ustar00rootroot00000000000000# vsock [![Test Status](https://github.com/mdlayher/vsock/workflows/Linux%20Test/badge.svg)](https://github.com/mdlayher/vsock/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/vsock.svg)](https://pkg.go.dev/github.com/mdlayher/vsock) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/vsock)](https://goreportcard.com/report/github.com/mdlayher/vsock) Package `vsock` provides access to Linux VM sockets (`AF_VSOCK`) for communication between a hypervisor and its virtual machines. MIT Licensed. For more information about VM sockets, see my blog about [Linux VM sockets in Go](https://mdlayher.com/blog/linux-vm-sockets-in-go/) or the [QEMU wiki page on virtio-vsock](http://wiki.qemu-project.org/Features/VirtioVsock). ## Stability See the [CHANGELOG](./CHANGELOG.md) file for a description of changes between releases. This package has a stable v1 API and any future breaking changes will prompt the release of a new major version. Features and bug fixes will continue to occur in the v1.x.x series. This package only supports the two most recent major versions of Go, mirroring Go's own release policy. Older versions of Go may lack critical features and bug fixes which are necessary for this package to function correctly. vsock-1.2.1/cmd/000077500000000000000000000000001442322355400134075ustar00rootroot00000000000000vsock-1.2.1/cmd/vscp/000077500000000000000000000000001442322355400143625ustar00rootroot00000000000000vsock-1.2.1/cmd/vscp/README.md000066400000000000000000000054111442322355400156420ustar00rootroot00000000000000vscp ==== 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 ``` vsock-1.2.1/cmd/vscp/main.go000066400000000000000000000120621442322355400156360ustar00rootroot00000000000000// 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 ( "crypto/sha256" "flag" "hash" "io" "log" "os" "time" "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") flagHash = flag.Bool("h", false, "display a checksum hash of the input or output data after transfer completes") flagTimeout = flag.Duration("t", 0, "receive only: timeout for read operations (default: no timeout)") ) 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), *flagTimeout, *flagHash) case *flagSend: send(target, uint32(*flagContextID), uint32(*flagPort), *flagHash) 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, timeout time.Duration, checksum bool) { // 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 } // Optionally compute a checksum of the data. var h hash.Hash if checksum { h = sha256.New() w = io.MultiWriter(w, h) } logf("opening listener: %d", port) // TODO(mdlayher): support vsock.Local binds for testing. l, err := vsock.Listen(port, nil) 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) } _ = l.Close() defer c.Close() if timeout != 0 { if err := c.SetDeadline(time.Now().Add(timeout)); err != nil { fatalf("failed to set timeout: %v", err) } } 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") if h != nil { log.Printf("sha256 checksum: %x", h.Sum(nil)) } } // 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, checksum bool) { // 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 } // Optionally compute a checksum of the data. var h hash.Hash if checksum { h = sha256.New() r = io.TeeReader(r, h) } logf("dialing: %d.%d", cid, port) // Dial a remote server and send a stream to that server. c, err := vsock.Dial(cid, port, nil) 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") if h != nil { log.Printf("sha256 checksum: %x", h.Sum(nil)) } } // 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...) } vsock-1.2.1/conn_linux.go000066400000000000000000000025171442322355400153540ustar00rootroot00000000000000//go:build linux // +build linux package vsock import ( "context" "github.com/mdlayher/socket" "golang.org/x/sys/unix" ) // A conn is the net.Conn implementation for connection-oriented VM sockets. // We can use socket.Conn directly on Linux to implement all of the necessary // methods. type conn = socket.Conn // dial is the entry point for Dial on Linux. func dial(cid, port uint32, _ *Config) (*Conn, error) { // TODO(mdlayher): Config default nil check and initialize. Pass options to // socket.Config where necessary. c, err := socket.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0, "vsock", nil) if err != nil { return nil, err } sa := &unix.SockaddrVM{CID: cid, Port: port} rsa, err := c.Connect(context.Background(), sa) if err != nil { _ = c.Close() return nil, err } // TODO(mdlayher): getpeername(2) appears to return nil in the GitHub CI // environment, so in the event of a nil sockaddr, fall back to the previous // method of synthesizing the remote address. if rsa == nil { rsa = sa } lsa, err := c.Getsockname() if err != nil { _ = c.Close() return nil, err } lsavm := lsa.(*unix.SockaddrVM) rsavm := rsa.(*unix.SockaddrVM) return &Conn{ c: c, local: &Addr{ ContextID: lsavm.CID, Port: lsavm.Port, }, remote: &Addr{ ContextID: rsavm.CID, Port: rsavm.Port, }, }, nil } vsock-1.2.1/doc.go000066400000000000000000000006301442322355400137370ustar00rootroot00000000000000// Package vsock provides access to Linux VM sockets (AF_VSOCK) for // communication between a hypervisor and its virtual machines. // // The types in this package implement interfaces provided by package net and // may be used in applications that expect a net.Listener or net.Conn. // // - *Addr implements net.Addr // - *Conn implements net.Conn // - *Listener implements net.Listener package vsock vsock-1.2.1/fd_linux.go000066400000000000000000000013321442322355400150020ustar00rootroot00000000000000package vsock import ( "fmt" "os" "golang.org/x/sys/unix" ) // contextID retrieves the local context ID for this system. func contextID() (uint32, error) { f, err := os.Open(devVsock) if err != nil { return 0, err } defer f.Close() return unix.IoctlGetUint32(int(f.Fd()), unix.IOCTL_VM_SOCKETS_GET_LOCAL_CID) } // isErrno determines if an error a matches UNIX error number. func isErrno(err error, errno int) bool { switch errno { case ebadf: return err == unix.EBADF case enotconn: return err == unix.ENOTCONN default: panicf("vsock: isErrno called with unhandled error number parameter: %d", errno) return false } } func panicf(format string, a ...interface{}) { panic(fmt.Sprintf(format, a...)) } vsock-1.2.1/go.mod000066400000000000000000000003071442322355400137520ustar00rootroot00000000000000module github.com/mdlayher/vsock go 1.20 require ( github.com/google/go-cmp v0.5.9 github.com/mdlayher/socket v0.4.1 golang.org/x/net v0.9.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.7.0 ) vsock-1.2.1/go.sum000066400000000000000000000023621442322355400140020ustar00rootroot00000000000000github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw= github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= vsock-1.2.1/integration_linux_test.go000066400000000000000000000316771442322355400200120ustar00rootroot00000000000000//go:build linux // +build linux package vsock_test import ( "bytes" "fmt" "io" "math" "net" "os" "regexp" "strconv" "sync" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/mdlayher/vsock" "github.com/mdlayher/vsock/internal/vsutil" "golang.org/x/net/nettest" "golang.org/x/sync/errgroup" "golang.org/x/sys/unix" ) func TestIntegrationContextIDGuest(t *testing.T) { if vsutil.IsHypervisor(t) { t.Skip("skipping, machine is not a guest") } cid, err := vsock.ContextID() if err != nil { t.Fatalf("failed to retrieve guest's context ID: %v", err) } t.Logf("guest context ID: %d", cid) // Guests should always have a context ID of 3 or more, since // 0-2 are invalid or reserved. if cid < 3 { t.Fatalf("unexpected guest context ID: %d", cid) } } func TestIntegrationContextIDHost(t *testing.T) { if !vsutil.IsHypervisor(t) { t.Skip("skipping, machine is not a hypervisor") } cid, err := vsock.ContextID() if err != nil { t.Fatalf("failed to retrieve host's context ID: %v", err) } t.Logf("host context ID: %d", cid) if want, got := uint32(vsock.Host), cid; want != got { t.Fatalf("unexpected host context ID:\n- want: %d\n- got: %d", want, got) } } func TestIntegrationListenerUnblockAcceptTimeout(t *testing.T) { l, done := newListener(t) defer done() if err := l.SetDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { t.Fatalf("failed to set listener deadline: %v", err) } _, err := l.Accept() if err == nil { t.Fatal("expected an error, but none occurred") } if nerr, ok := err.(net.Error); !ok || (ok && !nerr.Timeout()) { t.Errorf("expected timeout network error, but got: %#v", err) } } func TestIntegrationConnShutdown(t *testing.T) { vsutil.SkipHostIntegration(t) // This functionality is proposed for inclusion in x/net/nettest, and should // be removed from here if the proposal is accepted. See: // https://github.com/golang/go/issues/31033. timer := time.AfterFunc(10*time.Second, func() { panic("test took too long") }) defer timer.Stop() mp := makeVSockPipe() c1, c2, stop, err := mp() if err != nil { t.Fatalf("failed to make pipe: %v", err) } defer stop() vc1, vc2 := c1.(*vsock.Conn), c2.(*vsock.Conn) var wg sync.WaitGroup wg.Add(1) defer wg.Wait() // Perform CloseRead/CloseWrite in lock-step. var ( readClosed = make(chan struct{}) writeClosed = make(chan struct{}) ) go func() { defer wg.Done() b := make([]byte, 8) if _, err := io.ReadFull(vc2, b); err != nil { panicf("failed to vc2.Read: %v", err) } if err := vc2.CloseRead(); err != nil { panicf("failed to vc2.CloseRead: %v", err) } close(readClosed) // Any further read should return io.EOF. <-writeClosed if _, err := io.ReadFull(vc2, b); err != io.EOF { panicf("expected vc2.Read EOF, but got: %v", err) } if err := vc2.CloseWrite(); err != nil { panicf("failed to vc2.CloseWrite: %v", err) } }() b := make([]byte, 8) if _, err := vc1.Write(b); err != nil { t.Fatalf("failed to vc1.Write: %v", err) } // Any write to a read-closed connection should return EPIPE. // // TODO(mdlayher): this test was flappy until err != nil check was added; // sometimes it returns EPIPE and sometimes it does not. Check this. <-readClosed if _, err := vc1.Write(b); err != nil && !isBrokenPipe(err) { t.Fatalf("expected vc1.Write broken pipe, but got: %v", err) } if err := vc1.CloseWrite(); err != nil { t.Fatalf("failed to vc1.CloseWrite: %v", err) } close(writeClosed) // Any further read should return io.EOF after the other end write-closes. if _, err := io.ReadFull(vc1, b); err != io.EOF { panicf("expected vc1.Read EOF, but got: %v", err) } } func TestIntegrationConnSyscallConn(t *testing.T) { vsutil.SkipHostIntegration(t) mp := makeVSockPipe() c, _, stop, err := mp() if err != nil { t.Fatalf("failed to make pipe: %v", err) } defer stop() rc, err := c.(*vsock.Conn).SyscallConn() if err != nil { t.Fatalf("failed to syscallconn: %v", err) } // Greatly reduce the size of the socket buffer. const ( size uint64 = 64 name = unix.SO_VM_SOCKETS_BUFFER_MAX_SIZE ) err = rc.Control(func(fd uintptr) { err := unix.SetsockoptUint64(int(fd), unix.AF_VSOCK, name, size) if err != nil { t.Fatalf("failed to setsockopt: %v", err) } out, err := unix.GetsockoptUint64(int(fd), unix.AF_VSOCK, name) if err != nil { t.Fatalf("failed to getsockopt: %v", err) } if diff := cmp.Diff(size, out); diff != "" { t.Fatalf("unexpected socket buffer size (-want +got):\n%s", diff) } }) if err != nil { t.Fatalf("failed to control: %v", err) } } func TestIntegrationConnDialNoListener(t *testing.T) { // Dial out to vsock listeners which do not exist, and expect an immediate // error rather than hanging. This mostly relies on changes to the // underlying socket library, but we test it anyway to lock things in. // // See: https://github.com/mdlayher/vsock/issues/47. const max = math.MaxUint32 for _, port := range []uint32{max - 2, max - 1, max} { _, err := vsock.Dial(vsock.Local, port, nil) if err == nil { // It seems local binds don't work in GitHub actions right now. // Skip for now. skipOldKernel(t) } want := &net.OpError{ Op: "dial", Net: "vsock", Addr: &vsock.Addr{ContextID: vsock.Local, Port: port}, Err: os.NewSyscallError("connect", unix.ECONNRESET), } if diff := cmp.Diff(want, err); diff != "" { t.Errorf("unexpected error (-want +got):\n%s", diff) } } } func TestIntegrationFileListenerOK(t *testing.T) { // Use raw system calls to set up the socket for FileListener. Although the // socket library does the heavy lifting, we want to verify that this also // works specifically for AF_VSOCK. fd, err := unix.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0) if err != nil { t.Fatalf("failed to open socket: %v", err) } // Bind to local, any available port. err = unix.Bind(fd, &unix.SockaddrVM{ CID: unix.VMADDR_CID_LOCAL, Port: unix.VMADDR_PORT_ANY, }) if err != nil { // Same problem with GitHub actions kernel, investigate. switch err { case unix.EADDRNOTAVAIL: skipOldKernel(t) default: t.Fatalf("failed to bind: %v", err) } } if err := unix.Listen(fd, unix.SOMAXCONN); err != nil { t.Fatalf("failed to listen: %v", err) } // The socket should be ready, create a blocking file which is ready to be // passed into FileListener. f := os.NewFile(uintptr(fd), "vsock-listener") defer f.Close() l, err := vsock.FileListener(f) if err != nil { t.Fatalf("failed to open file listener: %v", err) } defer l.Close() // To exercise the listener, attempt to accept and then immediately close a // single vsock connection. Dial to the listener from the main goroutine and // wait for everything to finish. var eg errgroup.Group eg.Go(func() error { c, err := l.Accept() if err != nil { return fmt.Errorf("failed to accept: %v", err) } _ = c.Close() return nil }) addr := l.Addr().(*vsock.Addr) c, err := vsock.Dial(addr.ContextID, addr.Port, nil) if err != nil { t.Fatalf("failed to dial listener: %v", err) } _ = c.Close() if err := eg.Wait(); err != nil { t.Fatalf("failed to wait for listener goroutine: %v", err) } } func TestIntegrationFileListenerInvalid(t *testing.T) { // Same idea as the previous test, but intentionally create a TCP socket // instead of a vsock so we can verify the library rejects the socket. fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_STREAM, 0) if err != nil { t.Fatalf("failed to open socket: %v", err) } // Bind to any address. if err := unix.Bind(fd, &unix.SockaddrInet6{}); err != nil { t.Fatalf("failed to bind: %v", err) } if err := unix.Listen(fd, unix.SOMAXCONN); err != nil { t.Fatalf("failed to listen: %v", err) } // The library should reject this file for having the wrong address family. f := os.NewFile(uintptr(fd), "tcpv6-listener") defer f.Close() _, got := vsock.FileListener(f) want := &net.OpError{ Op: "listen", Net: "vsock", Err: os.NewSyscallError("listen", unix.EINVAL), } if diff := cmp.Diff(want, got); diff != "" { t.Fatalf("unexpected error (-want +got):\n%s", diff) } } func isBrokenPipe(err error) bool { if err == nil { return false } nerr, ok := err.(*net.OpError) if !ok { return false } return nerr.Err == unix.EPIPE } func TestIntegrationNettestTestConn(t *testing.T) { vsutil.SkipHostIntegration(t) nettest.TestConn(t, makeVSockPipe()) } var cidRe = regexp.MustCompile(`\S+\((\d+)\)`) func TestIntegrationNettestTestListener(t *testing.T) { vsutil.SkipHostIntegration(t) // This test uses the nettest.TestListener API which is being built in: // https://go-review.googlesource.com/c/net/+/123056. // // TODO(mdlayher): stop skipping this test once that CL lands. mos := func() (ln net.Listener, dial func(string, string) (net.Conn, error), stop func(), err error) { l, err := vsock.ListenContextID(vsock.Local, 0, nil) if err != nil { return nil, nil, nil, err } stop = func() { // TODO(mdlayher): cancel context if we use vsock.DialContext. _ = l.Close() } dial = func(_, addr string) (net.Conn, error) { host, sport, err := net.SplitHostPort(addr) if err != nil { return nil, err } // Extract the CID value from the surrounding text. scid := cidRe.FindStringSubmatch(host) cid, err := strconv.Atoi(scid[1]) if err != nil { return nil, err } port, err := strconv.Atoi(sport) if err != nil { return nil, err } return vsock.Dial(uint32(cid), uint32(port), nil) } return l, dial, stop, nil } _ = mos t.Skip("skipping, enable once https://go-review.googlesource.com/c/net/+/123056 is merged") // nettest.TestListener(t, mos) } func newListener(t *testing.T) (*vsock.Listener, func()) { t.Helper() timer := time.AfterFunc(10*time.Second, func() { panic("test took too long") }) // Bind to Local for all integration tests to avoid the need to run a // hypervisor and VM setup. l, err := vsock.ListenContextID(vsock.Local, 0, nil) if err != nil { vsutil.SkipDeviceError(t, err) // Unwrap net.OpError + os.SyscallError if needed. // TODO(mdlayher): errors.Unwrap in Go 1.13. nerr, ok := err.(*net.OpError) if !ok { t.Fatalf("failed to create vsock listener: %v", err) } serr, ok := nerr.Err.(*os.SyscallError) if !ok { t.Fatalf("unexpected inner error for *net.OpError: %#v", nerr.Err) } switch serr.Err { case unix.EADDRNOTAVAIL: skipOldKernel(t) default: t.Fatalf("unexpected vsock listener system call error: %v", err) } } return l, func() { // Clean up the timer and this listener. timer.Stop() _ = l.Close() } } func skipOldKernel(t *testing.T) { t.Helper() // The kernel in use is to old to support Local binds, so this // test must be skipped. Print an informative message. var utsname unix.Utsname if err := unix.Uname(&utsname); err != nil { t.Fatalf("failed to get uname: %v", err) } t.Skipf("skipping, kernel %s is too old to support AF_VSOCK local binds", string(bytes.TrimRight(utsname.Release[:], "\x00"))) } func makeVSockPipe() nettest.MakePipe { return makeLocalPipe( func() (net.Listener, error) { return vsock.ListenContextID(vsock.Local, 0, nil) }, func(addr net.Addr) (net.Conn, error) { // ContextID will always be Local. a := addr.(*vsock.Addr) return vsock.Dial(a.ContextID, a.Port, nil) }, ) } // makeLocalPipe produces a nettest.MakePipe function using the input functions // to configure a net.Listener and point a net.Conn at the listener. // // This function is proposed for inclusion in x/net/nettest, and should be // removed from here if the proposal is accepted. See: // https://github.com/golang/go/issues/30984. func makeLocalPipe( listen func() (net.Listener, error), dial func(addr net.Addr) (net.Conn, error), ) nettest.MakePipe { if listen == nil { panic("nil listen function passed to makeLocalPipe") } if dial == nil { dial = func(addr net.Addr) (net.Conn, error) { return net.Dial(addr.Network(), addr.String()) } } // The majority of this code is taken from golang.org/x/net/nettest: // https://go.googlesource.com/net/+/9e4ed9723b84cb6661bb04e4104f7bfb3ff5d016/nettest/conntest_test.go. // // Copyright 2016 The Go Authors. All rights reserved. return func() (c1, c2 net.Conn, stop func(), err error) { ln, err := listen() if err != nil { return nil, nil, nil, fmt.Errorf("failed to create local listener: %v", err) } // Start a connection between two endpoints. var err1, err2 error done := make(chan bool) go func() { c2, err2 = ln.Accept() close(done) }() c1, err1 = dial(ln.Addr()) <-done stop = func() { if err1 == nil { c1.Close() } if err2 == nil { c2.Close() } ln.Close() switch ln.Addr().Network() { case "unix", "unixpacket": os.Remove(ln.Addr().String()) } } switch { case err1 != nil: stop() return nil, nil, nil, err1 case err2 != nil: stop() return nil, nil, nil, err2 default: return c1, c2, stop, nil } } } func panicf(format string, a ...interface{}) { panic(fmt.Sprintf(format, a...)) } vsock-1.2.1/internal/000077500000000000000000000000001442322355400144605ustar00rootroot00000000000000vsock-1.2.1/internal/vsutil/000077500000000000000000000000001442322355400160065ustar00rootroot00000000000000vsock-1.2.1/internal/vsutil/vsutil.go000066400000000000000000000024561442322355400176720ustar00rootroot00000000000000// Package vsutil provides added functionality for package vsock-internal use. package vsutil import ( "net" "os" "testing" "github.com/mdlayher/vsock" ) // IsHypervisor detects if this machine is a hypervisor by determining if // /dev/vsock is available, and then if its context ID matches the one assigned // to hosts. func IsHypervisor(t *testing.T) bool { t.Helper() cid, err := vsock.ContextID() if err != nil { SkipDeviceError(t, err) t.Fatalf("failed to retrieve context ID: %v", err) } return cid == vsock.Host } // SkipDeviceError skips this test if err is related to a failure to access the // /dev/vsock device. func SkipDeviceError(t *testing.T, err error) { t.Helper() // Unwrap net.OpError if needed. // TODO(mdlayher): errors.Unwrap in Go 1.13. if nerr, ok := err.(*net.OpError); ok { err = nerr.Err } if os.IsNotExist(err) { t.Skipf("skipping, vsock device does not exist (try: 'modprobe vhost_vsock'): %v", err) } if os.IsPermission(err) { t.Skipf("skipping, permission denied (try: 'chmod 666 /dev/vsock'): %v", err) } } // SkipHostIntegration skips this test if this machine is a host and cannot // perform a given test. func SkipHostIntegration(t *testing.T) { t.Helper() if IsHypervisor(t) { t.Skip("skipping, this integration test must be run in a guest") } } vsock-1.2.1/listener_linux.go000066400000000000000000000054651442322355400162510ustar00rootroot00000000000000//go:build linux // +build linux package vsock import ( "context" "net" "os" "time" "github.com/mdlayher/socket" "golang.org/x/sys/unix" ) var _ net.Listener = &listener{} // A listener is the net.Listener implementation for connection-oriented // VM sockets. type listener struct { c *socket.Conn addr *Addr } // Addr and Close implement the net.Listener interface for listener. func (l *listener) Addr() net.Addr { return l.addr } func (l *listener) Close() error { return l.c.Close() } func (l *listener) SetDeadline(t time.Time) error { return l.c.SetDeadline(t) } // Accept accepts a single connection from the listener, and sets up // a net.Conn backed by conn. func (l *listener) Accept() (net.Conn, error) { c, rsa, err := l.c.Accept(context.Background(), 0) if err != nil { return nil, err } savm := rsa.(*unix.SockaddrVM) remote := &Addr{ ContextID: savm.CID, Port: savm.Port, } return &Conn{ c: c, local: l.addr, remote: remote, }, nil } // name is the socket name passed to package socket. const name = "vsock" // listen is the entry point for Listen on Linux. func listen(cid, port uint32, _ *Config) (*Listener, error) { // TODO(mdlayher): Config default nil check and initialize. Pass options to // socket.Config where necessary. c, err := socket.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0, name, nil) if err != nil { return nil, err } // Be sure to close the Conn if any of the system calls fail before we // return the Conn to the caller. if port == 0 { port = unix.VMADDR_PORT_ANY } if err := c.Bind(&unix.SockaddrVM{CID: cid, Port: port}); err != nil { _ = c.Close() return nil, err } if err := c.Listen(unix.SOMAXCONN); err != nil { _ = c.Close() return nil, err } l, err := newListener(c) if err != nil { _ = c.Close() return nil, err } return l, nil } // fileListener is the entry point for FileListener on Linux. func fileListener(f *os.File) (*Listener, error) { c, err := socket.FileConn(f, name) if err != nil { return nil, err } l, err := newListener(c) if err != nil { _ = c.Close() return nil, err } return l, nil } // newListener creates a Listener from a raw socket.Conn. func newListener(c *socket.Conn) (*Listener, error) { lsa, err := c.Getsockname() if err != nil { return nil, err } // Now that the library can also accept arbitrary os.Files, we have to // verify the address family so we don't accidentally create a // *vsock.Listener backed by TCP or some other socket type. lsavm, ok := lsa.(*unix.SockaddrVM) if !ok { // All errors should wrapped with os.SyscallError. return nil, os.NewSyscallError("listen", unix.EINVAL) } addr := &Addr{ ContextID: lsavm.CID, Port: lsavm.Port, } return &Listener{ l: &listener{ c: c, addr: addr, }, }, nil } vsock-1.2.1/vsock.go000066400000000000000000000321621442322355400143240ustar00rootroot00000000000000package vsock import ( "errors" "fmt" "io" "net" "os" "strings" "syscall" "time" ) const ( // Hypervisor specifies that a socket should communicate with the hypervisor // process. Note that this is _not_ the same as a socket owned by a process // running on the hypervisor. Most users should probably use Host instead. Hypervisor = 0x0 // Local specifies that a socket should communicate with a matching socket // on the same machine. This provides an alternative to UNIX sockets or // similar and may be useful in testing VM sockets applications. Local = 0x1 // Host specifies that a socket should communicate with processes other than // the hypervisor on the host machine. This is the correct choice to // communicate with a process running on a hypervisor using a socket dialed // from a guest. Host = 0x2 // Error numbers we recognize, copied here to avoid importing x/sys/unix in // cross-platform code. ebadf = 9 enotconn = 107 // devVsock is the location of /dev/vsock. It is exposed on both the // hypervisor and on virtual machines. devVsock = "/dev/vsock" // network is the vsock network reported in net.OpError. network = "vsock" // Operation names which may be returned in net.OpError. opAccept = "accept" opClose = "close" opDial = "dial" opListen = "listen" opRawControl = "raw-control" opRawRead = "raw-read" opRawWrite = "raw-write" opRead = "read" opSet = "set" opSyscallConn = "syscall-conn" opWrite = "write" ) // TODO(mdlayher): plumb through socket.Config.NetNS if it makes sense. // Config contains options for a Conn or Listener. type Config struct{} // Listen opens a connection-oriented net.Listener for incoming VM sockets // connections. The port parameter specifies the port for the Listener. Config // specifies optional configuration for the Listener. If config is nil, a // default configuration will be used. // // To allow the server to assign a port automatically, specify 0 for port. The // address of the server can be retrieved using the Addr method. // // Listen automatically infers the appropriate context ID for this machine by // calling ContextID and passing that value to ListenContextID. Callers with // advanced use cases (such as using the Local context ID) may wish to use // ListenContextID directly. // // When the Listener is no longer needed, Close must be called to free // resources. func Listen(port uint32, cfg *Config) (*Listener, error) { cid, err := ContextID() if err != nil { // No addresses available. return nil, opError(opListen, err, nil, nil) } return ListenContextID(cid, port, cfg) } // ListenContextID is the same as Listen, but also accepts an explicit context // ID parameter. This function is intended for advanced use cases and most // callers should use Listen instead. // // See the documentation of Listen for more details. func ListenContextID(contextID, port uint32, cfg *Config) (*Listener, error) { l, err := listen(contextID, port, cfg) if err != nil { // No remote address available. return nil, opError(opListen, err, &Addr{ ContextID: contextID, Port: port, }, nil) } return l, nil } // FileListener returns a copy of the network listener corresponding to an open // os.File. It is the caller's responsibility to close the Listener when // finished. Closing the Listener does not affect the os.File, and closing the // os.File does not affect the Listener. // // This function is intended for advanced use cases and most callers should use // Listen instead. func FileListener(f *os.File) (*Listener, error) { l, err := fileListener(f) if err != nil { // No addresses available. return nil, opError(opListen, err, nil, nil) } return l, nil } var _ net.Listener = &Listener{} // A Listener is a VM sockets implementation of a net.Listener. type Listener struct { l *listener } // Accept implements the Accept method in the net.Listener interface; it waits // for the next call and returns a generic net.Conn. The returned net.Conn will // always be of type *Conn. func (l *Listener) Accept() (net.Conn, error) { c, err := l.l.Accept() if err != nil { return nil, l.opError(opAccept, err) } return c, nil } // Addr returns the listener's network address, a *Addr. The Addr returned is // shared by all invocations of Addr, so do not modify it. func (l *Listener) Addr() net.Addr { return l.l.Addr() } // Close stops listening on the VM sockets address. Already Accepted connections // are not closed. func (l *Listener) Close() error { return l.opError(opClose, l.l.Close()) } // SetDeadline sets the deadline associated with the listener. A zero time value // disables the deadline. func (l *Listener) SetDeadline(t time.Time) error { return l.opError(opSet, l.l.SetDeadline(t)) } // opError is a convenience for the function opError that also passes the local // address of the Listener. func (l *Listener) opError(op string, err error) error { // No remote address for a Listener. return opError(op, err, l.Addr(), nil) } // Dial dials a connection-oriented net.Conn to a VM sockets listener. The // context ID and port parameters specify the address of the listener. Config // specifies optional configuration for the Conn. If config is nil, a default // configuration will be used. // // If dialing a connection from the hypervisor to a virtual machine, the VM's // context ID should be specified. // // If dialing from a VM to the hypervisor, Hypervisor should be used to // communicate with the hypervisor process, or Host should be used to // communicate with other processes on the host machine. // // When the connection is no longer needed, Close must be called to free // resources. func Dial(contextID, port uint32, cfg *Config) (*Conn, error) { c, err := dial(contextID, port, cfg) if err != nil { // No local address, but we have a remote address we can return. return nil, opError(opDial, err, nil, &Addr{ ContextID: contextID, Port: port, }) } return c, nil } var ( _ net.Conn = &Conn{} _ syscall.Conn = &Conn{} ) // A Conn is a VM sockets implementation of a net.Conn. type Conn struct { c *conn local *Addr remote *Addr } // Close closes the connection. func (c *Conn) Close() error { return c.opError(opClose, c.c.Close()) } // CloseRead shuts down the reading side of the VM sockets connection. Most // callers should just use Close. func (c *Conn) CloseRead() error { return c.opError(opClose, c.c.CloseRead()) } // CloseWrite shuts down the writing side of the VM sockets connection. Most // callers should just use Close. func (c *Conn) CloseWrite() error { return c.opError(opClose, c.c.CloseWrite()) } // LocalAddr returns the local network address. The Addr returned is shared by // all invocations of LocalAddr, so do not modify it. func (c *Conn) LocalAddr() net.Addr { return c.local } // RemoteAddr returns the remote network address. The Addr returned is shared by // all invocations of RemoteAddr, so do not modify it. func (c *Conn) RemoteAddr() net.Addr { return c.remote } // Read implements the net.Conn Read method. func (c *Conn) Read(b []byte) (int, error) { n, err := c.c.Read(b) if err != nil { return n, c.opError(opRead, err) } return n, nil } // Write implements the net.Conn Write method. func (c *Conn) Write(b []byte) (int, error) { n, err := c.c.Write(b) if err != nil { return n, c.opError(opWrite, err) } return n, nil } // SetDeadline implements the net.Conn SetDeadline method. func (c *Conn) SetDeadline(t time.Time) error { return c.opError(opSet, c.c.SetDeadline(t)) } // SetReadDeadline implements the net.Conn SetReadDeadline method. func (c *Conn) SetReadDeadline(t time.Time) error { return c.opError(opSet, c.c.SetReadDeadline(t)) } // SetWriteDeadline implements the net.Conn SetWriteDeadline method. func (c *Conn) SetWriteDeadline(t time.Time) error { return c.opError(opSet, c.c.SetWriteDeadline(t)) } // SyscallConn returns a raw network connection. This implements the // syscall.Conn interface. func (c *Conn) SyscallConn() (syscall.RawConn, error) { rc, err := c.c.SyscallConn() if err != nil { return nil, c.opError(opSyscallConn, err) } return &rawConn{ rc: rc, local: c.local, remote: c.remote, }, nil } // opError is a convenience for the function opError that also passes the local // and remote addresses of the Conn. func (c *Conn) opError(op string, err error) error { return opError(op, err, c.local, c.remote) } // TODO(mdlayher): see if we can port smarter net.OpError with local/remote // address error logic into socket.Conn's SyscallConn type to avoid the need for // this wrapper. var _ syscall.RawConn = &rawConn{} // A rawConn is a syscall.RawConn that wraps an internal syscall.RawConn in order // to produce net.OpError error values. type rawConn struct { rc syscall.RawConn local, remote *Addr } // Control implements the syscall.RawConn Control method. func (rc *rawConn) Control(fn func(fd uintptr)) error { return rc.opError(opRawControl, rc.rc.Control(fn)) } // Control implements the syscall.RawConn Read method. func (rc *rawConn) Read(fn func(fd uintptr) (done bool)) error { return rc.opError(opRawRead, rc.rc.Read(fn)) } // Control implements the syscall.RawConn Write method. func (rc *rawConn) Write(fn func(fd uintptr) (done bool)) error { return rc.opError(opRawWrite, rc.rc.Write(fn)) } // opError is a convenience for the function opError that also passes the local // and remote addresses of the rawConn. func (rc *rawConn) opError(op string, err error) error { return opError(op, err, rc.local, rc.remote) } var _ net.Addr = &Addr{} // An Addr is the address of a VM sockets endpoint. type Addr struct { ContextID, Port uint32 } // Network returns the address's network name, "vsock". func (a *Addr) Network() string { return network } // String returns a human-readable representation of Addr, and indicates if // ContextID is meant to be used for a hypervisor, host, VM, etc. func (a *Addr) String() string { var host string switch a.ContextID { case Hypervisor: host = fmt.Sprintf("hypervisor(%d)", a.ContextID) case Local: host = fmt.Sprintf("local(%d)", a.ContextID) case Host: host = fmt.Sprintf("host(%d)", a.ContextID) default: host = fmt.Sprintf("vm(%d)", a.ContextID) } return fmt.Sprintf("%s:%d", host, a.Port) } // fileName returns a file name for use with os.NewFile for Addr. func (a *Addr) fileName() string { return fmt.Sprintf("%s:%s", a.Network(), a.String()) } // ContextID retrieves the local VM sockets context ID for this system. // ContextID can be used to directly determine if a system is capable of using // VM sockets. // // If the kernel module is unavailable, access to the kernel module is denied, // or VM sockets are unsupported on this system, it returns an error. func ContextID() (uint32, error) { return contextID() } // opError unpacks err if possible, producing a net.OpError with the input // parameters in order to implement net.Conn. As a convenience, opError returns // nil if the input error is nil. func opError(op string, err error, local, remote net.Addr) error { if err == nil { return nil } // TODO(mdlayher): this entire function is suspect and should probably be // looked at carefully, especially with Go 1.13+ error wrapping. // // Eventually this *net.OpError logic should probably be ported into // mdlayher/socket because similar checks are necessary to comply with // nettest.TestConn. // Unwrap inner errors from error types. // // TODO(mdlayher): errors.Cause or similar in Go 1.13. switch xerr := err.(type) { // os.PathError produced by os.File method calls. case *os.PathError: // Although we could make use of xerr.Op here, we're passing it manually // for consistency, since some of the Conn calls we are making don't // wrap an os.File, which would return an Op for us. // // As a special case, if the error is related to access to the /dev/vsock // device, we don't unwrap it, so the caller has more context as to why // their operation actually failed than "permission denied" or similar. if xerr.Path != devVsock { err = xerr.Err } } switch { case err == io.EOF, isErrno(err, enotconn): // We may see a literal io.EOF as happens with x/net/nettest, but // "transport not connected" also means io.EOF in Go. return io.EOF case err == os.ErrClosed, isErrno(err, ebadf), strings.Contains(err.Error(), "use of closed"): // Different operations may return different errors that all effectively // indicate a closed file. // // To rectify the differences, net.TCPConn uses an error with this text // from internal/poll for the backing file already being closed. err = errors.New("use of closed network connection") default: // Nothing to do, return this directly. } // Determine source and addr using the rules defined by net.OpError's // documentation: https://golang.org/pkg/net/#OpError. var source, addr net.Addr switch op { case opClose, opDial, opRawRead, opRawWrite, opRead, opWrite: if local != nil { source = local } if remote != nil { addr = remote } case opAccept, opListen, opRawControl, opSet, opSyscallConn: if local != nil { addr = local } } return &net.OpError{ Op: op, Net: network, Source: source, Addr: addr, Err: err, } } vsock-1.2.1/vsock_linux_test.go000066400000000000000000000110701442322355400165750ustar00rootroot00000000000000package vsock import ( "errors" "io" "net" "os" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/sys/unix" ) func Test_opError(t *testing.T) { // The default op for empty op fields. const defaultOp = "read" var ( // Unfortunate, but string matching it is for now. errClosed = errors.New("use of closed network connection") local = &Addr{ ContextID: Host, Port: 1024, } remote = &Addr{ ContextID: 3, Port: 2048, } ) tests := []struct { name string op string err error local net.Addr remote net.Addr want error }{ { name: "nil error", }, { name: "unknown", err: errors.New("foo"), want: &net.OpError{ Err: errors.New("foo"), }, }, { name: "EOF", err: io.EOF, want: io.EOF, }, { name: "ENOTCONN", err: unix.ENOTCONN, want: io.EOF, }, { name: "PathError ENOTCONN", err: &os.PathError{ Err: unix.ENOTCONN, }, want: io.EOF, }, { name: "ErrClosed", err: os.ErrClosed, want: &net.OpError{ Err: errClosed, }, }, { name: "EBADF", err: unix.EBADF, want: &net.OpError{ Err: errClosed, }, }, { name: "string use of closed", err: errors.New("use of closed file"), want: &net.OpError{ Err: errClosed, }, }, { name: "special PathError /dev/vsock", err: &os.PathError{ Op: "open", Path: devVsock, Err: unix.ENOENT, }, want: &net.OpError{ Err: &os.PathError{ Op: "open", Path: devVsock, Err: unix.ENOENT, }, }, }, { name: "op close", op: opClose, err: errClosed, local: local, remote: remote, want: &net.OpError{ Op: opClose, Source: local, Addr: remote, Err: errClosed, }, }, { name: "op dial", op: opDial, err: errClosed, local: local, remote: remote, want: &net.OpError{ Op: opDial, Source: local, Addr: remote, Err: errClosed, }, }, { name: "op raw-read", op: opRawRead, err: errClosed, local: local, remote: remote, want: &net.OpError{ Op: opRawRead, Source: local, Addr: remote, Err: errClosed, }, }, { name: "op raw-write", op: opRawWrite, err: errClosed, local: local, remote: remote, want: &net.OpError{ Op: opRawWrite, Source: local, Addr: remote, Err: errClosed, }, }, { name: "op read", op: opRead, err: errClosed, local: local, remote: remote, want: &net.OpError{ Op: opRead, Source: local, Addr: remote, Err: errClosed, }, }, { name: "op write", op: opWrite, err: errClosed, local: local, remote: remote, want: &net.OpError{ Op: opWrite, Source: local, Addr: remote, Err: errClosed, }, }, { name: "op accept", op: opAccept, err: errClosed, local: local, want: &net.OpError{ Op: opAccept, Addr: local, Err: errClosed, }, }, { name: "op listen", op: opListen, err: errClosed, local: local, want: &net.OpError{ Op: opListen, Addr: local, Err: errClosed, }, }, { name: "op raw-control", op: opRawControl, err: errClosed, local: local, want: &net.OpError{ Op: opRawControl, Addr: local, Err: errClosed, }, }, { name: "op set", op: opSet, err: errClosed, local: local, want: &net.OpError{ Op: opSet, Addr: local, Err: errClosed, }, }, { name: "op syscall-conn", op: opSyscallConn, err: errClosed, local: local, want: &net.OpError{ Op: opSyscallConn, Addr: local, Err: errClosed, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { op := tt.op if op == "" { op = defaultOp } err := opError(op, tt.err, tt.local, tt.remote) if err == nil { if tt.want != nil { t.Fatal("expected an output error, but none occurred") } return } // Populate sane defaults to save some typing. want := tt.want if nerr, ok := tt.want.(*net.OpError); ok { if nerr.Op == "" { nerr.Op = defaultOp } if nerr.Net == "" { nerr.Net = network } want = nerr } if diff := cmp.Diff(want, err, cmp.Comparer(errorsEqual)); diff != "" { t.Fatalf("unexpected error (-want +got):\n%s", diff) } }) } } func errorsEqual(x, y error) bool { if x == nil || y == nil { return x == nil && y == nil } return x.Error() == y.Error() } vsock-1.2.1/vsock_others.go000066400000000000000000000033201442322355400157020ustar00rootroot00000000000000//go:build !linux // +build !linux package vsock import ( "fmt" "net" "os" "runtime" "syscall" "time" ) // errUnimplemented is returned by all functions on platforms that // cannot make use of VM sockets. var errUnimplemented = fmt.Errorf("vsock: not implemented on %s", runtime.GOOS) func fileListener(_ *os.File) (*Listener, error) { return nil, errUnimplemented } func listen(_, _ uint32, _ *Config) (*Listener, error) { return nil, errUnimplemented } type listener struct{} func (*listener) Accept() (net.Conn, error) { return nil, errUnimplemented } func (*listener) Addr() net.Addr { return nil } func (*listener) Close() error { return errUnimplemented } func (*listener) SetDeadline(_ time.Time) error { return errUnimplemented } func dial(_, _ uint32, _ *Config) (*Conn, error) { return nil, errUnimplemented } type conn struct{} func (*conn) Close() error { return errUnimplemented } func (*conn) CloseRead() error { return errUnimplemented } func (*conn) CloseWrite() error { return errUnimplemented } func (*conn) Read(_ []byte) (int, error) { return 0, errUnimplemented } func (*conn) Write(_ []byte) (int, error) { return 0, errUnimplemented } func (*conn) SetDeadline(_ time.Time) error { return errUnimplemented } func (*conn) SetReadDeadline(_ time.Time) error { return errUnimplemented } func (*conn) SetWriteDeadline(_ time.Time) error { return errUnimplemented } func (*conn) SyscallConn() (syscall.RawConn, error) { return nil, errUnimplemented } func contextID() (uint32, error) { return 0, errUnimplemented } func isErrno(_ error, _ int) bool { return false } vsock-1.2.1/vsock_others_test.go000066400000000000000000000010201442322355400167340ustar00rootroot00000000000000//go:build !linux // +build !linux package vsock import "testing" func TestUnimplemented(t *testing.T) { want := errUnimplemented if _, got := ContextID(); want != got { t.Fatalf("unexpected error from ContextID:\n- want: %v\n- got: %v", want, got) } if _, got := listen(0, 0, nil); want != got { t.Fatalf("unexpected error from listen:\n- want: %v\n- got: %v", want, got) } if _, got := dial(0, 0, nil); want != got { t.Fatalf("unexpected error from dial:\n- want: %v\n- got: %v", want, got) } } vsock-1.2.1/vsock_test.go000066400000000000000000000013001442322355400153510ustar00rootroot00000000000000package vsock import ( "testing" ) func TestAddr_fileName(t *testing.T) { tests := []struct { cid uint32 port uint32 s string }{ { cid: Hypervisor, port: 10, s: "vsock:hypervisor(0):10", }, { cid: Local, port: 20, s: "vsock:local(1):20", }, { cid: Host, port: 30, s: "vsock:host(2):30", }, { cid: 3, port: 40, s: "vsock:vm(3):40", }, } for _, tt := range tests { t.Run(tt.s, func(t *testing.T) { addr := &Addr{ ContextID: tt.cid, Port: tt.port, } if want, got := tt.s, addr.fileName(); want != got { t.Fatalf("unexpected file name:\n- want: %q\n- got: %q", want, got) } }) } }