1 #include "ncsi_util.hpp"
2 
3 #include <fmt/format.h>
4 #include <linux/ncsi.h>
5 #include <netlink/genl/ctrl.h>
6 #include <netlink/genl/genl.h>
7 #include <netlink/netlink.h>
8 
9 #include <iomanip>
10 #include <iostream>
11 #include <phosphor-logging/elog-errors.hpp>
12 #include <phosphor-logging/log.hpp>
13 #include <xyz/openbmc_project/Common/error.hpp>
14 
15 namespace phosphor
16 {
17 namespace network
18 {
19 namespace ncsi
20 {
21 
22 using namespace phosphor::logging;
23 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
24 
25 using CallBack = int (*)(struct nl_msg* msg, void* arg);
26 
27 namespace internal
28 {
29 
30 struct NCSIPacketHeader
31 {
32     uint8_t MCID;
33     uint8_t revision;
34     uint8_t reserved;
35     uint8_t id;
36     uint8_t type;
37     uint8_t channel;
38     uint16_t length;
39     uint32_t rsvd[2];
40 };
41 
42 class Command
43 {
44   public:
45     Command() = delete;
46     ~Command() = default;
47     Command(const Command&) = delete;
48     Command& operator=(const Command&) = delete;
49     Command(Command&&) = default;
50     Command& operator=(Command&&) = default;
51     Command(
52         int c, int nc = DEFAULT_VALUE,
53         std::span<const unsigned char> p = std::span<const unsigned char>()) :
54         cmd(c),
55         ncsi_cmd(nc), payload(p)
56     {
57     }
58 
59     int cmd;
60     int ncsi_cmd;
61     std::span<const unsigned char> payload;
62 };
63 
64 using nlMsgPtr = std::unique_ptr<nl_msg, decltype(&::nlmsg_free)>;
65 using nlSocketPtr = std::unique_ptr<nl_sock, decltype(&::nl_socket_free)>;
66 
67 CallBack infoCallBack = [](struct nl_msg* msg, void* arg) {
68     using namespace phosphor::network::ncsi;
69     auto nlh = nlmsg_hdr(msg);
70 
71     struct nlattr* tb[NCSI_ATTR_MAX + 1] = {nullptr};
72     struct nla_policy ncsiPolicy[NCSI_ATTR_MAX + 1] = {
73         {NLA_UNSPEC, 0, 0}, {NLA_U32, 0, 0}, {NLA_NESTED, 0, 0},
74         {NLA_U32, 0, 0},    {NLA_U32, 0, 0},
75     };
76 
77     struct nlattr* packagetb[NCSI_PKG_ATTR_MAX + 1] = {nullptr};
78     struct nla_policy packagePolicy[NCSI_PKG_ATTR_MAX + 1] = {
79         {NLA_UNSPEC, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_U32, 0, 0},
80         {NLA_FLAG, 0, 0},   {NLA_NESTED, 0, 0},
81     };
82 
83     struct nlattr* channeltb[NCSI_CHANNEL_ATTR_MAX + 1] = {nullptr};
84     struct nla_policy channelPolicy[NCSI_CHANNEL_ATTR_MAX + 1] = {
85         {NLA_UNSPEC, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_U32, 0, 0},
86         {NLA_FLAG, 0, 0},   {NLA_NESTED, 0, 0}, {NLA_UNSPEC, 0, 0},
87     };
88 
89     *(int*)arg = 0;
90 
91     auto ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsiPolicy);
92     if (!tb[NCSI_ATTR_PACKAGE_LIST])
93     {
94         std::cerr << "No Packages" << std::endl;
95         return -1;
96     }
97 
98     auto attrTgt = static_cast<nlattr*>(nla_data(tb[NCSI_ATTR_PACKAGE_LIST]));
99     if (!attrTgt)
100     {
101         std::cerr << "Package list attribute is null" << std::endl;
102         return -1;
103     }
104 
105     auto rem = nla_len(tb[NCSI_ATTR_PACKAGE_LIST]);
106     nla_for_each_nested(attrTgt, tb[NCSI_ATTR_PACKAGE_LIST], rem)
107     {
108         ret = nla_parse_nested(packagetb, NCSI_PKG_ATTR_MAX, attrTgt,
109                                packagePolicy);
110         if (ret < 0)
111         {
112             std::cerr << "Failed to parse package nested" << std::endl;
113             return -1;
114         }
115 
116         if (packagetb[NCSI_PKG_ATTR_ID])
117         {
118             auto attrID = nla_get_u32(packagetb[NCSI_PKG_ATTR_ID]);
119             std::cout << "Package has id : " << std::hex << attrID << std::endl;
120         }
121         else
122         {
123             std::cout << "Package with no id" << std::endl;
124         }
125 
126         if (packagetb[NCSI_PKG_ATTR_FORCED])
127         {
128             std::cout << "This package is forced" << std::endl;
129         }
130 
131         auto channelListTarget = static_cast<nlattr*>(
132             nla_data(packagetb[NCSI_PKG_ATTR_CHANNEL_LIST]));
133 
134         auto channelrem = nla_len(packagetb[NCSI_PKG_ATTR_CHANNEL_LIST]);
135         nla_for_each_nested(channelListTarget,
136                             packagetb[NCSI_PKG_ATTR_CHANNEL_LIST], channelrem)
137         {
138             ret = nla_parse_nested(channeltb, NCSI_CHANNEL_ATTR_MAX,
139                                    channelListTarget, channelPolicy);
140             if (ret < 0)
141             {
142                 std::cerr << "Failed to parse channel nested" << std::endl;
143                 return -1;
144             }
145 
146             if (channeltb[NCSI_CHANNEL_ATTR_ID])
147             {
148                 auto channel = nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_ID]);
149                 if (channeltb[NCSI_CHANNEL_ATTR_ACTIVE])
150                 {
151                     std::cout << "Channel Active : " << std::hex << channel
152                               << std::endl;
153                 }
154                 else
155                 {
156                     std::cout << "Channel Not Active : " << std::hex << channel
157                               << std::endl;
158                 }
159 
160                 if (channeltb[NCSI_CHANNEL_ATTR_FORCED])
161                 {
162                     std::cout << "Channel is forced" << std::endl;
163                 }
164             }
165             else
166             {
167                 std::cout << "Channel with no ID" << std::endl;
168             }
169 
170             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR])
171             {
172                 auto major =
173                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR]);
174                 std::cout << "Channel Major Version : " << std::hex << major
175                           << std::endl;
176             }
177             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR])
178             {
179                 auto minor =
180                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR]);
181                 std::cout << "Channel Minor Version : " << std::hex << minor
182                           << std::endl;
183             }
184             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_STR])
185             {
186                 auto str =
187                     nla_get_string(channeltb[NCSI_CHANNEL_ATTR_VERSION_STR]);
188                 std::cout << "Channel Version Str :" << str << std::endl;
189             }
190             if (channeltb[NCSI_CHANNEL_ATTR_LINK_STATE])
191             {
192 
193                 auto link =
194                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_LINK_STATE]);
195                 std::cout << "Channel Link State : " << std::hex << link
196                           << std::endl;
197             }
198             if (channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST])
199             {
200                 std::cout << "Active Vlan ids" << std::endl;
201                 auto vids = channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST];
202                 auto vid = static_cast<nlattr*>(nla_data(vids));
203                 auto len = nla_len(vids);
204                 while (nla_ok(vid, len))
205                 {
206                     auto id = nla_get_u16(vid);
207                     std::cout << "VID : " << id << std::endl;
208                     vid = nla_next(vid, &len);
209                 }
210             }
211         }
212     }
213     return (int)NL_SKIP;
214 };
215 
216 CallBack sendCallBack = [](struct nl_msg* msg, void* arg) {
217     using namespace phosphor::network::ncsi;
218     auto nlh = nlmsg_hdr(msg);
219     struct nlattr* tb[NCSI_ATTR_MAX + 1] = {nullptr};
220     static struct nla_policy ncsiPolicy[NCSI_ATTR_MAX + 1] = {
221         {NLA_UNSPEC, 0, 0}, {NLA_U32, 0, 0}, {NLA_NESTED, 0, 0},
222         {NLA_U32, 0, 0},    {NLA_U32, 0, 0}, {NLA_BINARY, 0, 0},
223         {NLA_FLAG, 0, 0},   {NLA_U32, 0, 0}, {NLA_U32, 0, 0},
224     };
225 
226     *(int*)arg = 0;
227 
228     auto ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsiPolicy);
229     if (ret)
230     {
231         std::cerr << "Failed to parse package" << std::endl;
232         return ret;
233     }
234 
235     auto data_len = nla_len(tb[NCSI_ATTR_DATA]) - sizeof(NCSIPacketHeader);
236     unsigned char* data =
237         (unsigned char*)nla_data(tb[NCSI_ATTR_DATA]) + sizeof(NCSIPacketHeader);
238     auto s = std::span<const unsigned char>(data, data_len);
239 
240     // Dump the response to stdout. Enhancement: option to save response data
241     std::cout << "Response : " << std::dec << data_len << " bytes" << std::endl;
242     fmt::print("{:02x}", fmt::join(s.begin(), s.end(), " "));
243     std::cout << std::endl;
244 
245     return 0;
246 };
247 
248 int applyCmd(int ifindex, const Command& cmd, int package = DEFAULT_VALUE,
249              int channel = DEFAULT_VALUE, int flags = NONE,
250              CallBack function = nullptr)
251 {
252     int cb_ret = 0;
253     nlSocketPtr socket(nl_socket_alloc(), &::nl_socket_free);
254     if (socket == nullptr)
255     {
256         log<level::ERR>("Unable to allocate memory for the socket.");
257         return -ENOMEM;
258     }
259 
260     auto ret = genl_connect(socket.get());
261     if (ret < 0)
262     {
263         std::cerr << "Failed to open the socket , RC : " << ret << std::endl;
264         return ret;
265     }
266 
267     auto driverID = genl_ctrl_resolve(socket.get(), "NCSI");
268     if (driverID < 0)
269     {
270         std::cerr << "Failed to resolve, RC : " << ret << std::endl;
271         return driverID;
272     }
273 
274     nlMsgPtr msg(nlmsg_alloc(), &::nlmsg_free);
275     if (msg == nullptr)
276     {
277         log<level::ERR>("Unable to allocate memory for the message.");
278         return -ENOMEM;
279     }
280 
281     auto msgHdr = genlmsg_put(msg.get(), 0, 0, driverID, 0, flags, cmd.cmd, 0);
282     if (!msgHdr)
283     {
284         std::cerr << "Unable to add the netlink headers , COMMAND : " << cmd.cmd
285                   << std::endl;
286         return -ENOMEM;
287     }
288 
289     if (package != DEFAULT_VALUE)
290     {
291         ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_PACKAGE_ID,
292                           package);
293         if (ret < 0)
294         {
295             std::cerr << "Failed to set the attribute , RC : " << ret
296                       << "PACKAGE " << std::hex << package << std::endl;
297             return ret;
298         }
299     }
300 
301     if (channel != DEFAULT_VALUE)
302     {
303         ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_CHANNEL_ID,
304                           channel);
305         if (ret < 0)
306         {
307             std::cerr << "Failed to set the attribute , RC : " << ret
308                       << "CHANNEL : " << std::hex << channel << std::endl;
309             return ret;
310         }
311     }
312 
313     ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_IFINDEX, ifindex);
314     if (ret < 0)
315     {
316         std::cerr << "Failed to set the attribute , RC : " << ret
317                   << "INTERFACE : " << std::hex << ifindex << std::endl;
318         return ret;
319     }
320 
321     if (cmd.ncsi_cmd != DEFAULT_VALUE)
322     {
323         std::vector<unsigned char> pl(sizeof(NCSIPacketHeader) +
324                                       cmd.payload.size());
325         NCSIPacketHeader* hdr = (NCSIPacketHeader*)pl.data();
326 
327         std::copy(cmd.payload.begin(), cmd.payload.end(),
328                   pl.begin() + sizeof(NCSIPacketHeader));
329 
330         hdr->type = cmd.ncsi_cmd;
331         hdr->length = htons(cmd.payload.size());
332 
333         ret = nla_put(msg.get(), ncsi_nl_attrs::NCSI_ATTR_DATA, pl.size(),
334                       pl.data());
335         if (ret < 0)
336         {
337             std::cerr << "Failed to set the data attribute, RC : " << ret
338                       << std::endl;
339             return ret;
340         }
341 
342         nl_socket_disable_seq_check(socket.get());
343     }
344 
345     if (function)
346     {
347         cb_ret = 1;
348 
349         // Add a callback function to the socket
350         nl_socket_modify_cb(socket.get(), NL_CB_VALID, NL_CB_CUSTOM, function,
351                             &cb_ret);
352     }
353 
354     ret = nl_send_auto(socket.get(), msg.get());
355     if (ret < 0)
356     {
357         std::cerr << "Failed to send the message , RC : " << ret << std::endl;
358         return ret;
359     }
360 
361     do
362     {
363         ret = nl_recvmsgs_default(socket.get());
364         if (ret < 0)
365         {
366             std::cerr << "Failed to receive the message , RC : " << ret
367                       << std::endl;
368             break;
369         }
370     } while (cb_ret);
371 
372     return ret;
373 }
374 
375 } // namespace internal
376 
377 int sendOemCommand(int ifindex, int package, int channel,
378                    std::span<const unsigned char> payload)
379 {
380     constexpr auto cmd = 0x50;
381 
382     std::cout << "Send OEM Command, CHANNEL : " << std::hex << channel
383               << ", PACKAGE : " << std::hex << package
384               << ", IFINDEX: " << std::hex << ifindex << std::endl;
385     if (!payload.empty())
386     {
387         std::cout << "Payload :";
388         for (auto& i : payload)
389         {
390             std::cout << " " << std::hex << std::setfill('0') << std::setw(2)
391                       << (int)i;
392         }
393         std::cout << std::endl;
394     }
395 
396     return internal::applyCmd(
397         ifindex,
398         internal::Command(ncsi_nl_commands::NCSI_CMD_SEND_CMD, cmd, payload),
399         package, channel, NONE, internal::sendCallBack);
400 }
401 
402 int setChannel(int ifindex, int package, int channel)
403 {
404     std::cout << "Set Channel : " << std::hex << channel
405               << ", PACKAGE : " << std::hex << package
406               << ", IFINDEX :  " << std::hex << ifindex << std::endl;
407     return internal::applyCmd(
408         ifindex, internal::Command(ncsi_nl_commands::NCSI_CMD_SET_INTERFACE),
409         package, channel);
410 }
411 
412 int clearInterface(int ifindex)
413 {
414     std::cout << "ClearInterface , IFINDEX :" << std::hex << ifindex
415               << std::endl;
416     return internal::applyCmd(
417         ifindex, internal::Command(ncsi_nl_commands::NCSI_CMD_CLEAR_INTERFACE));
418 }
419 
420 int getInfo(int ifindex, int package)
421 {
422     std::cout << "Get Info , PACKAGE :  " << std::hex << package
423               << ", IFINDEX :  " << std::hex << ifindex << std::endl;
424     if (package == DEFAULT_VALUE)
425     {
426         return internal::applyCmd(
427             ifindex, internal::Command(ncsi_nl_commands::NCSI_CMD_PKG_INFO),
428             package, DEFAULT_VALUE, NLM_F_DUMP, internal::infoCallBack);
429     }
430     else
431     {
432         return internal::applyCmd(ifindex, ncsi_nl_commands::NCSI_CMD_PKG_INFO,
433                                   package, DEFAULT_VALUE, NONE,
434                                   internal::infoCallBack);
435     }
436 }
437 
438 } // namespace ncsi
439 } // namespace network
440 } // namespace phosphor
441