xref: /openbmc/phosphor-networkd/src/system_queries.cpp (revision 09f3a4ade17b076ce611320725e0871c3fa40031)
1  #include "system_queries.hpp"
2  
3  #include "netlink.hpp"
4  #include "util.hpp"
5  
6  #include <fmt/format.h>
7  #include <linux/ethtool.h>
8  #include <linux/sockios.h>
9  #include <net/if.h>
10  
11  #include <algorithm>
12  #include <optional>
13  #include <phosphor-logging/log.hpp>
14  #include <stdexcept>
15  #include <stdplus/fd/create.hpp>
16  #include <stdplus/raw.hpp>
17  #include <stdplus/util/cexec.hpp>
18  #include <string_view>
19  #include <system_error>
20  
21  namespace phosphor::network::system
22  {
23  
24  using std::literals::string_view_literals::operator""sv;
25  using phosphor::logging::entry;
26  using phosphor::logging::level;
27  using phosphor::logging::log;
28  
29  static stdplus::Fd& getIFSock()
30  {
31      using namespace stdplus::fd;
32      static auto fd =
33          socket(SocketDomain::INet, SocketType::Datagram, SocketProto::IP);
34      return fd;
35  }
36  
37  static ifreq makeIFReq(std::string_view ifname)
38  {
39      ifreq ifr = {};
40      const auto copied = std::min<std::size_t>(ifname.size(), IFNAMSIZ - 1);
41      std::copy_n(ifname.begin(), copied, ifr.ifr_name);
42      return ifr;
43  }
44  
45  static ifreq executeIFReq(std::string_view ifname, unsigned long cmd,
46                            void* data = nullptr)
47  {
48      ifreq ifr = makeIFReq(ifname);
49      ifr.ifr_data = reinterpret_cast<char*>(data);
50      getIFSock().ioctl(cmd, &ifr);
51      return ifr;
52  }
53  
54  inline auto optionalIFReq(stdplus::zstring_view ifname, unsigned long long cmd,
55                            std::string_view cmdname, auto&& complete,
56                            void* data = nullptr)
57  {
58      ifreq ifr;
59      std::optional<decltype(complete(ifr))> ret;
60      auto ukey = std::make_tuple(std::string(ifname), cmd);
61      static std::unordered_set<std::tuple<std::string, unsigned long long>>
62          unsupported;
63      try
64      {
65          ifr = executeIFReq(ifname, cmd, data);
66      }
67      catch (const std::system_error& e)
68      {
69          if (e.code() == std::errc::operation_not_supported)
70          {
71              if (unsupported.find(ukey) == unsupported.end())
72              {
73                  unsupported.emplace(std::move(ukey));
74                  auto msg =
75                      fmt::format("{} not supported on {}", cmdname, ifname);
76                  log<level::INFO>(msg.c_str(),
77                                   entry("INTERFACE=%s", ifname.c_str()));
78              }
79              return ret;
80          }
81          throw;
82      }
83      unsupported.erase(ukey);
84      ret.emplace(complete(ifr));
85      return ret;
86  }
87  
88  EthInfo getEthInfo(stdplus::zstring_view ifname)
89  {
90      ethtool_cmd edata = {};
91      edata.cmd = ETHTOOL_GSET;
92      return optionalIFReq(
93                 ifname, SIOCETHTOOL, "ETHTOOL"sv,
94                 [&](const ifreq&) {
95                     return EthInfo{.autoneg = edata.autoneg != 0,
96                                    .speed = edata.speed};
97                 },
98                 &edata)
99          .value_or(EthInfo{});
100  }
101  
102  bool intfIsRunning(std::string_view ifname)
103  {
104      return executeIFReq(ifname, SIOCGIFFLAGS).ifr_flags & IFF_RUNNING;
105  }
106  
107  std::optional<unsigned> getMTU(stdplus::zstring_view ifname)
108  {
109      return optionalIFReq(ifname, SIOCGIFMTU, "GMTU",
110                           [](const ifreq& ifr) { return ifr.ifr_mtu; });
111  }
112  
113  void setMTU(std::string_view ifname, unsigned mtu)
114  {
115      auto ifr = makeIFReq(ifname);
116      ifr.ifr_mtu = mtu;
117      getIFSock().ioctl(SIOCSIFMTU, &ifr);
118  }
119  
120  void setNICUp(std::string_view ifname, bool up)
121  {
122      ifreq ifr = executeIFReq(ifname, SIOCGIFFLAGS);
123      ifr.ifr_flags &= ~IFF_UP;
124      ifr.ifr_flags |= up ? IFF_UP : 0;
125      getIFSock().ioctl(SIOCSIFFLAGS, &ifr);
126  }
127  
128  static void parseVlanInfo(InterfaceInfo& info, std::string_view msg)
129  {
130      if (msg.data() == nullptr)
131      {
132          throw std::runtime_error("Missing VLAN data");
133      }
134      while (!msg.empty())
135      {
136          auto [hdr, data] = netlink::extractRtAttr(msg);
137          switch (hdr.rta_type)
138          {
139              case IFLA_VLAN_ID:
140                  info.vlan_id.emplace(stdplus::raw::copyFrom<uint16_t>(data));
141                  break;
142          }
143      }
144  }
145  
146  static void parseLinkInfo(InterfaceInfo& info, std::string_view msg)
147  {
148      std::string_view submsg;
149      while (!msg.empty())
150      {
151          auto [hdr, data] = netlink::extractRtAttr(msg);
152          switch (hdr.rta_type)
153          {
154              case IFLA_INFO_KIND:
155                  data.remove_suffix(1);
156                  info.kind.emplace(data);
157                  break;
158              case IFLA_INFO_DATA:
159                  submsg = data;
160                  break;
161          }
162      }
163      if (info.kind == "vlan"sv)
164      {
165          parseVlanInfo(info, submsg);
166      }
167  }
168  
169  InterfaceInfo detail::parseInterface(const nlmsghdr& hdr, std::string_view msg)
170  {
171      if (hdr.nlmsg_type != RTM_NEWLINK)
172      {
173          throw std::runtime_error("Not an interface msg");
174      }
175      const auto& ifinfo = netlink::extractRtData<ifinfomsg>(msg);
176      InterfaceInfo ret;
177      ret.flags = ifinfo.ifi_flags;
178      ret.idx = ifinfo.ifi_index;
179      while (!msg.empty())
180      {
181          auto [hdr, data] = netlink::extractRtAttr(msg);
182          switch (hdr.rta_type)
183          {
184              case IFLA_IFNAME:
185                  ret.name.emplace(data.begin(), data.end() - 1);
186                  break;
187              case IFLA_ADDRESS:
188                  if (data.size() != sizeof(ether_addr))
189                  {
190                      // Some interfaces have IP addresses for their LLADDR
191                      break;
192                  }
193                  ret.mac.emplace(stdplus::raw::copyFrom<ether_addr>(data));
194                  break;
195              case IFLA_MTU:
196                  ret.mtu.emplace(stdplus::raw::copyFrom<unsigned>(data));
197                  break;
198              case IFLA_LINK:
199                  ret.parent_idx.emplace(stdplus::raw::copyFrom<unsigned>(data));
200                  break;
201              case IFLA_LINKINFO:
202                  parseLinkInfo(ret, data);
203                  break;
204          }
205      }
206      return ret;
207  }
208  
209  bool detail::validateNewInterface(const InterfaceInfo& info)
210  {
211      if (info.flags & IFF_LOOPBACK)
212      {
213          return false;
214      }
215      if (!info.name)
216      {
217          throw std::invalid_argument("Interface Dump missing name");
218      }
219      const auto& ignored = internal::getIgnoredInterfaces();
220      if (ignored.find(*info.name) != ignored.end())
221      {
222          return false;
223      }
224      return true;
225  }
226  
227  std::vector<InterfaceInfo> getInterfaces()
228  {
229      std::vector<InterfaceInfo> ret;
230      auto cb = [&](const nlmsghdr& hdr, std::string_view msg) {
231          auto info = detail::parseInterface(hdr, msg);
232          if (detail::validateNewInterface(info))
233          {
234              ret.emplace_back(std::move(info));
235          }
236      };
237      ifinfomsg msg{};
238      netlink::performRequest(NETLINK_ROUTE, RTM_GETLINK, NLM_F_DUMP, msg, cb);
239      return ret;
240  }
241  
242  } // namespace phosphor::network::system
243