pax_global_header00006660000000000000000000000064141313477270014523gustar00rootroot0000000000000052 comment=79ca7dfa23c1a1b6ea5dbacf13bbaffc52bd1b0d golang-github-mdlayher-vsock-0.0~git20210303.10d5918/000077500000000000000000000000001413134772700214665ustar00rootroot00000000000000golang-github-mdlayher-vsock-0.0~git20210303.10d5918/.builds/000077500000000000000000000000001413134772700230265ustar00rootroot00000000000000golang-github-mdlayher-vsock-0.0~git20210303.10d5918/.builds/freebsd.yml000066400000000000000000000006711413134772700251670ustar00rootroot00000000000000image: freebsd/latest packages: - go sources: - https://github.com/mdlayher/vsock environment: GO111MODULE: "on" tasks: - build: | go version go get golang.org/x/lint/golint go get honnef.co/go/tools/cmd/staticcheck cd vsock/ /home/build/go/bin/staticcheck ./... /home/build/go/bin/golint -set_exit_status ./... # Make sure vsock non-Linux stubs are functional. go test -v -race ./... golang-github-mdlayher-vsock-0.0~git20210303.10d5918/.builds/go-1.10.yml000066400000000000000000000014661413134772700245420ustar00rootroot00000000000000image: archlinux packages: - wget sources: - https://github.com/mdlayher/vsock tasks: - install: | wget -q https://dl.google.com/go/go1.10.8.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.10.8.linux-amd64.tar.gz sudo ln -s /usr/local/go/bin/go /usr/bin/go - build: | go version # Force build in GOPATH mode because Go 1.10 doesn't support modules. mkdir -p /home/build/go/src/github.com/mdlayher/ mv vsock /home/build/go/src/github.com/mdlayher/ cd /home/build/go/src/github.com/mdlayher/vsock go get -d -t -v ./... go get golang.org/x/lint/golint /home/build/go/bin/golint -set_exit_status ./... # Enable access to the vsock device for tests. sudo modprobe vhost_vsock sudo chmod 666 /dev/vsock go test -v -race ./... golang-github-mdlayher-vsock-0.0~git20210303.10d5918/.builds/go-1.11.yml000066400000000000000000000012651413134772700245400ustar00rootroot00000000000000image: archlinux packages: - wget sources: - https://github.com/mdlayher/vsock environment: GO111MODULE: "on" tasks: - install: | wget -q https://dl.google.com/go/go1.11.6.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.11.6.linux-amd64.tar.gz sudo ln -s /usr/local/go/bin/go /usr/bin/go - build: | go version cd vsock/ go get golang.org/x/lint/golint go get honnef.co/go/tools/cmd/staticcheck /home/build/go/bin/staticcheck ./... /home/build/go/bin/golint -set_exit_status ./... # Enable access to the vsock device for tests. sudo modprobe vhost_vsock sudo chmod 666 /dev/vsock go test -v -race ./... golang-github-mdlayher-vsock-0.0~git20210303.10d5918/.builds/go-stable.yml000066400000000000000000000007611413134772700254320ustar00rootroot00000000000000image: archlinux packages: - go sources: - https://github.com/mdlayher/vsock environment: GO111MODULE: "on" tasks: - build: | go version go get golang.org/x/lint/golint go get honnef.co/go/tools/cmd/staticcheck cd vsock/ /home/build/go/bin/staticcheck ./... /home/build/go/bin/golint -set_exit_status ./... # Enable access to the vsock device for tests. sudo modprobe vhost_vsock sudo chmod 666 /dev/vsock go test -v -race ./... golang-github-mdlayher-vsock-0.0~git20210303.10d5918/.gitignore000066400000000000000000000000731413134772700234560ustar00rootroot00000000000000cover.out vsock.test cmd/vscp/vscp cmd/vsockhttp/vsockhttp golang-github-mdlayher-vsock-0.0~git20210303.10d5918/LICENSE.md000066400000000000000000000020701413134772700230710ustar00rootroot00000000000000MIT License =========== Copyright (C) 2017 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. golang-github-mdlayher-vsock-0.0~git20210303.10d5918/README.md000066400000000000000000000054321413134772700227510ustar00rootroot00000000000000# vsock [![builds.sr.ht status](https://builds.sr.ht/~mdlayher/vsock.svg)](https://builds.sr.ht/~mdlayher/vsock?) [![GoDoc](https://godoc.org/github.com/mdlayher/vsock?status.svg)](https://godoc.org/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, check out my blog about [Linux VM sockets in Go](https://medium.com/@mdlayher/linux-vm-sockets-in-go-ea11768e9e67). ## Go version support This package supports varying levels of functionality depending on the version of Go used during compilation. The `Listener` and `Conn` types produced by this package are backed by non-blocking I/O, in order to integrate with Go's runtime network poller in Go 1.11+. Additional functionality is available starting in Go 1.12+. The older Go 1.10 is only supported in a blocking-only mode. A comprehensive list of functionality for supported Go versions can be found on [package vsock's GoDoc page](https://godoc.org/github.com/mdlayher/vsock#hdr-Go_version_support). ## Stability At this time, package `vsock` is in a pre-v1.0.0 state. Changes are being made which may impact the exported API of this package and others in its ecosystem. **If you depend on this package in your application, please use Go modules when building your application.** ## 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 Before using VM sockets, following modules must be removed on hypervisor: - `modprobe -r vmw_vsock_vmci_transport` - `modprobe -r vmw_vsock_virtio_transport_common` - `modprobe -r vsock` Once removed, `vhost_vsock` module needs to be enabled on hypervisor: - `modprobe vhost_vsock` On VM, you have to enable `vmw_vsock_virtio_transport` module. This module should automatically load during boot when the vsock device is detected. To utilize VM sockets, VM needs to be powered on with following `-device` flag: - `-device vhost-vsock-pci,id=vhost-vsock-pci0,guest-cid=3` 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. golang-github-mdlayher-vsock-0.0~git20210303.10d5918/cmd/000077500000000000000000000000001413134772700222315ustar00rootroot00000000000000golang-github-mdlayher-vsock-0.0~git20210303.10d5918/cmd/vscp/000077500000000000000000000000001413134772700232045ustar00rootroot00000000000000golang-github-mdlayher-vsock-0.0~git20210303.10d5918/cmd/vscp/README.md000066400000000000000000000054111413134772700244640ustar00rootroot00000000000000vscp ==== 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 ``` golang-github-mdlayher-vsock-0.0~git20210303.10d5918/cmd/vscp/main.go000066400000000000000000000120551413134772700244620ustar00rootroot00000000000000// 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" "github.com/mdlayher/vsock/internal/vsutil" ) 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) l, err := vsock.Listen(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 := vsutil.Accept(l, timeout) 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) 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...) } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/conn_linux.go000066400000000000000000000026401413134772700241730ustar00rootroot00000000000000//+build linux package vsock import ( "golang.org/x/sys/unix" ) // newConn creates a Conn using a connFD, immediately setting the connFD to // non-blocking mode for use with the runtime network poller. func newConn(cfd connFD, local, remote *Addr) (*Conn, error) { // Note: if any calls fail after this point, cfd.Close should be invoked // for cleanup because the socket is now non-blocking. if err := cfd.SetNonblocking(local.fileName()); err != nil { return nil, err } return &Conn{ fd: cfd, local: local, remote: remote, }, nil } // dial is the entry point for Dial on Linux. func dial(cid, port uint32) (*Conn, error) { cfd, err := newConnFD() if err != nil { return nil, err } return dialLinux(cfd, cid, port) } // dialLinux is the entry point for tests on Linux. func dialLinux(cfd connFD, cid, port uint32) (c *Conn, err error) { defer func() { if err != nil { // If any system calls fail during setup, the socket must be closed // to avoid file descriptor leaks. _ = cfd.EarlyClose() } }() rsa := &unix.SockaddrVM{ CID: cid, Port: port, } if err := cfd.Connect(rsa); err != nil { return nil, err } lsa, err := cfd.Getsockname() if err != nil { return nil, err } lsavm := lsa.(*unix.SockaddrVM) local := &Addr{ ContextID: lsavm.CID, Port: lsavm.Port, } remote := &Addr{ ContextID: cid, Port: port, } return newConn(cfd, local, remote) } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/conn_linux_test.go000066400000000000000000000060421413134772700252320ustar00rootroot00000000000000//+build linux package vsock import ( "errors" "syscall" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/sys/unix" ) func Test_dialLinuxErrorClosesFile(t *testing.T) { var closed bool cfd := &testConnFD{ // Track when fd.Close is called. close: func() error { closed = true return nil }, // Always return an error on connect. connect: func(sa unix.Sockaddr) error { return errors.New("error during connect") }, } if _, err := dialLinux(cfd, 0, 0); err == nil { t.Fatal("expected an error, but none occurred") } if diff := cmp.Diff(true, closed); diff != "" { t.Fatalf("unexpected closed value (-want +got):\n%s", diff) } } func Test_dialLinuxFull(t *testing.T) { const ( localCID uint32 = 3 localPort uint32 = 1024 remoteCID uint32 = Host remotePort uint32 = 2048 ) lsa := &unix.SockaddrVM{ CID: localCID, Port: localPort, } rsa := &unix.SockaddrVM{ CID: remoteCID, Port: remotePort, } var ( closed bool closedRead bool closedWrite bool syscallConn bool ) cfd := &testConnFD{ connect: func(sa unix.Sockaddr) error { if diff := cmp.Diff(rsa, sa.(*unix.SockaddrVM), cmp.AllowUnexported(*rsa)); diff != "" { t.Fatalf("unexpected connect sockaddr (-want +got):\n%s", diff) } return nil }, getsockname: func() (unix.Sockaddr, error) { return lsa, nil }, setNonblocking: func(name string) error { if diff := cmp.Diff(name, "vsock:vm(3):1024"); diff != "" { t.Fatalf("unexpected non-blocking file name (-want +got):\n%s", diff) } return nil }, close: func() error { closed = true return nil }, shutdown: func(how int) error { switch how { case unix.SHUT_RD: closedRead = true case unix.SHUT_WR: closedWrite = true default: t.Fatalf("unexpected how constant in shutdown: %d", how) } return nil }, syscallConn: func() (syscall.RawConn, error) { // No need to really do anything. syscallConn = true return nil, nil }, } c, err := dialLinux(cfd, remoteCID, remotePort) if err != nil { t.Fatalf("failed to dial: %v", err) } localAddr := &Addr{ ContextID: localCID, Port: localPort, } if diff := cmp.Diff(localAddr, c.LocalAddr()); diff != "" { t.Fatalf("unexpected local address (-want +got):\n%s", diff) } remoteAddr := &Addr{ ContextID: remoteCID, Port: remotePort, } if diff := cmp.Diff(remoteAddr, c.RemoteAddr()); diff != "" { t.Fatalf("unexpected remote address (-want +got):\n%s", diff) } if _, err := c.SyscallConn(); err != nil { t.Fatalf("failed to test syscall conn: %v", err) } if !syscallConn { t.Fatal("expected call to SyscallConn, but none occurred") } // Verify Close/Shutdown plumbing. funcs := []func() error{ c.Close, c.CloseRead, c.CloseWrite, } for i, fn := range funcs { if err := fn(); err != nil { t.Fatalf("failed to invoke function %d: %v", i, err) } } if !closed || !closedRead || !closedWrite { t.Fatalf("expected calls to Close (%t), CloseRead (%t), and CloseWrite (%t)", closed, closedRead, closedWrite) } } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/doc.go000066400000000000000000000052061413134772700225650ustar00rootroot00000000000000// 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 // // Go version support // // This package supports varying levels of functionality depending on the version // of Go used during compilation. The Listener and Conn types produced by this // package are backed by non-blocking I/O, in order to integrate with Go's // runtime network poller in Go 1.11+. Additional functionality is available // starting in Go 1.12+. // // Go 1.12+ (recommended): // - *Listener: // - Accept blocks until a connection is received // - Close can interrupt Accept and make it return a permanent error // - SetDeadline can set timeouts which can interrupt Accept and make it return a // temporary error // - *Conn: // - SetDeadline family of methods are fully supported // - CloseRead and CloseWrite can close the reading or writing sides of a // Conn, respectively // - SyscallConn provides access to raw network control/read/write functionality // // Go 1.11 (not recommended): // - *Listener: // - Accept is non-blocking and should be called in a loop, checking for // net.Error.Temporary() == true and sleeping for a short period to avoid wasteful // CPU cycle consumption // - Close makes Accept return a permanent error on the next loop iteration // - SetDeadline is not supported and will always return an error // - *Conn: // - SetDeadline family of methods are fully supported // - CloseRead and CloseWrite are not supported and will always return an error // - SyscallConn is not supported and will always return an error // // Go 1.10 (not recommended): // - *Listener: // - Accept blocks until a connection is received // - Close cannot unblock Accept // - SetDeadline is not supported and will always return an error // - *Conn: // - SetDeadline is not supported and will always return an error // - CloseRead and CloseWrite are not supported and will always return an error // - SyscallConn is not supported and will always return an error // // Stability // // At this time, package vsock is in a pre-v1.0.0 state. Changes are being made // which may impact the exported API of this package and others in its ecosystem. // // If you depend on this package in your application, please use Go modules when // building your application. package vsock golang-github-mdlayher-vsock-0.0~git20210303.10d5918/fd_linux.go000066400000000000000000000147771413134772700236450ustar00rootroot00000000000000package vsock import ( "fmt" "io" "os" "syscall" "time" "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) } // A listenFD is a type that wraps a file descriptor used to implement // net.Listener. type listenFD interface { io.Closer EarlyClose() error Accept4(flags int) (connFD, unix.Sockaddr, error) Bind(sa unix.Sockaddr) error Listen(n int) error Getsockname() (unix.Sockaddr, error) SetNonblocking(name string) error SetDeadline(t time.Time) error } var _ listenFD = &sysListenFD{} // A sysListenFD is the system call implementation of listenFD. type sysListenFD struct { // These fields should never be non-zero at the same time. fd int // Used in blocking mode. f *os.File // Used in non-blocking mode. } // newListenFD creates a sysListenFD in its default blocking mode. func newListenFD() (*sysListenFD, error) { fd, err := socket() if err != nil { return nil, err } return &sysListenFD{ fd: fd, }, nil } // Blocking mode methods. func (lfd *sysListenFD) Bind(sa unix.Sockaddr) error { return unix.Bind(lfd.fd, sa) } func (lfd *sysListenFD) Getsockname() (unix.Sockaddr, error) { return unix.Getsockname(lfd.fd) } func (lfd *sysListenFD) Listen(n int) error { return unix.Listen(lfd.fd, n) } func (lfd *sysListenFD) SetNonblocking(name string) error { return lfd.setNonblocking(name) } // EarlyClose is a blocking version of Close, only used for cleanup before // entering non-blocking mode. func (lfd *sysListenFD) EarlyClose() error { return unix.Close(lfd.fd) } // Non-blocking mode methods. func (lfd *sysListenFD) Accept4(flags int) (connFD, unix.Sockaddr, error) { // Invoke Go version-specific logic for accept. newFD, sa, err := lfd.accept4(flags) if err != nil { return nil, nil, err } // Create a non-blocking connFD which will be used to implement net.Conn. cfd := &sysConnFD{fd: newFD} return cfd, sa, nil } func (lfd *sysListenFD) Close() error { // In Go 1.12+, *os.File.Close will also close the runtime network poller // file descriptor, so that net.Listener.Accept can stop blocking. return lfd.f.Close() } func (lfd *sysListenFD) SetDeadline(t time.Time) error { // Invoke Go version-specific logic for setDeadline. return lfd.setDeadline(t) } // A connFD is a type that wraps a file descriptor used to implement net.Conn. type connFD interface { io.ReadWriteCloser EarlyClose() error Connect(sa unix.Sockaddr) error Getsockname() (unix.Sockaddr, error) Shutdown(how int) error SetNonblocking(name string) error SetDeadline(t time.Time, typ deadlineType) error SyscallConn() (syscall.RawConn, error) } var _ connFD = &sysConnFD{} // newConnFD creates a sysConnFD in its default blocking mode. func newConnFD() (*sysConnFD, error) { fd, err := socket() if err != nil { return nil, err } return &sysConnFD{ fd: fd, }, nil } // A sysConnFD is the system call implementation of connFD. type sysConnFD struct { // These fields should never be non-zero at the same time. fd int // Used in blocking mode. f *os.File // Used in non-blocking mode. } // Blocking mode methods. func (cfd *sysConnFD) Getsockname() (unix.Sockaddr, error) { return unix.Getsockname(cfd.fd) } func (cfd *sysConnFD) Connect(sa unix.Sockaddr) error { var err error for { err = unix.Connect(cfd.fd, sa) if err == unix.EINTR { // Retry on interrupted syscalls. continue } break } return err } // EarlyClose is a blocking version of Close, only used for cleanup before // entering non-blocking mode. func (cfd *sysConnFD) EarlyClose() error { return unix.Close(cfd.fd) } func (cfd *sysConnFD) SetNonblocking(name string) error { return cfd.setNonblocking(name) } // Non-blocking mode methods. func (cfd *sysConnFD) Close() error { // *os.File.Close will also close the runtime network poller file descriptor, // so that read/write can stop blocking. return cfd.f.Close() } func (cfd *sysConnFD) Read(b []byte) (int, error) { return cfd.f.Read(b) } func (cfd *sysConnFD) Write(b []byte) (int, error) { return cfd.f.Write(b) } func (cfd *sysConnFD) Shutdown(how int) error { switch how { case unix.SHUT_RD, unix.SHUT_WR: return cfd.shutdown(how) default: panicf("vsock: sysConnFD.Shutdown method invoked with invalid how constant: %d", how) return nil } } func (cfd *sysConnFD) SetDeadline(t time.Time, typ deadlineType) error { return cfd.setDeadline(t, typ) } func (cfd *sysConnFD) SyscallConn() (syscall.RawConn, error) { return cfd.syscallConn() } // socket invokes unix.Socket with the correct arguments to produce a vsock // file descriptor. func socket() (int, error) { // "Mirror what the standard library does when creating file // descriptors: avoid racing a fork/exec with the creation // of new file descriptors, so that child processes do not // inherit [socket] file descriptors unexpectedly. // // On Linux, SOCK_CLOEXEC was introduced in 2.6.27. OTOH, // Go supports Linux 2.6.23 and above. If we get EINVAL on // the first try, it may be that we are running on a kernel // older than 2.6.27. In that case, take syscall.ForkLock // and try again without SOCK_CLOEXEC. // // For a more thorough explanation, see similar work in the // Go tree: func sysSocket in net/sock_cloexec.go, as well // as the detailed comment in syscall/exec_unix.go." // // Explanation copied from netlink, courtesy of acln: // https://github.com/mdlayher/netlink/pull/138. for { fd, err := unix.Socket(unix.AF_VSOCK, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) switch err { case nil: return fd, nil case unix.EINTR: // Retry on interrupted syscalls. continue case unix.EINVAL: syscall.ForkLock.RLock() fd, err = unix.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0) if err != nil { syscall.ForkLock.RUnlock() if err == unix.EINTR { // Retry on interrupted syscalls. continue } return 0, err } unix.CloseOnExec(fd) syscall.ForkLock.RUnlock() return fd, nil default: return 0, err } } } // 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...)) } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/fd_linux_1.10.go000066400000000000000000000033751413134772700242740ustar00rootroot00000000000000//+build go1.10,!go1.11,linux package vsock import ( "fmt" "os" "runtime" "syscall" "time" "golang.org/x/sys/unix" ) func (lfd *sysListenFD) accept4(flags int) (int, unix.Sockaddr, error) { // In Go 1.11, accept on the raw file descriptor directly, because lfd.f // may be attached to the runtime network poller, forcing this call to block // even if Close is called. return unix.Accept4(lfd.fd, flags) } func (lfd *sysListenFD) setNonblocking(name string) error { // Go 1.10 doesn't support non-blocking I/O. if err := unix.SetNonblock(lfd.fd, false); err != nil { return err } lfd.f = os.NewFile(uintptr(lfd.fd), name) return nil } func (*sysListenFD) setDeadline(_ time.Time) error { // Listener deadlines won't work as expected in this version of Go, so // return an early error. return fmt.Errorf("vsock: listener deadlines not supported on %s", runtime.Version()) } func (*sysConnFD) shutdown(_ int) error { // Shutdown functionality is not available in this version on Go. return fmt.Errorf("vsock: close conn read/write not supported on %s", runtime.Version()) } func (*sysConnFD) syscallConn() (syscall.RawConn, error) { // SyscallConn functionality is not available in this version on Go. return nil, fmt.Errorf("vsock: syscall conn not supported on %s", runtime.Version()) } func (cfd *sysConnFD) setNonblocking(name string) error { // Go 1.10 doesn't support non-blocking I/O. if err := unix.SetNonblock(cfd.fd, false); err != nil { return err } cfd.f = os.NewFile(uintptr(cfd.fd), name) return nil } func (cfd *sysConnFD) setDeadline(t time.Time, typ deadlineType) error { // Deadline functionality is not available in this version on Go. return fmt.Errorf("vsock: connection deadlines not supported on %s", runtime.Version()) } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/fd_linux_1.11.go000066400000000000000000000042541413134772700242720ustar00rootroot00000000000000//+build go1.11,!go1.12,linux package vsock import ( "fmt" "os" "runtime" "syscall" "time" "golang.org/x/sys/unix" ) func (lfd *sysListenFD) accept4(flags int) (int, unix.Sockaddr, error) { // In Go 1.11, accept on the raw file descriptor directly, because lfd.f // may be attached to the runtime network poller, forcing this call to block // even if Close is called. return unix.Accept4(lfd.fd, flags) } func (lfd *sysListenFD) setNonblocking(name string) error { // From now on, we must perform non-blocking I/O, so that our // net.Listener.Accept method can be interrupted by closing the socket. if err := unix.SetNonblock(lfd.fd, true); err != nil { return err } // Transition from blocking mode to non-blocking mode. lfd.f = os.NewFile(uintptr(lfd.fd), name) return nil } func (*sysListenFD) setDeadline(_ time.Time) error { // Listener deadlines won't work as expected in this version of Go, so // return an early error. return fmt.Errorf("vsock: listener deadlines not supported on %s", runtime.Version()) } func (*sysConnFD) shutdown(_ int) error { // Shutdown functionality is not available in this version on Go. return fmt.Errorf("vsock: close conn read/write not supported on %s", runtime.Version()) } func (*sysConnFD) syscallConn() (syscall.RawConn, error) { // SyscallConn functionality is not available in this version on Go. return nil, fmt.Errorf("vsock: syscall conn not supported on %s", runtime.Version()) } func (cfd *sysConnFD) setNonblocking(name string) error { // From now on, we must perform non-blocking I/O, so that our deadline // methods work, and the connection can be interrupted by net.Conn.Close. if err := unix.SetNonblock(cfd.fd, true); err != nil { return err } // Transition from blocking mode to non-blocking mode. cfd.f = os.NewFile(uintptr(cfd.fd), name) return nil } func (cfd *sysConnFD) setDeadline(t time.Time, typ deadlineType) error { switch typ { case deadline: return cfd.f.SetDeadline(t) case readDeadline: return cfd.f.SetReadDeadline(t) case writeDeadline: return cfd.f.SetWriteDeadline(t) } panicf("vsock: sysConnFD.SetDeadline method invoked with invalid deadline type constant: %d", typ) return nil } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/fd_linux_gteq_1.12.go000066400000000000000000000053511413134772700253120ustar00rootroot00000000000000//+build go1.12,linux package vsock import ( "os" "syscall" "time" "golang.org/x/sys/unix" ) func (lfd *sysListenFD) accept4(flags int) (int, unix.Sockaddr, error) { // In Go 1.12+, we make use of runtime network poller integration to allow // net.Listener.Accept to be unblocked by a call to net.Listener.Close. rc, err := lfd.f.SyscallConn() if err != nil { return 0, nil, err } var ( newFD int sa unix.Sockaddr ) doErr := rc.Read(func(fd uintptr) bool { newFD, sa, err = unix.Accept4(int(fd), flags) switch err { case unix.EAGAIN, unix.ECONNABORTED: // Return false to let the poller wait for readiness. See the // source code for internal/poll.FD.RawRead for more details. // // When the socket is in non-blocking mode, we might see EAGAIN if // the socket is not ready for reading. // // In addition, the network poller's accept implementation also // deals with ECONNABORTED, in case a socket is closed before it is // pulled from our listen queue. return false default: // No error or some unrecognized error, treat this Read operation // as completed. return true } }) if doErr != nil { return 0, nil, doErr } return newFD, sa, err } func (lfd *sysListenFD) setDeadline(t time.Time) error { return lfd.f.SetDeadline(t) } func (lfd *sysListenFD) setNonblocking(name string) error { // From now on, we must perform non-blocking I/O, so that our // net.Listener.Accept method can be interrupted by closing the socket. if err := unix.SetNonblock(lfd.fd, true); err != nil { return err } // Transition from blocking mode to non-blocking mode. lfd.f = os.NewFile(uintptr(lfd.fd), name) return nil } func (cfd *sysConnFD) shutdown(how int) error { rc, err := cfd.f.SyscallConn() if err != nil { return err } doErr := rc.Control(func(fd uintptr) { err = unix.Shutdown(int(fd), how) }) if doErr != nil { return doErr } return err } func (cfd *sysConnFD) syscallConn() (syscall.RawConn, error) { return cfd.f.SyscallConn() } func (cfd *sysConnFD) setNonblocking(name string) error { // From now on, we must perform non-blocking I/O, so that our deadline // methods work, and the connection can be interrupted by net.Conn.Close. if err := unix.SetNonblock(cfd.fd, true); err != nil { return err } // Transition from blocking mode to non-blocking mode. cfd.f = os.NewFile(uintptr(cfd.fd), name) return nil } func (cfd *sysConnFD) setDeadline(t time.Time, typ deadlineType) error { switch typ { case deadline: return cfd.f.SetDeadline(t) case readDeadline: return cfd.f.SetReadDeadline(t) case writeDeadline: return cfd.f.SetWriteDeadline(t) } panicf("vsock: sysConnFD.SetDeadline method invoked with invalid deadline type constant: %d", typ) return nil } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/fd_linux_test.go000066400000000000000000000047621413134772700246750ustar00rootroot00000000000000//+build linux package vsock import ( "syscall" "time" "golang.org/x/sys/unix" ) var _ listenFD = &testListenFD{} type testListenFD struct { accept4 func(flags int) (connFD, unix.Sockaddr, error) bind func(sa unix.Sockaddr) error close func() error listen func(n int) error getsockname func() (unix.Sockaddr, error) setNonblocking func(name string) error setDeadline func(t time.Time) error } func (lfd *testListenFD) Accept4(flags int) (connFD, unix.Sockaddr, error) { return lfd.accept4(flags) } func (lfd *testListenFD) Bind(sa unix.Sockaddr) error { return lfd.bind(sa) } func (lfd *testListenFD) Close() error { return lfd.close() } func (lfd *testListenFD) EarlyClose() error { // Share logic with close. return lfd.close() } func (lfd *testListenFD) Getsockname() (unix.Sockaddr, error) { return lfd.getsockname() } func (lfd *testListenFD) Listen(n int) error { return lfd.listen(n) } func (lfd *testListenFD) SetNonblocking(name string) error { return lfd.setNonblocking(name) } func (lfd *testListenFD) SetDeadline(t time.Time) error { return lfd.setDeadline(t) } var _ connFD = &testConnFD{} type testConnFD struct { read func(b []byte) (int, error) write func(b []byte) (int, error) close func() error connect func(sa unix.Sockaddr) error getsockname func() (unix.Sockaddr, error) setNonblocking func(name string) error setDeadline func(t time.Time, typ deadlineType) error shutdown func(how int) error syscallConn func() (syscall.RawConn, error) } func (cfd *testConnFD) Read(b []byte) (int, error) { return cfd.read(b) } func (cfd *testConnFD) Write(b []byte) (int, error) { return cfd.write(b) } func (cfd *testConnFD) Close() error { return cfd.close() } func (cfd *testConnFD) EarlyClose() error { // Share logic with close. return cfd.close() } func (cfd *testConnFD) Connect(sa unix.Sockaddr) error { return cfd.connect(sa) } func (cfd *testConnFD) Getsockname() (unix.Sockaddr, error) { return cfd.getsockname() } func (cfd *testConnFD) SetNonblocking(name string) error { return cfd.setNonblocking(name) } func (cfd *testConnFD) SetDeadline(t time.Time, typ deadlineType) error { return cfd.setDeadline(t, typ) } func (cfd *testConnFD) Shutdown(how int) error { return cfd.shutdown(how) } func (cfd *testConnFD) SyscallConn() (syscall.RawConn, error) { return cfd.syscallConn() } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/go.mod000066400000000000000000000003021413134772700225670ustar00rootroot00000000000000module github.com/mdlayher/vsock go 1.13 require ( github.com/google/go-cmp v0.4.0 golang.org/x/net v0.0.0-20200506145744-7e3656a0809f golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 ) golang-github-mdlayher-vsock-0.0~git20210303.10d5918/go.sum000066400000000000000000000024241413134772700226230ustar00rootroot00000000000000github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w= golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang-github-mdlayher-vsock-0.0~git20210303.10d5918/integration_linux_gteq_1.11_test.go000066400000000000000000000023311413134772700302750ustar00rootroot00000000000000//+build go1.11,linux package vsock_test import ( "net" "strings" "sync" "testing" "time" "github.com/mdlayher/vsock/internal/vsutil" ) func TestIntegrationListenerUnblockAcceptAfterClose(t *testing.T) { l, done := newListener(t) defer done() var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() t.Log("start accept") _, err := vsutil.Accept(l, 10*time.Second) t.Log("after accept") nerr, ok := err.(*net.OpError) if !ok { t.Errorf("expected a net.OpError, but got: %#v", err) } if nerr.Temporary() { t.Errorf("expected permanent error, but got temporary one: %v", err) } // We mimic what net.TCPConn does and return an error with the same // string as internal/poll, so string matching is the best we can do // for now. if !strings.Contains(nerr.Err.Error(), "use of closed") { t.Errorf("expected close network connection error, but got: %v", nerr.Err) } }() time.Sleep(100 * time.Millisecond) if err := l.Close(); err != nil { t.Fatalf("failed to close listener: %v", err) } doneC := make(chan struct{}) go func() { wg.Wait() close(doneC) }() select { case <-doneC: case <-time.After(5 * time.Second): t.Fatal("timeout waiting accept to unblock") } } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/integration_linux_gteq_1.12_test.go000066400000000000000000000072761413134772700303130ustar00rootroot00000000000000//+build go1.12,linux package vsock_test import ( "fmt" "io" "net" "sync" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/mdlayher/vsock" "github.com/mdlayher/vsock/internal/vsutil" "golang.org/x/sys/unix" ) 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.Temporary()) { t.Errorf("expected temporary 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. <-readClosed if _, err := vc1.Write(b); !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 isBrokenPipe(err error) bool { if err == nil { return false } nerr, ok := err.(*net.OpError) if !ok { return false } return nerr.Err == unix.EPIPE } func panicf(format string, a ...interface{}) { panic(fmt.Sprintf(format, a...)) } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/integration_linux_lt_1.12_test.go000066400000000000000000000013551413134772700277620ustar00rootroot00000000000000//+build !go1.12,linux package vsock_test import ( "testing" "time" "github.com/mdlayher/vsock" "github.com/mdlayher/vsock/internal/vsutil" ) func TestIntegrationListenerSetDeadlineError(t *testing.T) { l, done := newListener(t) defer done() err := l.SetDeadline(time.Time{}) if err == nil { t.Fatal("expected an error, but none occurred") } t.Logf("OK error: %v", err) } func TestIntegrationConnSyscallConnError(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() _, err = c.(*vsock.Conn).SyscallConn() if err == nil { t.Fatal("expected an error, but none occurred") } t.Logf("OK error: %v", err) } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/integration_linux_test.go000066400000000000000000000112331413134772700266160ustar00rootroot00000000000000//+build linux package vsock_test import ( "fmt" "net" "os" "regexp" "strconv" "testing" "time" "github.com/mdlayher/vsock" "github.com/mdlayher/vsock/internal/vsutil" "golang.org/x/net/nettest" ) 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 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.Listen(0) 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)) } 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") }) l, err := vsock.Listen(0) if err != nil { vsutil.SkipDeviceError(t, err) t.Fatalf("failed to create vsock listener: %v", err) } return l, func() { // Clean up the timer and this listener. timer.Stop() _ = l.Close() } } func makeVSockPipe() nettest.MakePipe { return makeLocalPipe( func() (net.Listener, error) { return vsock.Listen(0) }, func(addr net.Addr) (net.Conn, error) { a := addr.(*vsock.Addr) return vsock.Dial(a.ContextID, a.Port) }, ) } // 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 } } } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/internal/000077500000000000000000000000001413134772700233025ustar00rootroot00000000000000golang-github-mdlayher-vsock-0.0~git20210303.10d5918/internal/vsutil/000077500000000000000000000000001413134772700246305ustar00rootroot00000000000000golang-github-mdlayher-vsock-0.0~git20210303.10d5918/internal/vsutil/vsutil.go000066400000000000000000000046411413134772700265120ustar00rootroot00000000000000// Package vsutil provides added functionality for package vsock-internal use. package vsutil import ( "net" "os" "testing" "time" "github.com/mdlayher/vsock" ) // Accept blocks until a single connection is accepted by the net.Listener. // // If timeout is non-zero, the listener will be closed after the timeout // expires, even if no connection was accepted. func Accept(l net.Listener, timeout time.Duration) (net.Conn, error) { // This function accommodates both Go1.12+ and Go1.11 functionality to allow // net.Listener.Accept to be canceled by net.Listener.Close. // // If a timeout is set, set up a timer to close the listener and either: // - Go 1.12+: unblock the call to Accept // - Go 1.11 : eventually halt the loop due to closed file descriptor // // For Go 1.12+, we could use vsock.Listener.SetDeadline, but this approach // using a timer works for Go 1.11 as well. cancel := func() {} if timeout != 0 { timer := time.AfterFunc(timeout, func() { _ = l.Close() }) cancel = func() { timer.Stop() } } for { c, err := l.Accept() if err != nil { if nerr, ok := err.(net.Error); ok && nerr.Temporary() { time.Sleep(250 * time.Millisecond) continue } return nil, err } // Got a connection, stop the timer. cancel() return c, nil } } // 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") } } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/listener_linux.go000066400000000000000000000052731413134772700250700ustar00rootroot00000000000000//+build linux package vsock import ( "net" "time" "golang.org/x/sys/unix" ) var _ net.Listener = &listener{} // A listener is the net.Listener implementation for connection-oriented // VM sockets. type listener struct { fd listenFD 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.fd.Close() } func (l *listener) SetDeadline(t time.Time) error { return l.fd.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) { // Mimic what internal/poll does and close on exec, but leave it up to // newConn to set non-blocking mode. // See: https://golang.org/src/internal/poll/sock_cloexec.go. // // TODO(mdlayher): acquire syscall.ForkLock.RLock here once the Go 1.11 // code can be removed and we're fully using the runtime network poller in // non-blocking mode. cfd, sa, err := l.fd.Accept4(unix.SOCK_CLOEXEC) if err != nil { return nil, err } savm := sa.(*unix.SockaddrVM) remote := &Addr{ ContextID: savm.CID, Port: savm.Port, } return newConn(cfd, l.addr, remote) } // listen is the entry point for Listen on Linux. func listen(cid, port uint32) (*Listener, error) { lfd, err := newListenFD() if err != nil { return nil, err } return listenLinux(lfd, cid, port) } // listenLinux is the entry point for tests on Linux. func listenLinux(lfd listenFD, cid, port uint32) (l *Listener, err error) { defer func() { if err != nil { // If any system calls fail during setup, the socket must be closed // to avoid file descriptor leaks. _ = lfd.EarlyClose() } }() // Zero-value for "any port" is friendlier in Go than a constant. if port == 0 { port = unix.VMADDR_PORT_ANY } sa := &unix.SockaddrVM{ CID: cid, Port: port, } if err := lfd.Bind(sa); err != nil { return nil, err } if err := lfd.Listen(unix.SOMAXCONN); err != nil { return nil, err } lsa, err := lfd.Getsockname() if err != nil { return nil, err } // Done with blocking mode setup, transition to non-blocking before the // caller has a chance to start calling things concurrently that might make // the locking situation tricky. // // Note: if any calls fail after this point, lfd.Close should be invoked // for cleanup because the socket is now non-blocking. if err := lfd.SetNonblocking("vsock-listen"); err != nil { return nil, err } lsavm := lsa.(*unix.SockaddrVM) addr := &Addr{ ContextID: lsavm.CID, Port: lsavm.Port, } return &Listener{ l: &listener{ fd: lfd, addr: addr, }, }, nil } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/listener_linux_test.go000066400000000000000000000072651413134772700261320ustar00rootroot00000000000000package vsock import ( "errors" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/sys/unix" ) func Test_listenLinuxErrorClosesFile(t *testing.T) { var closed bool lfd := &testListenFD{ // Track when fd.Close is called. close: func() error { closed = true return nil }, // Always return an error on bind. bind: func(sa unix.Sockaddr) error { return errors.New("error during bind") }, } if _, err := listenLinux(lfd, 0, 0); err == nil { t.Fatal("expected an error, but none occurred") } if diff := cmp.Diff(true, closed); diff != "" { t.Fatalf("unexpected closed value (-want +got):\n%s", diff) } } func Test_listenLinuxPortZero(t *testing.T) { const ( cid uint32 = Host port uint32 = 0 ) lsa := &unix.SockaddrVM{ CID: cid, // Expect 0 to be turned into "any port". Port: unix.VMADDR_PORT_ANY, } lfd := &testListenFD{ bind: func(sa unix.Sockaddr) error { if diff := cmp.Diff(lsa, sa.(*unix.SockaddrVM), cmp.AllowUnexported(*lsa)); diff != "" { t.Fatalf("unexpected bind sockaddr (-want +got):\n%s", diff) } return nil }, listen: func(n int) error { return nil }, getsockname: func() (unix.Sockaddr, error) { return lsa, nil }, setNonblocking: func(_ string) error { return nil }, } if _, err := listenLinux(lfd, cid, port); err != nil { t.Fatalf("failed to listen: %v", err) } } func Test_listenLinuxFull(t *testing.T) { const ( cid uint32 = Host port uint32 = 1024 ) lsa := &unix.SockaddrVM{ CID: cid, Port: port, } var nonblocking bool lfd := &testListenFD{ bind: func(sa unix.Sockaddr) error { if diff := cmp.Diff(lsa, sa.(*unix.SockaddrVM), cmp.AllowUnexported(*lsa)); diff != "" { t.Fatalf("unexpected bind sockaddr (-want +got):\n%s", diff) } return nil }, listen: func(n int) error { if diff := cmp.Diff(unix.SOMAXCONN, n); diff != "" { t.Fatalf("unexpected listen backlog (-want +got):\n%s", diff) } return nil }, getsockname: func() (unix.Sockaddr, error) { return lsa, nil }, setNonblocking: func(_ string) error { nonblocking = true return nil }, } l, err := listenLinux(lfd, cid, port) if err != nil { t.Fatalf("failed to listen: %v", err) } want := &Addr{ ContextID: cid, Port: port, } if diff := cmp.Diff(true, nonblocking); diff != "" { t.Fatalf("unexpected non-blocking value (-want +got):\n%s", diff) } if diff := cmp.Diff(want, l.Addr()); diff != "" { t.Fatalf("unexpected local address (-want +got):\n%s", diff) } } func Test_listenerAccept(t *testing.T) { const ( cid uint32 = 3 port uint32 = 1024 ) var nonblocking bool accept4Fn := func(flags int) (connFD, unix.Sockaddr, error) { if diff := cmp.Diff(unix.SOCK_CLOEXEC, flags); diff != "" { t.Fatalf("unexpected accept4 flags (-want +got):\n%s", diff) } acceptFD := &testConnFD{ setNonblocking: func(_ string) error { nonblocking = true return nil }, } acceptSA := &unix.SockaddrVM{ CID: cid, Port: port, } return acceptFD, acceptSA, nil } localAddr := &Addr{ ContextID: Host, Port: port, } l := &listener{ fd: &testListenFD{ accept4: accept4Fn, }, addr: localAddr, } nc, err := l.Accept() if err != nil { t.Fatalf("failed to accept: %v", err) } // Accept must produce *Conn. c := nc.(*Conn) if !nonblocking { t.Fatal("file descriptor was not set to non-blocking mode") } if diff := cmp.Diff(localAddr, c.LocalAddr()); diff != "" { t.Fatalf("unexpected local address (-want +got):\n%s", diff) } remoteAddr := &Addr{ ContextID: cid, Port: port, } if diff := cmp.Diff(remoteAddr, c.RemoteAddr()); diff != "" { t.Fatalf("unexpected remote address (-want +got):\n%s", diff) } } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/vsock.go000066400000000000000000000263701413134772700231520ustar00rootroot00000000000000package vsock import ( "errors" "fmt" "io" "net" "os" "strings" "syscall" "time" ) const ( // Hypervisor specifies that a socket should communicate with the hypervisor // process. Hypervisor = 0x0 // Host specifies that a socket should communicate with processes other than // the hypervisor on the host machine. Host = 0x2 // cidReserved is a reserved context ID that is no longer in use, // and cannot be used for socket communications. cidReserved = 0x1 // shutRd and shutWr are arguments for unix.Shutdown, copied here to avoid // importing x/sys/unix in cross-platform code. shutRd = 0 // unix.SHUT_RD shutWr = 1 // unix.SHUT_WR // 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" ) // Listen opens a connection-oriented net.Listener for incoming VM sockets // connections. The port parameter specifies the port for the Listener. // // 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. // // When the Listener is no longer needed, Close must be called to free resources. func Listen(port uint32) (*Listener, error) { cid, err := ContextID() if err != nil { // No addresses available. return nil, opError(opListen, err, nil, nil) } l, err := listen(cid, port) if err != nil { // No remote address available. return nil, opError(opListen, err, &Addr{ ContextID: cid, Port: port, }, 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. // // SetDeadline only works with Go 1.12+. 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 server. // The contextID and port parameters specify the address of the server. // // 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) (*Conn, error) { c, err := dial(contextID, port) 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{} var _ syscall.Conn = &Conn{} // A Conn is a VM sockets implementation of a net.Conn. type Conn struct { fd connFD local *Addr remote *Addr } // Close closes the connection. func (c *Conn) Close() error { return c.opError(opClose, c.fd.Close()) } // CloseRead shuts down the reading side of the VM sockets connection. Most // callers should just use Close. // // CloseRead only works with Go 1.12+. func (c *Conn) CloseRead() error { return c.opError(opClose, c.fd.Shutdown(shutRd)) } // CloseWrite shuts down the writing side of the VM sockets connection. Most // callers should just use Close. // // CloseWrite only works with Go 1.12+. func (c *Conn) CloseWrite() error { return c.opError(opClose, c.fd.Shutdown(shutWr)) } // 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.fd.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.fd.Write(b) if err != nil { return n, c.opError(opWrite, err) } return n, nil } // A deadlineType specifies the type of deadline to set for a Conn. type deadlineType int // Possible deadlineType values. const ( deadline deadlineType = iota readDeadline writeDeadline ) // SetDeadline implements the net.Conn SetDeadline method. func (c *Conn) SetDeadline(t time.Time) error { return c.opError(opSet, c.fd.SetDeadline(t, deadline)) } // SetReadDeadline implements the net.Conn SetReadDeadline method. func (c *Conn) SetReadDeadline(t time.Time) error { return c.opError(opSet, c.fd.SetDeadline(t, readDeadline)) } // SetWriteDeadline implements the net.Conn SetWriteDeadline method. func (c *Conn) SetWriteDeadline(t time.Time) error { return c.opError(opSet, c.fd.SetDeadline(t, writeDeadline)) } // SyscallConn returns a raw network connection. This implements the // syscall.Conn interface. func (c *Conn) SyscallConn() (syscall.RawConn, error) { rc, err := c.fd.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) } 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 *Addr 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 uint32 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 cidReserved: host = fmt.Sprintf("reserved(%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 } // 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, } } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/vsock_linux_test.go000066400000000000000000000110701413134772700254170ustar00rootroot00000000000000package 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() } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/vsock_others.go000066400000000000000000000031151413134772700245260ustar00rootroot00000000000000//+build !linux package vsock import ( "fmt" "net" "runtime" "syscall" "time" ) var ( // errUnimplemented is returned by all functions on platforms that // cannot make use of VM sockets. errUnimplemented = fmt.Errorf("vsock: not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) ) func listen(_, _ uint32) (*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) (*Conn, error) { return nil, errUnimplemented } type connFD struct{} func (*connFD) LocalAddr() net.Addr { return nil } func (*connFD) RemoteAddr() net.Addr { return nil } func (*connFD) SetDeadline(_ time.Time, _ deadlineType) error { return errUnimplemented } func (*connFD) Read(_ []byte) (int, error) { return 0, errUnimplemented } func (*connFD) Write(_ []byte) (int, error) { return 0, errUnimplemented } func (*connFD) Close() error { return errUnimplemented } func (*connFD) Shutdown(_ int) error { return errUnimplemented } func (*connFD) SyscallConn() (syscall.RawConn, error) { return nil, errUnimplemented } func contextID() (uint32, error) { return 0, errUnimplemented } func isErrno(_ error, _ int) bool { return false } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/vsock_others_test.go000066400000000000000000000007631413134772700255730ustar00rootroot00000000000000//+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); want != got { t.Fatalf("unexpected error from listen:\n- want: %v\n- got: %v", want, got) } if _, got := dial(0, 0); want != got { t.Fatalf("unexpected error from dial:\n- want: %v\n- got: %v", want, got) } } golang-github-mdlayher-vsock-0.0~git20210303.10d5918/vsock_test.go000066400000000000000000000013111413134772700241750ustar00rootroot00000000000000package 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: cidReserved, port: 20, s: "vsock:reserved(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) } }) } }