| #include "config.h" |
| |
| #include "neighbor.hpp" |
| |
| #include "ethernet_interface.hpp" |
| #include "util.hpp" |
| |
| #include <linux/neighbour.h> |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| #include <net/if.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| |
| #include <cstring> |
| #include <stdexcept> |
| #include <string_view> |
| #include <system_error> |
| |
| namespace phosphor |
| { |
| namespace network |
| { |
| |
| NeighborInfo parseNeighbor(std::string_view msg) |
| { |
| struct ndmsg ndm; |
| if (msg.size() < sizeof(ndm)) |
| { |
| throw std::runtime_error("Bad neighbor msg"); |
| } |
| memcpy(&ndm, msg.data(), sizeof(ndm)); |
| auto attrs = msg.substr(sizeof(ndm)); |
| |
| NeighborInfo info; |
| info.interface.resize(IF_NAMESIZE); |
| if (if_indextoname(ndm.ndm_ifindex, info.interface.data()) == nullptr) |
| { |
| throw std::system_error(errno, std::generic_category(), |
| "if_indextoname"); |
| } |
| info.interface.resize(strlen(info.interface.c_str())); |
| info.permanent = ndm.ndm_state & NUD_PERMANENT; |
| bool set_addr = false; |
| while (!attrs.empty()) |
| { |
| struct rtattr hdr; |
| if (attrs.size() < sizeof(hdr)) |
| { |
| throw std::runtime_error("Bad rtattr header"); |
| } |
| memcpy(&hdr, attrs.data(), sizeof(hdr)); |
| if (hdr.rta_len < sizeof(hdr)) |
| { |
| throw std::runtime_error("Invalid rtattr length"); |
| } |
| if (attrs.size() < hdr.rta_len) |
| { |
| throw std::runtime_error("Not enough data for rtattr"); |
| } |
| auto data = attrs.substr(RTA_LENGTH(0), hdr.rta_len - RTA_LENGTH(0)); |
| if (hdr.rta_type == NDA_LLADDR) |
| { |
| info.mac = mac_address::fromBuf(data); |
| } |
| else if (hdr.rta_type == NDA_DST) |
| { |
| info.address = addrFromBuf(ndm.ndm_family, data); |
| set_addr = true; |
| } |
| attrs.remove_prefix(RTA_ALIGN(hdr.rta_len)); |
| } |
| if (!set_addr) |
| { |
| throw std::runtime_error("Missing address"); |
| } |
| return info; |
| } |
| |
| bool parseNeighborMsgs(std::string_view msgs, std::vector<NeighborInfo>& info) |
| { |
| while (!msgs.empty()) |
| { |
| struct nlmsghdr hdr; |
| if (msgs.size() < sizeof(hdr)) |
| { |
| throw std::runtime_error("Bad neighbor netlink header"); |
| } |
| memcpy(&hdr, msgs.data(), sizeof(hdr)); |
| if (hdr.nlmsg_type == NLMSG_DONE) |
| { |
| if (msgs.size() > hdr.nlmsg_len) |
| { |
| throw std::runtime_error("Unexpected extra netlink messages"); |
| } |
| return true; |
| } |
| else if (hdr.nlmsg_type != RTM_NEWNEIGH) |
| { |
| throw std::runtime_error("Bad neighbor msg type"); |
| } |
| if (hdr.nlmsg_len < sizeof(hdr)) |
| { |
| throw std::runtime_error("Invalid nlmsg length"); |
| } |
| if (msgs.size() < hdr.nlmsg_len) |
| { |
| throw std::runtime_error("Bad neighbor payload"); |
| } |
| auto msg = msgs.substr(NLMSG_HDRLEN, hdr.nlmsg_len - NLMSG_HDRLEN); |
| msgs.remove_prefix(NLMSG_ALIGN(hdr.nlmsg_len)); |
| info.push_back(parseNeighbor(msg)); |
| } |
| |
| return false; |
| } |
| |
| std::vector<NeighborInfo> receiveNeighbors(int sock) |
| { |
| // We need to make sure we have enough room for an entire packet otherwise |
| // it gets truncated. The netlink docs guarantee packets will not exceed 8K |
| char buf[8192]; |
| |
| struct iovec iov; |
| memset(&iov, 0, sizeof(iov)); |
| iov.iov_base = buf; |
| iov.iov_len = sizeof(buf); |
| |
| struct sockaddr_nl from; |
| memset(&from, 0, sizeof(from)); |
| from.nl_family = AF_NETLINK; |
| |
| struct msghdr hdr; |
| memset(&hdr, 0, sizeof(hdr)); |
| hdr.msg_name = &from; |
| hdr.msg_namelen = sizeof(from); |
| hdr.msg_iov = &iov; |
| hdr.msg_iovlen = 1; |
| |
| std::vector<NeighborInfo> info; |
| while (true) |
| { |
| ssize_t recvd = recvmsg(sock, &hdr, 0); |
| if (recvd <= 0) |
| { |
| throw std::system_error(errno, std::generic_category(), |
| "recvmsg neighbor"); |
| } |
| if (parseNeighborMsgs(std::string_view(buf, recvd), info)) |
| { |
| return info; |
| } |
| } |
| } |
| |
| void requestNeighbors(int sock) |
| { |
| struct sockaddr_nl dst; |
| memset(&dst, 0, sizeof(dst)); |
| dst.nl_family = AF_NETLINK; |
| |
| struct |
| { |
| struct nlmsghdr hdr; |
| struct ndmsg msg; |
| } data; |
| memset(&data, 0, sizeof(data)); |
| data.hdr.nlmsg_len = sizeof(data); |
| data.hdr.nlmsg_type = RTM_GETNEIGH; |
| data.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; |
| data.msg.ndm_family = AF_UNSPEC; |
| |
| struct iovec iov; |
| memset(&iov, 0, sizeof(iov)); |
| iov.iov_base = &data; |
| iov.iov_len = sizeof(data); |
| |
| struct msghdr hdr; |
| memset(&hdr, 0, sizeof(hdr)); |
| hdr.msg_name = reinterpret_cast<struct sockaddr*>(&dst); |
| hdr.msg_namelen = sizeof(dst); |
| hdr.msg_iov = &iov; |
| hdr.msg_iovlen = 1; |
| |
| if (sendmsg(sock, &hdr, 0) < 0) |
| { |
| throw std::system_error(errno, std::generic_category(), |
| "sendmsg neighbor dump"); |
| } |
| } |
| |
| int getNetlink(int protocol) |
| { |
| int sock = socket(AF_NETLINK, SOCK_DGRAM, protocol); |
| if (sock < 0) |
| { |
| throw std::system_error(errno, std::generic_category(), "netlink open"); |
| } |
| |
| struct sockaddr_nl local; |
| memset(&local, 0, sizeof(local)); |
| local.nl_family = AF_NETLINK; |
| int r = |
| bind(sock, reinterpret_cast<struct sockaddr*>(&local), sizeof(local)); |
| if (r < 0) |
| { |
| close(sock); |
| throw std::system_error(errno, std::generic_category(), "netlink bind"); |
| } |
| return sock; |
| } |
| |
| std::vector<NeighborInfo> getCurrentNeighbors() |
| { |
| Descriptor netlink(getNetlink(NETLINK_ROUTE)); |
| requestNeighbors(netlink()); |
| return receiveNeighbors(netlink()); |
| } |
| |
| Neighbor::Neighbor(sdbusplus::bus::bus& bus, const char* objPath, |
| EthernetInterface& parent, const std::string& ipAddress, |
| const std::string& macAddress, State state) : |
| NeighborObj(bus, objPath, true), |
| parent(parent) |
| { |
| this->iPAddress(ipAddress); |
| this->mACAddress(macAddress); |
| this->state(state); |
| |
| // Emit deferred signal. |
| emit_object_added(); |
| } |
| |
| void Neighbor::delete_() |
| { |
| parent.deleteStaticNeighborObject(iPAddress()); |
| } |
| |
| } // namespace network |
| } // namespace phosphor |