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