1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <android-base/logging.h>
20 #include <libnl++/Buffer.h>
21 #include <libnl++/types.h>
22 #include <utils/Mutex.h>
23 
24 #include <map>
25 
26 namespace android::nl {
27 
28 /**
29  * Netlink attribute map.
30  *
31  * This is a C++-style, memory safe(r) implementation of linux/netlink.h macros accessing Netlink
32  * message attributes. The class doesn't own the underlying data, so the instance is valid as long
33  * as the source buffer is allocated and unmodified.
34  *
35  * WARNING: this class is NOT thread-safe (it's safe to be used in multithreaded application, but
36  * a single instance can only be used by a single thread - the one owning the underlying buffer).
37  */
38 class Attributes : private Buffer<nlattr> {
39   public:
40     /**
41      * Constructs empty attribute map.
42      */
43     Attributes();
44 
45     /**
46      * Construct attribute map from underlying buffer.
47      *
48      * \param buffer Source buffer pointing at the first attribute.
49      */
50     Attributes(Buffer<nlattr> buffer);
51 
52     /**
53      * Checks, if the map contains given attribute type (key).
54      *
55      * \param attrtype Attribute type (such as IFLA_IFNAME).
56      * \return true if attribute is in the map, false otherwise.
57      */
58     bool contains(nlattrtype_t attrtype) const;
59 
60     /**
61      * Fetches attribute of a given type by copying it.
62      *
63      * While this is quite efficient for simple types, fetching nested attribute creates a new copy
64      * of child attribute map. This may be costly if you calculate the index for child maps multiple
65      * times. Examples below.
66      *
67      * BAD:
68      * ```
69      * const auto flags = msg->attributes.
70      *     get<nl::Attributes>(IFLA_AF_SPEC).
71      *     get<nl::Attributes>(AF_INET6).  // IFLA_AF_SPEC index lazy-calculated
72      *     get<uint32_t>(IFLA_INET6_FLAGS);  // AF_INET6 index lazy-calculated
73      * const auto& cacheinfo = msg->attributes.
74      *     get<nl::Attributes>(IFLA_AF_SPEC).  // new instance of IFLA_AF_SPEC index
75      *     get<nl::Attributes>(AF_INET6).  // IFLA_AF_SPEC index calculated again
76      *     getStruct<ifla_cacheinfo>(IFLA_INET6_CACHEINFO);  // AF_INET6 calculated again
77      * ```
78      *
79      * GOOD:
80      * ```
81      * const auto inet6 = msg->attributes.
82      *     get<nl::Attributes>(IFLA_AF_SPEC).
83      *     get<nl::Attributes>(AF_INET6);
84      * const auto flags = inet6.get<uint32_t>(IFLA_INET6_FLAGS);  // AF_INET6 index lazy-calculated
85      * const auto& cache = inet6.getStruct<ifla_cacheinfo>(IFLA_INET6_CACHEINFO);  // index reused
86      * ```
87      *
88      * If the attribute doesn't exists, default value of a given type is returned and warning
89      * spawned into the log. To check for attribute existence, \see contains(nlattrtype_t).
90      *
91      * \param attrtype Attribute to fetch.
92      * \return Attribute value.
93      */
94     template <typename T>
get(nlattrtype_t attrtype)95     T get(nlattrtype_t attrtype) const {
96         const auto buffer = getBuffer(attrtype);
97         if (!buffer.has_value()) {
98             LOG(WARNING) << "Netlink attribute is missing: " << attrtype;
99             return T{};
100         }
101 
102         return parse<T>(*buffer);
103     }
104 
105     /**
106      * Fetches underlying buffer of a given attribute.
107      *
108      * This is a low-level access method unlikely to be useful in most cases. Please consider
109      * using #get instead.
110      *
111      * \param attrtype Attribute to fetch
112      * \return Attribute buffer.
113      */
getBuffer(nlattrtype_t attrtype)114     std::optional<Buffer<nlattr>> getBuffer(nlattrtype_t attrtype) const {
115         const auto& ind = index();
116         const auto it = ind.find(attrtype);
117         if (it == ind.end()) return std::nullopt;
118         return it->second;
119     }
120 
121     /**
122      * Fetches a reference to a given attribute's data.
123      *
124      * This method is intended for arbitrary structures not specialized with get(nlattrtype_t)
125      * template and slightly more efficient for larger payloads due to not copying its data.
126      *
127      * If the attribute doesn't exists, a reference to empty value of a given type is returned and
128      * warning spawned into the log. To check for attribute existence, \see contains(nlattrtype_t).
129      *
130      * \param attrtype Attribute to fetch.
131      * \return Reference to the attribute's data.
132      */
133     template <typename T>
getStruct(nlattrtype_t attrtype)134     const T& getStruct(nlattrtype_t attrtype) const {
135         const auto& ind = index();
136         const auto it = ind.find(attrtype);
137         if (it == ind.end()) {
138             LOG(WARNING) << "Netlink attribute is missing: " << attrtype;
139             static const T empty = {};
140             return empty;
141         }
142 
143         const auto& [ok, val] = it->second.data<T>().getFirst();
144         if (!ok) LOG(WARNING) << "Can't fetch structure of size " << sizeof(T);
145         return val;
146     }
147 
148   private:
149     using Index = std::map<nlattrtype_t, Buffer<nlattr>>;
150 
151     /**
152      * Attribute index.
153      *
154      * Since this field is not protected by mutex, the use of \see index() dependent methods
155      * (such as \see get(nlattrtype_t)) is not thread-safe. This is a compromise made based on the
156      * following assumptions:
157      *
158      * 1. Most (or even all) use-cases involve attribute parsing in the same thread as where the
159      *    buffer was allocated. This is partly forced by a dependence of nlmsg lifecycle on the
160      *    underlying data buffer.
161      *
162      * 2. Index calculation and access would come with performance penalty never justified in most
163      *    or all use cases (see the previous point). Since Index is not a trivially assignable data
164      *    structure, it's not possible to use it with atomic types only and avoid mutexes.
165      */
166     mutable std::optional<Index> mIndex;
167 
168     /**
169      * Lazy-calculate and cache index.
170      *
171      * \return Attribute index.
172      */
173     const Index& index() const;
174 
175     /**
176      * Parse attribute data into a specific type.
177      *
178      * \param buf Raw attribute data.
179      * \return Parsed data.
180      */
181     template <typename T>
182     static T parse(Buffer<nlattr> buf);
183 };
184 
185 }  // namespace android::nl
186