• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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