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