| /* | |
| * Copyright (C) 2018 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. | |
| */ | |
| #define LOG_TAG "resolv" | |
| #include "Dns64Configuration.h" | |
| #include <android-base/logging.h> | |
| #include <netdb.h> | |
| #include <netdutils/BackoffSequence.h> | |
| #include <netdutils/DumpWriter.h> | |
| #include <netdutils/InternetAddresses.h> | |
| #include <netdutils/ThreadUtil.h> | |
| #include <thread> | |
| #include <utility> | |
| #include <arpa/inet.h> | |
| #include "DnsResolver.h" | |
| #include "getaddrinfo.h" | |
| #include "netd_resolv/resolv.h" | |
| #include "stats.pb.h" | |
| namespace android { | |
| using netdutils::DumpWriter; | |
| using netdutils::IPAddress; | |
| using netdutils::IPPrefix; | |
| using netdutils::ScopedAddrinfo; | |
| using netdutils::setThreadName; | |
| namespace net { | |
| const char Dns64Configuration::kIPv4OnlyHost[] = "ipv4only.arpa."; | |
| const char Dns64Configuration::kIPv4Literal1[] = "192.0.0.170"; | |
| const char Dns64Configuration::kIPv4Literal2[] = "192.0.0.171"; | |
| void Dns64Configuration::startPrefixDiscovery(unsigned netId) { | |
| std::lock_guard guard(mMutex); | |
| // TODO: Keep previous prefix for a while | |
| // Currently, we remove current prefix, if any, before starting a prefix discovery. | |
| // This causes that Netd and framework temporarily forgets DNS64 prefix even the prefix may be | |
| // discovered in a short time. | |
| removeDns64Config(netId); | |
| Dns64Config cfg(getNextId(), netId); | |
| // Emplace a copy of |cfg| in the map. | |
| mDns64Configs.emplace(std::make_pair(netId, cfg)); | |
| const std::shared_ptr<Dns64Configuration> thiz = shared_from_this(); | |
| // Note that capturing |cfg| in this lambda creates a copy. | |
| std::thread discovery_thread([thiz, cfg, netId] { | |
| setThreadName(fmt::format("Nat64Pfx_{}", netId)); | |
| // Make a mutable copy rather than mark the whole lambda mutable. | |
| // No particular reason. | |
| Dns64Config evalCfg(cfg); | |
| auto backoff = netdutils::BackoffSequence<>::Builder() | |
| .withInitialRetransmissionTime(std::chrono::seconds(1)) | |
| .withMaximumRetransmissionTime(std::chrono::seconds(3600)) | |
| .build(); | |
| while (true) { | |
| if (!thiz->shouldContinueDiscovery(evalCfg)) break; | |
| android_net_context netcontext{}; | |
| thiz->mGetNetworkContextCallback(evalCfg.netId, 0, &netcontext); | |
| // Prefix discovery must bypass private DNS because in strict mode | |
| // the server generally won't know the NAT64 prefix. | |
| netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS; | |
| if (doRfc7050PrefixDiscovery(netcontext, &evalCfg)) { | |
| thiz->recordDns64Config(evalCfg); | |
| break; | |
| } | |
| if (!thiz->shouldContinueDiscovery(evalCfg)) break; | |
| if (!backoff.hasNextTimeout()) break; | |
| { | |
| std::unique_lock<std::mutex> cvGuard(thiz->mMutex); | |
| // TODO: Consider some chrono math, combined with wait_until() | |
| // perhaps, to prevent early re-resolves from the removal of | |
| // other netids with IPv6-only nameservers. | |
| thiz->mCv.wait_for(cvGuard, backoff.getNextTimeout()); | |
| } | |
| } | |
| }); | |
| discovery_thread.detach(); | |
| } | |
| void Dns64Configuration::stopPrefixDiscovery(unsigned netId) { | |
| std::lock_guard guard(mMutex); | |
| removeDns64Config(netId); | |
| mCv.notify_all(); | |
| } | |
| IPPrefix Dns64Configuration::getPrefix64Locked(unsigned netId) const REQUIRES(mMutex) { | |
| const auto& iter = mDns64Configs.find(netId); | |
| if (iter != mDns64Configs.end()) return iter->second.prefix64; | |
| return IPPrefix{}; | |
| } | |
| IPPrefix Dns64Configuration::getPrefix64(unsigned netId) const { | |
| std::lock_guard guard(mMutex); | |
| return getPrefix64Locked(netId); | |
| } | |
| void Dns64Configuration::dump(DumpWriter& dw, unsigned netId) { | |
| static const char kLabel[] = "DNS64 config"; | |
| std::lock_guard guard(mMutex); | |
| const auto& iter = mDns64Configs.find(netId); | |
| if (iter == mDns64Configs.end()) { | |
| dw.println("%s: none", kLabel); | |
| return; | |
| } | |
| const Dns64Config& cfg = iter->second; | |
| if (cfg.prefix64.length() == 0) { | |
| dw.println("%s: no prefix yet discovered", kLabel); | |
| } else { | |
| dw.println("%s: %s prefix %s", kLabel, cfg.isFromPrefixDiscovery() ? "discovered" : "set", | |
| cfg.prefix64.toString().c_str()); | |
| } | |
| } | |
| // NOTE: The full RFC 7050 DNS64 discovery process is not implemented here. | |
| // Instead, and more simplistic version of the same thing is done, and it | |
| // currently assumes the DNS64 prefix is a /96. | |
| bool Dns64Configuration::doRfc7050PrefixDiscovery(const android_net_context& netcontext, | |
| Dns64Config* cfg) { | |
| LOG(WARNING) << "(" << cfg->netId << ", " << cfg->discoveryId | |
| << ") Detecting NAT64 prefix from DNS..."; | |
| const struct addrinfo hints = { | |
| .ai_family = AF_INET6, | |
| }; | |
| // TODO: Refactor so that netd can get all the regular getaddrinfo handling | |
| // that regular apps get. We bypass the UNIX socket connection back to | |
| // ourselves, which means we also bypass all the special netcontext flag | |
| // handling and the resolver event logging. | |
| struct addrinfo* res = nullptr; | |
| NetworkDnsEventReported event; | |
| const int status = resolv_getaddrinfo(kIPv4OnlyHost, nullptr, &hints, &netcontext, | |
| APP_SOCKET_NONE, &res, &event); | |
| ScopedAddrinfo result(res); | |
| if (status != 0) { | |
| LOG(WARNING) << "(" << cfg->netId << ", " << cfg->discoveryId << ") plat_prefix/dns(" | |
| << kIPv4OnlyHost << ") status = " << status << "/" << gai_strerror(status); | |
| return false; | |
| } | |
| // Use only the first result. If other records are present, possibly | |
| // with differing DNS64 prefixes they are ignored. Note that this is a | |
| // violation of https://tools.ietf.org/html/rfc7050#section-3 | |
| // | |
| // "A node MUST look through all of the received AAAA resource records | |
| // to collect one or more Pref64::/n." | |
| // | |
| // TODO: Consider remedying this. | |
| if (result->ai_family != AF_INET6) { | |
| LOG(WARNING) << "(" << cfg->netId << ", " << cfg->discoveryId | |
| << ") plat_prefix/unexpected address family: " << result->ai_family; | |
| return false; | |
| } | |
| const IPAddress ipv6(reinterpret_cast<sockaddr_in6*>(result->ai_addr)->sin6_addr); | |
| // Only /96 DNS64 prefixes are supported at this time. | |
| cfg->prefix64 = IPPrefix(ipv6, 96); | |
| LOG(WARNING) << "(" << cfg->netId << ", " << cfg->discoveryId << ") Detected NAT64 prefix " | |
| << cfg->prefix64.toString(); | |
| return true; | |
| } | |
| bool Dns64Configuration::isDiscoveryInProgress(const Dns64Config& cfg) const REQUIRES(mMutex) { | |
| const auto& iter = mDns64Configs.find(cfg.netId); | |
| if (iter == mDns64Configs.end()) return false; | |
| const Dns64Config& currentCfg = iter->second; | |
| return (currentCfg.discoveryId == cfg.discoveryId); | |
| } | |
| bool Dns64Configuration::reportNat64PrefixStatus(unsigned netId, bool added, const IPPrefix& pfx) { | |
| if (pfx.ip().family() != AF_INET6 || pfx.ip().scope_id() != 0) { | |
| LOG(WARNING) << "Abort to send NAT64 prefix notification. Unexpected NAT64 prefix (" | |
| << netId << ", " << added << ", " << pfx.toString() << ")."; | |
| return false; | |
| } | |
| Nat64PrefixInfo args = {netId, added, pfx.ip().toString(), (uint8_t)pfx.length()}; | |
| mPrefixCallback(args); | |
| return true; | |
| } | |
| bool Dns64Configuration::shouldContinueDiscovery(const Dns64Config& cfg) { | |
| std::lock_guard guard(mMutex); | |
| return isDiscoveryInProgress(cfg); | |
| } | |
| void Dns64Configuration::removeDns64Config(unsigned netId) REQUIRES(mMutex) { | |
| const auto& iter = mDns64Configs.find(netId); | |
| if (iter == mDns64Configs.end()) return; | |
| Dns64Config cfg = iter->second; | |
| mDns64Configs.erase(iter); | |
| // Only report a prefix removed event if the prefix was discovered, not if it was set. | |
| if (cfg.isFromPrefixDiscovery() && !cfg.prefix64.isUninitialized()) { | |
| reportNat64PrefixStatus(netId, PREFIX_REMOVED, cfg.prefix64); | |
| } | |
| } | |
| void Dns64Configuration::recordDns64Config(const Dns64Config& cfg) { | |
| std::lock_guard guard(mMutex); | |
| if (!isDiscoveryInProgress(cfg)) return; | |
| removeDns64Config(cfg.netId); | |
| mDns64Configs.emplace(std::make_pair(cfg.netId, cfg)); | |
| reportNat64PrefixStatus(cfg.netId, PREFIX_ADDED, cfg.prefix64); | |
| } | |
| int Dns64Configuration::setPrefix64(unsigned netId, const IPPrefix& pfx) { | |
| if (pfx.isUninitialized() || pfx.family() != AF_INET6 || pfx.length() != 96) { | |
| return -EINVAL; | |
| } | |
| std::lock_guard guard(mMutex); | |
| // This method may only be called if prefix discovery has been stopped or was never started. | |
| auto iter = mDns64Configs.find(netId); | |
| if (iter != mDns64Configs.end()) { | |
| if (iter->second.isFromPrefixDiscovery()) { | |
| return -EEXIST; | |
| } else { | |
| mDns64Configs.erase(iter); | |
| } | |
| } | |
| Dns64Config cfg(kNoDiscoveryId, netId); | |
| cfg.prefix64 = pfx; | |
| mDns64Configs.emplace(std::make_pair(netId, cfg)); | |
| return 0; | |
| } | |
| int Dns64Configuration::clearPrefix64(unsigned netId) { | |
| std::lock_guard guard(mMutex); | |
| const auto& iter = mDns64Configs.find(netId); | |
| if (iter == mDns64Configs.end() || iter->second.isFromPrefixDiscovery()) { | |
| return -ENOENT; | |
| } | |
| mDns64Configs.erase(iter); | |
| return 0; | |
| } | |
| } // namespace net | |
| } // namespace android |