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 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 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 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 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 240 int EventLoop::startEventLoop() 241 { 242 startRmcpReceive(); 243 244 io->run(); 245 246 return EXIT_SUCCESS; 247 } 248 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 if (udpSocket) 255 { 256 udpSocket->cancel(); 257 udpSocket->close(); 258 } 259 io->stop(); 260 }); 261 } 262 263 } // namespace eventloop 264