1 #include "sd_event_loop.hpp"
2 
3 #include "main.hpp"
4 #include "message_handler.hpp"
5 
6 #include <error.h>
7 #include <netinet/in.h>
8 #include <sys/ioctl.h>
9 #include <sys/socket.h>
10 #include <systemd/sd-daemon.h>
11 
12 #include <boost/asio/io_context.hpp>
13 #include <boost/asio/signal_set.hpp>
14 #include <phosphor-logging/lg2.hpp>
15 #include <sdbusplus/asio/sd_event.hpp>
16 #include <user_channel/channel_layer.hpp>
17 
18 namespace eventloop
19 {
20 
handleRmcpPacket()21 void EventLoop::handleRmcpPacket()
22 {
23     try
24     {
25         auto channelPtr = std::make_shared<udpsocket::Channel>(udpSocket);
26 
27         // Initialize the Message Handler with the socket channel
28         auto msgHandler = std::make_shared<message::Handler>(channelPtr, io);
29 
30         msgHandler->processIncoming();
31     }
32     catch (const std::exception& e)
33     {
34         lg2::error("Executing the IPMI message failed: {ERROR}", "ERROR", e);
35     }
36 }
37 
startRmcpReceive()38 void EventLoop::startRmcpReceive()
39 {
40     udpSocket->async_wait(
41         boost::asio::socket_base::wait_read,
42         [this](const boost::system::error_code& ec) {
43             if (!ec)
44             {
45                 boost::asio::post(*io, [this]() { startRmcpReceive(); });
46                 handleRmcpPacket();
47             }
48         });
49 }
50 
getVLANID(const std::string channel)51 int EventLoop::getVLANID(const std::string channel)
52 {
53     int vlanid = 0;
54     if (channel.empty())
55     {
56         return 0;
57     }
58 
59     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
60     // Enumerate all VLAN + ETHERNET interfaces
61     auto req = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF,
62                                    "GetSubTree");
63     req.append(PATH_ROOT, 0,
64                std::vector<std::string>{INTF_VLAN, INTF_ETHERNET});
65     ObjectTree objs;
66     try
67     {
68         auto reply = bus.call(req);
69         reply.read(objs);
70     }
71     catch (const std::exception& e)
72     {
73         lg2::error("getVLANID: failed to execute/read GetSubTree: {ERROR}",
74                    "ERROR", e);
75         return 0;
76     }
77 
78     std::string ifService, logicalPath;
79     for (const auto& [path, impls] : objs)
80     {
81         if (path.find(channel) == path.npos)
82         {
83             continue;
84         }
85         for (const auto& [service, intfs] : impls)
86         {
87             bool vlan = false;
88             bool ethernet = false;
89             for (const auto& intf : intfs)
90             {
91                 if (intf == INTF_VLAN)
92                 {
93                     vlan = true;
94                 }
95                 else if (intf == INTF_ETHERNET)
96                 {
97                     ethernet = true;
98                 }
99             }
100             if (ifService.empty() && (vlan || ethernet))
101             {
102                 ifService = service;
103             }
104             if (logicalPath.empty() && vlan)
105             {
106                 logicalPath = path;
107             }
108         }
109     }
110 
111     // VLAN devices will always have a separate logical object
112     if (logicalPath.empty())
113     {
114         return 0;
115     }
116 
117     Value value;
118     auto method = bus.new_method_call(ifService.c_str(), logicalPath.c_str(),
119                                       PROP_INTF, METHOD_GET);
120     method.append(INTF_VLAN, "Id");
121     try
122     {
123         auto method_reply = bus.call(method);
124         method_reply.read(value);
125     }
126     catch (const std::exception& e)
127     {
128         lg2::error("getVLANID: failed to execute/read VLAN Id: {ERROR}",
129                    "ERROR", e);
130         return 0;
131     }
132 
133     vlanid = std::get<uint32_t>(value);
134     if ((vlanid & VLAN_VALUE_MASK) != vlanid)
135     {
136         lg2::error("networkd returned an invalid vlan: {VLAN}", "VLAN", vlanid);
137         return 0;
138     }
139 
140     return vlanid;
141 }
142 
setupSocket(std::shared_ptr<sdbusplus::asio::connection> & bus,std::string channel,uint16_t reqPort)143 int EventLoop::setupSocket(std::shared_ptr<sdbusplus::asio::connection>& bus,
144                            std::string channel, uint16_t reqPort)
145 {
146     std::string iface = channel;
147     static constexpr const char* unboundIface = "rmcpp";
148     if (channel == "")
149     {
150         iface = channel = unboundIface;
151     }
152     else
153     {
154         // If VLANID of this channel is set, bind the socket to this
155         // VLAN logic device
156         auto vlanid = getVLANID(channel);
157         if (vlanid)
158         {
159             iface = iface + "." + std::to_string(vlanid);
160             lg2::debug("This channel has VLAN id: {VLAN}", "VLAN", vlanid);
161         }
162     }
163     // Create our own socket if SysD did not supply one.
164     int listensFdCount = sd_listen_fds(0);
165     if (listensFdCount > 1)
166     {
167         lg2::error("Too many file descriptors received, listensFdCount: {FD}",
168                    "FD", listensFdCount);
169         return EXIT_FAILURE;
170     }
171     if (listensFdCount == 1)
172     {
173         int openFd = SD_LISTEN_FDS_START;
174         if (!sd_is_socket(openFd, AF_UNSPEC, SOCK_DGRAM, -1))
175         {
176             lg2::error("Failed to set up systemd-passed socket: {ERROR}",
177                        "ERROR", strerror(errno));
178             return EXIT_FAILURE;
179         }
180         udpSocket = std::make_shared<boost::asio::ip::udp::socket>(
181             *io, boost::asio::ip::udp::v6(), openFd);
182     }
183     else
184     {
185         // asio does not natively offer a way to bind to an interface
186         // so it must be done in steps
187         boost::asio::ip::udp::endpoint ep(boost::asio::ip::udp::v6(), reqPort);
188         udpSocket = std::make_shared<boost::asio::ip::udp::socket>(*io);
189         udpSocket->open(ep.protocol());
190         // bind
191         udpSocket->set_option(
192             boost::asio::ip::udp::socket::reuse_address(true));
193         udpSocket->bind(ep);
194     }
195     // SO_BINDTODEVICE
196     char nameout[IFNAMSIZ];
197     unsigned int lenout = sizeof(nameout);
198     if ((::getsockopt(udpSocket->native_handle(), SOL_SOCKET, SO_BINDTODEVICE,
199                       nameout, &lenout) == -1))
200     {
201         lg2::error("Failed to read bound device: {ERROR}", "ERROR",
202                    strerror(errno));
203     }
204     if (iface != nameout && iface != unboundIface)
205     {
206         // SO_BINDTODEVICE
207         if ((::setsockopt(udpSocket->native_handle(), SOL_SOCKET,
208                           SO_BINDTODEVICE, iface.c_str(), iface.size() + 1) ==
209              -1))
210         {
211             lg2::error("Failed to bind to requested interface: {ERROR}",
212                        "ERROR", strerror(errno));
213             return EXIT_FAILURE;
214         }
215         lg2::info("Bind to interface: {INTERFACE}", "INTERFACE", iface);
216     }
217     // cannot be constexpr because it gets passed by address
218     const int option_enabled = 1;
219     // common socket stuff; set options to get packet info (DST addr)
220     ::setsockopt(udpSocket->native_handle(), IPPROTO_IP, IP_PKTINFO,
221                  &option_enabled, sizeof(option_enabled));
222     ::setsockopt(udpSocket->native_handle(), IPPROTO_IPV6, IPV6_RECVPKTINFO,
223                  &option_enabled, sizeof(option_enabled));
224 
225     // set the dbus name
226     std::string busName = "xyz.openbmc_project.Ipmi.Channel." + channel;
227     try
228     {
229         bus->request_name(busName.c_str());
230     }
231     catch (const std::exception& e)
232     {
233         lg2::error("Failed to acquire D-Bus name: {NAME}: {ERROR}", "NAME",
234                    busName, "ERROR", e);
235         return EXIT_FAILURE;
236     }
237     return 0;
238 }
239 
startEventLoop()240 int EventLoop::startEventLoop()
241 {
242     startRmcpReceive();
243 
244     io->run();
245 
246     return EXIT_SUCCESS;
247 }
248 
setupSignal()249 void EventLoop::setupSignal()
250 {
251     boost::asio::signal_set signals(*io, SIGINT, SIGTERM);
252     signals.async_wait([this](const boost::system::error_code& /* error */,
253                               int /* signalNumber */) {
254         udpSocket->cancel();
255         udpSocket->close();
256         io->stop();
257     });
258 }
259 
260 } // namespace eventloop
261