Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions conn_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//go:build darwin
// +build darwin

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(ctx context.Context, 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(ctx, 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
}
4 changes: 2 additions & 2 deletions conn_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
type conn = socket.Conn

// dial is the entry point for Dial on Linux.
func dial(cid, port uint32, _ *Config) (*Conn, error) {
func dial(ctx context.Context, cid, port uint32, _ *Config) (*Conn, error) {
// TODO(mdlayher): Config default nil check and initialize. Pass options to
// socket.Config where necessary.

Expand All @@ -25,7 +25,7 @@ func dial(cid, port uint32, _ *Config) (*Conn, error) {
}

sa := &unix.SockaddrVM{CID: cid, Port: port}
rsa, err := c.Connect(context.Background(), sa)
rsa, err := c.Connect(ctx, sa)
if err != nil {
_ = c.Close()
return nil, err
Expand Down
37 changes: 37 additions & 0 deletions fd_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package vsock

import (
"fmt"

"golang.org/x/sys/unix"
)

// contextID retrieves the local context ID for this system.
func contextID() (uint32, error) {
if fd, err := unix.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0); err != nil {
return 2, nil
} else {
defer unix.Close(fd)

cid, err := unix.IoctlGetInt(fd, unix.IOCTL_VM_SOCKETS_GET_LOCAL_CID)

return uint32(cid), err
}
}
Comment on lines +10 to +20

// 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
}
}
Comment on lines +22 to +33

func panicf(format string, a ...interface{}) {
panic(fmt.Sprintf(format, a...))
}
133 changes: 133 additions & 0 deletions listener_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//go:build darwin
// +build darwin

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) {
Comment on lines +54 to +55
// 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) {
Comment on lines +90 to +91
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)
Comment on lines +118 to +119
}

addr := &Addr{
ContextID: lsavm.CID,
Port: lsavm.Port,
}

return &Listener{
l: &listener{
c: c,
addr: addr,
},
}, nil
}
22 changes: 21 additions & 1 deletion vsock.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package vsock

import (
"context"
"fmt"
"io"
"net"
"os"
"runtime"
"strings"
"syscall"
"time"
Expand Down Expand Up @@ -53,6 +55,10 @@
opWrite = "write"
)

// 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)

Check failure on line 60 in vsock.go

View workflow job for this annotation

GitHub Actions / build (1.26)

var errUnimplemented is unused (unused)

// TODO(mdlayher): plumb through socket.Config.NetNS if it makes sense.

// Config contains options for a Conn or Listener.
Expand Down Expand Up @@ -175,7 +181,21 @@
// 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)
return dial(context.Background(), contextID, port, cfg)
}

// DialWithContext connects to the address on the named network using
// the provided context.
//
// The provided Context must be non-nil. If the context expires before
// the connection is complete, an error is returned. Once successfully
// connected, any expiration of the context will not affect the
// connection.
Comment on lines +190 to +193
//
// See func Dial for a description of the contextID and port
// parameters.
func DialWithContext(ctx context.Context, contextID, port uint32, cfg *Config) (*Conn, error) {
c, err := dial(ctx, contextID, port, cfg)
Comment on lines 183 to +198
if err != nil {
// No local address, but we have a remote address we can return.
return nil, opError(opDial, err, nil, &Addr{
Expand Down
Loading
Loading