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