1 /*
2 * Copyright 2020, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "NetworkUtils"
18
19 #include <android/file_descriptor_jni.h>
20 #include <android/multinetwork.h>
21 #include <linux/filter.h>
22 #include <linux/tcp.h>
23 #include <netinet/in.h>
24 #include <string.h>
25
26 #include <DnsProxydProtocol.h> // NETID_USE_LOCAL_NAMESERVERS
27 #include <nativehelper/JNIPlatformHelp.h>
28 #include <utils/Log.h>
29
30 #include "jni.h"
31
32 #define NETUTILS_PKG_NAME "android/net/NetworkUtils"
33
34 namespace android {
35
36 constexpr int MAXPACKETSIZE = 8 * 1024;
37 // FrameworkListener limits the size of commands to 4096 bytes.
38 constexpr int MAXCMDSIZE = 4096;
39
40 static volatile jclass class_Network = 0;
41 static volatile jmethodID method_fromNetworkHandle = 0;
42
FindClassOrDie(JNIEnv * env,const char * class_name)43 static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) {
44 jclass clazz = env->FindClass(class_name);
45 LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name);
46 return clazz;
47 }
48
49 template <typename T>
MakeGlobalRefOrDie(JNIEnv * env,T in)50 static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) {
51 jobject res = env->NewGlobalRef(in);
52 LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference.");
53 return static_cast<T>(res);
54 }
55
android_net_utils_attachDropAllBPFFilter(JNIEnv * env,jobject clazz,jobject javaFd)56 static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
57 {
58 struct sock_filter filter_code[] = {
59 // Reject all.
60 BPF_STMT(BPF_RET | BPF_K, 0)
61 };
62 struct sock_fprog filter = {
63 sizeof(filter_code) / sizeof(filter_code[0]),
64 filter_code,
65 };
66
67 int fd = AFileDescriptor_getFd(env, javaFd);
68 if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
69 jniThrowExceptionFmt(env, "java/net/SocketException",
70 "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
71 }
72 }
73
android_net_utils_detachBPFFilter(JNIEnv * env,jobject clazz,jobject javaFd)74 static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
75 {
76 int optval_ignored = 0;
77 int fd = AFileDescriptor_getFd(env, javaFd);
78 if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &optval_ignored, sizeof(optval_ignored)) !=
79 0) {
80 jniThrowExceptionFmt(env, "java/net/SocketException",
81 "setsockopt(SO_DETACH_FILTER): %s", strerror(errno));
82 }
83 }
84
android_net_utils_bindProcessToNetworkHandle(JNIEnv * env,jobject thiz,jlong netHandle)85 static jboolean android_net_utils_bindProcessToNetworkHandle(JNIEnv *env, jobject thiz,
86 jlong netHandle)
87 {
88 return (jboolean) !android_setprocnetwork(netHandle);
89 }
90
android_net_utils_getBoundNetworkHandleForProcess(JNIEnv * env,jobject thiz)91 static jlong android_net_utils_getBoundNetworkHandleForProcess(JNIEnv *env, jobject thiz)
92 {
93 net_handle_t network;
94 if (android_getprocnetwork(&network) != 0) {
95 jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
96 "android_getprocnetwork(): %s", strerror(errno));
97 return NETWORK_UNSPECIFIED;
98 }
99 return (jlong) network;
100 }
101
android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv * env,jobject thiz,jint netId,jlong netHandle)102 static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz,
103 jint netId, jlong netHandle)
104 {
105 return (jboolean) !android_setprocdns(netHandle);
106 }
107
android_net_utils_bindSocketToNetworkHandle(JNIEnv * env,jobject thiz,jobject javaFd,jlong netHandle)108 static jint android_net_utils_bindSocketToNetworkHandle(JNIEnv *env, jobject thiz, jobject javaFd,
109 jlong netHandle) {
110 return android_setsocknetwork(netHandle, AFileDescriptor_getFd(env, javaFd));
111 }
112
checkLenAndCopy(JNIEnv * env,const jbyteArray & addr,int len,void * dst)113 static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst)
114 {
115 if (env->GetArrayLength(addr) != len) {
116 return false;
117 }
118 env->GetByteArrayRegion(addr, 0, len, reinterpret_cast<jbyte*>(dst));
119 return true;
120 }
121
android_net_utils_resNetworkQuery(JNIEnv * env,jobject thiz,jlong netHandle,jstring dname,jint ns_class,jint ns_type,jint flags)122 static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jlong netHandle,
123 jstring dname, jint ns_class, jint ns_type, jint flags) {
124 const jsize javaCharsCount = env->GetStringLength(dname);
125 const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
126
127 // Only allow dname which could be simply formatted to UTF8.
128 // In native layer, res_mkquery would re-format the input char array to packet.
129 char queryname[byteCountUTF8 + 1];
130 memset(queryname, 0, (byteCountUTF8 + 1) * sizeof(char));
131
132 env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname);
133 int fd = android_res_nquery(netHandle, queryname, ns_class, ns_type, flags);
134
135 if (fd < 0) {
136 jniThrowErrnoException(env, "resNetworkQuery", -fd);
137 return nullptr;
138 }
139
140 return jniCreateFileDescriptor(env, fd);
141 }
142
android_net_utils_resNetworkSend(JNIEnv * env,jobject thiz,jlong netHandle,jbyteArray msg,jint msgLen,jint flags)143 static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jlong netHandle,
144 jbyteArray msg, jint msgLen, jint flags) {
145 uint8_t data[MAXCMDSIZE];
146
147 checkLenAndCopy(env, msg, msgLen, data);
148 int fd = android_res_nsend(netHandle, data, msgLen, flags);
149
150 if (fd < 0) {
151 jniThrowErrnoException(env, "resNetworkSend", -fd);
152 return nullptr;
153 }
154
155 return jniCreateFileDescriptor(env, fd);
156 }
157
android_net_utils_resNetworkResult(JNIEnv * env,jobject thiz,jobject javaFd)158 static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
159 int fd = AFileDescriptor_getFd(env, javaFd);
160 int rcode;
161 uint8_t buf[MAXPACKETSIZE] = {0};
162
163 int res = android_res_nresult(fd, &rcode, buf, MAXPACKETSIZE);
164 jniSetFileDescriptorOfFD(env, javaFd, -1);
165 if (res < 0) {
166 jniThrowErrnoException(env, "resNetworkResult", -res);
167 return nullptr;
168 }
169
170 jbyteArray answer = env->NewByteArray(res);
171 if (answer == nullptr) {
172 jniThrowErrnoException(env, "resNetworkResult", ENOMEM);
173 return nullptr;
174 } else {
175 env->SetByteArrayRegion(answer, 0, res, reinterpret_cast<jbyte*>(buf));
176 }
177
178 jclass class_DnsResponse = env->FindClass("android/net/DnsResolver$DnsResponse");
179 jmethodID ctor = env->GetMethodID(class_DnsResponse, "<init>", "([BI)V");
180
181 return env->NewObject(class_DnsResponse, ctor, answer, rcode);
182 }
183
android_net_utils_resNetworkCancel(JNIEnv * env,jobject thiz,jobject javaFd)184 static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
185 int fd = AFileDescriptor_getFd(env, javaFd);
186 android_res_cancel(fd);
187 jniSetFileDescriptorOfFD(env, javaFd, -1);
188 }
189
android_net_utils_getDnsNetwork(JNIEnv * env,jobject thiz)190 static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) {
191 net_handle_t dnsNetHandle = NETWORK_UNSPECIFIED;
192 if (int res = android_getprocdns(&dnsNetHandle) < 0) {
193 jniThrowErrnoException(env, "getDnsNetwork", -res);
194 return nullptr;
195 }
196
197 if (method_fromNetworkHandle == 0) {
198 // This may be called multiple times concurrently but that is fine
199 class_Network = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/net/Network"));
200 method_fromNetworkHandle = env->GetStaticMethodID(class_Network, "fromNetworkHandle",
201 "(J)Landroid/net/Network;");
202 }
203 return env->CallStaticObjectMethod(class_Network, method_fromNetworkHandle,
204 static_cast<jlong>(dnsNetHandle));
205 }
206
android_net_utils_getTcpRepairWindow(JNIEnv * env,jobject thiz,jobject javaFd)207 static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
208 if (javaFd == NULL) {
209 jniThrowNullPointerException(env, NULL);
210 return NULL;
211 }
212
213 int fd = AFileDescriptor_getFd(env, javaFd);
214 struct tcp_repair_window trw = {};
215 socklen_t size = sizeof(trw);
216
217 // Obtain the parameters of the TCP repair window.
218 int rc = getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &size);
219 if (rc == -1) {
220 jniThrowErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno);
221 return NULL;
222 }
223
224 struct tcp_info tcpinfo = {};
225 socklen_t tcpinfo_size = sizeof(tcp_info);
226
227 // Obtain the window scale from the tcp info structure. This contains a scale factor that
228 // should be applied to the window size.
229 rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpinfo, &tcpinfo_size);
230 if (rc == -1) {
231 jniThrowErrnoException(env, "getsockopt : TCP_INFO", errno);
232 return NULL;
233 }
234
235 jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow");
236 jmethodID ctor = env->GetMethodID(class_TcpRepairWindow, "<init>", "(IIIIII)V");
237
238 return env->NewObject(class_TcpRepairWindow, ctor, trw.snd_wl1, trw.snd_wnd, trw.max_window,
239 trw.rcv_wnd, trw.rcv_wup, tcpinfo.tcpi_rcv_wscale);
240 }
241
242 // ----------------------------------------------------------------------------
243
244 /*
245 * JNI registration.
246 */
247 // clang-format off
248 static const JNINativeMethod gNetworkUtilMethods[] = {
249 /* name, signature, funcPtr */
250 { "bindProcessToNetworkHandle", "(J)Z", (void*) android_net_utils_bindProcessToNetworkHandle },
251 { "getBoundNetworkHandleForProcess", "()J", (void*) android_net_utils_getBoundNetworkHandleForProcess },
252 { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
253 { "bindSocketToNetworkHandle", "(Ljava/io/FileDescriptor;J)I", (void*) android_net_utils_bindSocketToNetworkHandle },
254 { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
255 { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
256 { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
257 { "resNetworkSend", "(J[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
258 { "resNetworkQuery", "(JLjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
259 { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
260 { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
261 { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork },
262 };
263 // clang-format on
264
register_android_net_NetworkUtils(JNIEnv * env)265 int register_android_net_NetworkUtils(JNIEnv* env)
266 {
267 return jniRegisterNativeMethods(env, NETUTILS_PKG_NAME, gNetworkUtilMethods,
268 NELEM(gNetworkUtilMethods));
269 }
270
271 }; // namespace android
272