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