keksvpn/vendor/github.com/mdlayher/netlink/message.go
2022-02-27 04:22:11 +01:00

347 lines
9 KiB
Go

package netlink
import (
"errors"
"fmt"
"unsafe"
"github.com/mdlayher/netlink/nlenc"
)
// Flags which may apply to netlink attribute types when communicating with
// certain netlink families.
const (
Nested uint16 = 0x8000
NetByteOrder uint16 = 0x4000
// attrTypeMask masks off Type bits used for the above flags.
attrTypeMask uint16 = 0x3fff
)
// Various errors which may occur when attempting to marshal or unmarshal
// a Message to and from its binary form.
var (
errIncorrectMessageLength = errors.New("netlink message header length incorrect")
errShortMessage = errors.New("not enough data to create a netlink message")
errUnalignedMessage = errors.New("input data is not properly aligned for netlink message")
)
// HeaderFlags specify flags which may be present in a Header.
type HeaderFlags uint16
const (
// General netlink communication flags.
// Request indicates a request to netlink.
Request HeaderFlags = 1
// Multi indicates a multi-part message, terminated by Done on the
// last message.
Multi HeaderFlags = 2
// Acknowledge requests that netlink reply with an acknowledgement
// using Error and, if needed, an error code.
Acknowledge HeaderFlags = 4
// Echo requests that netlink echo this request back to the sender.
Echo HeaderFlags = 8
// DumpInterrupted indicates that a dump was inconsistent due to a
// sequence change.
DumpInterrupted HeaderFlags = 16
// DumpFiltered indicates that a dump was filtered as requested.
DumpFiltered HeaderFlags = 32
// Flags used to retrieve data from netlink.
// Root requests that netlink return a complete table instead of a
// single entry.
Root HeaderFlags = 0x100
// Match requests that netlink return a list of all matching entries.
Match HeaderFlags = 0x200
// Atomic requests that netlink send an atomic snapshot of its entries.
// Requires CAP_NET_ADMIN or an effective UID of 0.
Atomic HeaderFlags = 0x400
// Dump requests that netlink return a complete list of all entries.
Dump HeaderFlags = Root | Match
// Flags used to create objects.
// Replace indicates request replaces an existing matching object.
Replace HeaderFlags = 0x100
// Excl indicates request does not replace the object if it already exists.
Excl HeaderFlags = 0x200
// Create indicates request creates an object if it doesn't already exist.
Create HeaderFlags = 0x400
// Append indicates request adds to the end of the object list.
Append HeaderFlags = 0x800
// Flags for extended acknowledgements.
// Capped indicates the size of a request was capped in an extended
// acknowledgement.
Capped HeaderFlags = 0x100
// AcknowledgeTLVs indicates the presence of netlink extended
// acknowledgement TLVs in a response.
AcknowledgeTLVs HeaderFlags = 0x200
)
// String returns the string representation of a HeaderFlags.
func (f HeaderFlags) String() string {
names := []string{
"request",
"multi",
"acknowledge",
"echo",
"dumpinterrupted",
"dumpfiltered",
}
var s string
left := uint(f)
for i, name := range names {
if f&(1<<uint(i)) != 0 {
if s != "" {
s += "|"
}
s += name
left ^= (1 << uint(i))
}
}
if s == "" && left == 0 {
s = "0"
}
if left > 0 {
if s != "" {
s += "|"
}
s += fmt.Sprintf("%#x", left)
}
return s
}
// HeaderType specifies the type of a Header.
type HeaderType uint16
const (
// Noop indicates that no action was taken.
Noop HeaderType = 0x1
// Error indicates an error code is present, which is also used to indicate
// success when the code is 0.
Error HeaderType = 0x2
// Done indicates the end of a multi-part message.
Done HeaderType = 0x3
// Overrun indicates that data was lost from this message.
Overrun HeaderType = 0x4
)
// String returns the string representation of a HeaderType.
func (t HeaderType) String() string {
switch t {
case Noop:
return "noop"
case Error:
return "error"
case Done:
return "done"
case Overrun:
return "overrun"
default:
return fmt.Sprintf("unknown(%d)", t)
}
}
// NB: the memory layout of Header and Linux's syscall.NlMsgHdr must be
// exactly the same. Cannot reorder, change data type, add, or remove fields.
// Named types of the same size (e.g. HeaderFlags is a uint16) are okay.
// A Header is a netlink header. A Header is sent and received with each
// Message to indicate metadata regarding a Message.
type Header struct {
// Length of a Message, including this Header.
Length uint32
// Contents of a Message.
Type HeaderType
// Flags which may be used to modify a request or response.
Flags HeaderFlags
// The sequence number of a Message.
Sequence uint32
// The port ID of the sending process.
PID uint32
}
// A Message is a netlink message. It contains a Header and an arbitrary
// byte payload, which may be decoded using information from the Header.
//
// Data is often populated with netlink attributes. For easy encoding and
// decoding of attributes, see the AttributeDecoder and AttributeEncoder types.
type Message struct {
Header Header
Data []byte
}
// MarshalBinary marshals a Message into a byte slice.
func (m Message) MarshalBinary() ([]byte, error) {
ml := nlmsgAlign(int(m.Header.Length))
if ml < nlmsgHeaderLen || ml != int(m.Header.Length) {
return nil, errIncorrectMessageLength
}
b := make([]byte, ml)
nlenc.PutUint32(b[0:4], m.Header.Length)
nlenc.PutUint16(b[4:6], uint16(m.Header.Type))
nlenc.PutUint16(b[6:8], uint16(m.Header.Flags))
nlenc.PutUint32(b[8:12], m.Header.Sequence)
nlenc.PutUint32(b[12:16], m.Header.PID)
copy(b[16:], m.Data)
return b, nil
}
// UnmarshalBinary unmarshals the contents of a byte slice into a Message.
func (m *Message) UnmarshalBinary(b []byte) error {
if len(b) < nlmsgHeaderLen {
return errShortMessage
}
if len(b) != nlmsgAlign(len(b)) {
return errUnalignedMessage
}
// Don't allow misleading length
m.Header.Length = nlenc.Uint32(b[0:4])
if int(m.Header.Length) != len(b) {
return errShortMessage
}
m.Header.Type = HeaderType(nlenc.Uint16(b[4:6]))
m.Header.Flags = HeaderFlags(nlenc.Uint16(b[6:8]))
m.Header.Sequence = nlenc.Uint32(b[8:12])
m.Header.PID = nlenc.Uint32(b[12:16])
m.Data = b[16:]
return nil
}
// checkMessage checks a single Message for netlink errors.
func checkMessage(m Message) error {
// NB: All non-nil errors returned from this function *must* be of type
// OpError in order to maintain the appropriate contract with callers of
// this package.
// The libnl documentation indicates that type error can
// contain error codes:
// https://www.infradead.org/~tgr/libnl/doc/core.html#core_errmsg.
//
// However, rtnetlink at least seems to also allow errors to occur at the
// end of a multipart message with done/multi and an error number.
var hasHeader bool
switch {
case m.Header.Type == Error:
// Error code followed by nlmsghdr/ext ack attributes.
hasHeader = true
case m.Header.Type == Done && m.Header.Flags&Multi != 0:
// If no data, there must be no error number so just exit early. Some
// of the unit tests hard-coded this but I don't actually know if this
// case occurs in the wild.
if len(m.Data) == 0 {
return nil
}
// Done|Multi potentially followed by ext ack attributes.
default:
// Neither, nothing to do.
return nil
}
// Errno occupies 4 bytes.
const endErrno = 4
if len(m.Data) < endErrno {
return newOpError("receive", errShortErrorMessage)
}
c := nlenc.Int32(m.Data[:endErrno])
if c == 0 {
// 0 indicates no error.
return nil
}
oerr := &OpError{
Op: "receive",
// Error code is a negative integer, convert it into an OS-specific raw
// system call error, but do not wrap with os.NewSyscallError to signify
// that this error was produced by a netlink message; not a system call.
Err: newError(-1 * int(c)),
}
// TODO(mdlayher): investigate the Capped flag.
if m.Header.Flags&AcknowledgeTLVs == 0 {
// No extended acknowledgement.
return oerr
}
// Flags indicate an extended acknowledgement. The type/flags combination
// checked above determines the offset where the TLVs occur.
var off int
if hasHeader {
// There is an nlmsghdr preceding the TLVs.
if len(m.Data) < endErrno+nlmsgHeaderLen {
return newOpError("receive", errShortErrorMessage)
}
// The TLVs should be at the offset indicated by the nlmsghdr.length,
// plus the offset where the header began. But make sure the calculated
// offset is still in-bounds.
h := *(*Header)(unsafe.Pointer(&m.Data[endErrno : endErrno+nlmsgHeaderLen][0]))
off = endErrno + int(h.Length)
if len(m.Data) < off {
return newOpError("receive", errShortErrorMessage)
}
} else {
// There is no nlmsghdr preceding the TLVs, parse them directly.
off = endErrno
}
ad, err := NewAttributeDecoder(m.Data[off:])
if err != nil {
// Malformed TLVs, just return the OpError with the info we have.
return oerr
}
for ad.Next() {
switch ad.Type() {
case 1: // unix.NLMSGERR_ATTR_MSG
oerr.Message = ad.String()
case 2: // unix.NLMSGERR_ATTR_OFFS
oerr.Offset = int(ad.Uint32())
}
}
// Explicitly ignore ad.Err: malformed TLVs, just return the OpError with
// the info we have.
return oerr
}