xref: /openbmc/phosphor-networkd/src/ncsi_util.cpp (revision a42a8651da4462615e5d22a9c048583f9bde6fdd)
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 <phosphor-logging/lg2.hpp>
13 
14 #include <optional>
15 #include <span>
16 #include <system_error>
17 #include <vector>
18 
19 namespace phosphor
20 {
21 namespace network
22 {
23 namespace ncsi
24 {
25 
26 static const char* mctp_iid_path = "/run/ncsi-mctp-iids";
27 
NCSICommand(uint8_t opcode,uint8_t package,std::optional<uint8_t> channel,std::span<unsigned char> payload)28 NCSICommand::NCSICommand(uint8_t opcode, uint8_t package,
29                          std::optional<uint8_t> channel,
30                          std::span<unsigned char> payload) :
31     opcode(opcode), package(package), channel(channel)
32 {
33     this->payload.assign(payload.begin(), payload.end());
34 }
35 
getChannel()36 uint8_t NCSICommand::getChannel()
37 {
38     return channel.value_or(CHANNEL_ID_NONE);
39 }
40 
41 using CallBack = int (*)(struct nl_msg* msg, void* arg);
42 
43 namespace internal
44 {
45 
46 struct NCSIPacketHeader
47 {
48     uint8_t MCID;
49     uint8_t revision;
50     uint8_t reserved;
51     uint8_t id;
52     uint8_t type;
53     uint8_t channel;
54     uint16_t length;
55     uint32_t rsvd[2];
56 };
57 
58 struct NCSIResponsePayload
59 {
60     uint16_t response;
61     uint16_t reason;
62 };
63 
64 class NetlinkCommand
65 {
66   public:
67     NetlinkCommand() = delete;
68     ~NetlinkCommand() = default;
69     NetlinkCommand(const NetlinkCommand&) = delete;
70     NetlinkCommand& operator=(const NetlinkCommand&) = delete;
71     NetlinkCommand(NetlinkCommand&&) = default;
72     NetlinkCommand& operator=(NetlinkCommand&&) = default;
NetlinkCommand(int ncsiCmd,int operation=DEFAULT_VALUE,std::span<const unsigned char> p=std::span<const unsigned char> ())73     NetlinkCommand(
74         int ncsiCmd, int operation = DEFAULT_VALUE,
75         std::span<const unsigned char> p = std::span<const unsigned char>()) :
76         ncsi_cmd(ncsiCmd), operation(operation), payload(p)
77     {}
78 
79     int ncsi_cmd;
80     int operation;
81     std::span<const unsigned char> payload;
82 };
83 
84 using nlMsgPtr = std::unique_ptr<nl_msg, decltype(&::nlmsg_free)>;
85 using nlSocketPtr = std::unique_ptr<nl_sock, decltype(&::nl_socket_free)>;
86 
87 struct infoCallBackContext
88 {
89     InterfaceInfo* info;
90 };
91 
__anon66112f010102(struct nl_msg* msg, void* arg) 92 CallBack infoCallBack = [](struct nl_msg* msg, void* arg) {
93     if (arg == nullptr)
94     {
95         lg2::error("Internal error: invalid info callback context");
96         return -1;
97     }
98 
99     struct infoCallBackContext* info = (struct infoCallBackContext*)arg;
100     using namespace phosphor::network::ncsi;
101     auto nlh = nlmsg_hdr(msg);
102 
103     struct nlattr* tb[NCSI_ATTR_MAX + 1] = {nullptr};
104     struct nla_policy ncsiPolicy[NCSI_ATTR_MAX + 1] = {
105         {NLA_UNSPEC, 0, 0}, {NLA_U32, 0, 0}, {NLA_NESTED, 0, 0},
106         {NLA_U32, 0, 0},    {NLA_U32, 0, 0},
107     };
108 
109     struct nlattr* packagetb[NCSI_PKG_ATTR_MAX + 1] = {nullptr};
110     struct nla_policy packagePolicy[NCSI_PKG_ATTR_MAX + 1] = {
111         {NLA_UNSPEC, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_U32, 0, 0},
112         {NLA_FLAG, 0, 0},   {NLA_NESTED, 0, 0},
113     };
114 
115     struct nlattr* channeltb[NCSI_CHANNEL_ATTR_MAX + 1] = {nullptr};
116     struct nla_policy channelPolicy[NCSI_CHANNEL_ATTR_MAX + 1] = {
117         {NLA_UNSPEC, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_U32, 0, 0},
118         {NLA_FLAG, 0, 0},   {NLA_NESTED, 0, 0}, {NLA_UNSPEC, 0, 0},
119     };
120 
121     auto ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsiPolicy);
122     if (!tb[NCSI_ATTR_PACKAGE_LIST])
123     {
124         lg2::error("No Packages");
125         return -1;
126     }
127 
128     auto attrTgt = static_cast<nlattr*>(nla_data(tb[NCSI_ATTR_PACKAGE_LIST]));
129     if (!attrTgt)
130     {
131         lg2::error("Package list attribute is null");
132         return -1;
133     }
134 
135     auto rem = nla_len(tb[NCSI_ATTR_PACKAGE_LIST]);
136     nla_for_each_nested(attrTgt, tb[NCSI_ATTR_PACKAGE_LIST], rem)
137     {
138         ret = nla_parse_nested(packagetb, NCSI_PKG_ATTR_MAX, attrTgt,
139                                packagePolicy);
140         if (ret < 0)
141         {
142             lg2::error("Failed to parse package nested");
143             return -1;
144         }
145 
146         PackageInfo pkg;
147 
148         if (packagetb[NCSI_PKG_ATTR_ID])
149         {
150             auto attrID = nla_get_u32(packagetb[NCSI_PKG_ATTR_ID]);
151             pkg.id = attrID;
152         }
153         else
154         {
155             lg2::debug("Package with no id");
156         }
157 
158         if (packagetb[NCSI_PKG_ATTR_FORCED])
159         {
160             pkg.forced = true;
161         }
162 
163         auto channelListTarget = static_cast<nlattr*>(
164             nla_data(packagetb[NCSI_PKG_ATTR_CHANNEL_LIST]));
165 
166         auto channelrem = nla_len(packagetb[NCSI_PKG_ATTR_CHANNEL_LIST]);
167         nla_for_each_nested(channelListTarget,
168                             packagetb[NCSI_PKG_ATTR_CHANNEL_LIST], channelrem)
169         {
170             ret = nla_parse_nested(channeltb, NCSI_CHANNEL_ATTR_MAX,
171                                    channelListTarget, channelPolicy);
172             if (ret < 0)
173             {
174                 lg2::error("Failed to parse channel nested");
175                 continue;
176             }
177 
178             ChannelInfo chan;
179 
180             if (channeltb[NCSI_CHANNEL_ATTR_ID])
181             {
182                 chan.id = nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_ID]);
183                 chan.active = !!channeltb[NCSI_CHANNEL_ATTR_ACTIVE];
184                 chan.forced = !!channeltb[NCSI_CHANNEL_ATTR_FORCED];
185             }
186             else
187             {
188                 lg2::debug("Channel with no ID");
189                 continue;
190             }
191 
192             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR])
193             {
194                 chan.version_major =
195                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR]);
196             }
197             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR])
198             {
199                 chan.version_minor =
200                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR]);
201             }
202             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_STR])
203             {
204                 chan.version =
205                     nla_get_string(channeltb[NCSI_CHANNEL_ATTR_VERSION_STR]);
206             }
207             if (channeltb[NCSI_CHANNEL_ATTR_LINK_STATE])
208             {
209                 chan.link_state =
210                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_LINK_STATE]);
211             }
212             if (channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST])
213             {
214                 auto vids = channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST];
215                 auto vid = static_cast<nlattr*>(nla_data(vids));
216                 auto len = nla_len(vids);
217                 while (nla_ok(vid, len))
218                 {
219                     auto id = nla_get_u16(vid);
220                     chan.vlan_ids.push_back(id);
221                     vid = nla_next(vid, &len);
222                 }
223             }
224             pkg.channels.push_back(chan);
225         }
226 
227         info->info->packages.push_back(pkg);
228     }
229     return static_cast<int>(NL_STOP);
230 };
231 
232 struct sendCallBackContext
233 {
234     NCSIResponse resp;
235 };
236 
__anon66112f010202(struct nl_msg* msg, void* arg) 237 CallBack sendCallBack = [](struct nl_msg* msg, void* arg) {
238     using namespace phosphor::network::ncsi;
239     auto nlh = nlmsg_hdr(msg);
240     struct nlattr* tb[NCSI_ATTR_MAX + 1] = {nullptr};
241     static struct nla_policy ncsiPolicy[NCSI_ATTR_MAX + 1] = {
242         {NLA_UNSPEC, 0, 0}, {NLA_U32, 0, 0}, {NLA_NESTED, 0, 0},
243         {NLA_U32, 0, 0},    {NLA_U32, 0, 0}, {NLA_BINARY, 0, 0},
244         {NLA_FLAG, 0, 0},   {NLA_U32, 0, 0}, {NLA_U32, 0, 0},
245     };
246 
247     if (arg == nullptr)
248     {
249         lg2::error("Internal error: invalid send callback context");
250         return -1;
251     }
252 
253     struct sendCallBackContext* ctx = (struct sendCallBackContext*)arg;
254 
255     auto ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsiPolicy);
256     if (ret)
257     {
258         lg2::error("Failed to parse message");
259         return ret;
260     }
261 
262     if (tb[NCSI_ATTR_DATA] == nullptr)
263     {
264         lg2::error("Response: No data");
265         return -1;
266     }
267 
268     size_t data_len = nla_len(tb[NCSI_ATTR_DATA]);
269     unsigned char* data = (unsigned char*)nla_data(tb[NCSI_ATTR_DATA]);
270 
271     ctx->resp.full_payload.assign(data, data + data_len);
272 
273     int rc = ctx->resp.parseFullPayload();
274     if (rc)
275     {
276         return -1;
277     }
278 
279     return static_cast<int>(NL_STOP);
280 };
281 
applyCmd(NetlinkInterface & interface,const NetlinkCommand & cmd,int package=DEFAULT_VALUE,int channel=DEFAULT_VALUE,int flags=NONE,CallBack function=nullptr,void * arg=nullptr)282 int applyCmd(NetlinkInterface& interface, const NetlinkCommand& cmd,
283              int package = DEFAULT_VALUE, int channel = DEFAULT_VALUE,
284              int flags = NONE, CallBack function = nullptr, void* arg = nullptr)
285 {
286     nlSocketPtr socket(nl_socket_alloc(), &::nl_socket_free);
287     if (socket == nullptr)
288     {
289         lg2::error("Unable to allocate memory for the socket");
290         return -ENOMEM;
291     }
292 
293     nl_socket_disable_auto_ack(socket.get());
294 
295     auto ret = genl_connect(socket.get());
296     if (ret < 0)
297     {
298         lg2::error("Failed to open the socket , RC : {RC}", "RC", ret);
299         return ret;
300     }
301 
302     auto driverID = genl_ctrl_resolve(socket.get(), "NCSI");
303     if (driverID < 0)
304     {
305         lg2::error("Failed to resolve, RC : {RC}", "RC", ret);
306         return driverID;
307     }
308 
309     nlMsgPtr msg(nlmsg_alloc(), &::nlmsg_free);
310     if (msg == nullptr)
311     {
312         lg2::error("Unable to allocate memory for the message");
313         return -ENOMEM;
314     }
315 
316     auto msgHdr = genlmsg_put(msg.get(), NL_AUTO_PORT, NL_AUTO_SEQ, driverID, 0,
317                               flags, cmd.ncsi_cmd, 0);
318     if (!msgHdr)
319     {
320         lg2::error("Unable to add the netlink headers , COMMAND : {COMMAND}",
321                    "COMMAND", cmd.ncsi_cmd);
322         return -ENOMEM;
323     }
324 
325     if (package != DEFAULT_VALUE)
326     {
327         ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_PACKAGE_ID,
328                           package);
329         if (ret < 0)
330         {
331             lg2::error("Failed to set the attribute , RC : {RC} PACKAGE "
332                        "{PACKAGE}",
333                        "RC", ret, "PACKAGE", lg2::hex, package);
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             lg2::error("Failed to set the attribute , RC : {RC} CHANNEL : "
345                        "{CHANNEL}",
346                        "RC", ret, "CHANNEL", lg2::hex, channel);
347             return ret;
348         }
349     }
350 
351     ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_IFINDEX,
352                       interface.ifindex);
353     if (ret < 0)
354     {
355         lg2::error("Failed to set the attribute , RC : {RC} INTERFACE : "
356                    "{INTERFACE}",
357                    "RC", ret, "INTERFACE", interface);
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             lg2::error("Package/Channel mask must be 32-bits");
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             lg2::error("Failed to set the mask attribute, RC : {RC}", "RC",
379                        ret);
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 = (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             lg2::error("Failed to set the data attribute, RC : {RC}", "RC",
400                        ret);
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         lg2::error("Failed to send the message , RC : {RC}", "RC", ret);
415         return ret;
416     }
417 
418     ret = nl_recvmsgs_default(socket.get());
419     if (ret < 0)
420     {
421         lg2::error("Failed to receive the message , RC : {RC}", "RC", ret);
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     lg2::debug("Send Command, CHANNEL : {CHANNEL} , PACKAGE : {PACKAGE}, "
445                "INTERFACE: {INTERFACE}",
446                "CHANNEL", lg2::hex, cmd.getChannel(), "PACKAGE", lg2::hex,
447                cmd.package, "INTERFACE", this);
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     lg2::debug("Set CHANNEL : {CHANNEL} , PACKAGE : {PACKAGE}, INTERFACE : "
468                "{INTERFACE}",
469                "CHANNEL", lg2::hex, channel, "PACKAGE", lg2::hex, package,
470                "INTERFACE", this);
471 
472     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_INTERFACE);
473 
474     return internal::applyCmd(*this, cmd, package, channel);
475 }
476 
clearInterface()477 int NetlinkInterface::clearInterface()
478 {
479     lg2::debug("ClearInterface , INTERFACE : {INTERFACE}", "INTERFACE", this);
480 
481     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_CLEAR_INTERFACE);
482     return internal::applyCmd(*this, cmd);
483 }
484 
getInfo(int package)485 std::optional<InterfaceInfo> NetlinkInterface::getInfo(int package)
486 {
487     int rc, flags = package == DEFAULT_VALUE ? NLM_F_DUMP : NONE;
488     InterfaceInfo info;
489 
490     lg2::debug("Get Info , PACKAGE : {PACKAGE}, INTERFACE: {INTERFACE}",
491                "PACKAGE", lg2::hex, package, "INTERFACE", this);
492 
493     struct internal::infoCallBackContext ctx = {
494         .info = &info,
495     };
496 
497     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_PKG_INFO);
498 
499     rc = internal::applyCmd(*this, cmd, package, DEFAULT_VALUE, flags,
500                             internal::infoCallBack, &ctx);
501 
502     if (rc < 0)
503     {
504         return {};
505     }
506 
507     return info;
508 }
509 
setPackageMask(unsigned int mask)510 int NetlinkInterface::setPackageMask(unsigned int mask)
511 {
512     lg2::debug("Set Package Mask , INTERFACE: {INTERFACE} MASK: {MASK}",
513                "INTERFACE", this, "MASK", lg2::hex, mask);
514     auto payload = std::span<const unsigned char>(
515         reinterpret_cast<const unsigned char*>(&mask),
516         reinterpret_cast<const unsigned char*>(&mask) + sizeof(decltype(mask)));
517 
518     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK, 0,
519                                  payload);
520     return internal::applyCmd(*this, cmd);
521 }
522 
setChannelMask(int package,unsigned int mask)523 int NetlinkInterface::setChannelMask(int package, unsigned int mask)
524 {
525     lg2::debug(
526         "Set Channel Mask , INTERFACE: {INTERFACE}, PACKAGE : {PACKAGE} MASK: {MASK}",
527         "INTERFACE", this, "PACKAGE", lg2::hex, package, "MASK", lg2::hex,
528         mask);
529     auto payload = std::span<const unsigned char>(
530         reinterpret_cast<const unsigned char*>(&mask),
531         reinterpret_cast<const unsigned char*>(&mask) + sizeof(decltype(mask)));
532 
533     internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_CHANNEL_MASK, 0,
534                                  payload);
535     return internal::applyCmd(*this, cmd);
536 }
537 
parseFullPayload()538 int NCSIResponse::parseFullPayload()
539 {
540     if (this->full_payload.size() < sizeof(internal::NCSIPacketHeader) +
541                                         sizeof(internal::NCSIResponsePayload))
542     {
543         lg2::error("Response: Not enough data for a response message");
544         return -1;
545     }
546 
547     internal::NCSIPacketHeader* respHeader =
548         reinterpret_cast<decltype(respHeader)>(this->full_payload.data());
549 
550     unsigned int payloadLen = ntohs(respHeader->length & htons(0x0fff));
551     /* we have determined that the payload size is larger than *respHeader,
552      * so cannot underflow here */
553     if (payloadLen > this->full_payload.size() - sizeof(*respHeader))
554     {
555         lg2::error("Invalid header length {HDRLEN} (vs {LEN}) in response",
556                    "HDRLEN", payloadLen, "LEN",
557                    this->full_payload.size() - sizeof(*respHeader));
558         return -1;
559     }
560 
561     this->opcode = respHeader->type;
562     this->payload =
563         std::span(this->full_payload.begin() + sizeof(*respHeader), payloadLen);
564 
565     internal::NCSIResponsePayload* respPayload =
566         reinterpret_cast<decltype(respPayload)>(this->payload.data());
567     this->response = ntohs(respPayload->response);
568     this->reason = ntohs(respPayload->reason);
569 
570     return 0;
571 }
572 
573 static const uint8_t MCTP_TYPE_NCSI = 2;
574 
575 struct NCSIResponsePayload
576 {
577     uint16_t response;
578     uint16_t reason;
579 };
580 
sendCommand(NCSICommand & cmd)581 std::optional<NCSIResponse> MCTPInterface::sendCommand(NCSICommand& cmd)
582 {
583     static constexpr uint8_t mcid = 0; /* no need to distinguish controllers */
584     static constexpr size_t maxRespLen = 16384;
585     size_t payloadLen, padLen;
586     ssize_t wlen, rlen;
587 
588     payloadLen = cmd.payload.size();
589 
590     auto tmp = allocateIID();
591     if (!tmp.has_value())
592     {
593         return {};
594     }
595     uint8_t iid = *tmp;
596 
597     internal::NCSIPacketHeader cmdHeader{};
598     cmdHeader.MCID = mcid;
599     cmdHeader.revision = 1;
600     cmdHeader.id = iid;
601     cmdHeader.type = cmd.opcode;
602     cmdHeader.channel = (uint8_t)(cmd.package << 5 | cmd.getChannel());
603     cmdHeader.length = htons(payloadLen);
604 
605     struct iovec iov[3];
606     iov[0].iov_base = &cmdHeader;
607     iov[0].iov_len = sizeof(cmdHeader);
608     iov[1].iov_base = cmd.payload.data();
609     iov[1].iov_len = payloadLen;
610 
611     /* the checksum must appear on a 4-byte boundary */
612     padLen = 4 - (payloadLen & 0x3);
613     if (padLen == 4)
614     {
615         padLen = 0;
616     }
617     uint8_t crc32buf[8] = {};
618     /* todo: set csum; zeros currently indicate no checksum present */
619     uint32_t crc32 = 0;
620 
621     memcpy(crc32buf + padLen, &crc32, sizeof(crc32));
622     padLen += sizeof(crc32);
623 
624     iov[2].iov_base = crc32buf;
625     iov[2].iov_len = padLen;
626 
627     struct sockaddr_mctp addr = {};
628     addr.smctp_family = AF_MCTP;
629     addr.smctp_network = net;
630     addr.smctp_addr.s_addr = eid;
631     addr.smctp_tag = MCTP_TAG_OWNER;
632     addr.smctp_type = MCTP_TYPE_NCSI;
633 
634     struct msghdr msg = {};
635     msg.msg_name = &addr;
636     msg.msg_namelen = sizeof(addr);
637     msg.msg_iov = iov;
638     msg.msg_iovlen = 3;
639 
640     wlen = sendmsg(sd, &msg, 0);
641     if (wlen < 0)
642     {
643         lg2::error("Failed to send MCTP message, ERRNO: {ERRNO}", "ERRNO",
644                    -wlen);
645         return {};
646     }
647     else if ((size_t)wlen != sizeof(cmdHeader) + payloadLen + padLen)
648     {
649         lg2::error("Short write sending MCTP message, LEN: {LEN}", "LEN", wlen);
650         return {};
651     }
652 
653     internal::NCSIPacketHeader* respHeader;
654     NCSIResponsePayload* respPayload;
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         lg2::error("Failed to read MCTP response, ERRNO: {ERRNO}", "ERRNO",
671                    -rlen);
672         return {};
673     }
674     else if ((size_t)rlen < sizeof(*respHeader) + sizeof(*respPayload))
675     {
676         lg2::error("Short read receiving MCTP message, LEN: {LEN}", "LEN",
677                    rlen);
678         return {};
679     }
680     else if ((size_t)rlen > maxRespLen)
681     {
682         lg2::error("MCTP response is too large, LEN: {LEN}", "LEN", rlen);
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         lg2::error("Invalid MCID {MCID} in response", "MCID", lg2::hex,
695                    respHeader->MCID);
696         return {};
697     }
698 
699     if (respHeader->id != iid)
700     {
701         lg2::error("Invalid IID {IID} in response", "IID", lg2::hex,
702                    respHeader->id);
703         return {};
704     }
705 
706     if (respHeader->type != (cmd.opcode | 0x80))
707     {
708         lg2::error("Invalid opcode {OPCODE} in response", "OPCODE", lg2::hex,
709                    respHeader->type);
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         lg2::warning("Error opening IID database {FILE}: {ERROR}", "FILE",
776                      mctp_iid_path, "ERROR", strerror(errno));
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         lg2::warning("Error locking IID database {FILE}: {ERROR}", "FILE",
797                      mctp_iid_path, "ERROR", strerror(errno));
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         lg2::warning("Error reading IID database {FILE}: {ERROR}", "FILE",
809                      mctp_iid_path, "ERROR", strerror(errno));
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         lg2::warning("Error writing IID database {FILE}: {ERROR}", "FILE",
827                      mctp_iid_path, "ERROR", strerror(errno));
828         return {};
829     }
830 
831     return iid;
832 }
833 
834 } // namespace ncsi
835 } // namespace network
836 } // namespace phosphor
837