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