nbd

package module
v0.0.0-...-205f1b7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 2, 2026 License: BSD-3-Clause Imports: 10 Imported by: 1

README

nbd

Go Reference CI

A pure-Go (CGO_ENABLED=0), standard-library-only NBD (Network Block Device) server and client. The server exports a volume.Device so a remote client — the Linux kernel nbd-client, qemu-nbd/QEMU, libnbd/nbdinfo/nbdcopy, or this package's own client — can read and write a go-volumes block volume over the network. The client dials a fixed-newstyle NBD server and exposes the remote export as a volume.Device, so a remote volume is consumed exactly like a local one (e.g. as a synchronous replica in github.com/go-volumes/replica).

It implements the fixed-newstyle handshake and the transmission phase of the NBD protocol. All multi-byte integers are big-endian (network byte order); correctness is validated on big-endian s390x (and the other 64-bit targets) in CI.

Install

go get github.com/go-volumes/nbd

Dependencies: the Go standard library and github.com/go-volumes/interface only.

Usage

srv := &nbd.Server{
    Exports: []nbd.Export{
        {Name: "", Device: dev},          // default export
        {Name: "data", Device: other},    // named export
    },
    Log: log.Default(), // optional; nil discards
}

ln, _ := net.Listen("tcp", ":10809")
log.Fatal(srv.Serve(ln)) // one goroutine per connection

A read-only export from a volume.ReadOnly backing:

exp := nbd.ReadOnlyExport("iso", roBacking)

Server.Handle(conn net.Conn) runs a single connection (useful for custom listeners or net.Pipe in tests).

Client
cli, err := nbd.DialExport(ctx, "host:10809", "data")
if err != nil { /* ... */ }
defer cli.Close() // sends NBD_CMD_DISC, then closes the connection

var dev volume.Device = cli            // *Client satisfies volume.Device
n, _ := dev.ReadAt(buf, off)           // → NBD_CMD_READ
_, _ = dev.WriteAt(buf, off)           // → NBD_CMD_WRITE (rejected if read-only)
_ = dev.Sync()                         // → NBD_CMD_FLUSH

Dial/DialExport perform the client side of the fixed-newstyle handshake: they read NBDMAGIC/IHAVEOPT/flags, send C_FIXED_NEWSTYLE, then negotiate with NBD_OPT_GO (consuming NBD_REP_INFO/NBD_INFO_EXPORT → NBD_REP_ACK), falling back to NBD_OPT_EXPORT_NAME when the server lacks NBD_OPT_GO (WithExportNameOpt() forces the legacy path). The negotiated size feeds Size(); the transmission flags drive read-only rejection and Discard (only available when the server advertised SEND_TRIM, satisfying volume.Discarder). A *Client is safe for concurrent use — one background goroutine reads all replies and demultiplexes them to callers by request handle — so a replication engine can issue concurrent operations against it. Server errnos surface as Go errors matchable with errors.Is against nbd.ErrEPERM/ErrEINVAL/ErrEIO/ ErrENOSPC/ErrEOVERFLOW.

Connecting with a real client

With the bundled example (cmd/nbd-serve) serving a file on :10809:

# Query the default (empty-name) export
nbdinfo nbd://localhost:10809/

# Attach the default export as a block device
sudo nbd-client localhost 10809 /dev/nbd0

# Or via QEMU
sudo qemu-nbd --connect=/dev/nbd0 nbd://localhost:10809/

# A named export uses nbd://host:port/NAME
nbdinfo nbd://localhost:10809/data

Protocol coverage

Handshake (server, fixed-newstyle): sends NBDMAGIC + IHAVEOPT + FIXED_NEWSTYLE | NO_ZEROES; requires C_FIXED_NEWSTYLE from the client. Option haggling supports NBD_OPT_EXPORT_NAME (legacy), NBD_OPT_GO, NBD_OPT_INFO, NBD_OPT_LIST, and NBD_OPT_ABORT; unknown options reply NBD_REP_ERR_UNSUP, unknown exports NBD_REP_ERR_UNKNOWN.

Transmission flags advertised per export: HAS_FLAGS | SEND_FLUSH | SEND_FUA, plus SEND_TRIM when the device implements volume.Discarder, plus READ_ONLY for a read-only export.

Commands: READ, WRITE (honouring the FUA flag → Sync), FLUSH, TRIM (→ Discarder.Discard), DISC. Errors map to NBD/Linux errnos: EPERM (write to a read-only export), EINVAL (bad command, out-of-bounds, oversized > 64 MiB), EOVERFLOW, ENOSPC (via the nbd.ErrNoSpace sentinel), EIO (other device errors).

License

BSD-3-Clause — see LICENSE.

Documentation

Overview

Package nbd implements both sides of the Network Block Device (NBD) protocol over the fixed-newstyle handshake. Server exports a go-volumes volume.Device so a remote client (the Linux kernel nbd-client, qemu-nbd, libnbd/nbdinfo/nbdcopy, or this package's own Client) can read and write a block volume across a network connection; Client dials a remote NBD server and exposes the negotiated export as a volume.Device, so a remote volume is consumed exactly like a local one.

The wire protocol follows github.com/NetworkBlockDevice/nbd doc/proto.md. All multi-byte integers are big-endian (network byte order); both sides are therefore exercised on big-endian targets (s390x) in CI.

The implementation is pure Go, CGO-free, and depends only on the standard library and github.com/go-volumes/interface.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrEPERM     = errors.New("nbd: operation not permitted")
	ErrEIO       = errors.New("nbd: input/output error")
	ErrEINVAL    = errors.New("nbd: invalid argument")
	ErrENOSPC    = errors.New("nbd: no space left on device")
	ErrEOVERFLOW = errors.New("nbd: value too large")
)

Exported NBD errno sentinels for errors.Is matching on server-returned codes.

View Source
var ErrNoSpace = errNoSpace

ErrNoSpace is the sentinel a volume.Device may wrap from WriteAt to have the server report NBD ENOSPC to the client instead of the generic EIO.

View Source
var ErrReadOnly = errReadOnly

ErrReadOnly is returned by WriteAt/Discard when the negotiated export is read-only (it is the same sentinel the server adapter uses).

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is an NBD client: it dials a fixed-newstyle NBD server, negotiates an export, and exposes it as a volume.Device. ReadAt/WriteAt/Sync/Discard issue NBD_CMD_READ/WRITE/FLUSH/TRIM and Close sends NBD_CMD_DISC before closing the connection.

A Client is safe for concurrent use: a single background goroutine owns all reads from the connection and demultiplexes replies to waiting callers by request handle, while callers serialize their request writes under a mutex. This lets the replication engine in github.com/go-volumes/replica issue concurrent ReadAt/WriteAt/Sync against one Client.

func Dial

func Dial(ctx context.Context, addr string, opts ...ClientOption) (*Client, error)

Dial connects to an NBD server at addr (tcp) and negotiates the default export via the fixed-newstyle handshake, returning a Client over it.

func DialExport

func DialExport(ctx context.Context, addr, exportName string, opts ...ClientOption) (*Client, error)

DialExport is Dial with an explicit export name (equivalent to Dial(ctx, addr, WithExportName(name), opts...)).

func NewClient

func NewClient(conn net.Conn, opts ...ClientOption) (*Client, error)

NewClient negotiates over an already-connected net.Conn (e.g. one end of a net.Pipe), taking ownership of conn. It is the seam tests drive against the in-repo Server.

func (*Client) Close

func (c *Client) Close() error

Close sends NBD_CMD_DISC (best effort) and closes the connection. It is safe to call more than once.

func (*Client) Discard

func (c *Client) Discard(off, length int64) error

Discard issues NBD_CMD_TRIM for [off, off+length), letting a thin backing reclaim the space. It is only available when the server advertised SEND_TRIM (see TrimSupported); otherwise it returns an error. Discard satisfies volume.Discarder.

func (*Client) ReadAt

func (c *Client) ReadAt(p []byte, off int64) (int, error)

ReadAt issues NBD_CMD_READ and copies the returned data into p. It reads len(p) bytes at off; a short server reply is reported as io.ErrUnexpectedEOF.

func (*Client) ReadOnly

func (c *Client) ReadOnly() bool

ReadOnly reports whether the server advertised the export read-only.

func (*Client) Size

func (c *Client) Size() (int64, error)

Size reports the negotiated export size in bytes.

func (*Client) Sync

func (c *Client) Sync() error

Sync issues NBD_CMD_FLUSH, durably committing the server's buffered writes.

func (*Client) TrimSupported

func (c *Client) TrimSupported() bool

TrimSupported reports whether the negotiated export advertised SEND_TRIM, so callers can decide whether to type-assert for volume.Discarder.

func (*Client) WriteAt

func (c *Client) WriteAt(p []byte, off int64) (int, error)

WriteAt issues NBD_CMD_WRITE with p at off. It rejects the write locally when the server advertised the export read-only.

type ClientOption

type ClientOption func(*clientConfig)

ClientOption configures a Client during Dial.

func WithExportName

func WithExportName(name string) ClientOption

WithExportName selects the export to negotiate (empty = the default export).

func WithExportNameOpt

func WithExportNameOpt() ClientOption

WithExportNameOpt forces the legacy NBD_OPT_EXPORT_NAME negotiation instead of the preferred NBD_OPT_GO. It exists mainly to exercise and support servers that do not implement NBD_OPT_GO.

func WithTimeout

func WithTimeout(d time.Duration) ClientOption

WithTimeout sets a read/write deadline applied to the connection for every handshake and transmission operation. Zero (the default) means no deadline.

type Export

type Export struct {
	// Name is the export name a client selects during negotiation. Empty means
	// the default export.
	Name string
	// Device is the backing block device.
	Device volume.Device
	// ReadOnly forces the export read-only even if Device can write. An export
	// is also read-only when Device only satisfies volume.ReadOnly (see
	// ReadOnlyExport).
	ReadOnly bool
}

Export is a single named block device offered to clients. An export with an empty Name is the default export, served when a client requests the empty export name (e.g. nbd-client without -N, or qemu-nbd's default).

func ReadOnlyExport

func ReadOnlyExport(name string, ro volume.ReadOnly) Export

ReadOnlyExport builds a read-only Export from a volume.ReadOnly backing (a device that exposes only ReadAt/Size/Close). Writes, FUA and TRIM are rejected and the read-only transmission flag is advertised.

type Logger

type Logger interface {
	Printf(format string, v ...any)
}

Logger is the minimal logging sink the server writes connection-level errors to. The standard library's *log.Logger satisfies it. It is injectable so the package keeps no global state; a nil logger discards everything.

type Server

type Server struct {
	// Exports lists the devices offered to clients. The first export whose
	// Name matches the client's request is used; an export with an empty Name
	// is the default.
	Exports []Export
	// Log receives connection-level errors. Nil discards them.
	Log Logger
}

Server exports one or more block devices over NBD. The zero value is not usable; populate Exports. Server is safe for concurrent use and serves one goroutine per connection.

func (*Server) Handle

func (s *Server) Handle(conn net.Conn) (err error)

Handle runs the full NBD lifecycle (fixed-newstyle handshake, option haggling, transmission) on a single connection and closes it on return. It is exported so callers can drive it over a net.Pipe or a custom listener. A clean client disconnect (NBD_CMD_DISC or EOF after negotiation) returns nil.

func (*Server) Serve

func (s *Server) Serve(ln net.Listener) error

Serve accepts connections on ln until ln is closed, handling each in its own goroutine. It returns the accept error (e.g. net.ErrClosed after Close).

Directories

Path Synopsis
cmd
nbd-serve command
Command nbd-serve exports a file as an NBD device on a TCP port so a real external NBD client (the Linux kernel nbd-client, qemu-nbd, nbdcopy, …) can connect to it.
Command nbd-serve exports a file as an NBD device on a TCP port so a real external NBD client (the Linux kernel nbd-client, qemu-nbd, nbdcopy, …) can connect to it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL