xref: /openbmc/phosphor-networkd/src/system_queries.cpp (revision b800612229a53bae2ef9ae1cf51c0e86b6236bd6)
1 #include "system_queries.hpp"
2 
3 #include "netlink.hpp"
4 #include "rtnetlink.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::validateNewAddr(const AddressInfo& info,
210                              const AddressFilter& filter) noexcept
211 {
212     if (filter.ifidx != 0 && filter.ifidx != info.ifidx)
213     {
214         return false;
215     }
216     return true;
217 }
218 
219 bool detail::validateNewNeigh(const NeighborInfo& info,
220                               const NeighborFilter& filter) noexcept
221 {
222     if (filter.ifidx != 0 && filter.ifidx != info.ifidx)
223     {
224         return false;
225     }
226     return true;
227 }
228 
229 std::vector<InterfaceInfo> getInterfaces()
230 {
231     std::vector<InterfaceInfo> ret;
232     auto cb = [&](const nlmsghdr& hdr, std::string_view msg) {
233         try
234         {
235             ret.emplace_back(detail::parseInterface(hdr, msg));
236         }
237         catch (const std::exception& e)
238         {
239             auto msg = fmt::format("Failed parsing interface: {}", e.what());
240             log<level::ERR>(msg.c_str());
241         }
242     };
243     ifinfomsg msg{};
244     netlink::performRequest(NETLINK_ROUTE, RTM_GETLINK, NLM_F_DUMP, msg, cb);
245     return ret;
246 }
247 
248 std::vector<AddressInfo> getAddresses(const AddressFilter& filter)
249 {
250     std::vector<AddressInfo> ret;
251     auto cb = [&](const nlmsghdr&, std::string_view msg) {
252         try
253         {
254             auto info = netlink::addrFromRtm(msg);
255             if (detail::validateNewAddr(info, filter))
256             {
257                 ret.emplace_back(std::move(info));
258             }
259         }
260         catch (const std::exception& e)
261         {
262             auto msg = fmt::format("Failed parsing address for ifidx {}: {}",
263                                    filter.ifidx, e.what());
264             log<level::ERR>(msg.c_str());
265         }
266     };
267     ifaddrmsg msg{};
268     msg.ifa_index = filter.ifidx;
269     netlink::performRequest(NETLINK_ROUTE, RTM_GETADDR, NLM_F_DUMP, msg, cb);
270     return ret;
271 }
272 
273 std::vector<NeighborInfo> getNeighbors(const NeighborFilter& filter)
274 {
275     std::vector<NeighborInfo> ret;
276     auto cb = [&](const nlmsghdr&, std::string_view msg) {
277         try
278         {
279             auto info = netlink::neighFromRtm(msg);
280             if (detail::validateNewNeigh(info, filter))
281             {
282                 ret.push_back(std::move(info));
283             }
284         }
285         catch (const std::exception& e)
286         {
287             auto msg = fmt::format("Failed parsing neighbor for ifidx {}: {}",
288                                    filter.ifidx, e.what());
289             log<level::ERR>(msg.c_str());
290         }
291     };
292     ndmsg msg{};
293     msg.ndm_ifindex = filter.ifidx;
294     netlink::performRequest(NETLINK_ROUTE, RTM_GETNEIGH, NLM_F_DUMP, msg, cb);
295     return ret;
296 }
297 
298 } // namespace phosphor::network::system
299