Implement attribute map.

Bug: 162032964
Test: custom code to parse RTM_NEWLINK messages
Change-Id: Ib07b0a4e553307e2ddaf7359e25f332660a29aec
This commit is contained in:
Tomasz Wasilczyk 2020-07-31 12:22:39 -07:00
parent 47915045da
commit 34eb83aef2
5 changed files with 266 additions and 5 deletions

View file

@ -31,6 +31,7 @@ cc_library_static {
"protocols/MessageDefinition.cpp",
"protocols/NetlinkProtocol.cpp",
"protocols/all.cpp",
"Attributes.cpp",
"NetlinkRequest.cpp",
"NetlinkSocket.cpp",
"common.cpp",

View file

@ -0,0 +1,80 @@
/*
* 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

View file

@ -0,0 +1,170 @@
/*
* 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

View file

@ -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 = {};

View file

@ -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) {}
};