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