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 <linux/if.h> 18 #include <net/if_arp.h> 19 #include <sys/ioctl.h> 20 #include <sys/types.h> 21 #include <sys/wait.h> 22 #include <unistd.h> 23 24 #include <sdbusplus/bus.hpp> 25 #include <stdplus/fd/create.hpp> 26 #include <stdplus/fd/ops.hpp> 27 #include <stdplus/print.hpp> 28 #include <stdplus/raw.hpp> 29 #include <stdplus/util/string.hpp> 30 31 #include <cstdio> 32 #include <cstring> 33 #include <filesystem> 34 #include <format> 35 #include <thread> 36 #include <utility> 37 #include <variant> 38 39 /* Most of the code for interacting with DBus is from 40 * phosphor-host-ipmid/utils.cpp 41 */ 42 43 namespace net 44 { 45 46 namespace 47 { 48 49 constexpr auto IFACE_ROOT = "/xyz/openbmc_project/network/"; 50 constexpr auto MAC_FORMAT = "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"; 51 // 2 chars for every byte + 5 colons + Null byte 52 constexpr auto MAC_FORMAT_LENGTH = 6 * 2 + 5 + 1; 53 constexpr auto MAC_INTERFACE = "xyz.openbmc_project.Network.MACAddress"; 54 constexpr auto NETWORK_SERVICE = "xyz.openbmc_project.Network"; 55 constexpr auto PROP_INTERFACE = "org.freedesktop.DBus.Properties"; 56 57 std::string format_mac(const mac_addr_t& mac) 58 { 59 // 2 chars for every byte + 5 colons + Null byte 60 char mac_str[MAC_FORMAT_LENGTH]; 61 snprintf(mac_str, sizeof(mac_str), MAC_FORMAT, mac.octet[0], mac.octet[1], 62 mac.octet[2], mac.octet[3], mac.octet[4], mac.octet[5]); 63 64 return std::string{mac_str}; 65 } 66 67 } // namespace 68 69 PhosphorConfig::PhosphorConfig(const std::string& iface_name) : 70 iface_name_{iface_name}, iface_path_{std::string(IFACE_ROOT) + iface_name}, 71 shared_host_mac_(std::experimental::nullopt), 72 bus(sdbusplus::bus::new_default()) 73 {} 74 75 int PhosphorConfig::get_mac_addr(mac_addr_t* mac) 76 { 77 if (mac == nullptr) 78 { 79 stdplus::println(stderr, "mac is nullptr"); 80 return -1; 81 } 82 83 // Cache hit: we have stored host MAC. 84 if (shared_host_mac_) 85 { 86 *mac = shared_host_mac_.value(); 87 } 88 else // Cache miss: read from interface, cache it for future requests. 89 { 90 struct ifreq ifr = {}; 91 try 92 { 93 auto fd = stdplus::fd::socket(stdplus::fd::SocketDomain::INet6, 94 stdplus::fd::SocketType::Datagram, 95 stdplus::fd::SocketProto::IP); 96 call_nic(fd, ifr, SIOCGIFHWADDR); 97 } 98 catch (const std::exception& ex) 99 { 100 stdplus::println( 101 stderr, 102 "Failed to get MAC Addr for Interface {} writing file: {}", 103 iface_name_, ex.what()); 104 return -1; 105 } 106 std::copy_n(ifr.ifr_addr.sa_data, sizeof(*mac), mac->octet); 107 shared_host_mac_ = *mac; 108 } 109 return 0; 110 } 111 112 void PhosphorConfig::call_nic(auto fd, struct ifreq& ifr, int op) 113 { 114 std::copy_n(iface_name_.c_str(), iface_name_.size() + 1, ifr.ifr_name); 115 fd.ioctl(op, &ifr); 116 } 117 118 int PhosphorConfig::set_mac_addr(const mac_addr_t& mac) 119 { 120 std::string mac_value = format_mac(mac); 121 struct ifreq ifr = {}; 122 short flags_copy; 123 mac_addr_t cur_mac; 124 int ret; 125 ret = get_mac_addr(&cur_mac); 126 if (ret == 0) 127 { 128 if (stdplus::raw::equal(cur_mac, mac)) 129 { 130 // mac value is the same not doing anything, returning 131 return 0; 132 } 133 } 134 135 try 136 { 137 auto netdir = std::format("/run/systemd/network/00-bmc-{}.network.d", 138 iface_name_); 139 std::filesystem::create_directories(netdir); 140 auto netfile = std::format("{}/60-ncsi-mac.conf", netdir); 141 auto fd = stdplus::fd::open( 142 netfile, 143 stdplus::fd::OpenFlags(stdplus::fd::OpenAccess::WriteOnly) 144 .set(stdplus::fd::OpenFlag::Create), 145 0644); 146 auto contents = std::format("[Link]\nMACAddress={}\n", mac_value); 147 stdplus::fd::writeExact(fd, contents); 148 } 149 catch (const std::exception& ex) 150 { 151 stdplus::println(stderr, "Failed to set MAC Addr `{}` writing file: {}", 152 mac_value, ex.what()); 153 return -1; 154 } 155 try 156 { 157 auto fd = stdplus::fd::socket(stdplus::fd::SocketDomain::INet6, 158 stdplus::fd::SocketType::Datagram, 159 stdplus::fd::SocketProto::IP); 160 // Try setting MAC Address directly without bringing interface down 161 try 162 { 163 std::copy_n(mac.octet, 6, ifr.ifr_hwaddr.sa_data); 164 call_nic(fd, ifr, SIOCSIFHWADDR); 165 } 166 catch (const std::exception& e) 167 { 168 // Regardless of error attempt to set MAC Address again after 169 // bringing interface down 170 stdplus::println( 171 stderr, 172 "Could not set MAC Address directly, retrying after bringing interface down, error = {}", 173 e.what()); 174 try 175 { 176 // Read interface flags configuration and store (once interface 177 // is brought down, existing state is lost) 178 call_nic(fd, ifr, SIOCGIFFLAGS); 179 flags_copy = ifr.ifr_flags; 180 // set interface down 181 ifr.ifr_flags &= ~IFF_UP; 182 call_nic(fd, ifr, SIOCSIFFLAGS); 183 // Wait for 1 milliseconds - sometimes interface is still 184 // going down 185 std::this_thread::sleep_for(std::chrono::milliseconds(1)); 186 // set MAC Address 187 ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER; 188 std::copy_n(mac.octet, 6, ifr.ifr_hwaddr.sa_data); 189 call_nic(fd, ifr, SIOCSIFHWADDR); 190 // set interface up with the flags state prior to bringing 191 // it down 192 ifr.ifr_flags = flags_copy | IFF_UP; 193 call_nic(fd, ifr, SIOCSIFFLAGS); 194 } 195 catch (const std::exception& e) 196 { 197 stdplus::println( 198 stderr, "Failed to set MAC Address {} writing file: {}", 199 mac_value, e.what()); 200 return -1; 201 } 202 } 203 } 204 catch (const std::exception& e) 205 { 206 stdplus::println(stderr, "Error creating socket: {}", e.what()); 207 return -1; 208 } 209 stdplus::println(stderr, "Success setting Mac address for {}: {}", 210 iface_name_, mac_value); 211 shared_host_mac_ = std::experimental::nullopt; 212 return 0; 213 } 214 215 int PhosphorConfig::set_nic_hostless(bool is_nic_hostless) 216 { 217 // Ensure that we don't trigger the target multiple times. This is 218 // undesirable because it will cause any inactive services to re-trigger 219 // every time we run this code. Since the loop calling this executes this 220 // code every 1s, we don't want to keep re-executing services. A fresh 221 // start of the daemon will always trigger the service to ensure system 222 // consistency. 223 if (was_nic_hostless_ && is_nic_hostless == *was_nic_hostless_) 224 { 225 return 0; 226 } 227 228 static constexpr auto systemdService = "org.freedesktop.systemd1"; 229 static constexpr auto systemdRoot = "/org/freedesktop/systemd1"; 230 static constexpr auto systemdInterface = "org.freedesktop.systemd1.Manager"; 231 232 auto method = bus.new_method_call(systemdService, systemdRoot, 233 systemdInterface, "StartUnit"); 234 if (is_nic_hostless) 235 { 236 method.append( 237 stdplus::util::strCat("nic-hostless@", iface_name_, ".target")); 238 } 239 else 240 { 241 method.append( 242 stdplus::util::strCat("nic-hostful@", iface_name_, ".target")); 243 } 244 245 // Specify --job-mode (see systemctl(1) for detail). 246 method.append("replace"); 247 248 try 249 { 250 bus.call_noreply(method); 251 was_nic_hostless_ = is_nic_hostless; 252 return 0; 253 } 254 catch (const sdbusplus::exception::SdBusError& ex) 255 { 256 stdplus::println(stderr, "Failed to set systemd nic status: {}", 257 ex.what()); 258 return 1; 259 } 260 } 261 262 } // namespace net 263