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 @@ -16,7 +16,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 @@ -26,7 +26,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
}
}

// 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...))
}
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ module 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
github.com/google/go-cmp v0.6.0
github.com/mdlayher/socket v0.5.1
golang.org/x/net v0.33.0
golang.org/x/sync v0.10.0
golang.org/x/sys v0.28.0
)
26 changes: 10 additions & 16 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
github.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=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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) {
// 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
}
25 changes: 22 additions & 3 deletions vsock.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package vsock

import (
"errors"
"context"
"fmt"
"io"
"net"
"os"
"runtime"
"strings"
"syscall"
"time"
Expand Down Expand Up @@ -54,6 +55,10 @@ const (
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)

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

// Config contains options for a Conn or Listener.
Expand Down Expand Up @@ -176,7 +181,21 @@ func (l *Listener) opError(op string, err error) error {
// 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.
//
// 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)
if err != nil {
// No local address, but we have a remote address we can return.
return nil, opError(opDial, err, nil, &Addr{
Expand Down Expand Up @@ -403,7 +422,7 @@ func opError(op string, err error, local, remote net.Addr) error {
//
// 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")
err = net.ErrClosed
default:
// Nothing to do, return this directly.
}
Expand Down
Loading