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