1 #include "config.h"
2
3 #include "inventory_mac.hpp"
4
5 #include "network_manager.hpp"
6 #include "types.hpp"
7
8 #include <nlohmann/json.hpp>
9 #include <phosphor-logging/elog-errors.hpp>
10 #include <phosphor-logging/lg2.hpp>
11 #include <sdbusplus/bus.hpp>
12 #include <sdbusplus/bus/match.hpp>
13 #include <stdplus/str/maps.hpp>
14 #include <xyz/openbmc_project/Common/error.hpp>
15
16 #include <filesystem>
17 #include <fstream>
18 #include <memory>
19 #include <string>
20 #include <vector>
21
22 namespace phosphor::network::inventory
23 {
24
25 using phosphor::logging::elog;
26 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
27
28 using DbusObjectPath = std::string;
29 using DbusInterface = std::string;
30 using PropertyValue = std::string;
31 using DbusService = std::string;
32 using ObjectTree =
33 stdplus::string_umap<stdplus::string_umap<std::vector<std::string>>>;
34
35 constexpr auto firstBootPath = "/var/lib/network/firstBoot_";
36 constexpr auto configFile = "/usr/share/network/config.json";
37
38 constexpr auto invNetworkIntf =
39 "xyz.openbmc_project.Inventory.Item.NetworkInterface";
40 constexpr auto invRoot = "/xyz/openbmc_project/inventory";
41 constexpr auto mapperBus = "xyz.openbmc_project.ObjectMapper";
42 constexpr auto mapperObj = "/xyz/openbmc_project/object_mapper";
43 constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
44 constexpr auto propIntf = "org.freedesktop.DBus.Properties";
45 constexpr auto methodGet = "Get";
46
47 Manager* manager = nullptr;
48 std::unique_ptr<sdbusplus::bus::match_t> EthInterfaceMatch = nullptr;
49 std::unique_ptr<sdbusplus::bus::match_t> MacAddressMatch = nullptr;
50 std::vector<std::string> first_boot_status;
51 nlohmann::json configJson;
52
setFirstBootMACOnInterface(const std::string & intf,const std::string & mac)53 void setFirstBootMACOnInterface(const std::string& intf, const std::string& mac)
54 {
55 for (const auto& interface : manager->interfaces)
56 {
57 if (interface.first == intf)
58 {
59 auto returnMAC = interface.second->macAddress(mac);
60 if (returnMAC == mac)
61 {
62 lg2::info("Setting MAC {NET_MAC} on interface {NET_INTF}",
63 "NET_MAC", mac, "NET_INTF", intf);
64 std::error_code ec;
65 if (std::filesystem::is_directory("/var/lib/network", ec))
66 {
67 std::ofstream persistentFile(firstBootPath + intf);
68 }
69 break;
70 }
71 else
72 {
73 lg2::info("MAC is Not Set on ethernet Interface");
74 }
75 }
76 }
77 }
78
getfromInventory(sdbusplus::bus_t & bus,const std::string & intfName)79 stdplus::EtherAddr getfromInventory(sdbusplus::bus_t& bus,
80 const std::string& intfName)
81 {
82 std::string interfaceName = configJson[intfName];
83
84 std::vector<DbusInterface> interfaces;
85 interfaces.emplace_back(invNetworkIntf);
86
87 auto depth = 0;
88
89 auto mapperCall =
90 bus.new_method_call(mapperBus, mapperObj, mapperIntf, "GetSubTree");
91
92 mapperCall.append(invRoot, depth, interfaces);
93
94 auto mapperReply = [&]() {
95 try
96 {
97 return bus.call(mapperCall);
98 }
99 catch (const sdbusplus::exception::SdBusError& e)
100 {
101 lg2::error("Error in mapper call");
102 elog<InternalFailure>();
103 }
104 }();
105
106 auto objectTree = mapperReply.unpack<ObjectTree>();
107
108 if (objectTree.empty())
109 {
110 lg2::error("No Object has implemented the interface {NET_INTF}",
111 "NET_INTF", invNetworkIntf);
112 elog<InternalFailure>();
113 }
114
115 DbusObjectPath objPath;
116 DbusService service;
117
118 if (1 == objectTree.size())
119 {
120 objPath = objectTree.begin()->first;
121 service = objectTree.begin()->second.begin()->first;
122 }
123 else
124 {
125 // If there are more than 2 objects, object path must contain the
126 // interface name
127 for (const auto& object : objectTree)
128 {
129 lg2::info("Get info on interface {NET_INTF}, object {OBJ}",
130 "NET_INTF", interfaceName, "OBJ", object.first);
131 if (object.first.ends_with("/" + interfaceName))
132 {
133 objPath = object.first;
134 service = object.second.begin()->first;
135 break;
136 }
137 }
138
139 if (objPath.empty())
140 {
141 lg2::error("Can't find the object for the interface {NET_INTF}",
142 "NET_INTF", interfaceName);
143 elog<InternalFailure>();
144 }
145 }
146
147 auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
148 propIntf, methodGet);
149
150 method.append(invNetworkIntf, "MACAddress");
151
152 auto reply = [&]() {
153 try
154 {
155 return bus.call(method);
156 }
157 catch (const sdbusplus::exception::SdBusError& e)
158 {
159 lg2::error(
160 "Failed to get MACAddress for path {DBUS_PATH} interface {DBUS_INTF}",
161 "DBUS_PATH", objPath, "DBUS_INTF", invNetworkIntf);
162 elog<InternalFailure>();
163 }
164 }();
165
166 auto value = reply.unpack<std::variant<std::string>>();
167
168 return stdplus::fromStr<stdplus::EtherAddr>(std::get<std::string>(value));
169 }
170
setInventoryMACOnSystem(sdbusplus::bus_t & bus,const std::string & intfname)171 bool setInventoryMACOnSystem(sdbusplus::bus_t& bus, const std::string& intfname)
172 {
173 try
174 {
175 auto inventoryMAC = getfromInventory(bus, intfname);
176 if (inventoryMAC != stdplus::EtherAddr{})
177 {
178 auto macStr = stdplus::toStr(inventoryMAC);
179 lg2::info(
180 "Mac Address {NET_MAC} in Inventory on Interface {NET_INTF}",
181 "NET_MAC", macStr, "NET_INTF", intfname);
182 setFirstBootMACOnInterface(intfname, macStr);
183 first_boot_status.push_back(intfname);
184 bool status = true;
185 for (const auto& keys : configJson.items())
186 {
187 if (!(std::find(first_boot_status.begin(),
188 first_boot_status.end(), keys.key()) !=
189 first_boot_status.end()))
190 {
191 lg2::info("Interface {NET_INTF} MAC is NOT set from VPD",
192 "NET_INTF", keys.key());
193 status = false;
194 }
195 }
196 if (status)
197 {
198 lg2::info("Removing the match for ethernet interfaces");
199 EthInterfaceMatch = nullptr;
200 }
201 }
202 else
203 {
204 lg2::info("Nothing is present in Inventory");
205 return false;
206 }
207 }
208 catch (const std::exception& e)
209 {
210 lg2::error("Exception occurred during getting of MAC "
211 "address from Inventory");
212 return false;
213 }
214 return true;
215 }
216
217 // register the matches to be monitored from inventory manager
registerSignals(sdbusplus::bus_t & bus)218 void registerSignals(sdbusplus::bus_t& bus)
219 {
220 lg2::info("Registering the Inventory Signals Matcher");
221
222 auto callback = [&](sdbusplus::message_t& m) {
223 std::map<DbusObjectPath,
224 std::map<DbusInterface, std::variant<PropertyValue>>>
225 interfacesProperties;
226
227 sdbusplus::message::object_path objPath;
228 m.read(objPath, interfacesProperties);
229
230 for (const auto& pattern : configJson.items())
231 {
232 if (objPath.str.ends_with("/" + pattern.value().get<std::string>()))
233 {
234 for (auto& interface : interfacesProperties)
235 {
236 if (interface.first == invNetworkIntf)
237 {
238 for (const auto& property : interface.second)
239 {
240 if (property.first == "MACAddress")
241 {
242 // Only set mac address on interface once the
243 // firstboot file does not exist or it is being
244 // FORCE_SYNC_MAC_FROM_INVENTORY
245 if (FORCE_SYNC_MAC_FROM_INVENTORY ||
246 !std::filesystem::exists(
247 firstBootPath + pattern.key()))
248 {
249 setFirstBootMACOnInterface(
250 pattern.key(),
251 std::get<std::string>(property.second));
252 }
253 break;
254 }
255 }
256 break;
257 }
258 }
259 }
260 }
261 };
262
263 MacAddressMatch = std::make_unique<sdbusplus::bus::match_t>(
264 bus,
265 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
266 "member='InterfacesAdded',path='/xyz/openbmc_project/"
267 "inventory'",
268 callback);
269 }
270
watchEthernetInterface(sdbusplus::bus_t & bus)271 void watchEthernetInterface(sdbusplus::bus_t& bus)
272 {
273 auto handle_interface = [&](auto infname) {
274 if (configJson.find(infname) == configJson.end())
275 {
276 // ethernet interface not found in configJSON
277 // check if it is not sit0 interface, as it is
278 // expected.
279 if (infname != "sit0")
280 {
281 lg2::error("Wrong Interface Name in Config Json");
282 }
283 }
284 else
285 {
286 registerSignals(bus);
287
288 if (setInventoryMACOnSystem(bus, infname))
289 {
290 MacAddressMatch = nullptr;
291 }
292 }
293 };
294
295 auto mycallback = [&, handle_interface](sdbusplus::message_t& m) {
296 std::map<DbusObjectPath,
297 std::map<DbusInterface, std::variant<PropertyValue>>>
298 interfacesProperties;
299
300 sdbusplus::message::object_path objPath;
301 std::pair<std::string, std::string> ethPair;
302 m.read(objPath, interfacesProperties);
303
304 for (const auto& interfaces : interfacesProperties)
305 {
306 lg2::info("Check {DBUS_INTF} for sdbus response", "DBUS_INTF",
307 interfaces.first);
308 if (interfaces.first ==
309 "xyz.openbmc_project.Network.EthernetInterface")
310 {
311 for (const auto& property : interfaces.second)
312 {
313 if (property.first == "InterfaceName")
314 {
315 handle_interface(
316 std::get<std::string>(property.second));
317
318 break;
319 }
320 }
321 break;
322 }
323 }
324 };
325
326 // The VPD may already have been assigned because phosphor-inventory-manager
327 // started ahead of the network service. Read the VPD directly and assign
328 // the MAC address despite this possibility.
329
330 for (const auto& interfaceString : configJson.items())
331 {
332 if (FORCE_SYNC_MAC_FROM_INVENTORY ||
333 !std::filesystem::exists(firstBootPath + interfaceString.key()))
334 {
335 lg2::info("Check VPD for MAC: {REASON}", "REASON",
336 (FORCE_SYNC_MAC_FROM_INVENTORY)
337 ? "Force sync enabled"
338 : "First boot file is not present");
339 EthInterfaceMatch = std::make_unique<sdbusplus::bus::match_t>(
340 bus,
341 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
342 "member='InterfacesAdded',path='/xyz/openbmc_project/network'",
343 mycallback);
344
345 for (const auto& intf : manager->interfaces)
346 {
347 if (intf.first == interfaceString.key())
348 {
349 handle_interface(intf.first);
350 }
351 }
352 }
353 }
354 }
355
watch(stdplus::PinnedRef<sdbusplus::bus_t> bus,stdplus::PinnedRef<Manager> m)356 std::unique_ptr<Runtime> watch(stdplus::PinnedRef<sdbusplus::bus_t> bus,
357 stdplus::PinnedRef<Manager> m)
358 {
359 manager = &m.get();
360 std::ifstream in(configFile);
361 in >> configJson;
362 watchEthernetInterface(bus);
363 return nullptr;
364 }
365
366 } // namespace phosphor::network::inventory
367