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(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 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 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 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