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