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