1 #include "config.h"
2 
3 #include "inventory_mac.hpp"
4 
5 #include "network_manager.hpp"
6 #include "types.hpp"
7 
8 #include <filesystem>
9 #include <fstream>
10 #include <memory>
11 #include <nlohmann/json.hpp>
12 #include <phosphor-logging/elog-errors.hpp>
13 #include <phosphor-logging/log.hpp>
14 #include <sdbusplus/bus.hpp>
15 #include <sdbusplus/bus/match.hpp>
16 #include <string>
17 #include <vector>
18 #include <xyz/openbmc_project/Common/error.hpp>
19 
20 namespace phosphor::network::inventory
21 {
22 
23 using phosphor::logging::elog;
24 using phosphor::logging::entry;
25 using phosphor::logging::level;
26 using phosphor::logging::log;
27 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
28 
29 using DbusObjectPath = std::string;
30 using DbusInterface = std::string;
31 using PropertyValue = std::string;
32 using DbusService = std::string;
33 using ObjectTree = string_umap<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::vector<std::string> first_boot_status;
50 
51 void setFirstBootMACOnInterface(const std::string& intf, const std::string& mac)
52 {
53     for (const auto& interface : manager->interfaces)
54     {
55         if (interface.first == intf)
56         {
57             auto returnMAC = interface.second->macAddress(mac);
58             if (returnMAC == mac)
59             {
60                 log<level::INFO>(fmt::format("Setting MAC on {}", intf).c_str(),
61                                  entry("INTF=%s", intf.c_str()),
62                                  entry("MAC=%s", mac.c_str()));
63                 std::error_code ec;
64                 if (std::filesystem::is_directory("/var/lib/network", ec))
65                 {
66                     std::ofstream persistentFile(firstBootPath + intf);
67                 }
68                 break;
69             }
70             else
71             {
72                 log<level::INFO>("MAC is Not Set on ethernet Interface");
73             }
74         }
75     }
76 }
77 
78 ether_addr getfromInventory(sdbusplus::bus_t& bus, const std::string& intfName)
79 {
80     std::string interfaceName = intfName;
81 
82     // load the config JSON from the Read Only Path
83     std::ifstream in(configFile);
84     nlohmann::json configJson;
85     in >> configJson;
86     interfaceName = configJson[intfName];
87 
88     std::vector<DbusInterface> interfaces;
89     interfaces.emplace_back(invNetworkIntf);
90 
91     auto depth = 0;
92 
93     auto mapperCall =
94         bus.new_method_call(mapperBus, mapperObj, mapperIntf, "GetSubTree");
95 
96     mapperCall.append(invRoot, depth, interfaces);
97 
98     auto mapperReply = bus.call(mapperCall);
99     if (mapperReply.is_method_error())
100     {
101         log<level::ERR>("Error in mapper call");
102         elog<InternalFailure>();
103     }
104 
105     ObjectTree objectTree;
106     mapperReply.read(objectTree);
107 
108     if (objectTree.empty())
109     {
110         log<level::ERR>("No Object has implemented the interface",
111                         entry("INTERFACE=%s", 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 (auto const& object : objectTree)
128         {
129             log<level::INFO>("interface",
130                              entry("INT=%s", interfaceName.c_str()));
131             log<level::INFO>("object", entry("OBJ=%s", object.first.c_str()));
132 
133             if (std::string::npos != object.first.find(interfaceName.c_str()))
134             {
135                 objPath = object.first;
136                 service = object.second.begin()->first;
137                 break;
138             }
139         }
140 
141         if (objPath.empty())
142         {
143             log<level::ERR>("Can't find the object for the interface",
144                             entry("intfName=%s", interfaceName.c_str()));
145             elog<InternalFailure>();
146         }
147     }
148 
149     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
150                                       propIntf, methodGet);
151 
152     method.append(invNetworkIntf, "MACAddress");
153 
154     auto reply = bus.call(method);
155     if (reply.is_method_error())
156     {
157         log<level::ERR>("Failed to get MACAddress",
158                         entry("PATH=%s", objPath.c_str()),
159                         entry("INTERFACE=%s", invNetworkIntf));
160         elog<InternalFailure>();
161     }
162 
163     std::variant<std::string> value;
164     reply.read(value);
165     return ToAddr<ether_addr>{}(std::get<std::string>(value));
166 }
167 
168 bool setInventoryMACOnSystem(sdbusplus::bus_t& bus,
169                              const nlohmann::json& configJson,
170                              const std::string& intfname)
171 {
172     try
173     {
174         auto inventoryMAC = getfromInventory(bus, intfname);
175         if (inventoryMAC != ether_addr{})
176         {
177             auto macStr = std::to_string(inventoryMAC);
178             log<level::INFO>("Mac Address in Inventory on ",
179                              entry("Interface : ", intfname.c_str()),
180                              entry("MAC Address :", macStr.c_str()));
181             setFirstBootMACOnInterface(intfname, macStr);
182             first_boot_status.push_back(intfname);
183             bool status = true;
184             for (const auto& keys : configJson.items())
185             {
186                 if (!(std::find(first_boot_status.begin(),
187                                 first_boot_status.end(),
188                                 keys.key()) != first_boot_status.end()))
189                 {
190                     log<level::INFO>("Interface MAC is NOT set from VPD"),
191                         entry("INTERFACE", keys.key().c_str());
192                     status = false;
193                 }
194             }
195             if (status)
196             {
197                 log<level::INFO>("Removing the match for ethernet interfaces");
198                 EthInterfaceMatch = nullptr;
199             }
200         }
201         else
202         {
203             log<level::INFO>("Nothing is present in Inventory");
204             return false;
205         }
206     }
207     catch (const std::exception& e)
208     {
209         log<level::ERR>("Exception occurred during getting of MAC "
210                         "address from Inventory");
211         return false;
212     }
213     return true;
214 }
215 
216 // register the macthes to be monitored from inventory manager
217 void registerSignals(sdbusplus::bus_t& bus, const nlohmann::json& configJson)
218 {
219     log<level::INFO>("Registering the Inventory Signals Matcher");
220 
221     static std::unique_ptr<sdbusplus::bus::match_t> MacAddressMatch;
222 
223     auto callback = [&](sdbusplus::message_t& m) {
224         std::map<DbusObjectPath,
225                  std::map<DbusInterface, std::variant<PropertyValue>>>
226             interfacesProperties;
227 
228         sdbusplus::message::object_path objPath;
229         m.read(objPath, interfacesProperties);
230 
231         for (const auto& pattern : configJson.items())
232         {
233             if (objPath.str.find(pattern.value()) != std::string::npos)
234             {
235                 for (auto& interface : interfacesProperties)
236                 {
237                     if (interface.first == invNetworkIntf)
238                     {
239                         for (const auto& property : interface.second)
240                         {
241                             if (property.first == "MACAddress")
242                             {
243                                 setFirstBootMACOnInterface(
244                                     pattern.key(),
245                                     std::get<std::string>(property.second));
246                                 break;
247                             }
248                         }
249                         break;
250                     }
251                 }
252             }
253         }
254     };
255 
256     MacAddressMatch = std::make_unique<sdbusplus::bus::match_t>(
257         bus,
258         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
259         "member='InterfacesAdded',path='/xyz/openbmc_project/"
260         "inventory'",
261         callback);
262 }
263 
264 void watchEthernetInterface(sdbusplus::bus_t& bus,
265                             const nlohmann::json& configJson)
266 {
267     auto mycallback = [&](sdbusplus::message_t& m) {
268         std::map<DbusObjectPath,
269                  std::map<DbusInterface, std::variant<PropertyValue>>>
270             interfacesProperties;
271 
272         sdbusplus::message::object_path objPath;
273         std::pair<std::string, std::string> ethPair;
274         m.read(objPath, interfacesProperties);
275         for (const auto& interfaces : interfacesProperties)
276         {
277             if (interfaces.first ==
278                 "xyz.openbmc_project.Network.EthernetInterface")
279             {
280                 for (const auto& property : interfaces.second)
281                 {
282                     if (property.first == "InterfaceName")
283                     {
284                         std::string infname =
285                             std::get<std::string>(property.second);
286 
287                         if (configJson.find(infname) == configJson.end())
288                         {
289                             // ethernet interface not found in configJSON
290                             // check if it is not sit0 interface, as it is
291                             // expected.
292                             if (infname != "sit0")
293                             {
294                                 log<level::ERR>(
295                                     "Wrong Interface Name in Config Json");
296                             }
297                         }
298                         else
299                         {
300                             if (!setInventoryMACOnSystem(bus, configJson,
301                                                          infname))
302                             {
303                                 registerSignals(bus, configJson);
304                                 EthInterfaceMatch = nullptr;
305                             }
306                         }
307                         break;
308                     }
309                 }
310                 break;
311             }
312         }
313     };
314     // Incase if phosphor-inventory-manager started early and the VPD is already
315     // collected by the time network service has come up, better to check the
316     // VPD directly and set the MAC Address on the respective Interface.
317 
318     bool registeredSignals = false;
319     for (const auto& interfaceString : configJson.items())
320     {
321         if ((FORCE_SYNC_MAC_FROM_INVENTORY ||
322              !std::filesystem::exists(firstBootPath + interfaceString.key())) &&
323             !registeredSignals)
324         {
325             auto msg = fmt::format("{}, check VPD for MAC",
326                                    (FORCE_SYNC_MAC_FROM_INVENTORY)
327                                        ? "Force sync enabled"
328                                        : "First boot file is not present");
329             log<level::INFO>(msg.c_str());
330             EthInterfaceMatch = std::make_unique<sdbusplus::bus::match_t>(
331                 bus,
332                 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
333                 "member='InterfacesAdded',path='/xyz/openbmc_project/network'",
334                 mycallback);
335             registeredSignals = true;
336         }
337     }
338 }
339 
340 std::unique_ptr<Runtime> watch(sdbusplus::bus_t& bus, Manager& m)
341 {
342     manager = &m;
343     std::ifstream in(configFile);
344     nlohmann::json configJson;
345     in >> configJson;
346     watchEthernetInterface(bus, configJson);
347     return nullptr;
348 }
349 
350 } // namespace phosphor::network::inventory
351