The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

/* MIT License
*
* Copyright (c) John Schember
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#if defined(ANDROID) || defined(__ANDROID__)
# include "ares_private.h"
# include <jni.h>
# include <sys/prctl.h>
# include "ares_android.h"
static JavaVM *android_jvm = NULL;
static jobject android_connectivity_manager = NULL;
/* ConnectivityManager.getActiveNetwork */
static jmethodID android_cm_active_net_mid = NULL;
/* ConnectivityManager.getLinkProperties */
static jmethodID android_cm_link_props_mid = NULL;
/* LinkProperties.getDnsServers */
static jmethodID android_lp_dns_servers_mid = NULL;
/* LinkProperties.getDomains */
static jmethodID android_lp_domains_mid = NULL;
/* List.size */
static jmethodID android_list_size_mid = NULL;
/* List.get */
static jmethodID android_list_get_mid = NULL;
/* InetAddress.getHostAddress */
static jmethodID android_ia_host_addr_mid = NULL;
static jclass jni_get_class(JNIEnv *env, const char *path)
{
jclass cls = NULL;
if (env == NULL || path == NULL || *path == '\0') {
return NULL;
}
cls = (*env)->FindClass(env, path);
if ((*env)->ExceptionOccurred(env)) {
(*env)->ExceptionClear(env);
return NULL;
}
return cls;
}
static jmethodID jni_get_method_id(JNIEnv *env, jclass cls,
const char *func_name, const char *signature)
{
jmethodID mid = NULL;
if (env == NULL || cls == NULL || func_name == NULL || *func_name == '\0' ||
signature == NULL || *signature == '\0') {
return NULL;
}
mid = (*env)->GetMethodID(env, cls, func_name, signature);
if ((*env)->ExceptionOccurred(env)) {
(*env)->ExceptionClear(env);
return NULL;
}
return mid;
}
static int jvm_attach(JNIEnv **env)
{
char name[17] = { 0 };
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6;
if (prctl(PR_GET_NAME, name) == 0) {
args.name = name;
} else {
args.name = NULL;
}
args.group = NULL;
return (*android_jvm)->AttachCurrentThread(android_jvm, env, &args);
}
void ares_library_init_jvm(JavaVM *jvm)
{
android_jvm = jvm;
}
int ares_library_init_android(jobject connectivity_manager)
{
JNIEnv *env = NULL;
int need_detatch = 0;
int res;
ares_status_t ret = ARES_ENOTINITIALIZED;
jclass obj_cls = NULL;
if (android_jvm == NULL) {
goto cleanup;
}
res = (*android_jvm)->GetEnv(android_jvm, (void **)&env, JNI_VERSION_1_6);
if (res == JNI_EDETACHED) {
env = NULL;
res = jvm_attach(&env);
need_detatch = 1;
}
if (res != JNI_OK || env == NULL) {
goto cleanup;
}
android_connectivity_manager =
(*env)->NewGlobalRef(env, connectivity_manager);
if (android_connectivity_manager == NULL) {
goto cleanup;
}
/* Initialization has succeeded. Now attempt to cache the methods that will be
* called by ares_get_android_server_list. */
ret = ARES_SUCCESS;
/* ConnectivityManager in API 1. */
obj_cls = jni_get_class(env, "android/net/ConnectivityManager");
if (obj_cls == NULL) {
goto cleanup;
}
/* ConnectivityManager.getActiveNetwork in API 23. */
android_cm_active_net_mid = jni_get_method_id(
env, obj_cls, "getActiveNetwork", "()Landroid/net/Network;");
if (android_cm_active_net_mid == NULL) {
goto cleanup;
}
/* ConnectivityManager.getLinkProperties in API 21. */
android_cm_link_props_mid =
jni_get_method_id(env, obj_cls, "getLinkProperties",
"(Landroid/net/Network;)Landroid/net/LinkProperties;");
if (android_cm_link_props_mid == NULL) {
goto cleanup;
}
/* LinkProperties in API 21. */
(*env)->DeleteLocalRef(env, obj_cls);
obj_cls = jni_get_class(env, "android/net/LinkProperties");
if (obj_cls == NULL) {
goto cleanup;
}
/* getDnsServers in API 21. */
android_lp_dns_servers_mid =
jni_get_method_id(env, obj_cls, "getDnsServers", "()Ljava/util/List;");
if (android_lp_dns_servers_mid == NULL) {
goto cleanup;
}
/* getDomains in API 21. */
android_lp_domains_mid =
jni_get_method_id(env, obj_cls, "getDomains", "()Ljava/lang/String;");
if (android_lp_domains_mid == NULL) {
goto cleanup;
}
(*env)->DeleteLocalRef(env, obj_cls);
obj_cls = jni_get_class(env, "java/util/List");
if (obj_cls == NULL) {
goto cleanup;
}
android_list_size_mid = jni_get_method_id(env, obj_cls, "size", "()I");
if (android_list_size_mid == NULL) {
goto cleanup;
}
android_list_get_mid =
jni_get_method_id(env, obj_cls, "get", "(I)Ljava/lang/Object;");
if (android_list_get_mid == NULL) {
goto cleanup;
}
(*env)->DeleteLocalRef(env, obj_cls);
obj_cls = jni_get_class(env, "java/net/InetAddress");
if (obj_cls == NULL) {
goto cleanup;
}
android_ia_host_addr_mid =
jni_get_method_id(env, obj_cls, "getHostAddress", "()Ljava/lang/String;");
if (android_ia_host_addr_mid == NULL) {
goto cleanup;
}
(*env)->DeleteLocalRef(env, obj_cls);
goto done;
cleanup:
if (obj_cls != NULL) {
(*env)->DeleteLocalRef(env, obj_cls);
}
android_cm_active_net_mid = NULL;
android_cm_link_props_mid = NULL;
android_lp_dns_servers_mid = NULL;
android_lp_domains_mid = NULL;
android_list_size_mid = NULL;
android_list_get_mid = NULL;
android_ia_host_addr_mid = NULL;
done:
if (need_detatch) {
(*android_jvm)->DetachCurrentThread(android_jvm);
}
return (int)ret;
}
int ares_library_android_initialized(void)
{
if (android_jvm == NULL || android_connectivity_manager == NULL) {
return ARES_ENOTINITIALIZED;
}
return ARES_SUCCESS;
}
void ares_library_cleanup_android(void)
{
JNIEnv *env = NULL;
int need_detatch = 0;
int res;
if (android_jvm == NULL || android_connectivity_manager == NULL) {
return;
}
res = (*android_jvm)->GetEnv(android_jvm, (void **)&env, JNI_VERSION_1_6);
if (res == JNI_EDETACHED) {
env = NULL;
res = jvm_attach(&env);
need_detatch = 1;
}
if (res != JNI_OK || env == NULL) {
return;
}
android_cm_active_net_mid = NULL;
android_cm_link_props_mid = NULL;
android_lp_dns_servers_mid = NULL;
android_lp_domains_mid = NULL;
android_list_size_mid = NULL;
android_list_get_mid = NULL;
android_ia_host_addr_mid = NULL;
(*env)->DeleteGlobalRef(env, android_connectivity_manager);
android_connectivity_manager = NULL;
if (need_detatch) {
(*android_jvm)->DetachCurrentThread(android_jvm);
}
}
char **ares_get_android_server_list(size_t max_servers, size_t *num_servers)
{
JNIEnv *env = NULL;
jobject active_network = NULL;
jobject link_properties = NULL;
jobject server_list = NULL;
jobject server = NULL;
jstring str = NULL;
jint nserv;
const char *ch_server_address;
int res;
size_t i;
char **dns_list = NULL;
int need_detatch = 0;
if (android_jvm == NULL || android_connectivity_manager == NULL ||
max_servers == 0 || num_servers == NULL) {
return NULL;
}
if (android_cm_active_net_mid == NULL || android_cm_link_props_mid == NULL ||
android_lp_dns_servers_mid == NULL || android_list_size_mid == NULL ||
android_list_get_mid == NULL || android_ia_host_addr_mid == NULL) {
return NULL;
}
res = (*android_jvm)->GetEnv(android_jvm, (void **)&env, JNI_VERSION_1_6);
if (res == JNI_EDETACHED) {
env = NULL;
res = jvm_attach(&env);
need_detatch = 1;
}
if (res != JNI_OK || env == NULL) {
goto done;
}
/* JNI below is equivalent to this Java code.
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import java.net.InetAddress;
import java.util.List;
ConnectivityManager cm = (ConnectivityManager)this.getApplicationContext()
.getSystemService(Context.CONNECTIVITY_SERVICE);
Network an = cm.getActiveNetwork();
LinkProperties lp = cm.getLinkProperties(an);
List<InetAddress> dns = lp.getDnsServers();
for (InetAddress ia: dns) {
String ha = ia.getHostAddress();
}
Note: The JNI ConnectivityManager object and all method IDs were previously
initialized in ares_library_init_android.
*/
active_network = (*env)->CallObjectMethod(env, android_connectivity_manager,
android_cm_active_net_mid);
if (active_network == NULL) {
goto done;
}
link_properties =
(*env)->CallObjectMethod(env, android_connectivity_manager,
android_cm_link_props_mid, active_network);
if (link_properties == NULL) {
goto done;
}
server_list =
(*env)->CallObjectMethod(env, link_properties, android_lp_dns_servers_mid);
if (server_list == NULL) {
goto done;
}
nserv = (*env)->CallIntMethod(env, server_list, android_list_size_mid);
if (nserv > (jint)max_servers) {
nserv = (jint)max_servers;
}
if (nserv <= 0) {
goto done;
}
*num_servers = (size_t)nserv;
dns_list = ares_malloc(sizeof(*dns_list) * (*num_servers));
for (i = 0; i < *num_servers; i++) {
size_t len = 64;
server =
(*env)->CallObjectMethod(env, server_list, android_list_get_mid, (jint)i);
dns_list[i] = ares_malloc(len);
dns_list[i][0] = 0;
if (server == NULL) {
continue;
}
str = (*env)->CallObjectMethod(env, server, android_ia_host_addr_mid);
ch_server_address = (*env)->GetStringUTFChars(env, str, 0);
ares_strcpy(dns_list[i], ch_server_address, len);
(*env)->ReleaseStringUTFChars(env, str, ch_server_address);
(*env)->DeleteLocalRef(env, str);
(*env)->DeleteLocalRef(env, server);
}
done:
if ((*env)->ExceptionOccurred(env)) {
(*env)->ExceptionClear(env);
}
if (server_list != NULL) {
(*env)->DeleteLocalRef(env, server_list);
}
if (link_properties != NULL) {
(*env)->DeleteLocalRef(env, link_properties);
}
if (active_network != NULL) {
(*env)->DeleteLocalRef(env, active_network);
}
if (need_detatch) {
(*android_jvm)->DetachCurrentThread(android_jvm);
}
return dns_list;
}
char *ares_get_android_search_domains_list(void)
{
JNIEnv *env = NULL;
jobject active_network = NULL;
jobject link_properties = NULL;
jstring domains = NULL;
const char *domain;
int res;
char *domain_list = NULL;
int need_detatch = 0;
if (android_jvm == NULL || android_connectivity_manager == NULL) {
return NULL;
}
if (android_cm_active_net_mid == NULL || android_cm_link_props_mid == NULL ||
android_lp_domains_mid == NULL) {
return NULL;
}
res = (*android_jvm)->GetEnv(android_jvm, (void **)&env, JNI_VERSION_1_6);
if (res == JNI_EDETACHED) {
env = NULL;
res = jvm_attach(&env);
need_detatch = 1;
}
if (res != JNI_OK || env == NULL) {
goto done;
}
/* JNI below is equivalent to this Java code.
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
ConnectivityManager cm = (ConnectivityManager)this.getApplicationContext()
.getSystemService(Context.CONNECTIVITY_SERVICE);
Network an = cm.getActiveNetwork();
LinkProperties lp = cm.getLinkProperties(an);
String domains = lp.getDomains();
for (String domain: domains.split(",")) {
String d = domain;
}
Note: The JNI ConnectivityManager object and all method IDs were previously
initialized in ares_library_init_android.
*/
active_network = (*env)->CallObjectMethod(env, android_connectivity_manager,
android_cm_active_net_mid);
if (active_network == NULL) {
goto done;
}
link_properties =
(*env)->CallObjectMethod(env, android_connectivity_manager,
android_cm_link_props_mid, active_network);
if (link_properties == NULL) {
goto done;
}
/* Get the domains. It is a common separated list of domains to search. */
domains =
(*env)->CallObjectMethod(env, link_properties, android_lp_domains_mid);
if (domains == NULL) {
goto done;
}
/* Split on , */
domain = (*env)->GetStringUTFChars(env, domains, 0);
domain_list = ares_strdup(domain);
(*env)->ReleaseStringUTFChars(env, domains, domain);
(*env)->DeleteLocalRef(env, domains);
done:
if ((*env)->ExceptionOccurred(env)) {
(*env)->ExceptionClear(env);
}
if (link_properties != NULL) {
(*env)->DeleteLocalRef(env, link_properties);
}
if (active_network != NULL) {
(*env)->DeleteLocalRef(env, active_network);
}
if (need_detatch) {
(*android_jvm)->DetachCurrentThread(android_jvm);
}
return domain_list;
}
#else
/* warning: ISO C forbids an empty translation unit */
typedef int dummy_make_iso_compilers_happy;
#endif