Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 34eb83ae authored by Tomasz Wasilczyk's avatar Tomasz Wasilczyk
Browse files

Implement attribute map.

Bug: 162032964
Test: custom code to parse RTM_NEWLINK messages
Change-Id: Ib07b0a4e553307e2ddaf7359e25f332660a29aec
parent 47915045
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ cc_library_static {
        "protocols/MessageDefinition.cpp",
        "protocols/NetlinkProtocol.cpp",
        "protocols/all.cpp",
        "Attributes.cpp",
        "NetlinkRequest.cpp",
        "NetlinkSocket.cpp",
        "common.cpp",
+80 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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.
 */

#include <libnl++/Attributes.h>

namespace android::nl {

Attributes::Attributes() {}

Attributes::Attributes(nlbuf<nlattr> buffer) : nlbuf<nlattr>(buffer) {}

const Attributes::Index& Attributes::index() const {
    if (mIndex.has_value()) return *mIndex;

    mIndex = Index();
    auto& index = *mIndex;

    for (auto attr : static_cast<nlbuf<nlattr>>(*this)) {
        index.emplace(attr->nla_type, attr);
    }

    return index;
}

bool Attributes::contains(nlattrtype_t attrtype) const {
    return index().count(attrtype) > 0;
}

/* Parser specializations for selected types (more to come if necessary). */

template <>
Attributes Attributes::parse(nlbuf<nlattr> buf) {
    return buf.data<nlattr>();
}

template <>
std::string Attributes::parse(nlbuf<nlattr> buf) {
    const auto rawString = buf.data<char>().getRaw();
    return std::string(rawString.ptr(), rawString.len());
}

template <typename T>
static T parseUnsigned(nlbuf<nlattr> buf) {
    return buf.data<T>().copyFirst();
}

template <>
uint8_t Attributes::parse(nlbuf<nlattr> buf) {
    return parseUnsigned<uint8_t>(buf);
}

template <>
uint16_t Attributes::parse(nlbuf<nlattr> buf) {
    return parseUnsigned<uint16_t>(buf);
}

template <>
uint32_t Attributes::parse(nlbuf<nlattr> buf) {
    return parseUnsigned<uint32_t>(buf);
}

template <>
uint64_t Attributes::parse(nlbuf<nlattr> buf) {
    return parseUnsigned<uint64_t>(buf);
}

}  // namespace android::nl
+170 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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.
 */

#pragma once

#include <android-base/logging.h>
#include <libnl++/nlbuf.h>
#include <libnl++/types.h>
#include <utils/Mutex.h>

#include <map>

namespace android::nl {

/**
 * Netlink attribute map.
 *
 * This is a C++-style, memory safe(r) implementation of linux/netlink.h macros accessing Netlink
 * message attributes. The class doesn't own the underlying data, so the instance is valid as long
 * as the source buffer is allocated and unmodified.
 *
 * WARNING: this class is NOT thread-safe (it's safe to be used in multithreaded application, but
 * a single instance can only be used by a single thread - the one owning the underlying buffer).
 */
class Attributes : private nlbuf<nlattr> {
  public:
    /**
     * Constructs empty attribute map.
     */
    Attributes();

    /**
     * Construct attribute map from underlying buffer.
     *
     * \param buffer Source buffer pointing at the first attribute.
     */
    Attributes(nlbuf<nlattr> buffer);

    /**
     * Checks, if the map contains given attribute type (key).
     *
     * \param attrtype Attribute type (such as IFLA_IFNAME).
     * \return true if attribute is in the map, false otherwise.
     */
    bool contains(nlattrtype_t attrtype) const;

    /**
     * Fetches attribute of a given type by copying it.
     *
     * While this is quite efficient for simple types, fetching nested attribute creates a new copy
     * of child attribute map. This may be costly if you calculate the index for child maps multiple
     * times. Examples below.
     *
     * BAD:
     * ```
     * const auto flags = msg->attributes.
     *     get<nl::Attributes>(IFLA_AF_SPEC).
     *     get<nl::Attributes>(AF_INET6).  // IFLA_AF_SPEC index lazy-calculated
     *     get<uint32_t>(IFLA_INET6_FLAGS);  // AF_INET6 index lazy-calculated
     * const auto& cacheinfo = msg->attributes.
     *     get<nl::Attributes>(IFLA_AF_SPEC).  // new instance of IFLA_AF_SPEC index
     *     get<nl::Attributes>(AF_INET6).  // IFLA_AF_SPEC index calculated again
     *     getStruct<ifla_cacheinfo>(IFLA_INET6_CACHEINFO);  // AF_INET6 calculated again
     * ```
     *
     * GOOD:
     * ```
     * const auto inet6 = msg->attributes.
     *     get<nl::Attributes>(IFLA_AF_SPEC).
     *     get<nl::Attributes>(AF_INET6);
     * const auto flags = inet6.get<uint32_t>(IFLA_INET6_FLAGS);  // AF_INET6 index lazy-calculated
     * const auto& cache = inet6.getStruct<ifla_cacheinfo>(IFLA_INET6_CACHEINFO);  // index reused
     * ```
     *
     * If the attribute doesn't exists, default value of a given type is returned and warning
     * spawned into the log. To check for attribute existence, \see contains(nlattrtype_t).
     *
     * \param attrtype Attribute to fetch.
     * \return Attribute value.
     */
    template <typename T>
    T get(nlattrtype_t attrtype) const {
        const auto& ind = index();
        const auto it = ind.find(attrtype);
        if (it == ind.end()) {
            LOG(WARNING) << "Netlink attribute is missing: " << attrtype;
            return T{};
        }

        return parse<T>(it->second);
    }

    /**
     * Fetches a reference to a given attribute's data.
     *
     * This method is intended for arbitrary structures not specialized with get(nlattrtype_t)
     * template and slightly more efficient for larger payloads due to not copying its data.
     *
     * If the attribute doesn't exists, a reference to empty value of a given type is returned and
     * warning spawned into the log. To check for attribute existence, \see contains(nlattrtype_t).
     *
     * \param attrtype Attribute to fetch.
     * \return Reference to the attribute's data.
     */
    template <typename T>
    const T& getStruct(nlattrtype_t attrtype) const {
        const auto& ind = index();
        const auto it = ind.find(attrtype);
        if (it == ind.end()) {
            LOG(WARNING) << "Netlink attribute is missing: " << attrtype;
            static const T empty = {};
            return empty;
        }

        const auto& [ok, val] = it->second.data<T>().getFirst();
        if (!ok) LOG(WARNING) << "Can't fetch structure of size " << sizeof(T);
        return val;
    }

  private:
    using Index = std::map<nlattrtype_t, nlbuf<nlattr>>;

    /**
     * Attribute index.
     *
     * Since this field is not protected by mutex, the use of \see index() dependent methods
     * (such as \see get(nlattrtype_t)) is not thread-safe. This is a compromise made based on the
     * following assumptions:
     *
     * 1. Most (or even all) use-cases involve attribute parsing in the same thread as where the
     *    buffer was allocated. This is partly forced by a dependence of nlmsg lifecycle on the
     *    underlying data buffer.
     *
     * 2. Index calculation and access would come with performance penalty never justified in most
     *    or all use cases (see the previous point). Since Index is not a trivially assignable data
     *    structure, it's not possible to use it with atomic types only and avoid mutexes.
     */
    mutable std::optional<Index> mIndex;

    /**
     * Lazy-calculate and cache index.
     *
     * \return Attribute index.
     */
    const Index& index() const;

    /**
     * Parse attribute data into a specific type.
     *
     * \param buf Raw attribute data.
     * \return Parsed data.
     */
    template <typename T>
    static T parse(nlbuf<nlattr> buf);
};

}  // namespace android::nl
+9 −3
Original line number Diff line number Diff line
@@ -53,6 +53,11 @@ class nlbuf {
    static constexpr size_t hdrlen = align(sizeof(T));

  public:
    /**
     * Constructs empty buffer of size 0.
     */
    nlbuf() : mData(nullptr), mBufferEnd(nullptr) {}

    /**
     * Constructor for nlbuf.
     *
@@ -68,8 +73,8 @@ class nlbuf {

    std::pair<bool, const T&> getFirst() const {
        if (!ok()) {
            static const T dummy = {};
            return {false, dummy};
            static const T empty = {};
            return {false, empty};
        }
        return {true, *mData};
    }
@@ -78,7 +83,8 @@ class nlbuf {
     * Copy the first element of the buffer.
     *
     * This is a memory-safe cast operation, useful for reading e.g. uint32_t values
     * from 1-byte buffer.
     * from 1-byte buffer. If the buffer is smaller than the copied type, the rest is
     * padded with default constructor output (usually zeros).
     */
    T copyFirst() const {
        T val = {};
+6 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

#pragma once

#include <libnl++/Attributes.h>
#include <libnl++/nlbuf.h>

namespace android::nl {
@@ -26,6 +27,9 @@ namespace android::nl {
 * This is a C++-style, memory safe(r) implementation of linux/netlink.h macros accessing Netlink
 * message contents. The class doesn't own the underlying data, so the instance is valid as long as
 * the source buffer is allocated and unmodified.
 *
 * WARNING: this class is NOT thread-safe (it's safe to be used in multithreaded application, but
 * a single instance can only be used by a single thread - the one owning the underlying buffer).
 */
template <typename T>
class nlmsg {
@@ -82,12 +86,12 @@ class nlmsg {
    /**
     * Netlink message attributes.
     */
    const nlbuf<nlattr> attributes;
    const Attributes attributes;

    const T* operator->() const { return &data; }

  private:
    nlmsg(const nlmsghdr& nlHeader, const T& dataHeader, nlbuf<nlattr> attributes)
    nlmsg(const nlmsghdr& nlHeader, const T& dataHeader, Attributes attributes)
        : header(nlHeader), data(dataHeader), attributes(attributes) {}
};