kvm/vendor/github.com/pojntfx/go-nbd/pkg/server/nbd.go

429 lines
11 KiB
Go

package server
import (
"bytes"
"encoding/binary"
"errors"
"io"
"net"
"github.com/pojntfx/go-nbd/pkg/backend"
"github.com/pojntfx/go-nbd/pkg/protocol"
)
var (
ErrInvalidMagic = errors.New("invalid magic")
ErrInvalidBlocksize = errors.New("invalid blocksize")
)
const (
defaultMaximumRequestSize = 32 * 1024 * 1024 // Support for a 32M maximum packet size is expected: https://sourceforge.net/p/nbd/mailman/message/35081223/
)
type Export struct {
Name string
Description string
Backend backend.Backend
}
type Options struct {
ReadOnly bool
MinimumBlockSize uint32
PreferredBlockSize uint32
MaximumBlockSize uint32
MaximumRequestSize int
SupportsMultiConn bool
}
func Handle(conn net.Conn, exports []*Export, options *Options) error {
if options == nil {
options = &Options{
ReadOnly: false,
SupportsMultiConn: true,
}
}
if options.MinimumBlockSize == 0 {
options.MinimumBlockSize = 1
}
if options.PreferredBlockSize == 0 {
options.PreferredBlockSize = 4096
}
if options.MaximumBlockSize == 0 {
options.MaximumBlockSize = defaultMaximumRequestSize
}
if options.MaximumRequestSize == 0 {
options.MaximumRequestSize = defaultMaximumRequestSize
}
// Negotiation
if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationNewstyleHeader{
OldstyleMagic: protocol.NEGOTIATION_MAGIC_OLDSTYLE,
OptionMagic: protocol.NEGOTIATION_MAGIC_OPTION,
HandshakeFlags: protocol.NEGOTIATION_HANDSHAKE_FLAG_FIXED_NEWSTYLE,
}); err != nil {
return err
}
_, err := io.CopyN(io.Discard, conn, 4) // Discard client flags (uint32)
if err != nil {
return err
}
var export *Export
n:
for {
var optionHeader protocol.NegotiationOptionHeader
if err := binary.Read(conn, binary.BigEndian, &optionHeader); err != nil {
return err
}
if optionHeader.OptionMagic != protocol.NEGOTIATION_MAGIC_OPTION {
return ErrInvalidMagic
}
switch optionHeader.ID {
case protocol.NEGOTIATION_ID_OPTION_INFO, protocol.NEGOTIATION_ID_OPTION_GO:
var exportNameLength uint32
if err := binary.Read(conn, binary.BigEndian, &exportNameLength); err != nil {
return err
}
exportName := make([]byte, exportNameLength)
if _, err := io.ReadFull(conn, exportName); err != nil {
return err
}
for _, candidate := range exports {
if candidate.Name == string(exportName) {
export = candidate
break
}
}
if export == nil {
if length := int64(optionHeader.Length) - 4 - int64(exportNameLength); length > 0 { // Discard the option's data, minus the export name length and export name we've already read
_, err := io.CopyN(io.Discard, conn, length)
if err != nil {
return err
}
}
if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
ID: optionHeader.ID,
Type: protocol.NEGOTIATION_TYPE_REPLY_ERR_UNKNOWN,
Length: 0,
}); err != nil {
return err
}
break
}
size, err := export.Backend.Size()
if err != nil {
return err
}
{
var informationRequestCount uint16
if err := binary.Read(conn, binary.BigEndian, &informationRequestCount); err != nil {
return err
}
_, err := io.CopyN(io.Discard, conn, 2*int64(informationRequestCount)) // Discard information requests (uint16s)
if err != nil {
return err
}
}
{
transmissionFlags := uint16(0)
if options.SupportsMultiConn {
transmissionFlags = protocol.NEGOTIATION_REPLY_FLAGS_HAS_FLAGS | protocol.NEGOTIATION_REPLY_FLAGS_CAN_MULTI_CONN
}
info := &bytes.Buffer{}
if err := binary.Write(info, binary.BigEndian, protocol.NegotiationReplyInfo{
Type: protocol.NEGOTIATION_TYPE_INFO_EXPORT,
Size: uint64(size),
TransmissionFlags: transmissionFlags,
}); err != nil {
return err
}
if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
ID: optionHeader.ID,
Type: protocol.NEGOTIATION_TYPE_REPLY_INFO,
Length: uint32(info.Len()),
}); err != nil {
return err
}
if _, err := io.Copy(conn, info); err != nil {
return err
}
}
{
info := &bytes.Buffer{}
if err := binary.Write(info, binary.BigEndian, protocol.NegotiationReplyNameHeader{
Type: protocol.NEGOTIATION_TYPE_INFO_NAME,
}); err != nil {
return err
}
if _, err := info.Write([]byte(exportName)); err != nil {
return err
}
if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
ID: optionHeader.ID,
Type: protocol.NEGOTIATION_TYPE_REPLY_INFO,
Length: uint32(info.Len()),
}); err != nil {
return err
}
if _, err := io.Copy(conn, info); err != nil {
return err
}
}
{
info := &bytes.Buffer{}
if err := binary.Write(info, binary.BigEndian, protocol.NegotiationReplyDescriptionHeader{
Type: protocol.NEGOTIATION_TYPE_INFO_DESCRIPTION,
}); err != nil {
return err
}
if err := binary.Write(info, binary.BigEndian, []byte(export.Description)); err != nil {
return err
}
if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
ID: optionHeader.ID,
Type: protocol.NEGOTIATION_TYPE_REPLY_INFO,
Length: uint32(info.Len()),
}); err != nil {
return err
}
if _, err := io.Copy(conn, info); err != nil {
return err
}
}
{
info := &bytes.Buffer{}
if err := binary.Write(info, binary.BigEndian, protocol.NegotiationReplyBlockSize{
Type: protocol.NEGOTIATION_TYPE_INFO_BLOCKSIZE,
MinimumBlockSize: options.MinimumBlockSize,
PreferredBlockSize: options.PreferredBlockSize,
MaximumBlockSize: options.MaximumBlockSize,
}); err != nil {
return err
}
if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
ID: optionHeader.ID,
Type: protocol.NEGOTIATION_TYPE_REPLY_INFO,
Length: uint32(info.Len()),
}); err != nil {
return err
}
if _, err := io.Copy(conn, info); err != nil {
return err
}
}
if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
ID: optionHeader.ID,
Type: protocol.NEGOTIATION_TYPE_REPLY_ACK,
Length: 0,
}); err != nil {
return err
}
if optionHeader.ID == protocol.NEGOTIATION_ID_OPTION_GO {
break n
}
case protocol.NEGOTIATION_ID_OPTION_ABORT:
if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
ID: optionHeader.ID,
Type: protocol.NEGOTIATION_TYPE_REPLY_ACK,
Length: 0,
}); err != nil {
return err
}
return nil
case protocol.NEGOTIATION_ID_OPTION_LIST:
{
info := &bytes.Buffer{}
for _, export := range exports {
exportName := []byte(export.Name)
if err := binary.Write(info, binary.BigEndian, uint32(len(exportName))); err != nil {
return err
}
if err := binary.Write(info, binary.BigEndian, exportName); err != nil {
return err
}
}
if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
ID: optionHeader.ID,
Type: protocol.NEGOTIATION_TYPE_REPLY_SERVER,
Length: uint32(info.Len()),
}); err != nil {
return err
}
if _, err := io.Copy(conn, info); err != nil {
return err
}
}
if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
ID: optionHeader.ID,
Type: protocol.NEGOTIATION_TYPE_REPLY_ACK,
Length: 0,
}); err != nil {
return err
}
default:
_, err := io.CopyN(io.Discard, conn, int64(optionHeader.Length)) // Discard the unknown option's data
if err != nil {
return err
}
if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
ID: optionHeader.ID,
Type: protocol.NEGOTIATION_TYPE_REPLY_ERR_UNSUPPORTED,
Length: 0,
}); err != nil {
return err
}
}
}
// Transmission
b := []byte{}
for {
var requestHeader protocol.TransmissionRequestHeader
if err := binary.Read(conn, binary.BigEndian, &requestHeader); err != nil {
return err
}
if requestHeader.RequestMagic != protocol.TRANSMISSION_MAGIC_REQUEST {
return ErrInvalidMagic
}
length := requestHeader.Length
if length > defaultMaximumRequestSize {
return ErrInvalidBlocksize
}
if length != uint32(len(b)) {
b = make([]byte, length)
}
switch requestHeader.Type {
case protocol.TRANSMISSION_TYPE_REQUEST_READ:
if err := binary.Write(conn, binary.BigEndian, protocol.TransmissionReplyHeader{
ReplyMagic: protocol.TRANSMISSION_MAGIC_REPLY,
Error: 0,
Handle: requestHeader.Handle,
}); err != nil {
return err
}
n, err := export.Backend.ReadAt(b[:length], int64(requestHeader.Offset))
if err != nil {
return err
}
if _, err := conn.Write(b[:n]); err != nil {
return err
}
case protocol.TRANSMISSION_TYPE_REQUEST_WRITE:
if options.ReadOnly {
_, err := io.CopyN(io.Discard, conn, int64(requestHeader.Length)) // Discard the write command's data
if err != nil {
return err
}
if err := binary.Write(conn, binary.BigEndian, protocol.TransmissionReplyHeader{
ReplyMagic: protocol.TRANSMISSION_MAGIC_REPLY,
Error: protocol.TRANSMISSION_ERROR_EPERM,
Handle: requestHeader.Handle,
}); err != nil {
return err
}
break
}
n, err := io.ReadAtLeast(conn, b[:length], int(requestHeader.Length))
if err != nil {
return err
}
if _, err := export.Backend.WriteAt(b[:n], int64(requestHeader.Offset)); err != nil {
return err
}
if err := binary.Write(conn, binary.BigEndian, protocol.TransmissionReplyHeader{
ReplyMagic: protocol.TRANSMISSION_MAGIC_REPLY,
Error: 0,
Handle: requestHeader.Handle,
}); err != nil {
return err
}
case protocol.TRANSMISSION_TYPE_REQUEST_DISC:
if !options.ReadOnly {
if err := export.Backend.Sync(); err != nil {
return err
}
}
return nil
default:
_, err := io.CopyN(io.Discard, conn, int64(requestHeader.Length)) // Discard the unknown command's data
if err != nil {
return err
}
if err := binary.Write(conn, binary.BigEndian, protocol.TransmissionReplyHeader{
ReplyMagic: protocol.TRANSMISSION_MAGIC_REPLY,
Error: protocol.TRANSMISSION_ERROR_EINVAL,
Handle: requestHeader.Handle,
}); err != nil {
return err
}
}
}
}