blob: 088ca47f41a260b0282b0e38fc585d0efd29e109 [file] [log] [blame]
/*
* Copyright 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "sdk/android/src/jni/android_network_monitor.h"
#include <dlfcn.h>
#ifndef RTLD_NOLOAD
// This was added in Lollipop to dlfcn.h
#define RTLD_NOLOAD 4
#endif
#include "api/sequence_checker.h"
#include "rtc_base/checks.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/task_utils/to_queued_task.h"
#include "sdk/android/generated_base_jni/NetworkChangeDetector_jni.h"
#include "sdk/android/generated_base_jni/NetworkMonitor_jni.h"
#include "sdk/android/native_api/jni/java_types.h"
#include "sdk/android/src/jni/jni_helpers.h"
#include "system_wrappers/include/field_trial.h"
namespace webrtc {
namespace jni {
namespace {
const char* NetworkTypeToString(NetworkType type) {
switch (type) {
case NETWORK_UNKNOWN:
return "UNKNOWN";
case NETWORK_ETHERNET:
return "ETHERNET";
case NETWORK_WIFI:
return "WIFI";
case NETWORK_5G:
return "5G";
case NETWORK_4G:
return "4G";
case NETWORK_3G:
return "3G";
case NETWORK_2G:
return "2G";
case NETWORK_UNKNOWN_CELLULAR:
return "UNKNOWN_CELLULAR";
case NETWORK_BLUETOOTH:
return "BLUETOOTH";
case NETWORK_VPN:
return "VPN";
case NETWORK_NONE:
return "NONE";
}
}
} // namespace
enum AndroidSdkVersion {
SDK_VERSION_LOLLIPOP = 21,
SDK_VERSION_MARSHMALLOW = 23
};
static NetworkType GetNetworkTypeFromJava(
JNIEnv* jni,
const JavaRef<jobject>& j_network_type) {
std::string enum_name = GetJavaEnumName(jni, j_network_type);
if (enum_name == "CONNECTION_UNKNOWN") {
return NetworkType::NETWORK_UNKNOWN;
}
if (enum_name == "CONNECTION_ETHERNET") {
return NetworkType::NETWORK_ETHERNET;
}
if (enum_name == "CONNECTION_WIFI") {
return NetworkType::NETWORK_WIFI;
}
if (enum_name == "CONNECTION_5G") {
return NetworkType::NETWORK_5G;
}
if (enum_name == "CONNECTION_4G") {
return NetworkType::NETWORK_4G;
}
if (enum_name == "CONNECTION_3G") {
return NetworkType::NETWORK_3G;
}
if (enum_name == "CONNECTION_2G") {
return NetworkType::NETWORK_2G;
}
if (enum_name == "CONNECTION_UNKNOWN_CELLULAR") {
return NetworkType::NETWORK_UNKNOWN_CELLULAR;
}
if (enum_name == "CONNECTION_BLUETOOTH") {
return NetworkType::NETWORK_BLUETOOTH;
}
if (enum_name == "CONNECTION_VPN") {
return NetworkType::NETWORK_VPN;
}
if (enum_name == "CONNECTION_NONE") {
return NetworkType::NETWORK_NONE;
}
RTC_NOTREACHED();
return NetworkType::NETWORK_UNKNOWN;
}
static rtc::AdapterType AdapterTypeFromNetworkType(
NetworkType network_type,
bool surface_cellular_types) {
switch (network_type) {
case NETWORK_UNKNOWN:
return rtc::ADAPTER_TYPE_UNKNOWN;
case NETWORK_ETHERNET:
return rtc::ADAPTER_TYPE_ETHERNET;
case NETWORK_WIFI:
return rtc::ADAPTER_TYPE_WIFI;
case NETWORK_5G:
return surface_cellular_types ? rtc::ADAPTER_TYPE_CELLULAR_5G
: rtc::ADAPTER_TYPE_CELLULAR;
case NETWORK_4G:
return surface_cellular_types ? rtc::ADAPTER_TYPE_CELLULAR_4G
: rtc::ADAPTER_TYPE_CELLULAR;
case NETWORK_3G:
return surface_cellular_types ? rtc::ADAPTER_TYPE_CELLULAR_3G
: rtc::ADAPTER_TYPE_CELLULAR;
case NETWORK_2G:
return surface_cellular_types ? rtc::ADAPTER_TYPE_CELLULAR_2G
: rtc::ADAPTER_TYPE_CELLULAR;
case NETWORK_UNKNOWN_CELLULAR:
return rtc::ADAPTER_TYPE_CELLULAR;
case NETWORK_VPN:
case NETWORK_BLUETOOTH:
// There is no corresponding mapping for bluetooth networks.
// Map it to VPN for now.
return rtc::ADAPTER_TYPE_VPN;
default:
RTC_NOTREACHED() << "Invalid network type " << network_type;
return rtc::ADAPTER_TYPE_UNKNOWN;
}
}
static rtc::IPAddress JavaToNativeIpAddress(
JNIEnv* jni,
const JavaRef<jobject>& j_ip_address) {
std::vector<int8_t> address =
JavaToNativeByteArray(jni, Java_IPAddress_getAddress(jni, j_ip_address));
size_t address_length = address.size();
if (address_length == 4) {
// IP4
struct in_addr ip4_addr;
memcpy(&ip4_addr.s_addr, address.data(), 4);
return rtc::IPAddress(ip4_addr);
}
// IP6
RTC_CHECK(address_length == 16);
struct in6_addr ip6_addr;
memcpy(ip6_addr.s6_addr, address.data(), address_length);
return rtc::IPAddress(ip6_addr);
}
static NetworkInformation GetNetworkInformationFromJava(
JNIEnv* jni,
const JavaRef<jobject>& j_network_info) {
NetworkInformation network_info;
network_info.interface_name = JavaToStdString(
jni, Java_NetworkInformation_getName(jni, j_network_info));
network_info.handle = static_cast<NetworkHandle>(
Java_NetworkInformation_getHandle(jni, j_network_info));
network_info.type = GetNetworkTypeFromJava(
jni, Java_NetworkInformation_getConnectionType(jni, j_network_info));
network_info.underlying_type_for_vpn = GetNetworkTypeFromJava(
jni, Java_NetworkInformation_getUnderlyingConnectionTypeForVpn(
jni, j_network_info));
ScopedJavaLocalRef<jobjectArray> j_ip_addresses =
Java_NetworkInformation_getIpAddresses(jni, j_network_info);
network_info.ip_addresses = JavaToNativeVector<rtc::IPAddress>(
jni, j_ip_addresses, &JavaToNativeIpAddress);
return network_info;
}
static bool AddressMatch(const rtc::IPAddress& ip1, const rtc::IPAddress& ip2) {
if (ip1.family() != ip2.family()) {
return false;
}
if (ip1.family() == AF_INET) {
return ip1.ipv4_address().s_addr == ip2.ipv4_address().s_addr;
}
if (ip1.family() == AF_INET6) {
// The last 64-bits of an ipv6 address are temporary address and it could
// change over time. So we only compare the first 64-bits.
return memcmp(ip1.ipv6_address().s6_addr, ip2.ipv6_address().s6_addr,
sizeof(in6_addr) / 2) == 0;
}
return false;
}
NetworkInformation::NetworkInformation() = default;
NetworkInformation::NetworkInformation(const NetworkInformation&) = default;
NetworkInformation::NetworkInformation(NetworkInformation&&) = default;
NetworkInformation::~NetworkInformation() = default;
NetworkInformation& NetworkInformation::operator=(const NetworkInformation&) =
default;
NetworkInformation& NetworkInformation::operator=(NetworkInformation&&) =
default;
std::string NetworkInformation::ToString() const {
rtc::StringBuilder ss;
ss << "NetInfo[name " << interface_name << "; handle " << handle << "; type "
<< type;
if (type == NETWORK_VPN) {
ss << "; underlying_type_for_vpn " << underlying_type_for_vpn;
}
ss << "]";
return ss.Release();
}
AndroidNetworkMonitor::AndroidNetworkMonitor(
JNIEnv* env,
const JavaRef<jobject>& j_application_context)
: android_sdk_int_(Java_NetworkMonitor_androidSdkInt(env)),
j_application_context_(env, j_application_context),
j_network_monitor_(env, Java_NetworkMonitor_getInstance(env)),
network_thread_(rtc::Thread::Current()) {}
AndroidNetworkMonitor::~AndroidNetworkMonitor() {
RTC_DCHECK(!started_);
}
void AndroidNetworkMonitor::Start() {
RTC_DCHECK_RUN_ON(network_thread_);
if (started_) {
return;
}
started_ = true;
surface_cellular_types_ =
webrtc::field_trial::IsEnabled("WebRTC-SurfaceCellularTypes");
find_network_handle_without_ipv6_temporary_part_ =
webrtc::field_trial::IsEnabled(
"WebRTC-FindNetworkHandleWithoutIpv6TemporaryPart");
bind_using_ifname_ =
!webrtc::field_trial::IsDisabled("WebRTC-BindUsingInterfaceName");
// This pointer is also accessed by the methods called from java threads.
// Assigning it here is safe, because the java monitor is in a stopped state,
// and will not make any callbacks.
safety_flag_ = PendingTaskSafetyFlag::Create();
JNIEnv* env = AttachCurrentThreadIfNeeded();
Java_NetworkMonitor_startMonitoring(
env, j_network_monitor_, j_application_context_, jlongFromPointer(this));
}
void AndroidNetworkMonitor::Stop() {
RTC_DCHECK_RUN_ON(network_thread_);
if (!started_) {
return;
}
started_ = false;
find_network_handle_without_ipv6_temporary_part_ = false;
// Cancel any pending tasks. We should not call SignalNetworksChanged when the
// monitor is stopped.
safety_flag_->SetNotAlive();
JNIEnv* env = AttachCurrentThreadIfNeeded();
Java_NetworkMonitor_stopMonitoring(env, j_network_monitor_,
jlongFromPointer(this));
network_handle_by_address_.clear();
network_info_by_handle_.clear();
}
// The implementation is largely taken from UDPSocketPosix::BindToNetwork in
// https://cs.chromium.org/chromium/src/net/udp/udp_socket_posix.cc
rtc::NetworkBindingResult AndroidNetworkMonitor::BindSocketToNetwork(
int socket_fd,
const rtc::IPAddress& address,
const std::string& if_name) {
RTC_DCHECK_RUN_ON(network_thread_);
// Android prior to Lollipop didn't have support for binding sockets to
// networks. This may also occur if there is no connectivity manager
// service.
JNIEnv* env = AttachCurrentThreadIfNeeded();
const bool network_binding_supported =
Java_NetworkMonitor_networkBindingSupported(env, j_network_monitor_);
if (!network_binding_supported) {
RTC_LOG(LS_WARNING)
<< "BindSocketToNetwork is not supported on this platform "
"(Android SDK: "
<< android_sdk_int_ << ")";
return rtc::NetworkBindingResult::NOT_IMPLEMENTED;
}
absl::optional<NetworkHandle> network_handle =
FindNetworkHandleFromAddressOrName(address, if_name);
if (!network_handle) {
RTC_LOG(LS_WARNING)
<< "BindSocketToNetwork unable to find network handle for"
<< " addr: " << address.ToSensitiveString() << " ifname: " << if_name;
return rtc::NetworkBindingResult::ADDRESS_NOT_FOUND;
}
if (*network_handle == 0 /* NETWORK_UNSPECIFIED */) {
RTC_LOG(LS_WARNING) << "BindSocketToNetwork 0 network handle for"
<< " addr: " << address.ToSensitiveString()
<< " ifname: " << if_name;
return rtc::NetworkBindingResult::NOT_IMPLEMENTED;
}
int rv = 0;
if (android_sdk_int_ >= SDK_VERSION_MARSHMALLOW) {
// See declaration of android_setsocknetwork() here:
// http://androidxref.com/6.0.0_r1/xref/development/ndk/platforms/android-M/include/android/multinetwork.h#65
// Function cannot be called directly as it will cause app to fail to load
// on pre-marshmallow devices.
typedef int (*MarshmallowSetNetworkForSocket)(NetworkHandle net,
int socket);
static MarshmallowSetNetworkForSocket marshmallowSetNetworkForSocket;
// This is not thread-safe, but we are running this only on the worker
// thread.
if (!marshmallowSetNetworkForSocket) {
const std::string android_native_lib_path = "libandroid.so";
void* lib = dlopen(android_native_lib_path.c_str(), RTLD_NOW);
if (lib == nullptr) {
RTC_LOG(LS_ERROR) << "Library " << android_native_lib_path
<< " not found!";
return rtc::NetworkBindingResult::NOT_IMPLEMENTED;
}
marshmallowSetNetworkForSocket =
reinterpret_cast<MarshmallowSetNetworkForSocket>(
dlsym(lib, "android_setsocknetwork"));
}
if (!marshmallowSetNetworkForSocket) {
RTC_LOG(LS_ERROR) << "Symbol marshmallowSetNetworkForSocket is not found";
return rtc::NetworkBindingResult::NOT_IMPLEMENTED;
}
rv = marshmallowSetNetworkForSocket(*network_handle, socket_fd);
} else {
// NOTE: This relies on Android implementation details, but it won't
// change because Lollipop is already released.
typedef int (*LollipopSetNetworkForSocket)(unsigned net, int socket);
static LollipopSetNetworkForSocket lollipopSetNetworkForSocket;
// This is not threadsafe, but we are running this only on the worker
// thread.
if (!lollipopSetNetworkForSocket) {
// Android's netd client library should always be loaded in our address
// space as it shims libc functions like connect().
const std::string net_library_path = "libnetd_client.so";
// Use RTLD_NOW to match Android's prior loading of the library:
// http://androidxref.com/6.0.0_r5/xref/bionic/libc/bionic/NetdClient.cpp#37
// Use RTLD_NOLOAD to assert that the library is already loaded and
// avoid doing any disk IO.
void* lib = dlopen(net_library_path.c_str(), RTLD_NOW | RTLD_NOLOAD);
if (lib == nullptr) {
RTC_LOG(LS_ERROR) << "Library " << net_library_path << " not found!";
return rtc::NetworkBindingResult::NOT_IMPLEMENTED;
}
lollipopSetNetworkForSocket =
reinterpret_cast<LollipopSetNetworkForSocket>(
dlsym(lib, "setNetworkForSocket"));
}
if (!lollipopSetNetworkForSocket) {
RTC_LOG(LS_ERROR) << "Symbol lollipopSetNetworkForSocket is not found ";
return rtc::NetworkBindingResult::NOT_IMPLEMENTED;
}
rv = lollipopSetNetworkForSocket(*network_handle, socket_fd);
}
// If `network` has since disconnected, `rv` will be ENONET. Surface this as
// ERR_NETWORK_CHANGED, rather than MapSystemError(ENONET) which gives back
// the less descriptive ERR_FAILED.
if (rv == 0) {
RTC_LOG(LS_VERBOSE) << "BindSocketToNetwork bound network handle for"
<< " addr: " << address.ToSensitiveString()
<< " ifname: " << if_name;
return rtc::NetworkBindingResult::SUCCESS;
}
RTC_LOG(LS_WARNING) << "BindSocketToNetwork got error: " << rv
<< " addr: " << address.ToSensitiveString()
<< " ifname: " << if_name;
if (rv == ENONET) {
return rtc::NetworkBindingResult::NETWORK_CHANGED;
}
return rtc::NetworkBindingResult::FAILURE;
}
void AndroidNetworkMonitor::OnNetworkConnected_n(
const NetworkInformation& network_info) {
RTC_DCHECK_RUN_ON(network_thread_);
RTC_LOG(LS_INFO) << "Network connected: " << network_info.ToString();
adapter_type_by_name_[network_info.interface_name] =
AdapterTypeFromNetworkType(network_info.type, surface_cellular_types_);
if (network_info.type == NETWORK_VPN) {
vpn_underlying_adapter_type_by_name_[network_info.interface_name] =
AdapterTypeFromNetworkType(network_info.underlying_type_for_vpn,
surface_cellular_types_);
}
network_info_by_handle_[network_info.handle] = network_info;
for (const rtc::IPAddress& address : network_info.ip_addresses) {
network_handle_by_address_[address] = network_info.handle;
}
SignalNetworksChanged();
}
absl::optional<NetworkHandle>
AndroidNetworkMonitor::FindNetworkHandleFromAddressOrName(
const rtc::IPAddress& ip_address,
const std::string& if_name) const {
RTC_DCHECK_RUN_ON(network_thread_);
RTC_LOG(LS_INFO) << "Find network handle.";
if (find_network_handle_without_ipv6_temporary_part_) {
for (auto const& iter : network_info_by_handle_) {
const std::vector<rtc::IPAddress>& addresses = iter.second.ip_addresses;
auto address_it = std::find_if(addresses.begin(), addresses.end(),
[ip_address](rtc::IPAddress address) {
return AddressMatch(ip_address, address);
});
if (address_it != addresses.end()) {
return absl::make_optional(iter.first);
}
}
} else {
auto iter = network_handle_by_address_.find(ip_address);
if (iter != network_handle_by_address_.end()) {
return absl::make_optional(iter->second);
}
}
return FindNetworkHandleFromIfname(if_name);
}
absl::optional<NetworkHandle>
AndroidNetworkMonitor::FindNetworkHandleFromIfname(
const std::string& if_name) const {
RTC_DCHECK_RUN_ON(network_thread_);
if (bind_using_ifname_) {
for (auto const& iter : network_info_by_handle_) {
if (if_name.find(iter.second.interface_name) != std::string::npos) {
// Use partial match so that e.g if_name="v4-wlan0" is matched
// agains iter.first="wlan0"
return absl::make_optional(iter.first);
}
}
}
return absl::nullopt;
}
void AndroidNetworkMonitor::OnNetworkDisconnected_n(NetworkHandle handle) {
RTC_DCHECK_RUN_ON(network_thread_);
RTC_LOG(LS_INFO) << "Network disconnected for handle " << handle;
auto iter = network_info_by_handle_.find(handle);
if (iter != network_info_by_handle_.end()) {
for (const rtc::IPAddress& address : iter->second.ip_addresses) {
network_handle_by_address_.erase(address);
}
network_info_by_handle_.erase(iter);
}
}
void AndroidNetworkMonitor::OnNetworkPreference_n(
NetworkType type,
rtc::NetworkPreference preference) {
RTC_DCHECK_RUN_ON(network_thread_);
RTC_LOG(LS_INFO) << "Android network monitor preference for "
<< NetworkTypeToString(type) << " changed to "
<< rtc::NetworkPreferenceToString(preference);
auto adapter_type = AdapterTypeFromNetworkType(type, surface_cellular_types_);
network_preference_by_adapter_type_[adapter_type] = preference;
SignalNetworksChanged();
}
void AndroidNetworkMonitor::SetNetworkInfos(
const std::vector<NetworkInformation>& network_infos) {
RTC_DCHECK_RUN_ON(network_thread_);
network_handle_by_address_.clear();
network_info_by_handle_.clear();
RTC_LOG(LS_INFO) << "Android network monitor found " << network_infos.size()
<< " networks";
for (const NetworkInformation& network : network_infos) {
OnNetworkConnected_n(network);
}
}
rtc::AdapterType AndroidNetworkMonitor::GetAdapterType(
const std::string& if_name) {
RTC_DCHECK_RUN_ON(network_thread_);
auto iter = adapter_type_by_name_.find(if_name);
rtc::AdapterType type = (iter == adapter_type_by_name_.end())
? rtc::ADAPTER_TYPE_UNKNOWN
: iter->second;
if (type == rtc::ADAPTER_TYPE_UNKNOWN && bind_using_ifname_) {
for (auto const& iter : adapter_type_by_name_) {
// Use partial match so that e.g if_name="v4-wlan0" is matched
// agains iter.first="wlan0"
if (if_name.find(iter.first) != std::string::npos) {
type = iter.second;
break;
}
}
}
if (type == rtc::ADAPTER_TYPE_UNKNOWN) {
RTC_LOG(LS_WARNING) << "Get an unknown type for the interface " << if_name;
}
return type;
}
rtc::AdapterType AndroidNetworkMonitor::GetVpnUnderlyingAdapterType(
const std::string& if_name) {
RTC_DCHECK_RUN_ON(network_thread_);
auto iter = vpn_underlying_adapter_type_by_name_.find(if_name);
rtc::AdapterType type = (iter == vpn_underlying_adapter_type_by_name_.end())
? rtc::ADAPTER_TYPE_UNKNOWN
: iter->second;
if (type == rtc::ADAPTER_TYPE_UNKNOWN && bind_using_ifname_) {
// Use partial match so that e.g if_name="v4-wlan0" is matched
// agains iter.first="wlan0"
for (auto const& iter : vpn_underlying_adapter_type_by_name_) {
if (if_name.find(iter.first) != std::string::npos) {
type = iter.second;
break;
}
}
}
return type;
}
rtc::NetworkPreference AndroidNetworkMonitor::GetNetworkPreference(
const std::string& if_name) {
RTC_DCHECK_RUN_ON(network_thread_);
auto iter = adapter_type_by_name_.find(if_name);
if (iter == adapter_type_by_name_.end()) {
return rtc::NetworkPreference::NEUTRAL;
}
rtc::AdapterType adapter_type = iter->second;
if (adapter_type == rtc::ADAPTER_TYPE_VPN) {
auto iter2 = vpn_underlying_adapter_type_by_name_.find(if_name);
if (iter2 != vpn_underlying_adapter_type_by_name_.end()) {
adapter_type = iter2->second;
}
}
auto preference_iter = network_preference_by_adapter_type_.find(adapter_type);
if (preference_iter == network_preference_by_adapter_type_.end()) {
return rtc::NetworkPreference::NEUTRAL;
}
return preference_iter->second;
}
AndroidNetworkMonitorFactory::AndroidNetworkMonitorFactory()
: j_application_context_(nullptr) {}
AndroidNetworkMonitorFactory::AndroidNetworkMonitorFactory(
JNIEnv* env,
const JavaRef<jobject>& j_application_context)
: j_application_context_(env, j_application_context) {}
AndroidNetworkMonitorFactory::~AndroidNetworkMonitorFactory() = default;
rtc::NetworkMonitorInterface*
AndroidNetworkMonitorFactory::CreateNetworkMonitor() {
return new AndroidNetworkMonitor(AttachCurrentThreadIfNeeded(),
j_application_context_);
}
void AndroidNetworkMonitor::NotifyConnectionTypeChanged(
JNIEnv* env,
const JavaRef<jobject>& j_caller) {
network_thread_->PostTask(ToQueuedTask(safety_flag_, [this] {
RTC_LOG(LS_INFO)
<< "Android network monitor detected connection type change.";
SignalNetworksChanged();
}));
}
void AndroidNetworkMonitor::NotifyOfActiveNetworkList(
JNIEnv* env,
const JavaRef<jobject>& j_caller,
const JavaRef<jobjectArray>& j_network_infos) {
std::vector<NetworkInformation> network_infos =
JavaToNativeVector<NetworkInformation>(env, j_network_infos,
&GetNetworkInformationFromJava);
SetNetworkInfos(network_infos);
}
void AndroidNetworkMonitor::NotifyOfNetworkConnect(
JNIEnv* env,
const JavaRef<jobject>& j_caller,
const JavaRef<jobject>& j_network_info) {
NetworkInformation network_info =
GetNetworkInformationFromJava(env, j_network_info);
network_thread_->PostTask(ToQueuedTask(
safety_flag_, [this, network_info = std::move(network_info)] {
OnNetworkConnected_n(network_info);
}));
}
void AndroidNetworkMonitor::NotifyOfNetworkDisconnect(
JNIEnv* env,
const JavaRef<jobject>& j_caller,
jlong network_handle) {
network_thread_->PostTask(ToQueuedTask(safety_flag_, [this, network_handle] {
OnNetworkDisconnected_n(static_cast<NetworkHandle>(network_handle));
}));
}
void AndroidNetworkMonitor::NotifyOfNetworkPreference(
JNIEnv* env,
const JavaRef<jobject>& j_caller,
const JavaRef<jobject>& j_connection_type,
jint jpreference) {
NetworkType type = GetNetworkTypeFromJava(env, j_connection_type);
rtc::NetworkPreference preference =
static_cast<rtc::NetworkPreference>(jpreference);
network_thread_->PostTask(ToQueuedTask(
safety_flag_,
[this, type, preference] { OnNetworkPreference_n(type, preference); }));
}
} // namespace jni
} // namespace webrtc