// Copyright 2015 Google Inc. All Rights Reserved. // // 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 ( "bytes" "encoding/binary" "fmt" "os" "syscall" "golang.org/x/sys/unix" info "github.com/google/cadvisor/info/v1" ) var ( // TODO(rjnagal): Verify and fix for other architectures. Endian = binary.LittleEndian ) type genMsghdr struct { Command uint8 Version uint8 Reserved uint16 } type netlinkMessage struct { Header syscall.NlMsghdr GenHeader genMsghdr Data []byte } func (m netlinkMessage) toRawMsg() (rawmsg syscall.NetlinkMessage) { rawmsg.Header = m.Header w := bytes.NewBuffer([]byte{}) binary.Write(w, Endian, m.GenHeader) w.Write(m.Data) rawmsg.Data = w.Bytes() return rawmsg } type loadStatsResp struct { Header syscall.NlMsghdr GenHeader genMsghdr Stats info.LoadStats } // Return required padding to align 'size' to 'alignment'. func padding(size int, alignment int) int { unalignedPart := size % alignment return (alignment - unalignedPart) % alignment } // Get family id for taskstats subsystem. func getFamilyID(conn *Connection) (uint16, error) { msg := prepareFamilyMessage() err := conn.WriteMessage(msg.toRawMsg()) if err != nil { return 0, err } resp, err := conn.ReadMessage() if err != nil { return 0, err } id, err := parseFamilyResp(resp) if err != nil { return 0, err } return id, nil } // Append an attribute to the message. // Adds attribute info (length and type), followed by the data and necessary padding. // Can be called multiple times to add attributes. Only fixed size and string type // attributes are handled. We don't need nested attributes for task stats. func addAttribute(buf *bytes.Buffer, attrType uint16, data interface{}, dataSize int) { attr := syscall.RtAttr{ Len: syscall.SizeofRtAttr, Type: attrType, } attr.Len += uint16(dataSize) binary.Write(buf, Endian, attr) switch data := data.(type) { case string: binary.Write(buf, Endian, []byte(data)) buf.WriteByte(0) // terminate default: binary.Write(buf, Endian, data) } for i := 0; i < padding(int(attr.Len), syscall.NLMSG_ALIGNTO); i++ { buf.WriteByte(0) } } // Prepares the message and generic headers and appends attributes as data. func prepareMessage(headerType uint16, cmd uint8, attributes []byte) (msg netlinkMessage) { msg.Header.Type = headerType msg.Header.Flags = syscall.NLM_F_REQUEST msg.GenHeader.Command = cmd msg.GenHeader.Version = 0x1 msg.Data = attributes return msg } // Prepares message to query family id for task stats. func prepareFamilyMessage() (msg netlinkMessage) { buf := bytes.NewBuffer([]byte{}) addAttribute(buf, unix.CTRL_ATTR_FAMILY_NAME, unix.TASKSTATS_GENL_NAME, len(unix.TASKSTATS_GENL_NAME)+1) return prepareMessage(unix.GENL_ID_CTRL, unix.CTRL_CMD_GETFAMILY, buf.Bytes()) } // Prepares message to query task stats for a task group. func prepareCmdMessage(id uint16, cfd uintptr) (msg netlinkMessage) { buf := bytes.NewBuffer([]byte{}) addAttribute(buf, unix.CGROUPSTATS_CMD_ATTR_FD, uint32(cfd), 4) return prepareMessage(id, unix.CGROUPSTATS_CMD_GET, buf.Bytes()) } // Extracts returned family id from the response. func parseFamilyResp(msg syscall.NetlinkMessage) (uint16, error) { m := new(netlinkMessage) m.Header = msg.Header err := verifyHeader(msg) if err != nil { return 0, err } buf := bytes.NewBuffer(msg.Data) // extract generic header from data. err = binary.Read(buf, Endian, &m.GenHeader) if err != nil { return 0, err } id := uint16(0) // Extract attributes. kernel reports family name, id, version, etc. // Scan till we find id. for buf.Len() > syscall.SizeofRtAttr { var attr syscall.RtAttr err = binary.Read(buf, Endian, &attr) if err != nil { return 0, err } if attr.Type == unix.CTRL_ATTR_FAMILY_ID { err = binary.Read(buf, Endian, &id) if err != nil { return 0, err } return id, nil } payload := int(attr.Len) - syscall.SizeofRtAttr skipLen := payload + padding(payload, syscall.SizeofRtAttr) name := make([]byte, skipLen) err = binary.Read(buf, Endian, name) if err != nil { return 0, err } } return 0, fmt.Errorf("family id not found in the response") } // Extract task stats from response returned by kernel. func parseLoadStatsResp(msg syscall.NetlinkMessage) (*loadStatsResp, error) { m := new(loadStatsResp) m.Header = msg.Header err := verifyHeader(msg) if err != nil { return m, err } buf := bytes.NewBuffer(msg.Data) // Scan the general header. err = binary.Read(buf, Endian, &m.GenHeader) if err != nil { return m, err } // cgroup stats response should have just one attribute. // Read it directly into the stats structure. var attr syscall.RtAttr err = binary.Read(buf, Endian, &attr) if err != nil { return m, err } err = binary.Read(buf, Endian, &m.Stats) if err != nil { return m, err } return m, err } // Verify and return any error reported by kernel. func verifyHeader(msg syscall.NetlinkMessage) error { switch msg.Header.Type { case syscall.NLMSG_DONE: return fmt.Errorf("expected a response, got nil") case syscall.NLMSG_ERROR: buf := bytes.NewBuffer(msg.Data) var errno int32 err := binary.Read(buf, Endian, errno) if err != nil { return err } return fmt.Errorf("netlink request failed with error %s", syscall.Errno(-errno)) } return nil } // Get load stats for a task group. // id: family id for taskstats. // cfd: open file to path to the cgroup directory under cpu hierarchy. // conn: open netlink connection used to communicate with kernel. func getLoadStats(id uint16, cfd *os.File, conn *Connection) (info.LoadStats, error) { msg := prepareCmdMessage(id, cfd.Fd()) err := conn.WriteMessage(msg.toRawMsg()) if err != nil { return info.LoadStats{}, err } resp, err := conn.ReadMessage() if err != nil { return info.LoadStats{}, err } parsedmsg, err := parseLoadStatsResp(resp) if err != nil { return info.LoadStats{}, err } return parsedmsg.Stats, nil }