xref: /openbmc/phosphor-networkd/src/ncsi_util.cpp (revision 6c4859edc29d14d97cf4bda82da37bbc4a4c0e05)
1 #include "ncsi_util.hpp"
2 
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <linux/mctp.h>
6 #include <linux/ncsi.h>
7 #include <netlink/genl/ctrl.h>
8 #include <netlink/genl/genl.h>
9 #include <netlink/netlink.h>
10 #include <unistd.h>
11 
12 #include <iostream>
13 #include <memory>
14 #include <optional>
15 #include <span>
16 #include <sstream>
17 #include <system_error>
18 #include <vector>
19 
20 namespace phosphor
21 {
22 namespace network
23 {
24 namespace ncsi
25 {
26 
27 static const char* mctp_iid_path = "/run/ncsi-mctp-iids";
28 
NCSICommand(uint8_t opcode,uint8_t package,std::optional<uint8_t> channel,std::span<unsigned char> payload)29 NCSICommand::NCSICommand(uint8_t opcode, uint8_t package,
30                          std::optional<uint8_t> channel,
31                          std::span<unsigned char> payload) :
32     opcode(opcode), package(package), channel(channel)
33 {
34     this->payload.assign(payload.begin(), payload.end());
35 }
36 
getChannel()37 uint8_t NCSICommand::getChannel()
38 {
39     return channel.value_or(CHANNEL_ID_NONE);
40 }
41 
42 using CallBack = int (*)(struct nl_msg* msg, void* arg);
43 
44 namespace internal
45 {
46 
47 struct NCSIPacketHeader
48 {
49     uint8_t MCID;
50     uint8_t revision;
51     uint8_t reserved;
52     uint8_t id;
53     uint8_t type;
54     uint8_t channel;
55     uint16_t length;
56     uint32_t rsvd[2];
57 };
58 
59 struct NCSIResponsePayload
60 {
61     uint16_t response;
62     uint16_t reason;
63 };
64 
65 class NetlinkCommand
66 {
67   public:
68     NetlinkCommand() = delete;
69     ~NetlinkCommand() = default;
70     NetlinkCommand(const NetlinkCommand&) = delete;
71     NetlinkCommand& operator=(const NetlinkCommand&) = delete;
72     NetlinkCommand(NetlinkCommand&&) = default;
73     NetlinkCommand& operator=(NetlinkCommand&&) = default;
NetlinkCommand(int ncsiCmd,int operation=DEFAULT_VALUE,std::span<const unsigned char> p=std::span<const unsigned char> ())74     NetlinkCommand(
75         int ncsiCmd, int operation = DEFAULT_VALUE,
76         std::span<const unsigned char> p = std::span<const unsigned char>()) :
77         ncsi_cmd(ncsiCmd), operation(operation), payload(p)
78     {}
79 
80     int ncsi_cmd;
81     int operation;
82     std::span<const unsigned char> payload;
83 };
84 
85 using nlMsgPtr = std::unique_ptr<nl_msg, decltype(&::nlmsg_free)>;
86 using nlSocketPtr = std::unique_ptr<nl_sock, decltype(&::nl_socket_free)>;
87 
88 struct infoCallBackContext
89 {
90     InterfaceInfo* info;
91 };
92 
__anon66112f010102(struct nl_msg* msg, void* arg) 93 CallBack infoCallBack = [](struct nl_msg* msg, void* arg) {
94     if (arg == nullptr)
95     {
96         std::cerr << "Internal error: invalid info callback context"
97                   << std::endl;
98         return -1;
99     }
100 
101     struct infoCallBackContext* info = (struct infoCallBackContext*)arg;
102     using namespace phosphor::network::ncsi;
103     auto nlh = nlmsg_hdr(msg);
104 
105     struct nlattr* tb[NCSI_ATTR_MAX + 1] = {nullptr};
106     struct nla_policy ncsiPolicy[NCSI_ATTR_MAX + 1] = {
107         {NLA_UNSPEC, 0, 0}, {NLA_U32, 0, 0}, {NLA_NESTED, 0, 0},
108         {NLA_U32, 0, 0},    {NLA_U32, 0, 0},
109     };
110 
111     struct nlattr* packagetb[NCSI_PKG_ATTR_MAX + 1] = {nullptr};
112     struct nla_policy packagePolicy[NCSI_PKG_ATTR_MAX + 1] = {
113         {NLA_UNSPEC, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_U32, 0, 0},
114         {NLA_FLAG, 0, 0},   {NLA_NESTED, 0, 0},
115     };
116 
117     struct nlattr* channeltb[NCSI_CHANNEL_ATTR_MAX + 1] = {nullptr};
118     struct nla_policy channelPolicy[NCSI_CHANNEL_ATTR_MAX + 1] = {
119         {NLA_UNSPEC, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_U32, 0, 0},
120         {NLA_FLAG, 0, 0},   {NLA_NESTED, 0, 0}, {NLA_UNSPEC, 0, 0},
121     };
122 
123     auto ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsiPolicy);
124     if (!tb[NCSI_ATTR_PACKAGE_LIST])
125     {
126         std::cerr << "No Packages" << std::endl;
127         return -1;
128     }
129 
130     auto attrTgt = static_cast<nlattr*>(nla_data(tb[NCSI_ATTR_PACKAGE_LIST]));
131     if (!attrTgt)
132     {
133         std::cerr << "Package list attribute is null" << std::endl;
134         return -1;
135     }
136 
137     auto rem = nla_len(tb[NCSI_ATTR_PACKAGE_LIST]);
138     nla_for_each_nested(attrTgt, tb[NCSI_ATTR_PACKAGE_LIST], rem)
139     {
140         ret = nla_parse_nested(packagetb, NCSI_PKG_ATTR_MAX, attrTgt,
141                                packagePolicy);
142         if (ret < 0)
143         {
144             std::cerr << "Failed to parse package nested" << std::endl;
145             return -1;
146         }
147 
148         PackageInfo pkg;
149 
150         if (packagetb[NCSI_PKG_ATTR_ID])
151         {
152             auto attrID = nla_get_u32(packagetb[NCSI_PKG_ATTR_ID]);
153             pkg.id = attrID;
154         }
155         else
156         {
157             std::cout << "Package with no id" << std::endl;
158         }
159 
160         if (packagetb[NCSI_PKG_ATTR_FORCED])
161         {
162             pkg.forced = true;
163         }
164 
165         auto channelListTarget = static_cast<nlattr*>(
166             nla_data(packagetb[NCSI_PKG_ATTR_CHANNEL_LIST]));
167 
168         auto channelrem = nla_len(packagetb[NCSI_PKG_ATTR_CHANNEL_LIST]);
169         nla_for_each_nested(channelListTarget,
170                             packagetb[NCSI_PKG_ATTR_CHANNEL_LIST], channelrem)
171         {
172             ret = nla_parse_nested(channeltb, NCSI_CHANNEL_ATTR_MAX,
173                                    channelListTarget, channelPolicy);
174             if (ret < 0)
175             {
176                 std::cerr << "Failed to parse channel nested" << std::endl;
177                 continue;
178             }
179 
180             ChannelInfo chan{};
181 
182             if (channeltb[NCSI_CHANNEL_ATTR_ID])
183             {
184                 chan.id = nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_ID]);
185                 chan.active = !!channeltb[NCSI_CHANNEL_ATTR_ACTIVE];
186                 chan.forced = !!channeltb[NCSI_CHANNEL_ATTR_FORCED];
187             }
188             else
189             {
190                 std::cout << "Channel with no ID" << std::endl;
191                 continue;
192             }
193 
194             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR])
195             {
196                 chan.version_major =
197                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR]);
198             }
199             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR])
200             {
201                 chan.version_minor =
202                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR]);
203             }
204             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_STR])
205             {
206                 chan.version =
207                     nla_get_string(channeltb[NCSI_CHANNEL_ATTR_VERSION_STR]);
208             }
209             if (channeltb[NCSI_CHANNEL_ATTR_LINK_STATE])
210             {
211                 chan.link_state =
212                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_LINK_STATE]);
213             }
214             if (channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST])
215             {
216                 auto vids = channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST];
217                 auto vid = static_cast<nlattr*>(nla_data(vids));
218                 auto len = nla_len(vids);
219                 while (nla_ok(vid, len))
220                 {
221                     auto id = nla_get_u16(vid);
222                     chan.vlan_ids.push_back(id);
223                     vid = nla_next(vid, &len);
224                 }
225             }
226             pkg.channels.push_back(chan);
227         }
228 
229         info->info->packages.push_back(pkg);
230     }
231     return static_cast<int>(NL_STOP);
232 };
233 
234 struct sendCallBackContext
235 {
236     NCSIResponse resp;
237 };
238 
__anon66112f010202(struct nl_msg* msg, void* arg) 239 CallBack sendCallBack = [](struct nl_msg* msg, void* arg) {
240     using namespace phosphor::network::ncsi;
241     auto nlh = nlmsg_hdr(msg);
242     struct nlattr* tb[NCSI_ATTR_MAX + 1] = {nullptr};
243     static struct nla_policy ncsiPolicy[NCSI_ATTR_MAX + 1] = {
244         {NLA_UNSPEC, 0, 0}, {NLA_U32, 0, 0}, {NLA_NESTED, 0, 0},
245         {NLA_U32, 0, 0},    {NLA_U32, 0, 0}, {NLA_BINARY, 0, 0},
246         {NLA_FLAG, 0, 0},   {NLA_U32, 0, 0}, {NLA_U32, 0, 0},
247     };
248 
249     if (arg == nullptr)
250     {
251         std::cerr << "Internal error: invalid send callback context"
252                   << std::endl;
253         return -1;
254     }
255 
256     struct sendCallBackContext* ctx = (struct sendCallBackContext*)arg;
257 
258     auto ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsiPolicy);
259     if (ret)
260     {
261         std::cerr << "Failed to parse message" << std::endl;
262         return ret;
263     }
264 
265     if (tb[NCSI_ATTR_DATA] == nullptr)
266     {
267         std::cerr << "Response: No data" << std::endl;
268         return -1;
269     }
270 
271     size_t data_len = nla_len(tb[NCSI_ATTR_DATA]);
272     unsigned char* data = (unsigned char*)nla_data(tb[NCSI_ATTR_DATA]);
273 
274     ctx->resp.full_payload.assign(data, data + data_len);
275 
276     int rc = ctx->resp.parseFullPayload();
277     if (rc)
278     {
279         return -1;
280     }
281 
282     return static_cast<int>(NL_STOP);
283 };
284 
applyCmd(NetlinkInterface & interface,const NetlinkCommand & cmd,int package=DEFAULT_VALUE,int channel=DEFAULT_VALUE,int flags=NONE,CallBack function=nullptr,void * arg=nullptr)285 int applyCmd(NetlinkInterface& interface, const NetlinkCommand& cmd,
286              int package = DEFAULT_VALUE, int channel = DEFAULT_VALUE,
287              int flags = NONE, CallBack function = nullptr, void* arg = nullptr)
288 {
289     nlSocketPtr socket(nl_socket_alloc(), &::nl_socket_free);
290     if (socket == nullptr)
291     {
292         std::cerr << "Unable to allocate memory for the socket" << std::endl;
293         return -ENOMEM;
294     }
295 
296     nl_socket_disable_auto_ack(socket.get());
297 
298     auto ret = genl_connect(socket.get());
299     if (ret < 0)
300     {
301         std::cerr << "Failed to open the socket , RC : " << ret << std::endl;
302         return ret;
303     }
304 
305     auto driverID = genl_ctrl_resolve(socket.get(), "NCSI");
306     if (driverID < 0)
307     {
308         std::cerr << "Failed to resolve, RC : " << ret << std::endl;
309         return driverID;
310     }
311 
312     nlMsgPtr msg(nlmsg_alloc(), &::nlmsg_free);
313     if (msg == nullptr)
314     {
315         std::cerr << "Unable to allocate memory for the message" << std::endl;
316         return -ENOMEM;
317     }
318 
319     auto msgHdr = genlmsg_put(msg.get(), NL_AUTO_PORT, NL_AUTO_SEQ, driverID, 0,
320                               flags, cmd.ncsi_cmd, 0);
321     if (!msgHdr)
322     {
323         std::cerr << "Unable to add the netlink headers , COMMAND : "
324                   << cmd.ncsi_cmd << std::endl;
325         return -ENOMEM;
326     }
327 
328     if (package != DEFAULT_VALUE)
329     {
330         ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_PACKAGE_ID,
331                           package);
332         if (ret < 0)
333         {
334             std::cerr << "Failed to set the attribute , RC : " << ret
335                       << " PACKAGE " << package << std::endl;
336             return ret;
337         }
338     }
339 
340     if (channel != DEFAULT_VALUE)
341     {
342         ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_CHANNEL_ID,
343                           channel);
344         if (ret < 0)
345         {
346             std::cerr << "Failed to set the attribute , RC : " << ret
347                       << " CHANNEL : " << channel << std::endl;
348             return ret;
349         }
350     }
351 
352     ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_IFINDEX,
353                       interface.ifindex);
354     if (ret < 0)
355     {
356         std::cerr << "Failed to set the attribute , RC : " << ret
357                   << " INTERFACE : " << interface.ifindex << std::endl;
358         return ret;
359     }
360 
361     if ((cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK) ||
362         (cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_CHANNEL_MASK))
363     {
364         if (cmd.payload.size() != sizeof(unsigned int))
365         {
366             std::cerr << "Package/Channel mask must be 32-bits" << std::endl;
367             return -EINVAL;
368         }
369         int maskAttr =
370             cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK
371                 ? NCSI_ATTR_PACKAGE_MASK
372                 : NCSI_ATTR_CHANNEL_MASK;
373         ret = nla_put_u32(
374             msg.get(), maskAttr,
375             *(reinterpret_cast<const unsigned int*>(cmd.payload.data())));
376         if (ret < 0)
377         {
378             std::cerr << "Failed to set the mask attribute, RC : " << ret
379                       << std::endl;
380             return ret;
381         }
382     }
383     else if (cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SEND_CMD)
384     {
385         std::vector<unsigned char> pl(
386             sizeof(NCSIPacketHeader) + cmd.payload.size());
387         NCSIPacketHeader* hdr = reinterpret_cast<NCSIPacketHeader*>(pl.data());
388 
389         std::copy(cmd.payload.begin(), cmd.payload.end(),
390                   pl.begin() + sizeof(NCSIPacketHeader));
391 
392         hdr->type = cmd.operation;
393         hdr->length = htons(cmd.payload.size());
394 
395         ret = nla_put(msg.get(), ncsi_nl_attrs::NCSI_ATTR_DATA, pl.size(),
396                       pl.data());
397         if (ret < 0)
398         {
399             std::cerr << "Failed to set the data attribute, RC : " << ret
400                       << std::endl;
401             return ret;
402         }
403 
404         nl_socket_disable_seq_check(socket.get());
405     }
406 
407     // Add a callback function to the socket
408     enum nl_cb_kind cb_kind = function ? NL_CB_CUSTOM : NL_CB_DEFAULT;
409     nl_socket_modify_cb(socket.get(), NL_CB_VALID, cb_kind, function, arg);
410 
411     ret = nl_send_auto(socket.get(), msg.get());
412     if (ret < 0)
413     {
414         std::cerr << "Failed to send the message , RC : " << ret << std::endl;
415         return ret;
416     }
417 
418     ret = nl_recvmsgs_default(socket.get());
419     if (ret < 0)
420     {
421         std::cerr << "Failed to receive the message , RC : " << ret
422                   << std::endl;
423         return ret;
424     }
425 
426     return 0;
427 }
428 
429 } // namespace internal
430 
to_string(Interface & interface)431 std::string to_string(Interface& interface)
432 {
433     return interface.toString();
434 }
435 
NetlinkInterface(int ifindex)436 NetlinkInterface::NetlinkInterface(int ifindex) : ifindex(ifindex) {}
437 
toString()438 std::string NetlinkInterface::toString()
439 {
440     return std::to_string(ifindex);
441 }
442 
sendCommand(NCSICommand & cmd)443 std::optional<NCSIResponse> NetlinkInterface::sendCommand(NCSICommand& cmd)
444 {
445     std::cout << "Send Command, CHANNEL : " << std::hex << (int)cmd.getChannel()
446               << " , PACKAGE : " << (int)cmd.package << " , INTERFACE: " << this
447               << std::dec << std::endl;
448 
449     internal::sendCallBackContext ctx{};
450 
451     internal::NetlinkCommand nl_cmd(ncsi_nl_commands::NCSI_CMD_SEND_CMD,
452                                     cmd.opcode, cmd.payload);
453 
454     int rc = internal::applyCmd(*this, nl_cmd, cmd.package, cmd.getChannel(),
455                                 NONE, internal::sendCallBack, &ctx);
456 
457     if (rc < 0)
458     {
459         return {};
460     }
461 
462     return ctx.resp;
463 }
464 
setChannel(int package,int channel)465 int NetlinkInterface::setChannel(int package, int channel)
466 {
467     std::cout << "Set CHANNEL : " << std::hex << channel << " , PACKAGE : "
468               << package << " , INTERFACE : " << this << std::dec << std::endl;
469 
470     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_INTERFACE);
471 
472     return internal::applyCmd(*this, cmd, package, channel);
473 }
474 
clearInterface()475 int NetlinkInterface::clearInterface()
476 {
477     std::cout << "ClearInterface , INTERFACE : " << this << std::endl;
478 
479     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_CLEAR_INTERFACE);
480     return internal::applyCmd(*this, cmd);
481 }
482 
getInfo(int package)483 std::optional<InterfaceInfo> NetlinkInterface::getInfo(int package)
484 {
485     int rc, flags = package == DEFAULT_VALUE ? NLM_F_DUMP : NONE;
486     InterfaceInfo info;
487 
488     std::cout << "Get Info , PACKAGE : " << std::hex << package
489               << " , INTERFACE: " << this << std::dec << std::endl;
490 
491     struct internal::infoCallBackContext ctx = {
492         .info = &info,
493     };
494 
495     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_PKG_INFO);
496 
497     rc = internal::applyCmd(*this, cmd, package, DEFAULT_VALUE, flags,
498                             internal::infoCallBack, &ctx);
499 
500     if (rc < 0)
501     {
502         return {};
503     }
504 
505     return info;
506 }
507 
setPackageMask(unsigned int mask)508 int NetlinkInterface::setPackageMask(unsigned int mask)
509 {
510     std::cout << "Set Package Mask , INTERFACE: " << this
511               << " MASK: " << std::hex << mask << std::dec << std::endl;
512     auto payload = std::span<const unsigned char>(
513         reinterpret_cast<const unsigned char*>(&mask),
514         reinterpret_cast<const unsigned char*>(&mask) + sizeof(decltype(mask)));
515 
516     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK, 0,
517                                  payload);
518     return internal::applyCmd(*this, cmd);
519 }
520 
setChannelMask(int package,unsigned int mask)521 int NetlinkInterface::setChannelMask(int package, unsigned int mask)
522 {
523     std::cout << "Set Channel Mask , INTERFACE: " << this
524               << " , PACKAGE : " << std::hex << package << " MASK: " << mask
525               << std::dec << std::endl;
526     auto payload = std::span<const unsigned char>(
527         reinterpret_cast<const unsigned char*>(&mask),
528         reinterpret_cast<const unsigned char*>(&mask) + sizeof(decltype(mask)));
529 
530     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_CHANNEL_MASK, 0,
531                                  payload);
532     return internal::applyCmd(*this, cmd);
533 }
534 
parseFullPayload()535 int NCSIResponse::parseFullPayload()
536 {
537     if (this->full_payload.size() < sizeof(internal::NCSIPacketHeader) +
538                                         sizeof(internal::NCSIResponsePayload))
539     {
540         std::cerr << "Response: Not enough data for a response message"
541                   << std::endl;
542         return -1;
543     }
544 
545     internal::NCSIPacketHeader* respHeader =
546         reinterpret_cast<decltype(respHeader)>(this->full_payload.data());
547 
548     unsigned int payloadLen = ntohs(respHeader->length & htons(0x0fff));
549     /* we have determined that the payload size is larger than *respHeader,
550      * so cannot underflow here */
551     if (payloadLen > this->full_payload.size() - sizeof(*respHeader))
552     {
553         std::cerr << "Invalid header length " << payloadLen << " (vs "
554                   << (this->full_payload.size() - sizeof(*respHeader))
555                   << ") in response" << std::endl;
556         return -1;
557     }
558 
559     this->opcode = respHeader->type;
560     this->payload =
561         std::span(this->full_payload.begin() + sizeof(*respHeader), payloadLen);
562 
563     internal::NCSIResponsePayload* respPayload =
564         reinterpret_cast<decltype(respPayload)>(this->payload.data());
565     this->response = ntohs(respPayload->response);
566     this->reason = ntohs(respPayload->reason);
567 
568     return 0;
569 }
570 
571 static const uint8_t MCTP_TYPE_NCSI = 2;
572 
573 struct NCSIResponsePayload
574 {
575     uint16_t response;
576     uint16_t reason;
577 };
578 
sendCommand(NCSICommand & cmd)579 std::optional<NCSIResponse> MCTPInterface::sendCommand(NCSICommand& cmd)
580 {
581     static constexpr uint8_t mcid = 0; /* no need to distinguish controllers */
582     static constexpr size_t maxRespLen = 16384;
583     size_t payloadLen, padLen;
584     ssize_t wlen, rlen;
585 
586     payloadLen = cmd.payload.size();
587 
588     auto tmp = allocateIID();
589     if (!tmp.has_value())
590     {
591         return {};
592     }
593     uint8_t iid = *tmp;
594 
595     internal::NCSIPacketHeader cmdHeader{};
596     cmdHeader.MCID = mcid;
597     cmdHeader.revision = 1;
598     cmdHeader.id = iid;
599     cmdHeader.type = cmd.opcode;
600     cmdHeader.channel = (uint8_t)(cmd.package << 5 | cmd.getChannel());
601     cmdHeader.length = htons(payloadLen);
602 
603     struct iovec iov[3];
604     iov[0].iov_base = &cmdHeader;
605     iov[0].iov_len = sizeof(cmdHeader);
606     iov[1].iov_base = cmd.payload.data();
607     iov[1].iov_len = payloadLen;
608 
609     /* the checksum must appear on a 4-byte boundary */
610     padLen = 4 - (payloadLen & 0x3);
611     if (padLen == 4)
612     {
613         padLen = 0;
614     }
615     uint8_t crc32buf[8] = {};
616     /* todo: set csum; zeros currently indicate no checksum present */
617     uint32_t crc32 = 0;
618 
619     memcpy(crc32buf + padLen, &crc32, sizeof(crc32));
620     padLen += sizeof(crc32);
621 
622     iov[2].iov_base = crc32buf;
623     iov[2].iov_len = padLen;
624 
625     struct sockaddr_mctp addr = {};
626     addr.smctp_family = AF_MCTP;
627     addr.smctp_network = net;
628     addr.smctp_addr.s_addr = eid;
629     addr.smctp_tag = MCTP_TAG_OWNER;
630     addr.smctp_type = MCTP_TYPE_NCSI;
631 
632     struct msghdr msg = {};
633     msg.msg_name = &addr;
634     msg.msg_namelen = sizeof(addr);
635     msg.msg_iov = iov;
636     msg.msg_iovlen = 3;
637 
638     wlen = sendmsg(sd, &msg, 0);
639     if (wlen < 0)
640     {
641         std::stringstream ss;
642         std::cout << "Failed to send MCTP message, ERRNO: " << -errno
643                   << std::endl;
644 
645         return {};
646     }
647     else if ((size_t)wlen != sizeof(cmdHeader) + payloadLen + padLen)
648     {
649         std::cout << "Short write sending MCTP message, LEN: " << wlen
650                   << std::endl;
651         return {};
652     }
653 
654     internal::NCSIPacketHeader* respHeader;
655     NCSIResponsePayload* respPayload = nullptr;
656     NCSIResponse resp{};
657 
658     resp.full_payload.resize(maxRespLen);
659     iov[0].iov_len = resp.full_payload.size();
660     iov[0].iov_base = resp.full_payload.data();
661 
662     msg.msg_name = &addr;
663     msg.msg_namelen = sizeof(addr);
664     msg.msg_iov = iov;
665     msg.msg_iovlen = 1;
666 
667     /* we have set SO_RCVTIMEO, so this won't block forever... */
668     rlen = recvmsg(sd, &msg, MSG_TRUNC);
669     if (rlen < 0)
670     {
671         std::cerr << "Failed to read MCTP response, ERRNO: " << -rlen
672                   << std::endl;
673         return {};
674     }
675     else if ((size_t)rlen < sizeof(*respHeader) + sizeof(*respPayload))
676     {
677         std::cerr << "Short read receiving MCTP message, LEN: " << rlen
678                   << std::endl;
679         return {};
680     }
681     else if ((size_t)rlen > maxRespLen)
682     {
683         std::cerr << "MCTP response is too large, LEN: " << rlen << std::endl;
684         return {};
685     }
686 
687     resp.full_payload.resize(rlen);
688 
689     respHeader =
690         reinterpret_cast<decltype(respHeader)>(resp.full_payload.data());
691 
692     /* header validation */
693     if (respHeader->MCID != mcid)
694     {
695         std::cerr << "Invalid MCID " << std::hex << (int)respHeader->MCID
696                   << std::dec << " in response" << std::endl;
697         return {};
698     }
699 
700     if (respHeader->id != iid)
701     {
702         std::cerr << "Invalid IID " << std::hex << (int)respHeader->id
703                   << std::dec << " in response" << std::endl;
704         return {};
705     }
706 
707     if (respHeader->type != (cmd.opcode | 0x80))
708     {
709         std::cerr << "Invalid opcode " << std::hex << (int)respHeader->type
710                   << std::dec << " in response" << std::endl;
711         return {};
712     }
713 
714     int rc = resp.parseFullPayload();
715     if (rc)
716     {
717         return {};
718     }
719 
720     return resp;
721 }
722 
toString()723 std::string MCTPInterface::toString()
724 {
725     return std::to_string(net) + "," + std::to_string(eid);
726 }
727 
MCTPInterface(int net,uint8_t eid)728 MCTPInterface::MCTPInterface(int net, uint8_t eid) : net(net), eid(eid)
729 {
730     static const struct timeval receiveTimeout = {
731         .tv_sec = 1,
732         .tv_usec = 0,
733     };
734 
735     int _sd = socket(AF_MCTP, SOCK_DGRAM, 0);
736     if (_sd < 0)
737     {
738         throw std::system_error(errno, std::system_category(),
739                                 "Can't create MCTP socket");
740     }
741 
742     int rc = setsockopt(_sd, SOL_SOCKET, SO_RCVTIMEO, &receiveTimeout,
743                         sizeof(receiveTimeout));
744     if (rc != 0)
745     {
746         throw std::system_error(errno, std::system_category(),
747                                 "Can't set socket receive timemout");
748     }
749 
750     sd = _sd;
751 }
752 
~MCTPInterface()753 MCTPInterface::~MCTPInterface()
754 {
755     close(sd);
756 }
757 
758 /* Small fd wrapper to provide RAII semantics, closing the IID file descriptor
759  * when we go out of scope.
760  */
761 struct IidFd
762 {
763     int fd;
IidFdphosphor::network::ncsi::IidFd764     IidFd(int _fd) : fd(_fd) {};
~IidFdphosphor::network::ncsi::IidFd765     ~IidFd()
766     {
767         close(fd);
768     };
769 };
770 
allocateIID()771 std::optional<uint8_t> MCTPInterface::allocateIID()
772 {
773     int fd = open(mctp_iid_path, O_RDWR | O_CREAT, 0600);
774     if (fd < 0)
775     {
776         std::cerr << "Error opening IID database " << mctp_iid_path << ": "
777                   << strerror(errno) << std::endl;
778         return {};
779     }
780 
781     IidFd iidFd(fd);
782 
783     /* lock while we read/modity/write; the lock will be short-lived, so
784      * we keep it simple and lock the entire file range
785      */
786     struct flock flock = {
787         .l_type = F_WRLCK,
788         .l_whence = SEEK_SET,
789         .l_start = 0,
790         .l_len = 0,
791         .l_pid = 0,
792     };
793 
794     int rc = fcntl(iidFd.fd, F_OFD_SETLKW, &flock);
795     if (rc)
796     {
797         std::cerr << "Error locking IID database " << mctp_iid_path << ": "
798                   << strerror(errno) << std::endl;
799         return {};
800     }
801 
802     /* An EOF (rc == 0) would indicate that we don't yet have an entry for that
803      * eid, which we handle as iid = 0.
804      */
805     uint8_t iid = 0;
806     rc = pread(iidFd.fd, &iid, sizeof(iid), eid);
807     if (rc < 0)
808     {
809         std::cerr << "Error reading IID database " << mctp_iid_path << ": "
810                   << strerror(errno) << std::endl;
811         return {};
812     }
813 
814     /* DSP0222 defines valid IIDs in the range [1, 0xff], so manually wrap */
815     if (iid == 0xff)
816     {
817         iid = 1;
818     }
819     else
820     {
821         iid++;
822     }
823 
824     rc = pwrite(iidFd.fd, &iid, sizeof(iid), eid);
825     if (rc != sizeof(iid))
826     {
827         std::cerr << "Error writing IID database " << mctp_iid_path << ": "
828                   << strerror(errno) << std::endl;
829         return {};
830     }
831 
832     return iid;
833 }
834 
835 } // namespace ncsi
836 } // namespace network
837 } // namespace phosphor
838