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 bool isValidPrefix(int family, uint8_t prefix) 173 { 174 return familyVisit( 175 [=]<int f>() noexcept { return isValidPrefix<f>(prefix); }, family); 176 } 177 178 void deleteInterface(stdplus::const_zstring intf) 179 { 180 pid_t pid = fork(); 181 int status{}; 182 183 if (pid == 0) 184 { 185 186 execl("/sbin/ip", "ip", "link", "delete", "dev", intf.c_str(), nullptr); 187 auto error = errno; 188 log<level::ERR>("Couldn't delete the device", entry("ERRNO=%d", error), 189 entry("INTF=%s", intf.c_str())); 190 elog<InternalFailure>(); 191 } 192 else if (pid < 0) 193 { 194 auto error = errno; 195 log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error)); 196 elog<InternalFailure>(); 197 } 198 else if (pid > 0) 199 { 200 while (waitpid(pid, &status, 0) == -1) 201 { 202 if (errno != EINTR) 203 { /* Error other than EINTR */ 204 status = -1; 205 break; 206 } 207 } 208 209 if (status < 0) 210 { 211 log<level::ERR>("Unable to delete the interface", 212 entry("INTF=%s", intf.c_str()), 213 entry("STATUS=%d", status)); 214 elog<InternalFailure>(); 215 } 216 } 217 } 218 219 std::optional<std::string> interfaceToUbootEthAddr(std::string_view intf) 220 { 221 constexpr auto pfx = "eth"sv; 222 if (!intf.starts_with(pfx)) 223 { 224 return std::nullopt; 225 } 226 intf.remove_prefix(pfx.size()); 227 auto last = intf.data() + intf.size(); 228 unsigned long idx; 229 auto res = std::from_chars(intf.data(), last, idx); 230 if (res.ec != std::errc() || res.ptr != last) 231 { 232 return std::nullopt; 233 } 234 if (idx == 0) 235 { 236 return "ethaddr"; 237 } 238 return fmt::format(FMT_COMPILE("eth{}addr"), idx); 239 } 240 241 static std::optional<DHCPVal> systemdParseDHCP(std::string_view str) 242 { 243 if (config::icaseeq(str, "ipv4")) 244 { 245 return DHCPVal{.v4 = true, .v6 = false}; 246 } 247 if (config::icaseeq(str, "ipv6")) 248 { 249 return DHCPVal{.v4 = false, .v6 = true}; 250 } 251 if (auto b = config::parseBool(str); b) 252 { 253 return DHCPVal{.v4 = *b, .v6 = *b}; 254 } 255 return std::nullopt; 256 } 257 258 inline auto systemdParseLast(const config::Parser& config, 259 std::string_view section, std::string_view key, 260 auto&& fun) 261 { 262 if (auto str = config.map.getLastValueString(section, key); str == nullptr) 263 { 264 auto err = fmt::format("Unable to get the value of {}[{}] from {}", 265 section, key, config.getFilename().native()); 266 log<level::NOTICE>(err.c_str(), 267 entry("FILE=%s", config.getFilename().c_str())); 268 } 269 else if (auto val = fun(*str); !val) 270 { 271 auto err = fmt::format("Invalid value of {}[{}] from {}: {}", section, 272 key, config.getFilename().native(), *str); 273 log<level::NOTICE>(err.c_str(), entry("VALUE=%s", str->c_str()), 274 entry("FILE=%s", config.getFilename().c_str())); 275 } 276 else 277 { 278 return val; 279 } 280 return decltype(fun(std::string_view{}))(std::nullopt); 281 } 282 283 bool getIPv6AcceptRA(const config::Parser& config) 284 { 285 #ifdef ENABLE_IPV6_ACCEPT_RA 286 constexpr bool def = true; 287 #else 288 constexpr bool def = false; 289 #endif 290 return systemdParseLast(config, "Network", "IPv6AcceptRA", 291 config::parseBool) 292 .value_or(def); 293 } 294 295 DHCPVal getDHCPValue(const config::Parser& config) 296 { 297 return systemdParseLast(config, "Network", "DHCP", systemdParseDHCP) 298 .value_or(DHCPVal{.v4 = true, .v6 = true}); 299 } 300 301 bool getDHCPProp(const config::Parser& config, std::string_view key) 302 { 303 return systemdParseLast(config, "DHCP", key, config::parseBool) 304 .value_or(true); 305 } 306 307 namespace mac_address 308 { 309 310 constexpr auto mapperBus = "xyz.openbmc_project.ObjectMapper"; 311 constexpr auto mapperObj = "/xyz/openbmc_project/object_mapper"; 312 constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper"; 313 constexpr auto propIntf = "org.freedesktop.DBus.Properties"; 314 constexpr auto methodGet = "Get"; 315 constexpr auto configFile = "/usr/share/network/config.json"; 316 317 using DbusObjectPath = std::string; 318 using DbusService = std::string; 319 using DbusInterface = std::string; 320 using ObjectTree = 321 std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>; 322 323 constexpr auto invBus = "xyz.openbmc_project.Inventory.Manager"; 324 constexpr auto invNetworkIntf = 325 "xyz.openbmc_project.Inventory.Item.NetworkInterface"; 326 constexpr auto invRoot = "/xyz/openbmc_project/inventory"; 327 328 ether_addr getfromInventory(sdbusplus::bus_t& bus, const std::string& intfName) 329 { 330 331 std::string interfaceName = intfName; 332 333 #ifdef SYNC_MAC_FROM_INVENTORY 334 // load the config JSON from the Read Only Path 335 std::ifstream in(configFile); 336 nlohmann::json configJson; 337 in >> configJson; 338 interfaceName = configJson[intfName]; 339 #endif 340 341 std::vector<DbusInterface> interfaces; 342 interfaces.emplace_back(invNetworkIntf); 343 344 auto depth = 0; 345 346 auto mapperCall = 347 bus.new_method_call(mapperBus, mapperObj, mapperIntf, "GetSubTree"); 348 349 mapperCall.append(invRoot, depth, interfaces); 350 351 auto mapperReply = bus.call(mapperCall); 352 if (mapperReply.is_method_error()) 353 { 354 log<level::ERR>("Error in mapper call"); 355 elog<InternalFailure>(); 356 } 357 358 ObjectTree objectTree; 359 mapperReply.read(objectTree); 360 361 if (objectTree.empty()) 362 { 363 log<level::ERR>("No Object has implemented the interface", 364 entry("INTERFACE=%s", invNetworkIntf)); 365 elog<InternalFailure>(); 366 } 367 368 DbusObjectPath objPath; 369 DbusService service; 370 371 if (1 == objectTree.size()) 372 { 373 objPath = objectTree.begin()->first; 374 service = objectTree.begin()->second.begin()->first; 375 } 376 else 377 { 378 // If there are more than 2 objects, object path must contain the 379 // interface name 380 for (auto const& object : objectTree) 381 { 382 log<level::INFO>("interface", 383 entry("INT=%s", interfaceName.c_str())); 384 log<level::INFO>("object", entry("OBJ=%s", object.first.c_str())); 385 386 if (std::string::npos != object.first.find(interfaceName.c_str())) 387 { 388 objPath = object.first; 389 service = object.second.begin()->first; 390 break; 391 } 392 } 393 394 if (objPath.empty()) 395 { 396 log<level::ERR>("Can't find the object for the interface", 397 entry("intfName=%s", interfaceName.c_str())); 398 elog<InternalFailure>(); 399 } 400 } 401 402 auto method = bus.new_method_call(service.c_str(), objPath.c_str(), 403 propIntf, methodGet); 404 405 method.append(invNetworkIntf, "MACAddress"); 406 407 auto reply = bus.call(method); 408 if (reply.is_method_error()) 409 { 410 log<level::ERR>("Failed to get MACAddress", 411 entry("PATH=%s", objPath.c_str()), 412 entry("INTERFACE=%s", invNetworkIntf)); 413 elog<InternalFailure>(); 414 } 415 416 std::variant<std::string> value; 417 reply.read(value); 418 return ToAddr<ether_addr>{}(std::get<std::string>(value)); 419 } 420 421 bool isEmpty(const ether_addr& mac) 422 { 423 return mac == ether_addr{}; 424 } 425 426 bool isMulticast(const ether_addr& mac) 427 { 428 return mac.ether_addr_octet[0] & 0b1; 429 } 430 431 bool isUnicast(const ether_addr& mac) 432 { 433 return !isEmpty(mac) && !isMulticast(mac); 434 } 435 436 } // namespace mac_address 437 } // namespace network 438 } // namespace phosphor 439