diff --git a/framework/Android.bp b/framework/Android.bp index 5b33328833..657d5a3d2e 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -100,9 +100,50 @@ java_sdk_library { libs: [ "unsupportedappusage", ], - permitted_packages: [ - "android.net", - "com.android.connectivity.aidl", + permitted_packages: ["android.net"], +} + +cc_defaults { + name: "libframework-connectivity-defaults", + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wthread-safety", + ], + shared_libs: [ + "libbase", + "liblog", + "libnativehelper", + "libnetd_client", + ], + header_libs: [ + "dnsproxyd_protocol_headers", + ], +} + +cc_library_static { + name: "libconnectivityframeworkutils", + defaults: ["libframework-connectivity-defaults"], + srcs: [ + "jni/android_net_NetworkUtils.cpp", + ], + apex_available: [ + "//apex_available:platform", + "com.android.tethering", + ], +} + +cc_library_shared { + name: "libframework-connectivity-jni", + defaults: ["libframework-connectivity-defaults"], + srcs: [ + "jni/onload.cpp", + ], + static_libs: ["libconnectivityframeworkutils"], + apex_available: [ + "//apex_available:platform", + "com.android.tethering", ], } @@ -124,7 +165,6 @@ java_library { "framework-tethering", "framework-wifi", "unsupportedappusage", - "ServiceConnectivityResources", ], static_libs: [ "framework-connectivity-protos", @@ -133,8 +173,5 @@ java_library { jarjar_rules: "jarjar-rules.txt", apex_available: ["com.android.tethering"], installable: true, - permitted_packages: [ - "android.net", - "com.android.connectivity.aidl", - ], + permitted_packages: ["android.net"], } diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp new file mode 100644 index 0000000000..c70309f6ce --- /dev/null +++ b/framework/jni/android_net_NetworkUtils.cpp @@ -0,0 +1,302 @@ +/* + * Copyright 2020, 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 "NetworkUtils" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // NETID_USE_LOCAL_NAMESERVERS +#include +#include +#include +#include +#include + +#include "NetdClient.h" +#include "jni.h" + +extern "C" { +int ifc_enable(const char *ifname); +int ifc_disable(const char *ifname); +} + +#define NETUTILS_PKG_NAME "android/net/NetworkUtils" + +namespace android { + +constexpr int MAXPACKETSIZE = 8 * 1024; +// FrameworkListener limits the size of commands to 4096 bytes. +constexpr int MAXCMDSIZE = 4096; + +static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) { + jclass clazz = env->FindClass(class_name); + LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name); + return clazz; +} + +static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name, + const char* method_signature) { + jmethodID res = env->GetMethodID(clazz, method_name, method_signature); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name, + method_signature); + return res; +} + +template +static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) { + jobject res = env->NewGlobalRef(in); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference."); + return static_cast(res); +} + +static void throwErrnoException(JNIEnv* env, const char* functionName, int error) { + ScopedLocalRef detailMessage(env, env->NewStringUTF(functionName)); + if (detailMessage.get() == NULL) { + // Not really much we can do here. We're probably dead in the water, + // but let's try to stumble on... + env->ExceptionClear(); + } + static jclass errnoExceptionClass = + MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException")); + + static jmethodID errnoExceptionCtor = + GetMethodIDOrDie(env, errnoExceptionClass, + "", "(Ljava/lang/String;I)V"); + + jobject exception = env->NewObject(errnoExceptionClass, + errnoExceptionCtor, + detailMessage.get(), + error); + env->Throw(reinterpret_cast(exception)); +} + +static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd) +{ + struct sock_filter filter_code[] = { + // Reject all. + BPF_STMT(BPF_RET | BPF_K, 0) + }; + struct sock_fprog filter = { + sizeof(filter_code) / sizeof(filter_code[0]), + filter_code, + }; + + int fd = AFileDescriptor_getFD(env, javaFd); + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) { + jniThrowExceptionFmt(env, "java/net/SocketException", + "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno)); + } +} + +static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd) +{ + int optval_ignored = 0; + int fd = AFileDescriptor_getFD(env, javaFd); + if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &optval_ignored, sizeof(optval_ignored)) != + 0) { + jniThrowExceptionFmt(env, "java/net/SocketException", + "setsockopt(SO_DETACH_FILTER): %s", strerror(errno)); + } +} + +static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId) +{ + return (jboolean) !setNetworkForProcess(netId); +} + +static jint android_net_utils_getBoundNetworkForProcess(JNIEnv *env, jobject thiz) +{ + return getNetworkForProcess(); +} + +static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz, + jint netId) +{ + return (jboolean) !setNetworkForResolv(netId); +} + +static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jobject javaFd, + jint netId) { + return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd)); +} + +static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst) +{ + if (env->GetArrayLength(addr) != len) { + return false; + } + env->GetByteArrayRegion(addr, 0, len, reinterpret_cast(dst)); + return true; +} + +static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId, + jstring dname, jint ns_class, jint ns_type, jint flags) { + const jsize javaCharsCount = env->GetStringLength(dname); + const jsize byteCountUTF8 = env->GetStringUTFLength(dname); + + // Only allow dname which could be simply formatted to UTF8. + // In native layer, res_mkquery would re-format the input char array to packet. + std::vector queryname(byteCountUTF8 + 1, 0); + + env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data()); + int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkQuery", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId, + jbyteArray msg, jint msgLen, jint flags) { + uint8_t data[MAXCMDSIZE]; + + checkLenAndCopy(env, msg, msgLen, data); + int fd = resNetworkSend(netId, data, msgLen, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkSend", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) { + int fd = AFileDescriptor_getFD(env, javaFd); + int rcode; + std::vector buf(MAXPACKETSIZE, 0); + + int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE); + jniSetFileDescriptorOfFD(env, javaFd, -1); + if (res < 0) { + throwErrnoException(env, "resNetworkResult", -res); + return nullptr; + } + + jbyteArray answer = env->NewByteArray(res); + if (answer == nullptr) { + throwErrnoException(env, "resNetworkResult", ENOMEM); + return nullptr; + } else { + env->SetByteArrayRegion(answer, 0, res, + reinterpret_cast(buf.data())); + } + + jclass class_DnsResponse = env->FindClass("android/net/DnsResolver$DnsResponse"); + jmethodID ctor = env->GetMethodID(class_DnsResponse, "", "([BI)V"); + + return env->NewObject(class_DnsResponse, ctor, answer, rcode); +} + +static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) { + int fd = AFileDescriptor_getFD(env, javaFd); + resNetworkCancel(fd); + jniSetFileDescriptorOfFD(env, javaFd, -1); +} + +static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) { + unsigned dnsNetId = 0; + if (int res = getNetworkForDns(&dnsNetId) < 0) { + throwErrnoException(env, "getDnsNetId", -res); + return nullptr; + } + bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS; + + static jclass class_Network = MakeGlobalRefOrDie( + env, FindClassOrDie(env, "android/net/Network")); + static jmethodID ctor = env->GetMethodID(class_Network, "", "(IZ)V"); + return env->NewObject( + class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass); +} + +static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) { + if (javaFd == NULL) { + jniThrowNullPointerException(env, NULL); + return NULL; + } + + int fd = AFileDescriptor_getFD(env, javaFd); + struct tcp_repair_window trw = {}; + socklen_t size = sizeof(trw); + + // Obtain the parameters of the TCP repair window. + int rc = getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &size); + if (rc == -1) { + throwErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno); + return NULL; + } + + struct tcp_info tcpinfo = {}; + socklen_t tcpinfo_size = sizeof(tcp_info); + + // Obtain the window scale from the tcp info structure. This contains a scale factor that + // should be applied to the window size. + rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpinfo, &tcpinfo_size); + if (rc == -1) { + throwErrnoException(env, "getsockopt : TCP_INFO", errno); + return NULL; + } + + jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow"); + jmethodID ctor = env->GetMethodID(class_TcpRepairWindow, "", "(IIIIII)V"); + + return env->NewObject(class_TcpRepairWindow, ctor, trw.snd_wl1, trw.snd_wnd, trw.max_window, + trw.rcv_wnd, trw.rcv_wup, tcpinfo.tcpi_rcv_wscale); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +// clang-format off +static const JNINativeMethod gNetworkUtilMethods[] = { + /* name, signature, funcPtr */ + { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork }, + { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess }, + { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution }, + { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork }, + { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter }, + { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter }, + { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow }, + { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend }, + { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, + { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult }, + { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel }, + { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork }, +}; +// clang-format on + +int register_android_net_NetworkUtils(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, NETUTILS_PKG_NAME, gNetworkUtilMethods, + NELEM(gNetworkUtilMethods)); +} + +}; // namespace android diff --git a/framework/jni/onload.cpp b/framework/jni/onload.cpp new file mode 100644 index 0000000000..435f4343ed --- /dev/null +++ b/framework/jni/onload.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 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 +#include + +namespace android { + +int register_android_net_NetworkUtils(JNIEnv* env); + +extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { + JNIEnv *env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + ALOGE("GetEnv failed"); + return JNI_ERR; + } + + if (register_android_net_NetworkUtils(env) < 0) { + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} + +}; \ No newline at end of file