// Copyright 2018 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package netlink import ( "fmt" "math" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/binary" "gvisor.dev/gvisor/pkg/usermem" ) // alignUp rounds a length up to an alignment. // // Preconditions: align is a power of two. func alignUp(length int, align uint) int { return (length + int(align) - 1) &^ (int(align) - 1) } // alignPad returns the length of padding required for alignment. // // Preconditions: align is a power of two. func alignPad(length int, align uint) int { return alignUp(length, align) - length } // Message contains a complete serialized netlink message. type Message struct { hdr linux.NetlinkMessageHeader buf []byte } // NewMessage creates a new Message containing the passed header. // // The header length will be updated by Finalize. func NewMessage(hdr linux.NetlinkMessageHeader) *Message { return &Message{ hdr: hdr, buf: binary.Marshal(nil, usermem.ByteOrder, hdr), } } // ParseMessage parses the first message seen at buf, returning the rest of the // buffer. If message is malformed, ok of false is returned. For last message, // padding check is loose, if there isn't enought padding, whole buf is consumed // and ok is set to true. func ParseMessage(buf []byte) (msg *Message, rest []byte, ok bool) { b := BytesView(buf) hdrBytes, ok := b.Extract(linux.NetlinkMessageHeaderSize) if !ok { return } var hdr linux.NetlinkMessageHeader binary.Unmarshal(hdrBytes, usermem.ByteOrder, &hdr) // Msg portion. totalMsgLen := int(hdr.Length) _, ok = b.Extract(totalMsgLen - linux.NetlinkMessageHeaderSize) if !ok { return } // Padding. numPad := alignPad(totalMsgLen, linux.NLMSG_ALIGNTO) // Linux permits the last message not being aligned, just consume all of it. // Ref: net/netlink/af_netlink.c:netlink_rcv_skb if numPad > len(b) { numPad = len(b) } _, ok = b.Extract(numPad) if !ok { return } return &Message{ hdr: hdr, buf: buf[:totalMsgLen], }, []byte(b), true } // Header returns the header of this message. func (m *Message) Header() linux.NetlinkMessageHeader { return m.hdr } // GetData unmarshals the payload message header from this netlink message, and // returns the attributes portion. func (m *Message) GetData(msg interface{}) (AttrsView, bool) { b := BytesView(m.buf) _, ok := b.Extract(linux.NetlinkMessageHeaderSize) if !ok { return nil, false } size := int(binary.Size(msg)) msgBytes, ok := b.Extract(size) if !ok { return nil, false } binary.Unmarshal(msgBytes, usermem.ByteOrder, msg) numPad := alignPad(linux.NetlinkMessageHeaderSize+size, linux.NLMSG_ALIGNTO) // Linux permits the last message not being aligned, just consume all of it. // Ref: net/netlink/af_netlink.c:netlink_rcv_skb if numPad > len(b) { numPad = len(b) } _, ok = b.Extract(numPad) if !ok { return nil, false } return AttrsView(b), true } // Finalize returns the []byte containing the entire message, with the total // length set in the message header. The Message must not be modified after // calling Finalize. func (m *Message) Finalize() []byte { // Update length, which is the first 4 bytes of the header. usermem.ByteOrder.PutUint32(m.buf, uint32(len(m.buf))) // Align the message. Note that the message length in the header (set // above) is the useful length of the message, not the total aligned // length. See net/netlink/af_netlink.c:__nlmsg_put. aligned := alignUp(len(m.buf), linux.NLMSG_ALIGNTO) m.putZeros(aligned - len(m.buf)) return m.buf } // putZeros adds n zeros to the message. func (m *Message) putZeros(n int) { for n > 0 { m.buf = append(m.buf, 0) n-- } } // Put serializes v into the message. func (m *Message) Put(v interface{}) { m.buf = binary.Marshal(m.buf, usermem.ByteOrder, v) } // PutAttr adds v to the message as a netlink attribute. // // Preconditions: The serialized attribute (linux.NetlinkAttrHeaderSize + // binary.Size(v) fits in math.MaxUint16 bytes. func (m *Message) PutAttr(atype uint16, v interface{}) { l := linux.NetlinkAttrHeaderSize + int(binary.Size(v)) if l > math.MaxUint16 { panic(fmt.Sprintf("attribute too large: %d", l)) } m.Put(linux.NetlinkAttrHeader{ Type: atype, Length: uint16(l), }) m.Put(v) // Align the attribute. aligned := alignUp(l, linux.NLA_ALIGNTO) m.putZeros(aligned - l) } // PutAttrString adds s to the message as a netlink attribute. func (m *Message) PutAttrString(atype uint16, s string) { l := linux.NetlinkAttrHeaderSize + len(s) + 1 m.Put(linux.NetlinkAttrHeader{ Type: atype, Length: uint16(l), }) // String + NUL-termination. m.Put([]byte(s)) m.putZeros(1) // Align the attribute. aligned := alignUp(l, linux.NLA_ALIGNTO) m.putZeros(aligned - l) } // MessageSet contains a series of netlink messages. type MessageSet struct { // Multi indicates that this a multi-part message, to be terminated by // NLMSG_DONE. NLMSG_DONE is sent even if the set contains only one // Message. // // If Multi is set, all added messages will have NLM_F_MULTI set. Multi bool // PortID is the destination port for all messages. PortID int32 // Seq is the sequence counter for all messages in the set. Seq uint32 // Messages contains the messages in the set. Messages []*Message } // NewMessageSet creates a new MessageSet. // // portID is the destination port to set as PortID in all messages. // // seq is the sequence counter to set as seq in all messages in the set. func NewMessageSet(portID int32, seq uint32) *MessageSet { return &MessageSet{ PortID: portID, Seq: seq, } } // AddMessage adds a new message to the set and returns it for further // additions. // // The passed header will have Seq, PortID and the multi flag set // automatically. func (ms *MessageSet) AddMessage(hdr linux.NetlinkMessageHeader) *Message { hdr.Seq = ms.Seq hdr.PortID = uint32(ms.PortID) if ms.Multi { hdr.Flags |= linux.NLM_F_MULTI } m := NewMessage(hdr) ms.Messages = append(ms.Messages, m) return m } // AttrsView is a view into the attributes portion of a netlink message. type AttrsView []byte // Empty returns whether there is no attribute left in v. func (v AttrsView) Empty() bool { return len(v) == 0 } // ParseFirst parses first netlink attribute at the beginning of v. func (v AttrsView) ParseFirst() (hdr linux.NetlinkAttrHeader, value []byte, rest AttrsView, ok bool) { b := BytesView(v) hdrBytes, ok := b.Extract(linux.NetlinkAttrHeaderSize) if !ok { return } binary.Unmarshal(hdrBytes, usermem.ByteOrder, &hdr) value, ok = b.Extract(int(hdr.Length) - linux.NetlinkAttrHeaderSize) if !ok { return } _, ok = b.Extract(alignPad(int(hdr.Length), linux.NLA_ALIGNTO)) if !ok { return } return hdr, value, AttrsView(b), ok } // BytesView supports extracting data from a byte slice with bounds checking. type BytesView []byte // Extract removes the first n bytes from v and returns it. If n is out of // bounds, it returns false. func (v *BytesView) Extract(n int) ([]byte, bool) { if n < 0 || n > len(*v) { return nil, false } extracted := (*v)[:n] *v = (*v)[n:] return extracted, true }