xref: /openbmc/phosphor-networkd/src/inventory_mac.cpp (revision 137d9441a873ca2b35351684737f18afb80defc8)
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