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 InAddrAny addrFromBuf(int addressFamily, std::string_view buf) 155 { 156 if (addressFamily == AF_INET) 157 { 158 struct in_addr ret; 159 if (buf.size() != sizeof(ret)) 160 { 161 throw std::runtime_error("Buf not in_addr sized"); 162 } 163 memcpy(&ret, buf.data(), sizeof(ret)); 164 return ret; 165 } 166 else if (addressFamily == AF_INET6) 167 { 168 struct in6_addr ret; 169 if (buf.size() != sizeof(ret)) 170 { 171 throw std::runtime_error("Buf not in6_addr sized"); 172 } 173 memcpy(&ret, buf.data(), sizeof(ret)); 174 return ret; 175 } 176 177 throw std::runtime_error("Unsupported address family"); 178 } 179 180 std::string toString(const struct in_addr& addr) 181 { 182 std::string ip(INET_ADDRSTRLEN, '\0'); 183 if (inet_ntop(AF_INET, &addr, ip.data(), ip.size()) == nullptr) 184 { 185 throw std::runtime_error("Failed to convert IP4 to string"); 186 } 187 188 ip.resize(strlen(ip.c_str())); 189 return ip; 190 } 191 192 std::string toString(const struct in6_addr& addr) 193 { 194 std::string ip(INET6_ADDRSTRLEN, '\0'); 195 if (inet_ntop(AF_INET6, &addr, ip.data(), ip.size()) == nullptr) 196 { 197 throw std::runtime_error("Failed to convert IP6 to string"); 198 } 199 200 ip.resize(strlen(ip.c_str())); 201 return ip; 202 } 203 204 std::string toString(const InAddrAny& addr) 205 { 206 if (std::holds_alternative<struct in_addr>(addr)) 207 { 208 const auto& v = std::get<struct in_addr>(addr); 209 return toString(v); 210 } 211 else if (std::holds_alternative<struct in6_addr>(addr)) 212 { 213 const auto& v = std::get<struct in6_addr>(addr); 214 return toString(v); 215 } 216 217 throw std::runtime_error("Invalid addr type"); 218 } 219 220 bool isValidIP(int addressFamily, stdplus::const_zstring address) 221 { 222 unsigned char buf[sizeof(struct in6_addr)]; 223 return inet_pton(addressFamily, address.c_str(), buf) > 0; 224 } 225 226 bool isValidPrefix(int family, uint8_t prefix) 227 { 228 return familyVisit( 229 [=]<int f>() noexcept { return isValidPrefix<f>(prefix); }, family); 230 } 231 232 InterfaceList getInterfaces() 233 { 234 InterfaceList interfaces{}; 235 struct ifaddrs* ifaddr = nullptr; 236 237 // attempt to fill struct with ifaddrs 238 if (getifaddrs(&ifaddr) == -1) 239 { 240 auto error = errno; 241 log<level::ERR>("Error occurred during the getifaddrs call", 242 entry("ERRNO=%d", error)); 243 elog<InternalFailure>(); 244 } 245 246 AddrPtr ifaddrPtr(ifaddr); 247 ifaddr = nullptr; 248 const auto& ignoredInterfaces = internal::getIgnoredInterfaces(); 249 250 for (ifaddrs* ifa = ifaddrPtr.get(); ifa != nullptr; ifa = ifa->ifa_next) 251 { 252 // walk interfaces 253 // if loopback ignore 254 if (ifa->ifa_flags & IFF_LOOPBACK || 255 ignoredInterfaces.find(ifa->ifa_name) != ignoredInterfaces.end()) 256 { 257 continue; 258 } 259 interfaces.emplace(ifa->ifa_name); 260 } 261 return interfaces; 262 } 263 264 void deleteInterface(stdplus::const_zstring intf) 265 { 266 pid_t pid = fork(); 267 int status{}; 268 269 if (pid == 0) 270 { 271 272 execl("/sbin/ip", "ip", "link", "delete", "dev", intf.c_str(), nullptr); 273 auto error = errno; 274 log<level::ERR>("Couldn't delete the device", entry("ERRNO=%d", error), 275 entry("INTF=%s", intf.c_str())); 276 elog<InternalFailure>(); 277 } 278 else if (pid < 0) 279 { 280 auto error = errno; 281 log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error)); 282 elog<InternalFailure>(); 283 } 284 else if (pid > 0) 285 { 286 while (waitpid(pid, &status, 0) == -1) 287 { 288 if (errno != EINTR) 289 { /* Error other than EINTR */ 290 status = -1; 291 break; 292 } 293 } 294 295 if (status < 0) 296 { 297 log<level::ERR>("Unable to delete the interface", 298 entry("INTF=%s", intf.c_str()), 299 entry("STATUS=%d", status)); 300 elog<InternalFailure>(); 301 } 302 } 303 } 304 305 std::optional<std::string> interfaceToUbootEthAddr(std::string_view intf) 306 { 307 constexpr auto pfx = "eth"sv; 308 if (!intf.starts_with(pfx)) 309 { 310 return std::nullopt; 311 } 312 intf.remove_prefix(pfx.size()); 313 auto last = intf.data() + intf.size(); 314 unsigned long idx; 315 auto res = std::from_chars(intf.data(), last, idx); 316 if (res.ec != std::errc() || res.ptr != last) 317 { 318 return std::nullopt; 319 } 320 if (idx == 0) 321 { 322 return "ethaddr"; 323 } 324 return fmt::format(FMT_COMPILE("eth{}addr"), idx); 325 } 326 327 static std::optional<DHCPVal> systemdParseDHCP(std::string_view str) 328 { 329 if (config::icaseeq(str, "ipv4")) 330 { 331 return DHCPVal{.v4 = true, .v6 = false}; 332 } 333 if (config::icaseeq(str, "ipv6")) 334 { 335 return DHCPVal{.v4 = false, .v6 = true}; 336 } 337 if (auto b = config::parseBool(str); b) 338 { 339 return DHCPVal{.v4 = *b, .v6 = *b}; 340 } 341 return std::nullopt; 342 } 343 344 inline auto systemdParseLast(const config::Parser& config, 345 std::string_view section, std::string_view key, 346 auto&& fun) 347 { 348 if (auto str = config.map.getLastValueString(section, key); str == nullptr) 349 { 350 auto err = fmt::format("Unable to get the value of {}[{}] from {}", 351 section, key, config.getFilename().native()); 352 log<level::NOTICE>(err.c_str(), 353 entry("FILE=%s", config.getFilename().c_str())); 354 } 355 else if (auto val = fun(*str); !val) 356 { 357 auto err = fmt::format("Invalid value of {}[{}] from {}: {}", section, 358 key, config.getFilename().native(), *str); 359 log<level::NOTICE>(err.c_str(), entry("VALUE=%s", str->c_str()), 360 entry("FILE=%s", config.getFilename().c_str())); 361 } 362 else 363 { 364 return val; 365 } 366 return decltype(fun(std::string_view{}))(std::nullopt); 367 } 368 369 bool getIPv6AcceptRA(const config::Parser& config) 370 { 371 #ifdef ENABLE_IPV6_ACCEPT_RA 372 constexpr bool def = true; 373 #else 374 constexpr bool def = false; 375 #endif 376 return systemdParseLast(config, "Network", "IPv6AcceptRA", 377 config::parseBool) 378 .value_or(def); 379 } 380 381 DHCPVal getDHCPValue(const config::Parser& config) 382 { 383 return systemdParseLast(config, "Network", "DHCP", systemdParseDHCP) 384 .value_or(DHCPVal{.v4 = true, .v6 = true}); 385 } 386 387 bool getDHCPProp(const config::Parser& config, std::string_view key) 388 { 389 return systemdParseLast(config, "DHCP", key, config::parseBool) 390 .value_or(true); 391 } 392 393 namespace mac_address 394 { 395 396 constexpr auto mapperBus = "xyz.openbmc_project.ObjectMapper"; 397 constexpr auto mapperObj = "/xyz/openbmc_project/object_mapper"; 398 constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper"; 399 constexpr auto propIntf = "org.freedesktop.DBus.Properties"; 400 constexpr auto methodGet = "Get"; 401 constexpr auto configFile = "/usr/share/network/config.json"; 402 403 using DbusObjectPath = std::string; 404 using DbusService = std::string; 405 using DbusInterface = std::string; 406 using ObjectTree = 407 std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>; 408 409 constexpr auto invBus = "xyz.openbmc_project.Inventory.Manager"; 410 constexpr auto invNetworkIntf = 411 "xyz.openbmc_project.Inventory.Item.NetworkInterface"; 412 constexpr auto invRoot = "/xyz/openbmc_project/inventory"; 413 414 ether_addr getfromInventory(sdbusplus::bus_t& bus, const std::string& intfName) 415 { 416 417 std::string interfaceName = intfName; 418 419 #ifdef SYNC_MAC_FROM_INVENTORY 420 // load the config JSON from the Read Only Path 421 std::ifstream in(configFile); 422 nlohmann::json configJson; 423 in >> configJson; 424 interfaceName = configJson[intfName]; 425 #endif 426 427 std::vector<DbusInterface> interfaces; 428 interfaces.emplace_back(invNetworkIntf); 429 430 auto depth = 0; 431 432 auto mapperCall = 433 bus.new_method_call(mapperBus, mapperObj, mapperIntf, "GetSubTree"); 434 435 mapperCall.append(invRoot, depth, interfaces); 436 437 auto mapperReply = bus.call(mapperCall); 438 if (mapperReply.is_method_error()) 439 { 440 log<level::ERR>("Error in mapper call"); 441 elog<InternalFailure>(); 442 } 443 444 ObjectTree objectTree; 445 mapperReply.read(objectTree); 446 447 if (objectTree.empty()) 448 { 449 log<level::ERR>("No Object has implemented the interface", 450 entry("INTERFACE=%s", invNetworkIntf)); 451 elog<InternalFailure>(); 452 } 453 454 DbusObjectPath objPath; 455 DbusService service; 456 457 if (1 == objectTree.size()) 458 { 459 objPath = objectTree.begin()->first; 460 service = objectTree.begin()->second.begin()->first; 461 } 462 else 463 { 464 // If there are more than 2 objects, object path must contain the 465 // interface name 466 for (auto const& object : objectTree) 467 { 468 log<level::INFO>("interface", 469 entry("INT=%s", interfaceName.c_str())); 470 log<level::INFO>("object", entry("OBJ=%s", object.first.c_str())); 471 472 if (std::string::npos != object.first.find(interfaceName.c_str())) 473 { 474 objPath = object.first; 475 service = object.second.begin()->first; 476 break; 477 } 478 } 479 480 if (objPath.empty()) 481 { 482 log<level::ERR>("Can't find the object for the interface", 483 entry("intfName=%s", interfaceName.c_str())); 484 elog<InternalFailure>(); 485 } 486 } 487 488 auto method = bus.new_method_call(service.c_str(), objPath.c_str(), 489 propIntf, methodGet); 490 491 method.append(invNetworkIntf, "MACAddress"); 492 493 auto reply = bus.call(method); 494 if (reply.is_method_error()) 495 { 496 log<level::ERR>("Failed to get MACAddress", 497 entry("PATH=%s", objPath.c_str()), 498 entry("INTERFACE=%s", invNetworkIntf)); 499 elog<InternalFailure>(); 500 } 501 502 std::variant<std::string> value; 503 reply.read(value); 504 return fromString(std::get<std::string>(value)); 505 } 506 507 static uint8_t decodeHex(std::string_view str) 508 { 509 uint8_t ret; 510 auto res = std::from_chars(str.begin(), str.end(), ret, 16); 511 if (res.ptr != str.end() || res.ec != std::errc()) 512 { 513 throw std::invalid_argument("Not hex"); 514 } 515 return ret; 516 } 517 518 ether_addr fromString(std::string_view str) 519 { 520 ether_addr ret; 521 if (str.size() == 12 && str.find(":") == str.npos) 522 { 523 for (size_t i = 0; i < 6; ++i) 524 { 525 ret.ether_addr_octet[i] = decodeHex(str.substr(i * 2, 2)); 526 } 527 } 528 else 529 { 530 for (size_t i = 0; i < 5; ++i) 531 { 532 auto loc = str.find(":"); 533 ret.ether_addr_octet[i] = decodeHex(str.substr(0, loc)); 534 str.remove_prefix(loc == str.npos ? str.size() : loc + 1); 535 if (str.empty()) 536 { 537 throw std::invalid_argument("Missing mac data"); 538 } 539 } 540 ret.ether_addr_octet[5] = decodeHex(str); 541 } 542 return ret; 543 } 544 545 std::string toString(const ether_addr& mac) 546 { 547 return fmt::format(FMT_COMPILE("{:02x}"), 548 fmt::join(mac.ether_addr_octet, ":")); 549 } 550 551 bool isEmpty(const ether_addr& mac) 552 { 553 return stdplus::raw::equal(mac, ether_addr{}); 554 } 555 556 bool isMulticast(const ether_addr& mac) 557 { 558 return mac.ether_addr_octet[0] & 0b1; 559 } 560 561 bool isUnicast(const ether_addr& mac) 562 { 563 return !isEmpty(mac) && !isMulticast(mac); 564 } 565 566 } // namespace mac_address 567 } // namespace network 568 } // namespace phosphor 569