1 #include "config.h" 2 3 #include "util.hpp" 4 5 #include "config_parser.hpp" 6 #include "types.hpp" 7 8 #include <arpa/inet.h> 9 #include <fmt/compile.h> 10 #include <fmt/format.h> 11 #include <sys/wait.h> 12 13 #include <cctype> 14 #include <charconv> 15 #include <fstream> 16 #ifdef SYNC_MAC_FROM_INVENTORY 17 #include <nlohmann/json.hpp> 18 #endif 19 #include <phosphor-logging/elog-errors.hpp> 20 #include <phosphor-logging/log.hpp> 21 #include <stdexcept> 22 #include <stdplus/raw.hpp> 23 #include <string> 24 #include <string_view> 25 #include <variant> 26 #include <xyz/openbmc_project/Common/error.hpp> 27 28 namespace phosphor 29 { 30 namespace network 31 { 32 33 using std::literals::string_view_literals::operator""sv; 34 using namespace phosphor::logging; 35 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 36 37 namespace internal 38 { 39 40 void executeCommandinChildProcess(stdplus::const_zstring path, char** args) 41 { 42 using namespace std::string_literals; 43 pid_t pid = fork(); 44 45 if (pid == 0) 46 { 47 execv(path.c_str(), args); 48 exit(255); 49 } 50 else if (pid < 0) 51 { 52 auto error = errno; 53 log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error)); 54 elog<InternalFailure>(); 55 } 56 else if (pid > 0) 57 { 58 int status; 59 while (waitpid(pid, &status, 0) == -1) 60 { 61 if (errno != EINTR) 62 { 63 status = -1; 64 break; 65 } 66 } 67 68 if (status < 0) 69 { 70 fmt::memory_buffer buf; 71 fmt::format_to(fmt::appender(buf), "`{}`", path); 72 for (size_t i = 0; args[i] != nullptr; ++i) 73 { 74 fmt::format_to(fmt::appender(buf), " `{}`", args[i]); 75 } 76 buf.push_back('\0'); 77 log<level::ERR>("Unable to execute the command", 78 entry("CMD=%s", buf.data()), 79 entry("STATUS=%d", status)); 80 elog<InternalFailure>(); 81 } 82 } 83 } 84 85 /** @brief Get ignored interfaces from environment */ 86 std::string_view getIgnoredInterfacesEnv() 87 { 88 auto r = std::getenv("IGNORED_INTERFACES"); 89 if (r == nullptr) 90 { 91 return ""; 92 } 93 return r; 94 } 95 96 /** @brief Parse the comma separated interface names */ 97 std::unordered_set<std::string_view> 98 parseInterfaces(std::string_view interfaces) 99 { 100 std::unordered_set<std::string_view> result; 101 while (true) 102 { 103 auto sep = interfaces.find(','); 104 auto interface = interfaces.substr(0, sep); 105 while (!interface.empty() && std::isspace(interface.front())) 106 { 107 interface.remove_prefix(1); 108 } 109 while (!interface.empty() && std::isspace(interface.back())) 110 { 111 interface.remove_suffix(1); 112 } 113 if (!interface.empty()) 114 { 115 result.insert(interface); 116 } 117 if (sep == interfaces.npos) 118 { 119 break; 120 } 121 interfaces = interfaces.substr(sep + 1); 122 } 123 return result; 124 } 125 126 /** @brief Get the ignored interfaces */ 127 const std::unordered_set<std::string_view>& getIgnoredInterfaces() 128 { 129 static auto ignoredInterfaces = parseInterfaces(getIgnoredInterfacesEnv()); 130 return ignoredInterfaces; 131 } 132 133 } // namespace internal 134 135 constexpr auto familyVisit(auto&& visitor, int family) 136 { 137 if (family == AF_INET) 138 { 139 return visitor.template operator()<AF_INET>(); 140 } 141 else if (family == AF_INET6) 142 { 143 return visitor.template operator()<AF_INET6>(); 144 } 145 throw std::invalid_argument("Invalid addr family"); 146 } 147 148 template <int family> 149 typename FamilyTraits<family>::addr addrFromBuf(std::string_view buf) 150 { 151 return stdplus::raw::copyFromStrict<typename FamilyTraits<family>::addr>( 152 buf); 153 } 154 155 InAddrAny addrFromBuf(int family, std::string_view buf) 156 { 157 return familyVisit( 158 [=]<int f>() -> InAddrAny { return addrFromBuf<f>(buf); }, family); 159 } 160 161 bool isValidIP(int family, stdplus::const_zstring address) noexcept 162 { 163 unsigned char buf[sizeof(struct in6_addr)]; 164 return inet_pton(family, address.c_str(), buf) > 0; 165 } 166 167 bool isValidIP(stdplus::const_zstring address) noexcept 168 { 169 return isValidIP(AF_INET, address) || isValidIP(AF_INET6, address); 170 } 171 172 void deleteInterface(stdplus::const_zstring intf) 173 { 174 pid_t pid = fork(); 175 int status{}; 176 177 if (pid == 0) 178 { 179 180 execl("/sbin/ip", "ip", "link", "delete", "dev", intf.c_str(), nullptr); 181 auto error = errno; 182 log<level::ERR>("Couldn't delete the device", entry("ERRNO=%d", error), 183 entry("INTF=%s", intf.c_str())); 184 elog<InternalFailure>(); 185 } 186 else if (pid < 0) 187 { 188 auto error = errno; 189 log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error)); 190 elog<InternalFailure>(); 191 } 192 else if (pid > 0) 193 { 194 while (waitpid(pid, &status, 0) == -1) 195 { 196 if (errno != EINTR) 197 { /* Error other than EINTR */ 198 status = -1; 199 break; 200 } 201 } 202 203 if (status < 0) 204 { 205 log<level::ERR>("Unable to delete the interface", 206 entry("INTF=%s", intf.c_str()), 207 entry("STATUS=%d", status)); 208 elog<InternalFailure>(); 209 } 210 } 211 } 212 213 std::optional<std::string> interfaceToUbootEthAddr(std::string_view intf) 214 { 215 constexpr auto pfx = "eth"sv; 216 if (!intf.starts_with(pfx)) 217 { 218 return std::nullopt; 219 } 220 intf.remove_prefix(pfx.size()); 221 auto last = intf.data() + intf.size(); 222 unsigned long idx; 223 auto res = std::from_chars(intf.data(), last, idx); 224 if (res.ec != std::errc() || res.ptr != last) 225 { 226 return std::nullopt; 227 } 228 if (idx == 0) 229 { 230 return "ethaddr"; 231 } 232 return fmt::format(FMT_COMPILE("eth{}addr"), idx); 233 } 234 235 static std::optional<DHCPVal> systemdParseDHCP(std::string_view str) 236 { 237 if (config::icaseeq(str, "ipv4")) 238 { 239 return DHCPVal{.v4 = true, .v6 = false}; 240 } 241 if (config::icaseeq(str, "ipv6")) 242 { 243 return DHCPVal{.v4 = false, .v6 = true}; 244 } 245 if (auto b = config::parseBool(str); b) 246 { 247 return DHCPVal{.v4 = *b, .v6 = *b}; 248 } 249 return std::nullopt; 250 } 251 252 inline auto systemdParseLast(const config::Parser& config, 253 std::string_view section, std::string_view key, 254 auto&& fun) 255 { 256 if (auto str = config.map.getLastValueString(section, key); str == nullptr) 257 { 258 auto err = fmt::format("Unable to get the value of {}[{}] from {}", 259 section, key, config.getFilename().native()); 260 log<level::NOTICE>(err.c_str(), 261 entry("FILE=%s", config.getFilename().c_str())); 262 } 263 else if (auto val = fun(*str); !val) 264 { 265 auto err = fmt::format("Invalid value of {}[{}] from {}: {}", section, 266 key, config.getFilename().native(), *str); 267 log<level::NOTICE>(err.c_str(), entry("VALUE=%s", str->c_str()), 268 entry("FILE=%s", config.getFilename().c_str())); 269 } 270 else 271 { 272 return val; 273 } 274 return decltype(fun(std::string_view{}))(std::nullopt); 275 } 276 277 bool getIPv6AcceptRA(const config::Parser& config) 278 { 279 #ifdef ENABLE_IPV6_ACCEPT_RA 280 constexpr bool def = true; 281 #else 282 constexpr bool def = false; 283 #endif 284 return systemdParseLast(config, "Network", "IPv6AcceptRA", 285 config::parseBool) 286 .value_or(def); 287 } 288 289 DHCPVal getDHCPValue(const config::Parser& config) 290 { 291 return systemdParseLast(config, "Network", "DHCP", systemdParseDHCP) 292 .value_or(DHCPVal{.v4 = true, .v6 = true}); 293 } 294 295 bool getDHCPProp(const config::Parser& config, std::string_view key) 296 { 297 return systemdParseLast(config, "DHCP", key, config::parseBool) 298 .value_or(true); 299 } 300 301 namespace mac_address 302 { 303 304 constexpr auto mapperBus = "xyz.openbmc_project.ObjectMapper"; 305 constexpr auto mapperObj = "/xyz/openbmc_project/object_mapper"; 306 constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper"; 307 constexpr auto propIntf = "org.freedesktop.DBus.Properties"; 308 constexpr auto methodGet = "Get"; 309 constexpr auto configFile = "/usr/share/network/config.json"; 310 311 using DbusObjectPath = std::string; 312 using DbusService = std::string; 313 using DbusInterface = std::string; 314 using ObjectTree = 315 std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>; 316 317 constexpr auto invBus = "xyz.openbmc_project.Inventory.Manager"; 318 constexpr auto invNetworkIntf = 319 "xyz.openbmc_project.Inventory.Item.NetworkInterface"; 320 constexpr auto invRoot = "/xyz/openbmc_project/inventory"; 321 322 ether_addr getfromInventory(sdbusplus::bus_t& bus, const std::string& intfName) 323 { 324 325 std::string interfaceName = intfName; 326 327 #ifdef SYNC_MAC_FROM_INVENTORY 328 // load the config JSON from the Read Only Path 329 std::ifstream in(configFile); 330 nlohmann::json configJson; 331 in >> configJson; 332 interfaceName = configJson[intfName]; 333 #endif 334 335 std::vector<DbusInterface> interfaces; 336 interfaces.emplace_back(invNetworkIntf); 337 338 auto depth = 0; 339 340 auto mapperCall = 341 bus.new_method_call(mapperBus, mapperObj, mapperIntf, "GetSubTree"); 342 343 mapperCall.append(invRoot, depth, interfaces); 344 345 auto mapperReply = bus.call(mapperCall); 346 if (mapperReply.is_method_error()) 347 { 348 log<level::ERR>("Error in mapper call"); 349 elog<InternalFailure>(); 350 } 351 352 ObjectTree objectTree; 353 mapperReply.read(objectTree); 354 355 if (objectTree.empty()) 356 { 357 log<level::ERR>("No Object has implemented the interface", 358 entry("INTERFACE=%s", invNetworkIntf)); 359 elog<InternalFailure>(); 360 } 361 362 DbusObjectPath objPath; 363 DbusService service; 364 365 if (1 == objectTree.size()) 366 { 367 objPath = objectTree.begin()->first; 368 service = objectTree.begin()->second.begin()->first; 369 } 370 else 371 { 372 // If there are more than 2 objects, object path must contain the 373 // interface name 374 for (auto const& object : objectTree) 375 { 376 log<level::INFO>("interface", 377 entry("INT=%s", interfaceName.c_str())); 378 log<level::INFO>("object", entry("OBJ=%s", object.first.c_str())); 379 380 if (std::string::npos != object.first.find(interfaceName.c_str())) 381 { 382 objPath = object.first; 383 service = object.second.begin()->first; 384 break; 385 } 386 } 387 388 if (objPath.empty()) 389 { 390 log<level::ERR>("Can't find the object for the interface", 391 entry("intfName=%s", interfaceName.c_str())); 392 elog<InternalFailure>(); 393 } 394 } 395 396 auto method = bus.new_method_call(service.c_str(), objPath.c_str(), 397 propIntf, methodGet); 398 399 method.append(invNetworkIntf, "MACAddress"); 400 401 auto reply = bus.call(method); 402 if (reply.is_method_error()) 403 { 404 log<level::ERR>("Failed to get MACAddress", 405 entry("PATH=%s", objPath.c_str()), 406 entry("INTERFACE=%s", invNetworkIntf)); 407 elog<InternalFailure>(); 408 } 409 410 std::variant<std::string> value; 411 reply.read(value); 412 return ToAddr<ether_addr>{}(std::get<std::string>(value)); 413 } 414 415 bool isEmpty(const ether_addr& mac) 416 { 417 return mac == ether_addr{}; 418 } 419 420 bool isMulticast(const ether_addr& mac) 421 { 422 return mac.ether_addr_octet[0] & 0b1; 423 } 424 425 bool isUnicast(const ether_addr& mac) 426 { 427 return !isEmpty(mac) && !isMulticast(mac); 428 } 429 430 } // namespace mac_address 431 } // namespace network 432 } // namespace phosphor 433