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