1 /*
2  * Copyright (C) 2019 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/macros.h>
20 #include <libnl++/Buffer.h>
21 #include <libnl++/types.h>
22 
23 #include <linux/netlink.h>
24 
25 #include <string>
26 
27 namespace android::nl {
28 
29 class MessageFactoryBase {
30   protected:
31     static nlattr* add(nlmsghdr* msg, size_t maxLen, nlattrtype_t type, const void* data,
32                        size_t dataLen);
33     static void closeNested(nlmsghdr* msg, nlattr* nested);
34 };
35 
36 /**
37  * Wrapper around NETLINK_ROUTE messages, to build them in C++ style.
38  *
39  * \param T Message payload type (such as ifinfomsg).
40  * \param BUFSIZE how much space to reserve for attributes.
41  */
42 template <class T, unsigned int BUFSIZE = 128>
43 class MessageFactory : private MessageFactoryBase {
44     struct alignas(NLMSG_ALIGNTO) Message {
45         nlmsghdr header;
46         T data;
47         uint8_t attributesBuffer[BUFSIZE];
48     };
49 
50   public:
51     /**
52      * Create empty message.
53      *
54      * \param type Message type (such as RTM_NEWLINK).
55      * \param flags Message flags (such as NLM_F_REQUEST).
56      */
MessageFactory(nlmsgtype_t type,uint16_t flags)57     MessageFactory(nlmsgtype_t type, uint16_t flags)
58         : header(mMessage.header), data(mMessage.data) {
59         mMessage.header.nlmsg_len = offsetof(Message, attributesBuffer);
60         mMessage.header.nlmsg_type = type;
61         mMessage.header.nlmsg_flags = flags;
62     }
63 
64     /**
65      * Netlink message header.
66      *
67      * This is a generic Netlink header containing information such as message flags.
68      */
69     nlmsghdr& header;
70 
71     /**
72      * Netlink message data.
73      *
74      * This is a payload specific to a given message type.
75      */
76     T& data;
77 
78     T* operator->() { return &mMessage.data; }
79 
80     /**
81      * Build netlink message.
82      *
83      * In fact, this operation is almost a no-op, since the factory builds the message in a single
84      * buffer, using native data structures.
85      *
86      * A likely failure case is when the BUFSIZE template parameter is too small to acommodate
87      * added attributes. In such a case, please increase this parameter.
88      *
89      * \return Netlink message or std::nullopt in case of failure.
90      */
build()91     std::optional<Buffer<nlmsghdr>> build() const {
92         if (!mIsGood) return std::nullopt;
93         return {{&mMessage.header, mMessage.header.nlmsg_len}};
94     }
95 
96     /**
97      * Adds an attribute of a trivially copyable type.
98      *
99      * Template specializations may extend this function for other types, such as std::string.
100      *
101      * If this method fails (i.e. due to insufficient space), a warning will be printed to the log
102      * and the message will be marked as bad, causing later \see build call to fail.
103      *
104      * \param type attribute type (such as IFLA_IFNAME)
105      * \param attr attribute data
106      */
107     template <class A>
add(nlattrtype_t type,const A & attr)108     void add(nlattrtype_t type, const A& attr) {
109         add(type, &attr, sizeof(attr));
110     }
111 
112     template <>
add(nlattrtype_t type,const std::string & s)113     void add(nlattrtype_t type, const std::string& s) {
114         add(type, s.c_str(), s.size() + 1);
115     }
116 
117     /** Guard class to frame nested attributes. \see addNested(nlattrtype_t). */
118     class [[nodiscard]] NestedGuard {
119       public:
NestedGuard(MessageFactory & req,nlattrtype_t type)120         NestedGuard(MessageFactory & req, nlattrtype_t type) : mReq(req), mAttr(req.add(type)) {}
~NestedGuard()121         ~NestedGuard() { closeNested(&mReq.mMessage.header, mAttr); }
122 
123       private:
124         MessageFactory& mReq;
125         nlattr* mAttr;
126 
127         DISALLOW_COPY_AND_ASSIGN(NestedGuard);
128     };
129 
130     /**
131      * Add nested attribute.
132      *
133      * The returned object is a guard for auto-nesting children inside the argument attribute.
134      * When the guard object goes out of scope, the nesting attribute is closed.
135      *
136      * Example usage nesting IFLA_CAN_BITTIMING inside IFLA_INFO_DATA, which is nested
137      * inside IFLA_LINKINFO:
138      *    MessageFactory<ifinfomsg> req(RTM_NEWLINK, NLM_F_REQUEST);
139      *    {
140      *        auto linkinfo = req.addNested(IFLA_LINKINFO);
141      *        req.add(IFLA_INFO_KIND, "can");
142      *        {
143      *            auto infodata = req.addNested(IFLA_INFO_DATA);
144      *            req.add(IFLA_CAN_BITTIMING, bitTimingStruct);
145      *        }
146      *    }
147      *    // use req
148      *
149      * \param type attribute type (such as IFLA_LINKINFO)
150      */
addNested(nlattrtype_t type)151     NestedGuard addNested(nlattrtype_t type) { return {*this, type}; }
152 
153   private:
154     Message mMessage = {};
155     bool mIsGood = true;
156 
157     nlattr* add(nlattrtype_t type, const void* data = nullptr, size_t len = 0) {
158         if (!mIsGood) return nullptr;
159         auto attr = MessageFactoryBase::add(&mMessage.header, sizeof(mMessage), type, data, len);
160         if (attr == nullptr) mIsGood = false;
161         return attr;
162     }
163 };
164 
165 }  // namespace android::nl
166