1 // Copyright 2021 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "net_config.h" 16 17 #include <fmt/format.h> 18 #include <sys/types.h> 19 #include <sys/wait.h> 20 #include <unistd.h> 21 22 #include <sdbusplus/bus.hpp> 23 #include <stdplus/fd/create.hpp> 24 #include <stdplus/fd/ops.hpp> 25 #include <stdplus/util/string.hpp> 26 27 #include <cstdio> 28 #include <cstring> 29 #include <filesystem> 30 #include <format> 31 #include <utility> 32 #include <variant> 33 34 /* Most of the code for interacting with DBus is from 35 * phosphor-host-ipmid/utils.cpp 36 */ 37 38 namespace net 39 { 40 41 namespace 42 { 43 44 constexpr auto IFACE_ROOT = "/xyz/openbmc_project/network/"; 45 constexpr auto MAC_FORMAT = "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"; 46 // 2 chars for every byte + 5 colons + Null byte 47 constexpr auto MAC_FORMAT_LENGTH = 6 * 2 + 5 + 1; 48 constexpr auto MAC_INTERFACE = "xyz.openbmc_project.Network.MACAddress"; 49 constexpr auto NETWORK_SERVICE = "xyz.openbmc_project.Network"; 50 constexpr auto PROP_INTERFACE = "org.freedesktop.DBus.Properties"; 51 52 int parse_mac(const std::string& mac_addr, mac_addr_t* mac) 53 { 54 int ret = sscanf(mac_addr.c_str(), MAC_FORMAT, mac->octet, mac->octet + 1, 55 mac->octet + 2, mac->octet + 3, mac->octet + 4, 56 mac->octet + 5); 57 58 return ret < 6 ? -1 : 0; 59 } 60 61 std::string format_mac(const mac_addr_t& mac) 62 { 63 // 2 chars for every byte + 5 colons + Null byte 64 char mac_str[MAC_FORMAT_LENGTH]; 65 snprintf(mac_str, sizeof(mac_str), MAC_FORMAT, mac.octet[0], mac.octet[1], 66 mac.octet[2], mac.octet[3], mac.octet[4], mac.octet[5]); 67 68 return std::string{mac_str}; 69 } 70 71 } // namespace 72 73 PhosphorConfig::PhosphorConfig(const std::string& iface_name) : 74 iface_name_{iface_name}, iface_path_{std::string(IFACE_ROOT) + iface_name}, 75 shared_host_mac_(std::experimental::nullopt), 76 bus(sdbusplus::bus::new_default()) 77 {} 78 79 sdbusplus::message_t PhosphorConfig::new_networkd_call(sdbusplus::bus_t* dbus, 80 bool get) const 81 { 82 auto networkd_call = 83 dbus->new_method_call(NETWORK_SERVICE, iface_path_.c_str(), 84 PROP_INTERFACE, get ? "Get" : "Set"); 85 86 networkd_call.append(MAC_INTERFACE, "MACAddress"); 87 88 return networkd_call; 89 } 90 91 int PhosphorConfig::get_mac_addr(mac_addr_t* mac) 92 { 93 if (mac == nullptr) 94 { 95 fmt::print(stderr, "mac is nullptr\n"); 96 return -1; 97 } 98 99 // Cache hit: we have stored host MAC. 100 if (shared_host_mac_) 101 { 102 *mac = shared_host_mac_.value(); 103 } 104 else // Cache miss: read MAC over DBus, and store in cache. 105 { 106 std::string mac_string; 107 try 108 { 109 auto networkd_call = new_networkd_call(&bus, true); 110 auto reply = bus.call(networkd_call); 111 std::variant<std::string> result; 112 reply.read(result); 113 mac_string = std::get<std::string>(result); 114 } 115 catch (const sdbusplus::exception::SdBusError& ex) 116 { 117 fmt::print(stderr, "Failed to get MACAddress: {}\n", ex.what()); 118 return -1; 119 } 120 121 if (parse_mac(mac_string, mac) < 0) 122 { 123 fmt::print(stderr, "Failed to parse MAC Address `{}`\n", 124 mac_string); 125 return -1; 126 } 127 128 shared_host_mac_ = *mac; 129 } 130 131 return 0; 132 } 133 134 int PhosphorConfig::set_mac_addr(const mac_addr_t& mac) 135 { 136 auto networkd_call = new_networkd_call(&bus, false); 137 std::variant<std::string> mac_value(format_mac(mac)); 138 networkd_call.append(mac_value); 139 140 try 141 { 142 auto netdir = std::format("/run/systemd/network/00-bmc-{}.network.d", 143 iface_name_); 144 std::filesystem::create_directories(netdir); 145 auto netfile = std::format("{}/60-ncsi-mac.conf", netdir); 146 auto fd = stdplus::fd::open( 147 netfile, 148 stdplus::fd::OpenFlags(stdplus::fd::OpenAccess::WriteOnly) 149 .set(stdplus::fd::OpenFlag::Create), 150 0644); 151 auto contents = std::format("[Link]\nMACAddress={}\n", 152 std::get<std::string>(mac_value)); 153 stdplus::fd::writeExact(fd, contents); 154 } 155 catch (const std::exception& ex) 156 { 157 fmt::print(stderr, "Failed to set MAC Addr `{}` writing file: {}\n", 158 std::get<std::string>(mac_value), ex.what()); 159 return -1; 160 } 161 162 try 163 { 164 auto reply = bus.call(networkd_call); 165 } 166 catch (const sdbusplus::exception::SdBusError& ex) 167 { 168 fmt::print(stderr, "Failed to set MAC Addr `{}`: {}\n", 169 std::get<std::string>(mac_value), ex.what()); 170 return -1; 171 } 172 173 shared_host_mac_ = std::experimental::nullopt; 174 return 0; 175 } 176 177 int PhosphorConfig::set_nic_hostless(bool is_nic_hostless) 178 { 179 // Ensure that we don't trigger the target multiple times. This is 180 // undesirable because it will cause any inactive services to re-trigger 181 // every time we run this code. Since the loop calling this executes this 182 // code every 1s, we don't want to keep re-executing services. A fresh 183 // start of the daemon will always trigger the service to ensure system 184 // consistency. 185 if (was_nic_hostless_ && is_nic_hostless == *was_nic_hostless_) 186 { 187 return 0; 188 } 189 190 static constexpr auto systemdService = "org.freedesktop.systemd1"; 191 static constexpr auto systemdRoot = "/org/freedesktop/systemd1"; 192 static constexpr auto systemdInterface = "org.freedesktop.systemd1.Manager"; 193 194 auto method = bus.new_method_call(systemdService, systemdRoot, 195 systemdInterface, "StartUnit"); 196 if (is_nic_hostless) 197 { 198 method.append( 199 stdplus::util::strCat("nic-hostless@", iface_name_, ".target")); 200 } 201 else 202 { 203 method.append( 204 stdplus::util::strCat("nic-hostful@", iface_name_, ".target")); 205 } 206 207 // Specify --job-mode (see systemctl(1) for detail). 208 method.append("replace"); 209 210 try 211 { 212 bus.call_noreply(method); 213 was_nic_hostless_ = is_nic_hostless; 214 return 0; 215 } 216 catch (const sdbusplus::exception::SdBusError& ex) 217 { 218 fmt::print(stderr, "Failed to set systemd nic status: {}\n", ex.what()); 219 return 1; 220 } 221 } 222 223 } // namespace net 224