| /* |
| * Copyright 2019, The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "ipv6_monitor.h" |
| |
| #include <errno.h> |
| #include <linux/filter.h> |
| #include <net/if.h> |
| #include <netinet/ether.h> |
| #include <netinet/icmp6.h> |
| #include <netinet/ip6.h> |
| #include <poll.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <array> |
| #include <mutex> |
| #include <string> |
| #include <thread> |
| #include <unordered_set> |
| #include <vector> |
| |
| #define LOG_TAG "RIL-IPV6MON" |
| #include <utils/Log.h> |
| |
| static constexpr size_t kReadBufferSize = 32768; |
| |
| static constexpr size_t kRecursiveDnsOptHeaderSize = 8; |
| |
| static constexpr size_t kControlClient = 0; |
| static constexpr size_t kControlServer = 1; |
| |
| static constexpr char kMonitorAckCommand = '\1'; |
| static constexpr char kMonitorStopCommand = '\2'; |
| |
| // The amount of time to wait before trying to initialize interface again if |
| // it's not ready when rild starts. |
| static constexpr int kDeferredTimeoutMilliseconds = 1000; |
| |
| bool operator==(const in6_addr& left, const in6_addr& right) { |
| return ::memcmp(left.s6_addr, right.s6_addr, sizeof(left.s6_addr)) == 0; |
| } |
| |
| bool operator!=(const in6_addr& left, const in6_addr& right) { |
| return ::memcmp(left.s6_addr, right.s6_addr, sizeof(left.s6_addr)) != 0; |
| } |
| |
| template<class T> |
| static inline void hash_combine(size_t& seed, const T& value) { |
| std::hash<T> hasher; |
| seed ^= hasher(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2); |
| } |
| |
| namespace std { |
| template<> struct hash<in6_addr> { |
| size_t operator()(const in6_addr& ad) const { |
| size_t seed = 0; |
| hash_combine(seed, *reinterpret_cast<const uint32_t*>(&ad.s6_addr[0])); |
| hash_combine(seed, *reinterpret_cast<const uint32_t*>(&ad.s6_addr[4])); |
| hash_combine(seed, *reinterpret_cast<const uint32_t*>(&ad.s6_addr[8])); |
| hash_combine(seed, *reinterpret_cast<const uint32_t*>(&ad.s6_addr[12])); |
| return seed; |
| } |
| }; |
| } // namespace std |
| |
| static constexpr uint32_t kIpTypeOffset = offsetof(ip6_hdr, ip6_nxt); |
| static constexpr uint32_t kIcmpTypeOffset = sizeof(ip6_hdr) + |
| offsetof(icmp6_hdr, icmp6_type); |
| |
| // This is BPF program that will filter out anything that is not an NDP router |
| // advertisement. It's a very basic assembler syntax. The jumps indicate how |
| // many instructions to jump in addition to the automatic increment of the |
| // program counter. So a jump statement with a zero means to go to the next |
| // instruction, a value of 3 means that the next instruction will be the 4th |
| // after the current one. |
| static const struct sock_filter kNdpFilter[] = { |
| // Load byte at absolute address kIpTypeOffset |
| BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIpTypeOffset), |
| // Jump, if byte is IPPROTO_ICMPV6 jump 0 instructions, if not jump 3. |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3), |
| // Load byte at absolute address kIcmpTypeOffset |
| BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIcmpTypeOffset), |
| // Jump, if byte is ND_ROUTER_ADVERT jump 0 instructions, if not jump 1 |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 0, 1), |
| // Return the number of bytes to accept, accept all of them |
| BPF_STMT(BPF_RET | BPF_K, std::numeric_limits<uint32_t>::max()), |
| // Accept zero bytes, this is where the failed jumps go |
| BPF_STMT(BPF_RET | BPF_K, 0) |
| }; |
| static constexpr size_t kNdpFilterSize = |
| sizeof(kNdpFilter) / sizeof(kNdpFilter[0]); |
| |
| class Ipv6Monitor { |
| public: |
| Ipv6Monitor(const char* interfaceName); |
| ~Ipv6Monitor(); |
| |
| enum class InitResult { |
| Error, |
| Deferred, |
| Success, |
| }; |
| InitResult init(); |
| void setCallback(ipv6MonitorCallback callback); |
| void runAsync(); |
| void stop(); |
| |
| private: |
| InitResult initInterfaces(); |
| void run(); |
| void onReadAvailable(); |
| |
| ipv6MonitorCallback mMonitorCallback; |
| |
| in6_addr mGateway; |
| std::unordered_set<in6_addr> mDnsServers; |
| |
| std::unique_ptr<std::thread> mThread; |
| std::mutex mThreadMutex; |
| |
| std::string mInterfaceName; |
| int mSocketFd; |
| int mControlSocket[2]; |
| int mPollTimeout = -1; |
| bool mFullyInitialized = false; |
| }; |
| |
| Ipv6Monitor::Ipv6Monitor(const char* interfaceName) : |
| mMonitorCallback(nullptr), |
| mInterfaceName(interfaceName), |
| mSocketFd(-1) { |
| memset(&mGateway, 0, sizeof(mGateway)); |
| mControlSocket[0] = -1; |
| mControlSocket[1] = -1; |
| } |
| |
| Ipv6Monitor::~Ipv6Monitor() { |
| for (int& fd : mControlSocket) { |
| if (fd != -1) { |
| ::close(fd); |
| fd = -1; |
| } |
| } |
| if (mSocketFd != -1) { |
| ::close(mSocketFd); |
| mSocketFd = -1; |
| } |
| } |
| |
| Ipv6Monitor::InitResult Ipv6Monitor::init() { |
| if (mSocketFd != -1) { |
| RLOGE("Ipv6Monitor already initialized"); |
| return InitResult::Error; |
| } |
| |
| if (::socketpair(AF_UNIX, SOCK_DGRAM, 0, mControlSocket) != 0) { |
| RLOGE("Ipv6Monitor failed to create control socket pair: %s", |
| strerror(errno)); |
| return InitResult::Error; |
| } |
| |
| mSocketFd = ::socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, ETH_P_IPV6); |
| if (mSocketFd == -1) { |
| RLOGE("Ipv6Monitor failed to open socket: %s", strerror(errno)); |
| return InitResult::Error; |
| } |
| // If interface initialization fails we'll retry later |
| return initInterfaces(); |
| } |
| |
| void Ipv6Monitor::setCallback(ipv6MonitorCallback callback) { |
| mMonitorCallback = callback; |
| } |
| |
| Ipv6Monitor::InitResult Ipv6Monitor::initInterfaces() { |
| if (mFullyInitialized) { |
| RLOGE("Ipv6Monitor already initialized"); |
| return InitResult::Error; |
| } |
| struct ifreq request; |
| memset(&request, 0, sizeof(request)); |
| strlcpy(request.ifr_name, mInterfaceName.c_str(), sizeof(request.ifr_name)); |
| |
| // Set the ALLMULTI flag so we can capture multicast traffic |
| int status = ::ioctl(mSocketFd, SIOCGIFFLAGS, &request); |
| if (status != 0) { |
| if (errno == ENODEV) { |
| // It is not guaranteed that the network is entirely set up by the |
| // time rild has started. If that's the case the radio interface |
| // might not be up yet, try again later. |
| RLOGE("Ipv6Monitor could not initialize %s yet, retrying later", |
| mInterfaceName.c_str()); |
| mPollTimeout = kDeferredTimeoutMilliseconds; |
| return InitResult::Deferred; |
| } |
| RLOGE("Ipv6Monitor failed to get interface flags for %s: %s", |
| mInterfaceName.c_str(), strerror(errno)); |
| return InitResult::Error; |
| } |
| |
| if ((request.ifr_flags & IFF_ALLMULTI) == 0) { |
| // The flag is not set, we have to make another call |
| request.ifr_flags |= IFF_ALLMULTI; |
| |
| status = ::ioctl(mSocketFd, SIOCSIFFLAGS, &request); |
| if (status != 0) { |
| RLOGE("Ipv6Monitor failed to set interface flags for %s: %s", |
| mInterfaceName.c_str(), strerror(errno)); |
| return InitResult::Error; |
| } |
| } |
| |
| // Add a BPF filter to the socket so that we only receive the specific |
| // type of packet we're interested in. Otherwise we will receive ALL |
| // traffic on this interface. |
| struct sock_fprog filter; |
| filter.len = kNdpFilterSize; |
| // The API doesn't have const but it's not going to modify it so this is OK |
| filter.filter = const_cast<struct sock_filter*>(kNdpFilter); |
| status = ::setsockopt(mSocketFd, |
| SOL_SOCKET, |
| SO_ATTACH_FILTER, |
| &filter, |
| sizeof(filter)); |
| if (status != 0) { |
| RLOGE("Ipv6Monitor failed to set socket filter: %s", strerror(errno)); |
| return InitResult::Error; |
| } |
| |
| // Get the hardware address of the interface into a sockaddr struct for bind |
| struct sockaddr_ll ethAddr; |
| memset(ðAddr, 0, sizeof(ethAddr)); |
| ethAddr.sll_family = AF_PACKET; |
| ethAddr.sll_protocol = htons(ETH_P_IPV6); |
| ethAddr.sll_ifindex = if_nametoindex(mInterfaceName.c_str()); |
| if (ethAddr.sll_ifindex == 0) { |
| RLOGE("Ipv6Monitor failed to find index for %s: %s", |
| mInterfaceName.c_str(), strerror(errno)); |
| return InitResult::Error; |
| } |
| |
| status = ::ioctl(mSocketFd, SIOCGIFHWADDR, &request); |
| if (status != 0) { |
| RLOGE("Ipv6Monitor failed to get hardware address for %s: %s", |
| mInterfaceName.c_str(), strerror(errno)); |
| return InitResult::Error; |
| } |
| memcpy(ethAddr.sll_addr, request.ifr_addr.sa_data, ETH_ALEN); |
| |
| // Now bind to the hardware address |
| status = ::bind(mSocketFd, |
| reinterpret_cast<const struct sockaddr*>(ðAddr), |
| sizeof(ethAddr)); |
| if (status != 0) { |
| RLOGE("Ipv6Monitor failed to bind to %s hardware address: %s", |
| mInterfaceName.c_str(), strerror(errno)); |
| return InitResult::Error; |
| } |
| mFullyInitialized = true; |
| return InitResult::Success; |
| } |
| |
| void Ipv6Monitor::runAsync() { |
| std::unique_lock<std::mutex> lock(mThreadMutex); |
| mThread = std::make_unique<std::thread>([this]() { run(); }); |
| } |
| |
| void Ipv6Monitor::stop() { |
| std::unique_lock<std::mutex> lock(mThreadMutex); |
| if (!mThread) { |
| return; |
| } |
| ::write(mControlSocket[kControlClient], &kMonitorStopCommand, 1); |
| char ack = -1; |
| while (ack != kMonitorAckCommand) { |
| ::read(mControlSocket[kControlClient], &ack, sizeof(ack)); |
| } |
| mThread->join(); |
| mThread.reset(); |
| } |
| |
| void Ipv6Monitor::run() { |
| std::array<struct pollfd, 2> fds; |
| fds[0].events = POLLIN; |
| fds[0].fd = mControlSocket[kControlServer]; |
| fds[1].events = POLLIN; |
| fds[1].fd = mSocketFd; |
| |
| bool running = true; |
| while (running) { |
| int status = ::poll(fds.data(), fds.size(), mPollTimeout); |
| if (status < 0) { |
| if (errno == EINTR) { |
| // Interrupted, keep going |
| continue; |
| } |
| // An error occurred |
| RLOGE("Ipv6Monitor fatal failure polling failed; %s", |
| strerror(errno)); |
| break; |
| } else if (status == 0) { |
| // Timeout, nothing to read |
| if (!mFullyInitialized) { |
| InitResult result = initInterfaces(); |
| switch (result) { |
| case InitResult::Error: |
| // Something went wrong this time and we can't recover |
| running = false; |
| break; |
| case InitResult::Deferred: |
| // We need to keep waiting and then try again |
| mPollTimeout = kDeferredTimeoutMilliseconds; |
| break; |
| case InitResult::Success: |
| // Interfaces are initialized, no need to timeout again |
| mPollTimeout = -1; |
| break; |
| } |
| } |
| continue; |
| } |
| |
| if (fds[0].revents & POLLIN) { |
| // Control message received |
| char command = -1; |
| if (::read(mControlSocket[kControlServer], |
| &command, |
| sizeof(command)) == 1) { |
| if (command == kMonitorStopCommand) { |
| break; |
| } |
| } |
| } else if (fds[1].revents & POLLIN) { |
| onReadAvailable(); |
| } |
| } |
| ::write(mControlSocket[kControlServer], &kMonitorAckCommand, 1); |
| } |
| |
| void Ipv6Monitor::onReadAvailable() { |
| char buffer[kReadBufferSize]; |
| |
| ssize_t bytesRead = 0; |
| while (true) { |
| bytesRead = ::recv(mSocketFd, buffer, sizeof(buffer), 0); |
| if (bytesRead < 0) { |
| if (errno == EINTR) { |
| // Interrupted, try again right away |
| continue; |
| } |
| if (errno != EAGAIN && errno != EWOULDBLOCK) { |
| // Do not report an error for the above error codes, they are |
| // part of the normal turn of events. We just need to try again |
| // later when we run into those errors. |
| RLOGE("Ipv6Monitor failed to receive data: %s", |
| strerror(errno)); |
| } |
| return; |
| } |
| break; |
| } |
| |
| if (mMonitorCallback == nullptr) { |
| // No point in doing anything, we have read the data so the socket |
| // buffer doesn't fill up and that's all we can do. |
| return; |
| } |
| |
| if (static_cast<size_t>(bytesRead) < sizeof(ip6_hdr) + sizeof(icmp6_hdr)) { |
| // This message cannot be an ICMPv6 packet, ignore it |
| return; |
| } |
| |
| auto ipv6 = reinterpret_cast<const ip6_hdr*>(buffer); |
| uint8_t version = (ipv6->ip6_vfc & 0xF0) >> 4; |
| if (version != 6 || ipv6->ip6_nxt != IPPROTO_ICMPV6) { |
| // This message is not an IPv6 packet or not an ICMPv6 packet, ignore it |
| return; |
| } |
| |
| // The ICMP header starts right after the IPv6 header |
| auto icmp = reinterpret_cast<const icmp6_hdr*>(buffer + sizeof(ip6_hdr)); |
| if (icmp->icmp6_code != 0) { |
| // All packets we care about have an icmp code of zero. |
| return; |
| } |
| |
| if (icmp->icmp6_type != ND_ROUTER_ADVERT) { |
| // We only care about router advertisements |
| return; |
| } |
| |
| // At this point we know it's a valid packet, let's look inside |
| |
| // The gateway is the same as the source in the IP header |
| in6_addr gateway = ipv6->ip6_src; |
| |
| // Search through the options for DNS servers |
| const char* options = buffer + sizeof(ip6_hdr) + sizeof(nd_router_advert); |
| const nd_opt_hdr* option = reinterpret_cast<const nd_opt_hdr*>(options); |
| |
| std::vector<in6_addr> dnsServers; |
| const nd_opt_hdr* nextOpt = nullptr; |
| for (const nd_opt_hdr* opt = option; opt; opt = nextOpt) { |
| auto nextOptLoc = |
| reinterpret_cast<const char*>(opt) + opt->nd_opt_len * 8u; |
| if (nextOptLoc > buffer + bytesRead) { |
| // Not enough room for this option, abort |
| break; |
| } |
| if (nextOptLoc < buffer + bytesRead) { |
| nextOpt = reinterpret_cast<const nd_opt_hdr*>(nextOptLoc); |
| } else { |
| nextOpt = nullptr; |
| } |
| if (opt->nd_opt_type != 25 || opt->nd_opt_len < 1) { |
| // Not an RNDSS option, skip it |
| continue; |
| } |
| |
| size_t numEntries = (opt->nd_opt_len - 1) / 2; |
| const char* addrLoc = reinterpret_cast<const char*>(opt); |
| addrLoc += kRecursiveDnsOptHeaderSize; |
| auto addrs = reinterpret_cast<const in6_addr*>(addrLoc); |
| |
| for (size_t i = 0; i < numEntries; ++i) { |
| dnsServers.push_back(addrs[i]); |
| } |
| } |
| |
| bool changed = false; |
| if (gateway != mGateway) { |
| changed = true; |
| mGateway = gateway; |
| } |
| |
| for (const auto& dns : dnsServers) { |
| if (mDnsServers.find(dns) == mDnsServers.end()) { |
| mDnsServers.insert(dns); |
| changed = true; |
| } |
| } |
| |
| if (changed) { |
| mMonitorCallback(&gateway, dnsServers.data(), dnsServers.size()); |
| } |
| } |
| |
| extern "C" |
| struct ipv6Monitor* ipv6MonitorCreate(const char* interfaceName) { |
| auto monitor = std::make_unique<Ipv6Monitor>(interfaceName); |
| if (!monitor || monitor->init() == Ipv6Monitor::InitResult::Error) { |
| return nullptr; |
| } |
| return reinterpret_cast<struct ipv6Monitor*>(monitor.release()); |
| } |
| |
| extern "C" |
| void ipv6MonitorFree(struct ipv6Monitor* ipv6Monitor) { |
| auto monitor = reinterpret_cast<Ipv6Monitor*>(ipv6Monitor); |
| delete monitor; |
| } |
| |
| extern "C" |
| void ipv6MonitorSetCallback(struct ipv6Monitor* ipv6Monitor, |
| ipv6MonitorCallback callback) { |
| auto monitor = reinterpret_cast<Ipv6Monitor*>(ipv6Monitor); |
| monitor->setCallback(callback); |
| } |
| |
| extern "C" |
| void ipv6MonitorRunAsync(struct ipv6Monitor* ipv6Monitor) { |
| auto monitor = reinterpret_cast<Ipv6Monitor*>(ipv6Monitor); |
| monitor->runAsync(); |
| } |
| |
| extern "C" |
| void ipv6MonitorStop(struct ipv6Monitor* ipv6Monitor) { |
| auto monitor = reinterpret_cast<Ipv6Monitor*>(ipv6Monitor); |
| monitor->stop(); |
| } |
| |