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_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 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 (const 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 interface", 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