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< 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 }