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 auto info = detail::parseInterface(hdr, msg); 253 if (detail::validateNewInterface(info)) 254 { 255 ret.emplace_back(std::move(info)); 256 } 257 }; 258 ifinfomsg msg{}; 259 netlink::performRequest(NETLINK_ROUTE, RTM_GETLINK, NLM_F_DUMP, msg, cb); 260 return ret; 261 } 262 263 std::vector<AddressInfo> getAddresses(const AddressFilter& filter) 264 { 265 std::vector<AddressInfo> ret; 266 auto cb = [&](const nlmsghdr&, std::string_view msg) { 267 auto info = netlink::addrFromRtm(msg); 268 if (detail::validateNewAddr(info, filter)) 269 { 270 ret.emplace_back(std::move(info)); 271 } 272 }; 273 ifaddrmsg msg{}; 274 msg.ifa_index = filter.ifidx; 275 netlink::performRequest(NETLINK_ROUTE, RTM_GETADDR, NLM_F_DUMP, msg, cb); 276 return ret; 277 } 278 279 std::vector<NeighborInfo> getNeighbors(const NeighborFilter& filter) 280 { 281 std::vector<NeighborInfo> ret; 282 auto cb = [&](const nlmsghdr&, std::string_view msg) { 283 auto info = netlink::neighFromRtm(msg); 284 if (detail::validateNewNeigh(info, filter)) 285 { 286 ret.push_back(std::move(info)); 287 } 288 }; 289 ndmsg msg{}; 290 msg.ndm_ifindex = filter.ifidx; 291 netlink::performRequest(NETLINK_ROUTE, RTM_GETNEIGH, NLM_F_DUMP, msg, cb); 292 return ret; 293 } 294 295 } // namespace phosphor::network::system 296