1 #include "config.h"
2 
3 #include "network_manager.hpp"
4 #include "rtnetlink_server.hpp"
5 #include "types.hpp"
6 #ifdef SYNC_MAC_FROM_INVENTORY
7 #include "util.hpp"
8 #endif
9 
10 #include <fmt/format.h>
11 #include <linux/netlink.h>
12 
13 #include <filesystem>
14 #include <fstream>
15 #include <functional>
16 #include <memory>
17 #ifdef SYNC_MAC_FROM_INVENTORY
18 #include <nlohmann/json.hpp>
19 #endif
20 #include <phosphor-logging/elog-errors.hpp>
21 #include <phosphor-logging/log.hpp>
22 #include <sdbusplus/bus.hpp>
23 #include <sdbusplus/bus/match.hpp>
24 #include <sdbusplus/server/manager.hpp>
25 #include <sdeventplus/event.hpp>
26 #include <xyz/openbmc_project/Common/error.hpp>
27 
28 using phosphor::logging::elog;
29 using phosphor::logging::entry;
30 using phosphor::logging::level;
31 using phosphor::logging::log;
32 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
33 using DbusObjectPath = std::string;
34 using DbusInterface = std::string;
35 using PropertyValue = std::string;
36 
37 constexpr char NETWORK_CONF_DIR[] = "/etc/systemd/network";
38 
39 constexpr char DEFAULT_OBJPATH[] = "/xyz/openbmc_project/network";
40 
41 constexpr auto firstBootPath = "/var/lib/network/firstBoot_";
42 constexpr auto configFile = "/usr/share/network/config.json";
43 
44 constexpr auto invNetworkIntf =
45     "xyz.openbmc_project.Inventory.Item.NetworkInterface";
46 
47 namespace phosphor
48 {
49 namespace network
50 {
51 
52 std::unique_ptr<Manager> manager = nullptr;
53 std::unique_ptr<Timer> refreshObjectTimer = nullptr;
54 std::unique_ptr<Timer> reloadTimer = nullptr;
55 
56 #ifdef SYNC_MAC_FROM_INVENTORY
57 std::unique_ptr<sdbusplus::bus::match_t> EthInterfaceMatch = nullptr;
58 std::vector<std::string> first_boot_status;
59 
60 bool setInventoryMACOnSystem(sdbusplus::bus_t& bus,
61                              const nlohmann::json& configJson,
62                              const std::string& intfname)
63 {
64     try
65     {
66         auto inventoryMAC = mac_address::getfromInventory(bus, intfname);
67         if (!mac_address::toString(inventoryMAC).empty())
68         {
69             log<level::INFO>("Mac Address in Inventory on "),
70                 entry("Interface : ", intfname.c_str()),
71                 entry("MAC Address :",
72                       (mac_address::toString(inventoryMAC)).c_str());
73             manager->setFistBootMACOnInterface(std::make_pair(
74                 intfname.c_str(), mac_address::toString(inventoryMAC)));
75             first_boot_status.push_back(intfname.c_str());
76             bool status = true;
77             for (const auto& keys : configJson.items())
78             {
79                 if (!(std::find(first_boot_status.begin(),
80                                 first_boot_status.end(),
81                                 keys.key()) != first_boot_status.end()))
82                 {
83                     log<level::INFO>("Interface MAC is NOT set from VPD"),
84                         entry("INTERFACE", keys.key().c_str());
85                     status = false;
86                 }
87             }
88             if (status)
89             {
90                 log<level::INFO>("Removing the match for ethernet interfaces");
91                 EthInterfaceMatch = nullptr;
92             }
93         }
94         else
95         {
96             log<level::INFO>("Nothing is present in Inventory");
97             return false;
98         }
99     }
100     catch (const std::exception& e)
101     {
102         log<level::ERR>("Exception occurred during getting of MAC "
103                         "address from Inventory");
104         return false;
105     }
106     return true;
107 }
108 
109 // register the macthes to be monitored from inventory manager
110 void registerSignals(sdbusplus::bus_t& bus, const nlohmann::json& configJson)
111 {
112     log<level::INFO>("Registering the Inventory Signals Matcher");
113 
114     static std::unique_ptr<sdbusplus::bus::match_t> MacAddressMatch;
115 
116     auto callback = [&](sdbusplus::message_t& m) {
117         std::map<DbusObjectPath,
118                  std::map<DbusInterface, std::variant<PropertyValue>>>
119             interfacesProperties;
120 
121         sdbusplus::message::object_path objPath;
122         std::pair<std::string, std::string> ethPair;
123         m.read(objPath, interfacesProperties);
124 
125         for (const auto& pattern : configJson.items())
126         {
127             if (objPath.str.find(pattern.value()) != std::string::npos)
128             {
129                 for (auto& interface : interfacesProperties)
130                 {
131                     if (interface.first == invNetworkIntf)
132                     {
133                         for (const auto& property : interface.second)
134                         {
135                             if (property.first == "MACAddress")
136                             {
137                                 ethPair = std::make_pair(
138                                     pattern.key(),
139                                     std::get<std::string>(property.second));
140                                 break;
141                             }
142                         }
143                         break;
144                     }
145                 }
146                 if (!(ethPair.first.empty() || ethPair.second.empty()))
147                 {
148                     manager->setFistBootMACOnInterface(ethPair);
149                 }
150             }
151         }
152     };
153 
154     MacAddressMatch = std::make_unique<sdbusplus::bus::match_t>(
155         bus,
156         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
157         "member='InterfacesAdded',path='/xyz/openbmc_project/"
158         "inventory'",
159         callback);
160 }
161 
162 void watchEthernetInterface(sdbusplus::bus_t& bus,
163                             const nlohmann::json& configJson)
164 {
165     auto mycallback = [&](sdbusplus::message_t& m) {
166         std::map<DbusObjectPath,
167                  std::map<DbusInterface, std::variant<PropertyValue>>>
168             interfacesProperties;
169 
170         sdbusplus::message::object_path objPath;
171         std::pair<std::string, std::string> ethPair;
172         m.read(objPath, interfacesProperties);
173         for (const auto& interfaces : interfacesProperties)
174         {
175             if (interfaces.first ==
176                 "xyz.openbmc_project.Network.EthernetInterface")
177             {
178                 for (const auto& property : interfaces.second)
179                 {
180                     if (property.first == "InterfaceName")
181                     {
182                         std::string infname =
183                             std::get<std::string>(property.second);
184 
185                         if (configJson.find(infname) == configJson.end())
186                         {
187                             // ethernet interface not found in configJSON
188                             // check if it is not sit0 interface, as it is
189                             // expected.
190                             if (infname != "sit0")
191                             {
192                                 log<level::ERR>(
193                                     "Wrong Interface Name in Config Json");
194                             }
195                         }
196                         else
197                         {
198                             if (!setInventoryMACOnSystem(bus, configJson,
199                                                          infname))
200                             {
201                                 registerSignals(bus, configJson);
202                                 EthInterfaceMatch = nullptr;
203                             }
204                         }
205                         break;
206                     }
207                 }
208                 break;
209             }
210         }
211     };
212     // Incase if phosphor-inventory-manager started early and the VPD is already
213     // collected by the time network service has come up, better to check the
214     // VPD directly and set the MAC Address on the respective Interface.
215 
216     bool registeredSignals = false;
217     for (const auto& interfaceString : configJson.items())
218     {
219         if (!std::filesystem::exists(firstBootPath + interfaceString.key()) &&
220             !registeredSignals)
221         {
222 
223             log<level::INFO>(
224                 "First boot file is not present, check VPD for MAC");
225             EthInterfaceMatch = std::make_unique<sdbusplus::bus::match_t>(
226                 bus,
227                 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
228                 "member='InterfacesAdded',path='/xyz/openbmc_project/network'",
229                 mycallback);
230             registeredSignals = true;
231         }
232     }
233 }
234 
235 #endif
236 
237 /** @brief refresh the network objects. */
238 void refreshObjects()
239 {
240     if (manager)
241     {
242         log<level::INFO>("Refreshing the objects.");
243         manager->createChildObjects();
244         log<level::INFO>("Refreshing complete.");
245     }
246 }
247 
248 void reloadNetworkd()
249 {
250     if (manager)
251     {
252         log<level::INFO>("Sending networkd reload");
253         manager->doReloadConfigs();
254         log<level::INFO>("Done networkd reload");
255     }
256 }
257 
258 void initializeTimers()
259 {
260     auto event = sdeventplus::Event::get_default();
261     refreshObjectTimer =
262         std::make_unique<Timer>(event, std::bind(refreshObjects));
263     reloadTimer = std::make_unique<Timer>(event, std::bind(reloadNetworkd));
264 }
265 
266 int main()
267 {
268     initializeTimers();
269 
270     auto bus = sdbusplus::bus::new_default();
271 
272     // Need sd_event to watch for OCC device errors
273     sd_event* event = nullptr;
274     auto r = sd_event_default(&event);
275     if (r < 0)
276     {
277         log<level::ERR>("Error creating a default sd_event handler");
278         return r;
279     }
280 
281     EventPtr eventPtr{event};
282     event = nullptr;
283 
284     // Attach the bus to sd_event to service user requests
285     bus.attach_event(eventPtr.get(), SD_EVENT_PRIORITY_NORMAL);
286 
287     // Add sdbusplus Object Manager for the 'root' path of the network manager.
288     sdbusplus::server::manager_t objManager(bus, DEFAULT_OBJPATH);
289     bus.request_name(DEFAULT_BUSNAME);
290 
291     manager = std::make_unique<Manager>(bus, DEFAULT_OBJPATH, NETWORK_CONF_DIR);
292 
293     // create the default network files if the network file
294     // is not there for any interface.
295     if (manager->createDefaultNetworkFiles())
296     {
297         manager->reloadConfigs();
298     }
299 
300     // RTNETLINK event handler
301     rtnetlink::Server svr(eventPtr);
302 
303 #ifdef SYNC_MAC_FROM_INVENTORY
304     std::ifstream in(configFile);
305     nlohmann::json configJson;
306     in >> configJson;
307     watchEthernetInterface(bus, configJson);
308 #endif
309 
310     // Trigger the initial object scan
311     // This is intentionally deferred, to ensure that systemd-networkd is
312     // fully configured.
313     refreshObjectTimer->restartOnce(refreshTimeout);
314 
315     return sd_event_loop(eventPtr.get());
316 }
317 
318 } // namespace network
319 } // namespace phosphor
320 
321 int main(int /*argc*/, char** /*argv*/)
322 {
323     try
324     {
325         return phosphor::network::main();
326     }
327     catch (const std::exception& e)
328     {
329         auto msg = fmt::format("FAILED: {}", e.what());
330         log<level::ERR>(msg.c_str(), entry("ERROR", e.what()));
331         return 1;
332     }
333 }
334