xref: /openbmc/phosphor-networkd/src/ncsi_util.cpp (revision 4882ffbc56731be4f70406f1f9a197bdbf4bffa2)
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     auto ret = genl_connect(socket.get());
297     if (ret < 0)
298     {
299         std::cerr << "Failed to open the socket , RC : " << ret << std::endl;
300         return ret;
301     }
302 
303     auto driverID = genl_ctrl_resolve(socket.get(), "NCSI");
304     if (driverID < 0)
305     {
306         std::cerr << "Failed to resolve, RC : " << ret << std::endl;
307         return driverID;
308     }
309 
310     nlMsgPtr msg(nlmsg_alloc(), &::nlmsg_free);
311     if (msg == nullptr)
312     {
313         std::cerr << "Unable to allocate memory for the message" << std::endl;
314         return -ENOMEM;
315     }
316 
317     auto msgHdr = genlmsg_put(msg.get(), NL_AUTO_PORT, NL_AUTO_SEQ, driverID, 0,
318                               flags, cmd.ncsi_cmd, 0);
319     if (!msgHdr)
320     {
321         std::cerr << "Unable to add the netlink headers , COMMAND : "
322                   << cmd.ncsi_cmd << std::endl;
323         return -ENOMEM;
324     }
325 
326     if (package != DEFAULT_VALUE)
327     {
328         ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_PACKAGE_ID,
329                           package);
330         if (ret < 0)
331         {
332             std::cerr << "Failed to set the attribute , RC : " << ret
333                       << " PACKAGE " << package << std::endl;
334             return ret;
335         }
336     }
337 
338     if (channel != DEFAULT_VALUE)
339     {
340         ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_CHANNEL_ID,
341                           channel);
342         if (ret < 0)
343         {
344             std::cerr << "Failed to set the attribute , RC : " << ret
345                       << " CHANNEL : " << channel << std::endl;
346             return ret;
347         }
348     }
349 
350     ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_IFINDEX,
351                       interface.ifindex);
352     if (ret < 0)
353     {
354         std::cerr << "Failed to set the attribute , RC : " << ret
355                   << " INTERFACE : " << interface.ifindex << std::endl;
356         return ret;
357     }
358 
359     if ((cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK) ||
360         (cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_CHANNEL_MASK))
361     {
362         if (cmd.payload.size() != sizeof(unsigned int))
363         {
364             std::cerr << "Package/Channel mask must be 32-bits" << std::endl;
365             return -EINVAL;
366         }
367         int maskAttr =
368             cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK
369                 ? NCSI_ATTR_PACKAGE_MASK
370                 : NCSI_ATTR_CHANNEL_MASK;
371         ret = nla_put_u32(
372             msg.get(), maskAttr,
373             *(reinterpret_cast<const unsigned int*>(cmd.payload.data())));
374         if (ret < 0)
375         {
376             std::cerr << "Failed to set the mask attribute, RC : " << ret
377                       << std::endl;
378             return ret;
379         }
380     }
381     else if (cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SEND_CMD)
382     {
383         std::vector<unsigned char> pl(
384             sizeof(NCSIPacketHeader) + cmd.payload.size());
385         NCSIPacketHeader* hdr = reinterpret_cast<NCSIPacketHeader*>(pl.data());
386 
387         std::copy(cmd.payload.begin(), cmd.payload.end(),
388                   pl.begin() + sizeof(NCSIPacketHeader));
389 
390         hdr->type = cmd.operation;
391         hdr->length = htons(cmd.payload.size());
392 
393         ret = nla_put(msg.get(), ncsi_nl_attrs::NCSI_ATTR_DATA, pl.size(),
394                       pl.data());
395         if (ret < 0)
396         {
397             std::cerr << "Failed to set the data attribute, RC : " << ret
398                       << std::endl;
399             return ret;
400         }
401 
402         nl_socket_disable_auto_ack(socket.get());
403         nl_socket_disable_seq_check(socket.get());
404     }
405 
406     // Add a callback function to the socket
407     enum nl_cb_kind cb_kind = function ? NL_CB_CUSTOM : NL_CB_DEFAULT;
408     nl_socket_modify_cb(socket.get(), NL_CB_VALID, cb_kind, function, arg);
409 
410     ret = nl_send_auto(socket.get(), msg.get());
411     if (ret < 0)
412     {
413         std::cerr << "Failed to send the message , RC : " << ret << std::endl;
414         return ret;
415     }
416 
417     ret = nl_recvmsgs_default(socket.get());
418     if (ret < 0)
419     {
420         std::cerr << "Failed to receive the message , RC : " << ret
421                   << std::endl;
422         return ret;
423     }
424 
425     return 0;
426 }
427 
428 } // namespace internal
429 
to_string(Interface & interface)430 std::string to_string(Interface& interface)
431 {
432     return interface.toString();
433 }
434 
NetlinkInterface(int ifindex)435 NetlinkInterface::NetlinkInterface(int ifindex) : ifindex(ifindex) {}
436 
toString()437 std::string NetlinkInterface::toString()
438 {
439     return std::to_string(ifindex);
440 }
441 
sendCommand(NCSICommand & cmd)442 std::optional<NCSIResponse> NetlinkInterface::sendCommand(NCSICommand& cmd)
443 {
444     std::cout << "Send Command, CHANNEL : " << std::hex << (int)cmd.getChannel()
445               << " , PACKAGE : " << (int)cmd.package << " , INTERFACE: " << this
446               << std::dec << std::endl;
447 
448     internal::sendCallBackContext ctx{};
449 
450     internal::NetlinkCommand nl_cmd(ncsi_nl_commands::NCSI_CMD_SEND_CMD,
451                                     cmd.opcode, cmd.payload);
452 
453     int rc = internal::applyCmd(*this, nl_cmd, cmd.package, cmd.getChannel(),
454                                 NONE, internal::sendCallBack, &ctx);
455 
456     if (rc < 0)
457     {
458         return {};
459     }
460 
461     return ctx.resp;
462 }
463 
setChannel(int package,int channel)464 int NetlinkInterface::setChannel(int package, int channel)
465 {
466     std::cout << "Set CHANNEL : " << std::hex << channel << " , PACKAGE : "
467               << package << " , INTERFACE : " << this << std::dec << std::endl;
468 
469     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_INTERFACE);
470 
471     return internal::applyCmd(*this, cmd, package, channel);
472 }
473 
clearInterface()474 int NetlinkInterface::clearInterface()
475 {
476     std::cout << "ClearInterface , INTERFACE : " << this << std::endl;
477 
478     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_CLEAR_INTERFACE);
479     return internal::applyCmd(*this, cmd);
480 }
481 
getInfo(int package)482 std::optional<InterfaceInfo> NetlinkInterface::getInfo(int package)
483 {
484     int rc, flags = package == DEFAULT_VALUE ? NLM_F_DUMP : NONE;
485     InterfaceInfo info;
486 
487     std::cout << "Get Info , PACKAGE : " << std::hex << package
488               << " , INTERFACE: " << this << std::dec << std::endl;
489 
490     struct internal::infoCallBackContext ctx = {
491         .info = &info,
492     };
493 
494     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_PKG_INFO);
495 
496     rc = internal::applyCmd(*this, cmd, package, DEFAULT_VALUE, flags,
497                             internal::infoCallBack, &ctx);
498 
499     if (rc < 0)
500     {
501         return {};
502     }
503 
504     return info;
505 }
506 
setPackageMask(unsigned int mask)507 int NetlinkInterface::setPackageMask(unsigned int mask)
508 {
509     std::cout << "Set Package Mask , INTERFACE: " << this
510               << " MASK: " << std::hex << mask << std::dec << std::endl;
511     auto payload = std::span<const unsigned char>(
512         reinterpret_cast<const unsigned char*>(&mask),
513         reinterpret_cast<const unsigned char*>(&mask) + sizeof(decltype(mask)));
514 
515     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK, 0,
516                                  payload);
517     return internal::applyCmd(*this, cmd);
518 }
519 
setChannelMask(int package,unsigned int mask)520 int NetlinkInterface::setChannelMask(int package, unsigned int mask)
521 {
522     std::cout << "Set Channel Mask , INTERFACE: " << this
523               << " , PACKAGE : " << std::hex << package << " MASK: " << mask
524               << std::dec << std::endl;
525     auto payload = std::span<const unsigned char>(
526         reinterpret_cast<const unsigned char*>(&mask),
527         reinterpret_cast<const unsigned char*>(&mask) + sizeof(decltype(mask)));
528 
529     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_CHANNEL_MASK, 0,
530                                  payload);
531     return internal::applyCmd(*this, cmd);
532 }
533 
parseFullPayload()534 int NCSIResponse::parseFullPayload()
535 {
536     if (this->full_payload.size() < sizeof(internal::NCSIPacketHeader) +
537                                         sizeof(internal::NCSIResponsePayload))
538     {
539         std::cerr << "Response: Not enough data for a response message"
540                   << std::endl;
541         return -1;
542     }
543 
544     internal::NCSIPacketHeader* respHeader =
545         reinterpret_cast<decltype(respHeader)>(this->full_payload.data());
546 
547     unsigned int payloadLen = ntohs(respHeader->length & htons(0x0fff));
548     /* we have determined that the payload size is larger than *respHeader,
549      * so cannot underflow here */
550     if (payloadLen > this->full_payload.size() - sizeof(*respHeader))
551     {
552         std::cerr << "Invalid header length " << payloadLen << " (vs "
553                   << (this->full_payload.size() - sizeof(*respHeader))
554                   << ") in response" << std::endl;
555         return -1;
556     }
557 
558     this->opcode = respHeader->type;
559     this->payload =
560         std::span(this->full_payload.begin() + sizeof(*respHeader), payloadLen);
561 
562     internal::NCSIResponsePayload* respPayload =
563         reinterpret_cast<decltype(respPayload)>(this->payload.data());
564     this->response = ntohs(respPayload->response);
565     this->reason = ntohs(respPayload->reason);
566 
567     return 0;
568 }
569 
570 static const uint8_t MCTP_TYPE_NCSI = 2;
571 
572 struct NCSIResponsePayload
573 {
574     uint16_t response;
575     uint16_t reason;
576 };
577 
sendCommand(NCSICommand & cmd)578 std::optional<NCSIResponse> MCTPInterface::sendCommand(NCSICommand& cmd)
579 {
580     static constexpr uint8_t mcid = 0; /* no need to distinguish controllers */
581     static constexpr size_t maxRespLen = 16384;
582     size_t payloadLen, padLen;
583     ssize_t wlen, rlen;
584 
585     payloadLen = cmd.payload.size();
586 
587     auto tmp = allocateIID();
588     if (!tmp.has_value())
589     {
590         return {};
591     }
592     uint8_t iid = *tmp;
593 
594     internal::NCSIPacketHeader cmdHeader{};
595     cmdHeader.MCID = mcid;
596     cmdHeader.revision = 1;
597     cmdHeader.id = iid;
598     cmdHeader.type = cmd.opcode;
599     cmdHeader.channel = (uint8_t)(cmd.package << 5 | cmd.getChannel());
600     cmdHeader.length = htons(payloadLen);
601 
602     struct iovec iov[3];
603     iov[0].iov_base = &cmdHeader;
604     iov[0].iov_len = sizeof(cmdHeader);
605     iov[1].iov_base = cmd.payload.data();
606     iov[1].iov_len = payloadLen;
607 
608     /* the checksum must appear on a 4-byte boundary */
609     padLen = 4 - (payloadLen & 0x3);
610     if (padLen == 4)
611     {
612         padLen = 0;
613     }
614     uint8_t crc32buf[8] = {};
615     /* todo: set csum; zeros currently indicate no checksum present */
616     uint32_t crc32 = 0;
617 
618     memcpy(crc32buf + padLen, &crc32, sizeof(crc32));
619     padLen += sizeof(crc32);
620 
621     iov[2].iov_base = crc32buf;
622     iov[2].iov_len = padLen;
623 
624     struct sockaddr_mctp addr = {};
625     addr.smctp_family = AF_MCTP;
626     addr.smctp_network = net;
627     addr.smctp_addr.s_addr = eid;
628     addr.smctp_tag = MCTP_TAG_OWNER;
629     addr.smctp_type = MCTP_TYPE_NCSI;
630 
631     struct msghdr msg = {};
632     msg.msg_name = &addr;
633     msg.msg_namelen = sizeof(addr);
634     msg.msg_iov = iov;
635     msg.msg_iovlen = 3;
636 
637     wlen = sendmsg(sd, &msg, 0);
638     if (wlen < 0)
639     {
640         std::stringstream ss;
641         std::cout << "Failed to send MCTP message, ERRNO: " << -errno
642                   << std::endl;
643 
644         return {};
645     }
646     else if ((size_t)wlen != sizeof(cmdHeader) + payloadLen + padLen)
647     {
648         std::cout << "Short write sending MCTP message, LEN: " << wlen
649                   << std::endl;
650         return {};
651     }
652 
653     internal::NCSIPacketHeader* respHeader;
654     NCSIResponsePayload* respPayload = nullptr;
655     NCSIResponse resp{};
656 
657     resp.full_payload.resize(maxRespLen);
658     iov[0].iov_len = resp.full_payload.size();
659     iov[0].iov_base = resp.full_payload.data();
660 
661     msg.msg_name = &addr;
662     msg.msg_namelen = sizeof(addr);
663     msg.msg_iov = iov;
664     msg.msg_iovlen = 1;
665 
666     /* we have set SO_RCVTIMEO, so this won't block forever... */
667     rlen = recvmsg(sd, &msg, MSG_TRUNC);
668     if (rlen < 0)
669     {
670         std::cerr << "Failed to read MCTP response, ERRNO: " << -rlen
671                   << std::endl;
672         return {};
673     }
674     else if ((size_t)rlen < sizeof(*respHeader) + sizeof(*respPayload))
675     {
676         std::cerr << "Short read receiving MCTP message, LEN: " << rlen
677                   << std::endl;
678         return {};
679     }
680     else if ((size_t)rlen > maxRespLen)
681     {
682         std::cerr << "MCTP response is too large, LEN: " << rlen << std::endl;
683         return {};
684     }
685 
686     resp.full_payload.resize(rlen);
687 
688     respHeader =
689         reinterpret_cast<decltype(respHeader)>(resp.full_payload.data());
690 
691     /* header validation */
692     if (respHeader->MCID != mcid)
693     {
694         std::cerr << "Invalid MCID " << std::hex << (int)respHeader->MCID
695                   << std::dec << " in response" << std::endl;
696         return {};
697     }
698 
699     if (respHeader->id != iid)
700     {
701         std::cerr << "Invalid IID " << std::hex << (int)respHeader->id
702                   << std::dec << " in response" << std::endl;
703         return {};
704     }
705 
706     if (respHeader->type != (cmd.opcode | 0x80))
707     {
708         std::cerr << "Invalid opcode " << std::hex << (int)respHeader->type
709                   << std::dec << " in response" << std::endl;
710         return {};
711     }
712 
713     int rc = resp.parseFullPayload();
714     if (rc)
715     {
716         return {};
717     }
718 
719     return resp;
720 }
721 
toString()722 std::string MCTPInterface::toString()
723 {
724     return std::to_string(net) + "," + std::to_string(eid);
725 }
726 
MCTPInterface(int net,uint8_t eid)727 MCTPInterface::MCTPInterface(int net, uint8_t eid) : net(net), eid(eid)
728 {
729     static const struct timeval receiveTimeout = {
730         .tv_sec = 1,
731         .tv_usec = 0,
732     };
733 
734     int _sd = socket(AF_MCTP, SOCK_DGRAM, 0);
735     if (_sd < 0)
736     {
737         throw std::system_error(errno, std::system_category(),
738                                 "Can't create MCTP socket");
739     }
740 
741     int rc = setsockopt(_sd, SOL_SOCKET, SO_RCVTIMEO, &receiveTimeout,
742                         sizeof(receiveTimeout));
743     if (rc != 0)
744     {
745         throw std::system_error(errno, std::system_category(),
746                                 "Can't set socket receive timemout");
747     }
748 
749     sd = _sd;
750 }
751 
~MCTPInterface()752 MCTPInterface::~MCTPInterface()
753 {
754     close(sd);
755 }
756 
757 /* Small fd wrapper to provide RAII semantics, closing the IID file descriptor
758  * when we go out of scope.
759  */
760 struct IidFd
761 {
762     int fd;
IidFdphosphor::network::ncsi::IidFd763     IidFd(int _fd) : fd(_fd) {};
~IidFdphosphor::network::ncsi::IidFd764     ~IidFd()
765     {
766         close(fd);
767     };
768 };
769 
allocateIID()770 std::optional<uint8_t> MCTPInterface::allocateIID()
771 {
772     int fd = open(mctp_iid_path, O_RDWR | O_CREAT, 0600);
773     if (fd < 0)
774     {
775         std::cerr << "Error opening IID database " << mctp_iid_path << ": "
776                   << strerror(errno) << std::endl;
777         return {};
778     }
779 
780     IidFd iidFd(fd);
781 
782     /* lock while we read/modity/write; the lock will be short-lived, so
783      * we keep it simple and lock the entire file range
784      */
785     struct flock flock = {
786         .l_type = F_WRLCK,
787         .l_whence = SEEK_SET,
788         .l_start = 0,
789         .l_len = 0,
790         .l_pid = 0,
791     };
792 
793     int rc = fcntl(iidFd.fd, F_OFD_SETLKW, &flock);
794     if (rc)
795     {
796         std::cerr << "Error locking IID database " << mctp_iid_path << ": "
797                   << strerror(errno) << std::endl;
798         return {};
799     }
800 
801     /* An EOF (rc == 0) would indicate that we don't yet have an entry for that
802      * eid, which we handle as iid = 0.
803      */
804     uint8_t iid = 0;
805     rc = pread(iidFd.fd, &iid, sizeof(iid), eid);
806     if (rc < 0)
807     {
808         std::cerr << "Error reading IID database " << mctp_iid_path << ": "
809                   << strerror(errno) << std::endl;
810         return {};
811     }
812 
813     /* DSP0222 defines valid IIDs in the range [1, 0xff], so manually wrap */
814     if (iid == 0xff)
815     {
816         iid = 1;
817     }
818     else
819     {
820         iid++;
821     }
822 
823     rc = pwrite(iidFd.fd, &iid, sizeof(iid), eid);
824     if (rc != sizeof(iid))
825     {
826         std::cerr << "Error writing IID database " << mctp_iid_path << ": "
827                   << strerror(errno) << std::endl;
828         return {};
829     }
830 
831     return iid;
832 }
833 
834 } // namespace ncsi
835 } // namespace network
836 } // namespace phosphor
837