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