Skip to content

vsock — gRPC over AF_VSOCK

github.com/grpc-transports/vsock is a pure-Go (CGO=0) transport for gRPC over the Linux AF_VSOCK address family, with no third-party dependencies.

AF_VSOCK is the host↔guest socket family used by KVM/QEMU virtio-vsock and Apple Virtualization (VZVirtioSocketDevice). Both expose the same kernel interface to a Linux guest, so one implementation serves either hypervisor. An address is a (context id, port) pair: the context id ("CID") names a VM, the port names a service inside it — no IP stack is needed in the guest.

github.com/grpc-transports/vsock

The server's Listen returns connections you hand straight to grpc.Server.Serve, and Dialer.DialContext has the signature grpc.WithContextDialer expects — but nothing here depends on gRPC; the surface is plain net.Conn / net.Listener.

Reserved CIDs

Constant Value Meaning
CIDAny 0xffffffff VMADDR_CID_ANY — listener wildcard
CIDHypervisor 0 VMADDR_CID_HYPERVISOR
CIDLocal 1 VMADDR_CID_LOCAL — loopback
CIDHost 2 VMADDR_CID_HOST — a guest dials this to reach its host

Real guest CIDs are 3 or higher.

API

Server

// Listen binds an AF_VSOCK listener on (CIDAny, port) and returns a
// net.Listener whose connections are ready for grpc.Server.Serve.
// port 0 lets the kernel choose an ephemeral port.
func Listen(port uint32) (net.Listener, error)

Accepted connections report the peer's Addr from RemoteAddr(), so gRPC handlers can read the caller's CID through peer.FromContext — enough to reject calls that did not originate from a guest VM.

Client

// Dial opens an AF_VSOCK stream connection to (cid, port).
func Dial(cid, port uint32) (net.Conn, error)

// Dialer adds an optional connect-retry policy; the zero Dialer does one attempt.
type Dialer struct {
    Retries int           // additional attempts after the first
    Delay   time.Duration // slept between attempts
}
func (d Dialer) Dial(cid, port uint32) (net.Conn, error)
func (d Dialer) DialContext(ctx context.Context, addr string) (net.Conn, error) // addr = "<cid>:<port>"

Helpers

func LocalCID() uint32                                  // this context's own guest CID (0 if unknown)
func Supported() bool                                   // does the running kernel expose AF_VSOCK?
func AllocateCID(namespace, identifier string) uint32   // deterministic non-reserved CID

Usage

Host side (agent) — listen for guests:

lis, err := vsock.Listen(5555)
if err != nil {
    log.Fatal(err)
}
grpcServer.Serve(lis)

Guest side — dial the host, with a short boot-race retry:

opt := grpc.WithContextDialer(
    (vsock.Dialer{Retries: 10, Delay: 100 * time.Millisecond}).DialContext,
)
conn, err := grpc.NewClient(
    fmt.Sprintf("%d:%d", vsock.CIDHost, 5555),
    opt,
    grpc.WithTransportCredentials(insecure.NewCredentials()),
)

Platform support

The transport is Linux-only. On every other GOOS the package still builds and go vets: Dial, Listen, LocalCID and Supported are present but return ErrUnsupported (or a zero value), so cross-platform callers compile without a build-tag fork of their own. The pure CID, address, and sockaddr_vm logic runs everywhere.

CI runs the suite on all six 64-bit Go arches — amd64, arm64, riscv64, loong64, ppc64le, and big-endian s390x (a genuine endianness check of the native-order sockaddr_vm marshalling) — behind a hard 100% statement-coverage gate, error branches included. No vsock device is needed: the syscall layer is reached through package seams the tests swap for in-memory fakes.