1 /*
2 * Copyright 2019, 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 "NetworkStackUtils-JNI"
18
19 #include <dlfcn.h>
20 #include <errno.h>
21 #include <jni.h>
22 #include <linux/filter.h>
23 #include <linux/if_arp.h>
24 #include <net/if.h>
25 #include <netinet/ether.h>
26 #include <netinet/icmp6.h>
27 #include <netinet/igmp.h>
28 #include <netinet/ip.h>
29 #include <netinet/ip6.h>
30 #include <netinet/udp.h>
31 #include <stdlib.h>
32 #include <sys/system_properties.h>
33
34 #include <string>
35
36 #include <nativehelper/JNIHelp.h>
37 #include <netjniutils/netjniutils.h>
38
39 #include <android/log.h>
40 #include <bpf/BpfClassic.h>
41
42 namespace android {
43 constexpr const char NETWORKSTACKUTILS_PKG_NAME[] =
44 "com/android/networkstack/util/NetworkStackUtils";
45
46 static const uint16_t kDhcpClientPort = 68;
47
checkLenAndCopy(JNIEnv * env,const jbyteArray & addr,int len,void * dst)48 static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst) {
49 if (env->GetArrayLength(addr) != len) {
50 return false;
51 }
52 env->GetByteArrayRegion(addr, 0, len, reinterpret_cast<jbyte*>(dst));
53 return true;
54 }
55
network_stack_utils_addArpEntry(JNIEnv * env,jclass clazz,jbyteArray ethAddr,jbyteArray ipv4Addr,jstring ifname,jobject javaFd)56 static void network_stack_utils_addArpEntry(JNIEnv *env, jclass clazz, jbyteArray ethAddr,
57 jbyteArray ipv4Addr, jstring ifname, jobject javaFd) {
58 arpreq req = {};
59 sockaddr_in& netAddrStruct = *reinterpret_cast<sockaddr_in*>(&req.arp_pa);
60 sockaddr& ethAddrStruct = req.arp_ha;
61
62 ethAddrStruct.sa_family = ARPHRD_ETHER;
63 if (!checkLenAndCopy(env, ethAddr, ETH_ALEN, ethAddrStruct.sa_data)) {
64 jniThrowException(env, "java/io/IOException", "Invalid ethAddr length");
65 return;
66 }
67
68 netAddrStruct.sin_family = AF_INET;
69 if (!checkLenAndCopy(env, ipv4Addr, sizeof(in_addr), &netAddrStruct.sin_addr)) {
70 jniThrowException(env, "java/io/IOException", "Invalid ipv4Addr length");
71 return;
72 }
73
74 int ifLen = env->GetStringLength(ifname);
75 // IFNAMSIZ includes the terminating NULL character
76 if (ifLen >= IFNAMSIZ) {
77 jniThrowException(env, "java/io/IOException", "ifname too long");
78 return;
79 }
80 env->GetStringUTFRegion(ifname, 0, ifLen, req.arp_dev);
81
82 req.arp_flags = ATF_COM; // Completed entry (ha valid)
83 int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
84 if (fd < 0) {
85 jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
86 return;
87 }
88 // See also: man 7 arp
89 if (ioctl(fd, SIOCSARP, &req)) {
90 jniThrowExceptionFmt(env, "java/io/IOException", "ioctl error: %s", strerror(errno));
91 return;
92 }
93 }
94
95 // fd is a "socket(AF_PACKET, SOCK_RAW, ETH_P_IP)"
96 // which guarantees packets already have skb->protocol == htons(ETH_P_IP)
network_stack_utils_attachDhcpFilter(JNIEnv * env,jclass clazz,jobject javaFd)97 static void network_stack_utils_attachDhcpFilter(JNIEnv *env, jclass clazz, jobject javaFd) {
98 static sock_filter filter_code[] = {
99 // Check the protocol is UDP.
100 BPF_LOAD_IPV4_U8(protocol),
101 BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_UDP),
102
103 // Check this is not a fragment.
104 BPF_LOAD_IPV4_BE16(frag_off),
105 BPF2_REJECT_IF_ANY_MASKED_BITS_SET(IP_MF | IP_OFFMASK),
106
107 // Get the IP header length.
108 BPF_LOADX_NET_RELATIVE_IPV4_HLEN,
109
110 // Check the destination port.
111 BPF_LOAD_NETX_RELATIVE_DST_PORT,
112 BPF2_REJECT_IF_NOT_EQUAL(kDhcpClientPort),
113
114 BPF_ACCEPT,
115 };
116 const sock_fprog filter = {
117 sizeof(filter_code) / sizeof(filter_code[0]),
118 filter_code,
119 };
120
121 int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
122 if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
123 jniThrowErrnoException(env, "setsockopt(SO_ATTACH_FILTER)", errno);
124 }
125 }
126
127 // fd is a "socket(AF_PACKET, SOCK_RAW, ETH_P_ALL)"
network_stack_units_attachEgressIgmpReportFilter(JNIEnv * env,jclass clazz,jobject javaFd)128 static void network_stack_units_attachEgressIgmpReportFilter(
129 JNIEnv *env, jclass clazz, jobject javaFd) {
130 static sock_filter filter_code[] = {
131 // Check if skb->pkt_type is PACKET_OUTGOING
132 BPF_LOAD_SKB_PKTTYPE,
133 BPF2_REJECT_IF_NOT_EQUAL(PACKET_OUTGOING),
134
135 // Check if skb->protocol is ETH_P_IP
136 BPF_LOAD_SKB_PROTOCOL,
137 BPF2_REJECT_IF_NOT_EQUAL(ETH_P_IP),
138
139 // Check the protocol is IGMP.
140 BPF_LOAD_IPV4_U8(protocol),
141 BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_IGMP),
142
143 // Check this is not a fragment.
144 BPF_LOAD_IPV4_BE16(frag_off),
145 BPF2_REJECT_IF_ANY_MASKED_BITS_SET(IP_MF | IP_OFFMASK),
146
147 // Get the IP header length.
148 BPF_LOADX_NET_RELATIVE_IPV4_HLEN,
149
150 // Check if IGMPv2/IGMPv3 join/leave message.
151 BPF_LOAD_NETX_RELATIVE_IGMP_TYPE,
152 BPF2_ACCEPT_IF_EQUAL(IGMPV2_HOST_MEMBERSHIP_REPORT),
153 BPF2_ACCEPT_IF_EQUAL(IGMP_HOST_LEAVE_MESSAGE),
154 BPF2_ACCEPT_IF_EQUAL(IGMPV3_HOST_MEMBERSHIP_REPORT),
155 BPF_REJECT,
156 };
157 static const sock_fprog filter = {
158 sizeof(filter_code) / sizeof(filter_code[0]),
159 filter_code,
160 };
161
162 int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
163 if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
164 jniThrowErrnoException(env, "setsockopt(SO_ATTACH_FILTER)", errno);
165 }
166 }
167
168 // fd is a "socket(AF_PACKET, SOCK_RAW, ETH_P_ALL)"
network_stack_units_attachEgressMulticastReportFilter(JNIEnv * env,jclass clazz,jobject javaFd)169 static void network_stack_units_attachEgressMulticastReportFilter(
170 JNIEnv *env, jclass clazz, jobject javaFd) {
171 static sock_filter filter_code[] = {
172 // Check if skb->pkt_type is PACKET_OUTGOING
173 BPF_LOAD_SKB_PKTTYPE,
174 BPF2_REJECT_IF_NOT_EQUAL(PACKET_OUTGOING),
175
176 // If IPv4: (otherwise jump to the 'IPv6 ...' below)
177 // Check if skb->protocol is ETH_P_IP
178 BPF_LOAD_SKB_PROTOCOL,
179 // Jump over instructions after this and before IPv6 handling section
180 BPF_JUMP_IF_NOT_EQUAL(ETH_P_IP, 15),
181
182 // Check the protocol is IGMP.
183 BPF_LOAD_IPV4_U8(protocol),
184 BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_IGMP),
185
186 // Check this is not a fragment.
187 BPF_LOAD_IPV4_BE16(frag_off),
188 BPF2_REJECT_IF_ANY_MASKED_BITS_SET(IP_MF | IP_OFFMASK),
189
190 // Get the IP header length.
191 BPF_LOADX_NET_RELATIVE_IPV4_HLEN,
192
193 // Check if IGMPv2/IGMPv3 join/leave message.
194 BPF_LOAD_NETX_RELATIVE_IGMP_TYPE,
195 BPF2_ACCEPT_IF_EQUAL(IGMPV2_HOST_MEMBERSHIP_REPORT),
196 BPF2_ACCEPT_IF_EQUAL(IGMP_HOST_LEAVE_MESSAGE),
197 BPF2_ACCEPT_IF_EQUAL(IGMPV3_HOST_MEMBERSHIP_REPORT),
198 BPF_REJECT,
199
200 // IPv6 ...
201 // Check if skb->protocol is ETH_P_IPV6
202 BPF2_REJECT_IF_NOT_EQUAL(ETH_P_IPV6),
203
204 BPF_LOADX_CONSTANT_IPV6_HLEN,
205
206 // Check IPv6 Next Header is HOPOPTS
207 BPF_LOAD_IPV6_U8(nexthdr),
208 BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_HOPOPTS),
209
210 // Check if HOPOPTS is ICMPv6
211 BPF_LOAD_NETX_RELATIVE_V6EXTHDR_NEXTHDR,
212 BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_ICMPV6),
213
214 // Skip the IPv6 extension header
215 BPF3_LOAD_NETX_RELATIVE_V6EXTHDR_LEN,
216 BPF2_ADD_A_TO_X,
217
218 // Check if MLDv1/MLDv2 report message
219 BPF_LOAD_NETX_RELATIVE_MLD_TYPE,
220 BPF2_ACCEPT_IF_EQUAL(MLD_LISTENER_REPORT),
221 BPF2_ACCEPT_IF_EQUAL(MLD_LISTENER_DONE),
222 BPF2_ACCEPT_IF_EQUAL(MLDV2_LISTENER_REPORT),
223 BPF_REJECT
224 };
225 static const sock_fprog filter = {
226 sizeof(filter_code) / sizeof(filter_code[0]),
227 filter_code,
228 };
229
230 int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
231 if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
232 jniThrowErrnoException(env, "setsockopt(SO_ATTACH_FILTER)", errno);
233 }
234 }
235
236 // fd is a "socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6)"
237 // which guarantees packets already have skb->protocol == htons(ETH_P_IPV6)
network_stack_utils_attachRaFilter(JNIEnv * env,jclass clazz,jobject javaFd)238 static void network_stack_utils_attachRaFilter(JNIEnv *env, jclass clazz, jobject javaFd) {
239 static sock_filter filter_code[] = {
240 BPF_LOADX_CONSTANT_IPV6_HLEN,
241
242 // Check IPv6 Next Header is ICMPv6.
243 BPF_LOAD_IPV6_U8(nexthdr),
244 BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_ICMPV6),
245
246 // Check ICMPv6 type is Router Advertisement.
247 BPF_LOAD_NETX_RELATIVE_ICMP_TYPE,
248 BPF2_REJECT_IF_NOT_EQUAL(ND_ROUTER_ADVERT),
249
250 BPF_ACCEPT,
251 };
252 static const sock_fprog filter = {
253 sizeof(filter_code) / sizeof(filter_code[0]),
254 filter_code,
255 };
256
257 int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
258 if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
259 jniThrowErrnoException(env, "setsockopt(SO_ATTACH_FILTER)", errno);
260 }
261 }
262
263 // TODO: Move all this filter code into libnetutils.
264 // fd is a "socket(AF_PACKET, SOCK_RAW, ETH_P_ALL)"
network_stack_utils_attachControlPacketFilter(JNIEnv * env,jclass clazz,jobject javaFd)265 static void network_stack_utils_attachControlPacketFilter(
266 JNIEnv *env, jclass clazz, jobject javaFd) {
267 // Capture all:
268 // - ARPs
269 // - DHCPv4 packets
270 // - Router Advertisements & Solicitations
271 // - Neighbor Advertisements & Solicitations
272 //
273 // tcpdump:
274 // arp or
275 // '(ip and udp port 68)' or
276 // '(icmp6 and ip6[40] >= 133 and ip6[40] <= 136)'
277 static sock_filter filter_code[] = {
278 // Load the ethertype from skb->protocol
279 BPF_LOAD_SKB_PROTOCOL,
280
281 // Accept all ARP.
282 // TODO: Figure out how to better filter ARPs on noisy networks.
283 BPF2_ACCEPT_IF_EQUAL(ETHERTYPE_ARP),
284
285 // If IPv4: (otherwise jump to the 'IPv6 ...' below)
286 BPF_JUMP_IF_NOT_EQUAL(ETHERTYPE_IP, 14),
287
288 // Check the protocol is UDP.
289 BPF_LOAD_IPV4_U8(protocol),
290 BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_UDP),
291
292 // Check this is not a fragment.
293 BPF_LOAD_IPV4_BE16(frag_off),
294 BPF2_REJECT_IF_ANY_MASKED_BITS_SET(IP_MF | IP_OFFMASK),
295
296 // Get the IP header length.
297 BPF_LOADX_NET_RELATIVE_IPV4_HLEN,
298
299 // Check the source port.
300 BPF_LOAD_NETX_RELATIVE_SRC_PORT,
301 BPF2_ACCEPT_IF_EQUAL(kDhcpClientPort),
302
303 // Check the destination port.
304 BPF_LOAD_NETX_RELATIVE_DST_PORT,
305 BPF2_ACCEPT_IF_EQUAL(kDhcpClientPort),
306
307 // Reject any other UDPv4
308 BPF_REJECT,
309
310 // IPv6 ...
311 BPF2_REJECT_IF_NOT_EQUAL(ETHERTYPE_IPV6),
312 // Assume standard, 40-byte, extension header-less ipv6 packet
313 BPF_LOADX_CONSTANT_IPV6_HLEN,
314 // ... check IPv6 Next Header is ICMPv6 (ignore fragments), ...
315 BPF_LOAD_IPV6_U8(nexthdr),
316 BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_ICMPV6),
317 // ... and check the ICMPv6 type is one of RS/RA/NS/NA.
318 BPF_LOAD_NETX_RELATIVE_ICMP_TYPE,
319 BPF3_REJECT_IF_NOT_IN_RANGE(ND_ROUTER_SOLICIT, ND_NEIGHBOR_ADVERT),
320
321 BPF_ACCEPT,
322 };
323 static const sock_fprog filter = {
324 sizeof(filter_code) / sizeof(filter_code[0]),
325 filter_code,
326 };
327
328 int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
329 if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
330 jniThrowErrnoException(env, "setsockopt(SO_ATTACH_FILTER)", errno);
331 }
332 }
333
334 /*
335 * JNI registration.
336 */
337 static const JNINativeMethod gNetworkStackUtilsMethods[] = {
338 /* name, signature, funcPtr */
addArpEntry([B[BLjava/lang/String;Ljava/io/FileDescriptor;)339 { "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_addArpEntry },
attachDhcpFilter(Ljava/io/FileDescriptor;)340 { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachDhcpFilter },
attachRaFilter(Ljava/io/FileDescriptor;)341 { "attachRaFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachRaFilter },
attachEgressIgmpReportFilter(Ljava/io/FileDescriptor;)342 { "attachEgressIgmpReportFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_units_attachEgressIgmpReportFilter },
attachEgressMulticastReportFilter(Ljava/io/FileDescriptor;)343 { "attachEgressMulticastReportFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_units_attachEgressMulticastReportFilter },
attachControlPacketFilter(Ljava/io/FileDescriptor;)344 { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachControlPacketFilter },
345 };
346
JNI_OnLoad(JavaVM * vm,void *)347 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
348 JNIEnv *env;
349 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
350 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
351 return JNI_ERR;
352 }
353
354 if (jniRegisterNativeMethods(env, NETWORKSTACKUTILS_PKG_NAME,
355 gNetworkStackUtilsMethods, NELEM(gNetworkStackUtilsMethods)) < 0) {
356 return JNI_ERR;
357 }
358
359 return JNI_VERSION_1_6;
360
361 }
362 }; // namespace android
363