1 #include "sd_event_loop.hpp"
2 
3 #include "main.hpp"
4 #include "message_handler.hpp"
5 
6 #include <netinet/in.h>
7 #include <sys/ioctl.h>
8 #include <sys/socket.h>
9 #include <systemd/sd-daemon.h>
10 
11 #include <boost/asio/io_context.hpp>
12 #include <boost/asio/signal_set.hpp>
13 #include <phosphor-logging/log.hpp>
14 #include <sdbusplus/asio/sd_event.hpp>
15 #include <user_channel/channel_layer.hpp>
16 
17 namespace eventloop
18 {
19 using namespace phosphor::logging;
20 
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         log<level::ERR>("Executing the IPMI message failed",
35                         entry("EXCEPTION=%s", e.what()));
36     }
37 }
38 
39 void EventLoop::startRmcpReceive()
40 {
41     udpSocket->async_wait(boost::asio::socket_base::wait_read,
42                           [this](const boost::system::error_code& ec) {
43                               if (!ec)
44                               {
45                                   io->post([this]() { startRmcpReceive(); });
46                                   handleRmcpPacket();
47                               }
48                           });
49 }
50 
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::bus 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 (std::exception& e)
72     {
73         log<level::ERR>("getVLANID: failed to execute/read GetSubTree");
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 (std::exception& e)
126     {
127         log<level::ERR>("getVLANID: failed to execute/read VLAN Id");
128         return 0;
129     }
130 
131     vlanid = std::get<uint32_t>(value);
132     if ((vlanid & VLAN_VALUE_MASK) != vlanid)
133     {
134         log<level::ERR>("networkd returned an invalid vlan",
135                         entry("VLAN=%", vlanid));
136         return 0;
137     }
138 
139     return vlanid;
140 }
141 
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             log<level::DEBUG>("This channel has VLAN id",
160                               entry("VLANID=%d", 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         log<level::ERR>("Too many file descriptors received");
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             log<level::ERR>("Failed to set up systemd-passed socket");
176             return EXIT_FAILURE;
177         }
178         udpSocket = std::make_shared<boost::asio::ip::udp::socket>(
179             *io, boost::asio::ip::udp::v6(), openFd);
180     }
181     else
182     {
183         // asio does not natively offer a way to bind to an interface
184         // so it must be done in steps
185         boost::asio::ip::udp::endpoint ep(boost::asio::ip::udp::v6(), reqPort);
186         udpSocket = std::make_shared<boost::asio::ip::udp::socket>(*io);
187         udpSocket->open(ep.protocol());
188         // bind
189         udpSocket->set_option(
190             boost::asio::ip::udp::socket::reuse_address(true));
191         udpSocket->bind(ep);
192     }
193     // SO_BINDTODEVICE
194     char nameout[IFNAMSIZ];
195     unsigned int lenout = sizeof(nameout);
196     if ((::getsockopt(udpSocket->native_handle(), SOL_SOCKET, SO_BINDTODEVICE,
197                       nameout, &lenout) == -1))
198     {
199         log<level::ERR>("Failed to read bound device",
200                         entry("ERROR=%s", strerror(errno)));
201     }
202     if (iface != nameout && iface != unboundIface)
203     {
204         // SO_BINDTODEVICE
205         if ((::setsockopt(udpSocket->native_handle(), SOL_SOCKET,
206                           SO_BINDTODEVICE, iface.c_str(),
207                           iface.size() + 1) == -1))
208         {
209             log<level::ERR>("Failed to bind to requested interface",
210                             entry("ERROR=%s", strerror(errno)));
211             return EXIT_FAILURE;
212         }
213         log<level::INFO>("Bind to interfae",
214                          entry("INTERFACE=%s", iface.c_str()));
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         log<level::ERR>("Failed to acquire D-Bus name",
233                         entry("NAME=%s", busName.c_str()),
234                         entry("ERROR=%s", e.what()));
235         return EXIT_FAILURE;
236     }
237     return 0;
238 }
239 
240 int EventLoop::startEventLoop()
241 {
242     // set up boost::asio signal handling
243     boost::asio::signal_set signals(*io, SIGINT, SIGTERM);
244     signals.async_wait(
245         [this](const boost::system::error_code& error, int signalNumber) {
246             udpSocket->cancel();
247             udpSocket->close();
248             io->stop();
249         });
250 
251     startRmcpReceive();
252 
253     io->run();
254 
255     return EXIT_SUCCESS;
256 }
257 
258 } // namespace eventloop
259