xref: /openbmc/phosphor-networkd/src/inventory_mac.cpp (revision 3c4f35ebe7ee527608bd57a2c51cccfa3f2ee836)
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 
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 
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     ObjectTree objectTree;
107     mapperReply.read(objectTree);
108 
109     if (objectTree.empty())
110     {
111         lg2::error("No Object has implemented the interface {NET_INTF}",
112                    "NET_INTF", invNetworkIntf);
113         elog<InternalFailure>();
114     }
115 
116     DbusObjectPath objPath;
117     DbusService service;
118 
119     if (1 == objectTree.size())
120     {
121         objPath = objectTree.begin()->first;
122         service = objectTree.begin()->second.begin()->first;
123     }
124     else
125     {
126         // If there are more than 2 objects, object path must contain the
127         // interface name
128         for (const auto& object : objectTree)
129         {
130             lg2::info("Get info on interface {NET_INTF}, object {OBJ}",
131                       "NET_INTF", interfaceName, "OBJ", object.first);
132             if (object.first.ends_with("/" + interfaceName))
133             {
134                 objPath = object.first;
135                 service = object.second.begin()->first;
136                 break;
137             }
138         }
139 
140         if (objPath.empty())
141         {
142             lg2::error("Can't find the object for the interface {NET_INTF}",
143                        "NET_INTF", interfaceName);
144             elog<InternalFailure>();
145         }
146     }
147 
148     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
149                                       propIntf, methodGet);
150 
151     method.append(invNetworkIntf, "MACAddress");
152 
153     auto reply = [&]() {
154         try
155         {
156             return bus.call(method);
157         }
158         catch (const sdbusplus::exception::SdBusError& e)
159         {
160             lg2::error(
161                 "Failed to get MACAddress for path {DBUS_PATH} interface {DBUS_INTF}",
162                 "DBUS_PATH", objPath, "DBUS_INTF", invNetworkIntf);
163             elog<InternalFailure>();
164         }
165     }();
166 
167     std::variant<std::string> value;
168     reply.read(value);
169     return stdplus::fromStr<stdplus::EtherAddr>(std::get<std::string>(value));
170 }
171 
172 bool setInventoryMACOnSystem(sdbusplus::bus_t& bus, const std::string& intfname)
173 {
174     try
175     {
176         auto inventoryMAC = getfromInventory(bus, intfname);
177         if (inventoryMAC != stdplus::EtherAddr{})
178         {
179             auto macStr = stdplus::toStr(inventoryMAC);
180             lg2::info(
181                 "Mac Address {NET_MAC} in Inventory on Interface {NET_INTF}",
182                 "NET_MAC", macStr, "NET_INTF", intfname);
183             setFirstBootMACOnInterface(intfname, macStr);
184             first_boot_status.push_back(intfname);
185             bool status = true;
186             for (const auto& keys : configJson.items())
187             {
188                 if (!(std::find(first_boot_status.begin(),
189                                 first_boot_status.end(), keys.key()) !=
190                       first_boot_status.end()))
191                 {
192                     lg2::info("Interface {NET_INTF} MAC is NOT set from VPD",
193                               "NET_INTF", keys.key());
194                     status = false;
195                 }
196             }
197             if (status)
198             {
199                 lg2::info("Removing the match for ethernet interfaces");
200                 EthInterfaceMatch = nullptr;
201             }
202         }
203         else
204         {
205             lg2::info("Nothing is present in Inventory");
206             return false;
207         }
208     }
209     catch (const std::exception& e)
210     {
211         lg2::error("Exception occurred during getting of MAC "
212                    "address from Inventory");
213         return false;
214     }
215     return true;
216 }
217 
218 // register the matches to be monitored from inventory manager
219 void registerSignals(sdbusplus::bus_t& bus)
220 {
221     lg2::info("Registering the Inventory Signals Matcher");
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.ends_with("/" + pattern.value().get<std::string>()))
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                                 // Only set mac address on interface once the
244                                 // firstboot file does not exist or it is being
245                                 // FORCE_SYNC_MAC_FROM_INVENTORY
246                                 if (FORCE_SYNC_MAC_FROM_INVENTORY ||
247                                     !std::filesystem::exists(
248                                         firstBootPath + pattern.key()))
249                                 {
250                                     setFirstBootMACOnInterface(
251                                         pattern.key(),
252                                         std::get<std::string>(property.second));
253                                 }
254                                 break;
255                             }
256                         }
257                         break;
258                     }
259                 }
260             }
261         }
262     };
263 
264     MacAddressMatch = std::make_unique<sdbusplus::bus::match_t>(
265         bus,
266         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
267         "member='InterfacesAdded',path='/xyz/openbmc_project/"
268         "inventory'",
269         callback);
270 }
271 
272 void watchEthernetInterface(sdbusplus::bus_t& bus)
273 {
274     auto handle_interface = [&](auto infname) {
275         if (configJson.find(infname) == configJson.end())
276         {
277             // ethernet interface not found in configJSON
278             // check if it is not sit0 interface, as it is
279             // expected.
280             if (infname != "sit0")
281             {
282                 lg2::error("Wrong Interface Name in Config Json");
283             }
284         }
285         else
286         {
287             registerSignals(bus);
288 
289             if (setInventoryMACOnSystem(bus, infname))
290             {
291                 MacAddressMatch = nullptr;
292             }
293         }
294     };
295 
296     auto mycallback = [&, handle_interface](sdbusplus::message_t& m) {
297         std::map<DbusObjectPath,
298                  std::map<DbusInterface, std::variant<PropertyValue>>>
299             interfacesProperties;
300 
301         sdbusplus::message::object_path objPath;
302         std::pair<std::string, std::string> ethPair;
303         m.read(objPath, interfacesProperties);
304 
305         for (const auto& interfaces : interfacesProperties)
306         {
307             lg2::info("Check {DBUS_INTF} for sdbus response", "DBUS_INTF",
308                       interfaces.first);
309             if (interfaces.first ==
310                 "xyz.openbmc_project.Network.EthernetInterface")
311             {
312                 for (const auto& property : interfaces.second)
313                 {
314                     if (property.first == "InterfaceName")
315                     {
316                         handle_interface(
317                             std::get<std::string>(property.second));
318 
319                         break;
320                     }
321                 }
322                 break;
323             }
324         }
325     };
326 
327     // The VPD may already have been assigned because phosphor-inventory-manager
328     // started ahead of the network service. Read the VPD directly and assign
329     // the MAC address despite this possibility.
330 
331     for (const auto& interfaceString : configJson.items())
332     {
333         if (FORCE_SYNC_MAC_FROM_INVENTORY ||
334             !std::filesystem::exists(firstBootPath + interfaceString.key()))
335         {
336             lg2::info("Check VPD for MAC: {REASON}", "REASON",
337                       (FORCE_SYNC_MAC_FROM_INVENTORY)
338                           ? "Force sync enabled"
339                           : "First boot file is not present");
340             EthInterfaceMatch = std::make_unique<sdbusplus::bus::match_t>(
341                 bus,
342                 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
343                 "member='InterfacesAdded',path='/xyz/openbmc_project/network'",
344                 mycallback);
345 
346             for (const auto& intf : manager->interfaces)
347             {
348                 if (intf.first == interfaceString.key())
349                 {
350                     handle_interface(intf.first);
351                 }
352             }
353         }
354     }
355 }
356 
357 std::unique_ptr<Runtime> watch(stdplus::PinnedRef<sdbusplus::bus_t> bus,
358                                stdplus::PinnedRef<Manager> m)
359 {
360     manager = &m.get();
361     std::ifstream in(configFile);
362     in >> configJson;
363     watchEthernetInterface(bus);
364     return nullptr;
365 }
366 
367 } // namespace phosphor::network::inventory
368