xref: /openbmc/phosphor-net-ipmid/sd_event_loop.cpp (revision bd2dde465b78e0515b3ba1598195f12d6d60c453)
19e801a2bSVernon Mauery #include "sd_event_loop.hpp"
29e801a2bSVernon Mauery 
39e801a2bSVernon Mauery #include "main.hpp"
49e801a2bSVernon Mauery #include "message_handler.hpp"
59e801a2bSVernon Mauery 
67b7f25f7SGeorge Liu #include <error.h>
72c15f0c3SDave Cobbley #include <netinet/in.h>
87fd26ddbSTom Joseph #include <sys/ioctl.h>
92c15f0c3SDave Cobbley #include <sys/socket.h>
107fd26ddbSTom Joseph #include <systemd/sd-daemon.h>
119e801a2bSVernon Mauery 
12cbccb05aSVernon Mauery #include <boost/asio/io_context.hpp>
1307bb0951SEd Tanous #include <boost/asio/signal_set.hpp>
147b7f25f7SGeorge Liu #include <phosphor-logging/lg2.hpp>
158c0bf983SAlvin Wang #include <user_channel/channel_layer.hpp>
167fd26ddbSTom Joseph 
177fd26ddbSTom Joseph namespace eventloop
187fd26ddbSTom Joseph {
197fd26ddbSTom Joseph 
handleRmcpPacket()207a0142c5SVernon Mauery void EventLoop::handleRmcpPacket()
217fd26ddbSTom Joseph {
227fd26ddbSTom Joseph     try
237fd26ddbSTom Joseph     {
247a0142c5SVernon Mauery         auto channelPtr = std::make_shared<udpsocket::Channel>(udpSocket);
257fd26ddbSTom Joseph 
267fd26ddbSTom Joseph         // Initialize the Message Handler with the socket channel
278d6f200cSVernon Mauery         auto msgHandler = std::make_shared<message::Handler>(channelPtr, io);
287fd26ddbSTom Joseph 
298d6f200cSVernon Mauery         msgHandler->processIncoming();
307fd26ddbSTom Joseph     }
317a0142c5SVernon Mauery     catch (const std::exception& e)
327fd26ddbSTom Joseph     {
337b7f25f7SGeorge Liu         lg2::error("Executing the IPMI message failed: {ERROR}", "ERROR", e);
347a0142c5SVernon Mauery     }
357fd26ddbSTom Joseph }
367fd26ddbSTom Joseph 
startRmcpReceive()377a0142c5SVernon Mauery void EventLoop::startRmcpReceive()
387a0142c5SVernon Mauery {
398425624aSPatrick Williams     udpSocket->async_wait(
408425624aSPatrick Williams         boost::asio::socket_base::wait_read,
417a0142c5SVernon Mauery         [this](const boost::system::error_code& ec) {
427a0142c5SVernon Mauery             if (!ec)
437a0142c5SVernon Mauery             {
44099fb097SPatrick Williams                 boost::asio::post(*io, [this]() { startRmcpReceive(); });
457a0142c5SVernon Mauery                 handleRmcpPacket();
467a0142c5SVernon Mauery             }
477a0142c5SVernon Mauery         });
487fd26ddbSTom Joseph }
497fd26ddbSTom Joseph 
getVLANID(const std::string channel)508c0bf983SAlvin Wang int EventLoop::getVLANID(const std::string channel)
518c0bf983SAlvin Wang {
528c0bf983SAlvin Wang     int vlanid = 0;
538c0bf983SAlvin Wang     if (channel.empty())
548c0bf983SAlvin Wang     {
558c0bf983SAlvin Wang         return 0;
568c0bf983SAlvin Wang     }
578c0bf983SAlvin Wang 
580a59062cSPatrick Williams     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
598c0bf983SAlvin Wang     // Enumerate all VLAN + ETHERNET interfaces
608c0bf983SAlvin Wang     auto req = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF,
618c0bf983SAlvin Wang                                    "GetSubTree");
628c0bf983SAlvin Wang     req.append(PATH_ROOT, 0,
638c0bf983SAlvin Wang                std::vector<std::string>{INTF_VLAN, INTF_ETHERNET});
648c0bf983SAlvin Wang     ObjectTree objs;
658c0bf983SAlvin Wang     try
668c0bf983SAlvin Wang     {
678c0bf983SAlvin Wang         auto reply = bus.call(req);
688c0bf983SAlvin Wang         reply.read(objs);
698c0bf983SAlvin Wang     }
7012d199b2SPatrick Williams     catch (const std::exception& e)
718c0bf983SAlvin Wang     {
727b7f25f7SGeorge Liu         lg2::error("getVLANID: failed to execute/read GetSubTree: {ERROR}",
737b7f25f7SGeorge Liu                    "ERROR", e);
748c0bf983SAlvin Wang         return 0;
758c0bf983SAlvin Wang     }
768c0bf983SAlvin Wang 
778c0bf983SAlvin Wang     std::string ifService, logicalPath;
788c0bf983SAlvin Wang     for (const auto& [path, impls] : objs)
798c0bf983SAlvin Wang     {
808c0bf983SAlvin Wang         if (path.find(channel) == path.npos)
818c0bf983SAlvin Wang         {
828c0bf983SAlvin Wang             continue;
838c0bf983SAlvin Wang         }
848c0bf983SAlvin Wang         for (const auto& [service, intfs] : impls)
858c0bf983SAlvin Wang         {
868c0bf983SAlvin Wang             bool vlan = false;
878c0bf983SAlvin Wang             bool ethernet = false;
888c0bf983SAlvin Wang             for (const auto& intf : intfs)
898c0bf983SAlvin Wang             {
908c0bf983SAlvin Wang                 if (intf == INTF_VLAN)
918c0bf983SAlvin Wang                 {
928c0bf983SAlvin Wang                     vlan = true;
938c0bf983SAlvin Wang                 }
948c0bf983SAlvin Wang                 else if (intf == INTF_ETHERNET)
958c0bf983SAlvin Wang                 {
968c0bf983SAlvin Wang                     ethernet = true;
978c0bf983SAlvin Wang                 }
988c0bf983SAlvin Wang             }
998c0bf983SAlvin Wang             if (ifService.empty() && (vlan || ethernet))
1008c0bf983SAlvin Wang             {
1018c0bf983SAlvin Wang                 ifService = service;
1028c0bf983SAlvin Wang             }
1038c0bf983SAlvin Wang             if (logicalPath.empty() && vlan)
1048c0bf983SAlvin Wang             {
1058c0bf983SAlvin Wang                 logicalPath = path;
1068c0bf983SAlvin Wang             }
1078c0bf983SAlvin Wang         }
1088c0bf983SAlvin Wang     }
1098c0bf983SAlvin Wang 
1108c0bf983SAlvin Wang     // VLAN devices will always have a separate logical object
1118c0bf983SAlvin Wang     if (logicalPath.empty())
1128c0bf983SAlvin Wang     {
1138c0bf983SAlvin Wang         return 0;
1148c0bf983SAlvin Wang     }
1158c0bf983SAlvin Wang 
1168c0bf983SAlvin Wang     Value value;
1178c0bf983SAlvin Wang     auto method = bus.new_method_call(ifService.c_str(), logicalPath.c_str(),
1188c0bf983SAlvin Wang                                       PROP_INTF, METHOD_GET);
1198c0bf983SAlvin Wang     method.append(INTF_VLAN, "Id");
1208c0bf983SAlvin Wang     try
1218c0bf983SAlvin Wang     {
1228c0bf983SAlvin Wang         auto method_reply = bus.call(method);
1238c0bf983SAlvin Wang         method_reply.read(value);
1248c0bf983SAlvin Wang     }
12512d199b2SPatrick Williams     catch (const std::exception& e)
1268c0bf983SAlvin Wang     {
1277b7f25f7SGeorge Liu         lg2::error("getVLANID: failed to execute/read VLAN Id: {ERROR}",
1287b7f25f7SGeorge Liu                    "ERROR", e);
1298c0bf983SAlvin Wang         return 0;
1308c0bf983SAlvin Wang     }
1318c0bf983SAlvin Wang 
1328c0bf983SAlvin Wang     vlanid = std::get<uint32_t>(value);
1338c0bf983SAlvin Wang     if ((vlanid & VLAN_VALUE_MASK) != vlanid)
1348c0bf983SAlvin Wang     {
1357b7f25f7SGeorge Liu         lg2::error("networkd returned an invalid vlan: {VLAN}", "VLAN", vlanid);
1368c0bf983SAlvin Wang         return 0;
1378c0bf983SAlvin Wang     }
1388c0bf983SAlvin Wang 
1398c0bf983SAlvin Wang     return vlanid;
1408c0bf983SAlvin Wang }
1418c0bf983SAlvin Wang 
setupSocket(std::shared_ptr<sdbusplus::asio::connection> & bus,std::string channel,uint16_t reqPort)142d92bc324SVernon Mauery int EventLoop::setupSocket(std::shared_ptr<sdbusplus::asio::connection>& bus,
1438c0bf983SAlvin Wang                            std::string channel, uint16_t reqPort)
144d92bc324SVernon Mauery {
1458c0bf983SAlvin Wang     std::string iface = channel;
146d92bc324SVernon Mauery     static constexpr const char* unboundIface = "rmcpp";
1478c0bf983SAlvin Wang     if (channel == "")
148d92bc324SVernon Mauery     {
1498c0bf983SAlvin Wang         iface = channel = unboundIface;
1508c0bf983SAlvin Wang     }
1518c0bf983SAlvin Wang     else
1528c0bf983SAlvin Wang     {
1538c0bf983SAlvin Wang         // If VLANID of this channel is set, bind the socket to this
1548c0bf983SAlvin Wang         // VLAN logic device
1558c0bf983SAlvin Wang         auto vlanid = getVLANID(channel);
1568c0bf983SAlvin Wang         if (vlanid)
1578c0bf983SAlvin Wang         {
1588c0bf983SAlvin Wang             iface = iface + "." + std::to_string(vlanid);
1597b7f25f7SGeorge Liu             lg2::debug("This channel has VLAN id: {VLAN}", "VLAN", vlanid);
1608c0bf983SAlvin Wang         }
161d92bc324SVernon Mauery     }
162d92bc324SVernon Mauery     // Create our own socket if SysD did not supply one.
163d92bc324SVernon Mauery     int listensFdCount = sd_listen_fds(0);
164d92bc324SVernon Mauery     if (listensFdCount > 1)
165d92bc324SVernon Mauery     {
1667b7f25f7SGeorge Liu         lg2::error("Too many file descriptors received, listensFdCount: {FD}",
1677b7f25f7SGeorge Liu                    "FD", listensFdCount);
168d92bc324SVernon Mauery         return EXIT_FAILURE;
169d92bc324SVernon Mauery     }
170d92bc324SVernon Mauery     if (listensFdCount == 1)
171d92bc324SVernon Mauery     {
172d92bc324SVernon Mauery         int openFd = SD_LISTEN_FDS_START;
173d92bc324SVernon Mauery         if (!sd_is_socket(openFd, AF_UNSPEC, SOCK_DGRAM, -1))
174d92bc324SVernon Mauery         {
1757b7f25f7SGeorge Liu             lg2::error("Failed to set up systemd-passed socket: {ERROR}",
1767b7f25f7SGeorge Liu                        "ERROR", strerror(errno));
177d92bc324SVernon Mauery             return EXIT_FAILURE;
178d92bc324SVernon Mauery         }
179d92bc324SVernon Mauery         udpSocket = std::make_shared<boost::asio::ip::udp::socket>(
180d92bc324SVernon Mauery             *io, boost::asio::ip::udp::v6(), openFd);
181d92bc324SVernon Mauery     }
182d92bc324SVernon Mauery     else
183d92bc324SVernon Mauery     {
184d92bc324SVernon Mauery         // asio does not natively offer a way to bind to an interface
185d92bc324SVernon Mauery         // so it must be done in steps
186d92bc324SVernon Mauery         boost::asio::ip::udp::endpoint ep(boost::asio::ip::udp::v6(), reqPort);
187d92bc324SVernon Mauery         udpSocket = std::make_shared<boost::asio::ip::udp::socket>(*io);
188d92bc324SVernon Mauery         udpSocket->open(ep.protocol());
189d92bc324SVernon Mauery         // bind
190d92bc324SVernon Mauery         udpSocket->set_option(
191d92bc324SVernon Mauery             boost::asio::ip::udp::socket::reuse_address(true));
192d92bc324SVernon Mauery         udpSocket->bind(ep);
193d92bc324SVernon Mauery     }
194d92bc324SVernon Mauery     // SO_BINDTODEVICE
195d92bc324SVernon Mauery     char nameout[IFNAMSIZ];
196d92bc324SVernon Mauery     unsigned int lenout = sizeof(nameout);
197d92bc324SVernon Mauery     if ((::getsockopt(udpSocket->native_handle(), SOL_SOCKET, SO_BINDTODEVICE,
198d92bc324SVernon Mauery                       nameout, &lenout) == -1))
199d92bc324SVernon Mauery     {
2007b7f25f7SGeorge Liu         lg2::error("Failed to read bound device: {ERROR}", "ERROR",
2017b7f25f7SGeorge Liu                    strerror(errno));
202d92bc324SVernon Mauery     }
203d92bc324SVernon Mauery     if (iface != nameout && iface != unboundIface)
204d92bc324SVernon Mauery     {
205d92bc324SVernon Mauery         // SO_BINDTODEVICE
206d92bc324SVernon Mauery         if ((::setsockopt(udpSocket->native_handle(), SOL_SOCKET,
2078425624aSPatrick Williams                           SO_BINDTODEVICE, iface.c_str(), iface.size() + 1) ==
2088425624aSPatrick Williams              -1))
209d92bc324SVernon Mauery         {
2107b7f25f7SGeorge Liu             lg2::error("Failed to bind to requested interface: {ERROR}",
2117b7f25f7SGeorge Liu                        "ERROR", strerror(errno));
212d92bc324SVernon Mauery             return EXIT_FAILURE;
213d92bc324SVernon Mauery         }
2147b7f25f7SGeorge Liu         lg2::info("Bind to interface: {INTERFACE}", "INTERFACE", iface);
215d92bc324SVernon Mauery     }
216d92bc324SVernon Mauery     // cannot be constexpr because it gets passed by address
217d92bc324SVernon Mauery     const int option_enabled = 1;
218d92bc324SVernon Mauery     // common socket stuff; set options to get packet info (DST addr)
219d92bc324SVernon Mauery     ::setsockopt(udpSocket->native_handle(), IPPROTO_IP, IP_PKTINFO,
220d92bc324SVernon Mauery                  &option_enabled, sizeof(option_enabled));
221d92bc324SVernon Mauery     ::setsockopt(udpSocket->native_handle(), IPPROTO_IPV6, IPV6_RECVPKTINFO,
222d92bc324SVernon Mauery                  &option_enabled, sizeof(option_enabled));
223d92bc324SVernon Mauery 
224d92bc324SVernon Mauery     // set the dbus name
2258c0bf983SAlvin Wang     std::string busName = "xyz.openbmc_project.Ipmi.Channel." + channel;
226d92bc324SVernon Mauery     try
227d92bc324SVernon Mauery     {
228d92bc324SVernon Mauery         bus->request_name(busName.c_str());
229d92bc324SVernon Mauery     }
230d92bc324SVernon Mauery     catch (const std::exception& e)
231d92bc324SVernon Mauery     {
2327b7f25f7SGeorge Liu         lg2::error("Failed to acquire D-Bus name: {NAME}: {ERROR}", "NAME",
2337b7f25f7SGeorge Liu                    busName, "ERROR", e);
234d92bc324SVernon Mauery         return EXIT_FAILURE;
235d92bc324SVernon Mauery     }
236d92bc324SVernon Mauery     return 0;
237d92bc324SVernon Mauery }
238d92bc324SVernon Mauery 
startEventLoop()239cbccb05aSVernon Mauery int EventLoop::startEventLoop()
2407fd26ddbSTom Joseph {
241afac94d1SGeorge Liu     startRmcpReceive();
242afac94d1SGeorge Liu 
243afac94d1SGeorge Liu     io->run();
244afac94d1SGeorge Liu 
245afac94d1SGeorge Liu     return EXIT_SUCCESS;
246afac94d1SGeorge Liu }
247afac94d1SGeorge Liu 
setupSignal()248afac94d1SGeorge Liu void EventLoop::setupSignal()
249afac94d1SGeorge Liu {
250*b1cbd6fbSGeorge Liu     static boost::asio::signal_set signals(*io, SIGINT, SIGTERM);
251be1470ccSGeorge Liu     signals.async_wait([this](const boost::system::error_code& /* error */,
252be1470ccSGeorge Liu                               int /* signalNumber */) {
25301373c2bSGeorge Liu         if (udpSocket)
25401373c2bSGeorge Liu         {
2557a0142c5SVernon Mauery             udpSocket->cancel();
2567a0142c5SVernon Mauery             udpSocket->close();
25701373c2bSGeorge Liu         }
2587a0142c5SVernon Mauery         io->stop();
2597a0142c5SVernon Mauery     });
2607fd26ddbSTom Joseph }
2617fd26ddbSTom Joseph 
2627fd26ddbSTom Joseph } // namespace eventloop
263