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