blob: f2ba5757ed80f59754453fd75f2036b0c40096a3 [file] [log] [blame]
paulhue455e2a2019-03-29 17:22:20 +08001/*
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 <errno.h>
20#include <jni.h>
21#include <linux/filter.h>
22#include <linux/if_arp.h>
23#include <net/if.h>
24#include <netinet/ether.h>
25#include <netinet/icmp6.h>
26#include <netinet/ip.h>
27#include <netinet/ip6.h>
28#include <netinet/udp.h>
29#include <stdlib.h>
30
31#include <string>
32
33#include <nativehelper/JNIHelp.h>
Lorenzo Colitti88ddd882019-05-01 11:54:33 +090034#include <android/log.h>
paulhue455e2a2019-03-29 17:22:20 +080035
36namespace android {
37constexpr const char NETWORKSTACKUTILS_PKG_NAME[] = "android/net/util/NetworkStackUtils";
38
39static const uint32_t kEtherTypeOffset = offsetof(ether_header, ether_type);
40static const uint32_t kEtherHeaderLen = sizeof(ether_header);
41static const uint32_t kIPv4Protocol = kEtherHeaderLen + offsetof(iphdr, protocol);
42static const uint32_t kIPv4FlagsOffset = kEtherHeaderLen + offsetof(iphdr, frag_off);
43static const uint32_t kIPv6NextHeader = kEtherHeaderLen + offsetof(ip6_hdr, ip6_nxt);
44static const uint32_t kIPv6PayloadStart = kEtherHeaderLen + sizeof(ip6_hdr);
45static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);
46static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, source);
47static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
48static const uint16_t kDhcpClientPort = 68;
49
50static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst) {
51 if (env->GetArrayLength(addr) != len) {
52 return false;
53 }
54 env->GetByteArrayRegion(addr, 0, len, reinterpret_cast<jbyte*>(dst));
55 return true;
56}
57
58static void network_stack_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray ethAddr,
59 jbyteArray ipv4Addr, jstring ifname, jobject javaFd) {
60 arpreq req = {};
61 sockaddr_in& netAddrStruct = *reinterpret_cast<sockaddr_in*>(&req.arp_pa);
62 sockaddr& ethAddrStruct = req.arp_ha;
63
64 ethAddrStruct.sa_family = ARPHRD_ETHER;
65 if (!checkLenAndCopy(env, ethAddr, ETH_ALEN, ethAddrStruct.sa_data)) {
66 jniThrowException(env, "java/io/IOException", "Invalid ethAddr length");
67 return;
68 }
69
70 netAddrStruct.sin_family = AF_INET;
71 if (!checkLenAndCopy(env, ipv4Addr, sizeof(in_addr), &netAddrStruct.sin_addr)) {
72 jniThrowException(env, "java/io/IOException", "Invalid ipv4Addr length");
73 return;
74 }
75
76 int ifLen = env->GetStringLength(ifname);
77 // IFNAMSIZ includes the terminating NULL character
78 if (ifLen >= IFNAMSIZ) {
79 jniThrowException(env, "java/io/IOException", "ifname too long");
80 return;
81 }
82 env->GetStringUTFRegion(ifname, 0, ifLen, req.arp_dev);
83
84 req.arp_flags = ATF_COM; // Completed entry (ha valid)
85 int fd = jniGetFDFromFileDescriptor(env, javaFd);
86 if (fd < 0) {
87 jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
88 return;
89 }
90 // See also: man 7 arp
91 if (ioctl(fd, SIOCSARP, &req)) {
92 jniThrowExceptionFmt(env, "java/io/IOException", "ioctl error: %s", strerror(errno));
93 return;
94 }
95}
96
97static void network_stack_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd) {
98 static sock_filter filter_code[] = {
99 // Check the protocol is UDP.
100 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol),
101 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 6),
102
103 // Check this is not a fragment.
104 BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset),
105 BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 4, 0),
106
107 // Get the IP header length.
108 BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen),
109
110 // Check the destination port.
111 BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset),
112 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 0, 1),
113
114 // Accept or reject.
115 BPF_STMT(BPF_RET | BPF_K, 0xffff),
116 BPF_STMT(BPF_RET | BPF_K, 0)
117 };
118 static const sock_fprog filter = {
119 sizeof(filter_code) / sizeof(filter_code[0]),
120 filter_code,
121 };
122
123 int fd = jniGetFDFromFileDescriptor(env, javaFd);
124 if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
125 jniThrowExceptionFmt(env, "java/net/SocketException",
126 "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
127 }
128}
129
130static void network_stack_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject javaFd,
131 jint hardwareAddressType) {
132 if (hardwareAddressType != ARPHRD_ETHER) {
133 jniThrowExceptionFmt(env, "java/net/SocketException",
134 "attachRaFilter only supports ARPHRD_ETHER");
135 return;
136 }
137
138 static sock_filter filter_code[] = {
139 // Check IPv6 Next Header is ICMPv6.
140 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader),
141 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
142
143 // Check ICMPv6 type is Router Advertisement.
144 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset),
145 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 0, 1),
146
147 // Accept or reject.
148 BPF_STMT(BPF_RET | BPF_K, 0xffff),
149 BPF_STMT(BPF_RET | BPF_K, 0)
150 };
151 static const sock_fprog filter = {
152 sizeof(filter_code) / sizeof(filter_code[0]),
153 filter_code,
154 };
155
156 int fd = jniGetFDFromFileDescriptor(env, javaFd);
157 if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
158 jniThrowExceptionFmt(env, "java/net/SocketException",
159 "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
160 }
161}
162
163// TODO: Move all this filter code into libnetutils.
164static void network_stack_utils_attachControlPacketFilter(
165 JNIEnv *env, jobject clazz, jobject javaFd, jint hardwareAddressType) {
166 if (hardwareAddressType != ARPHRD_ETHER) {
167 jniThrowExceptionFmt(env, "java/net/SocketException",
168 "attachControlPacketFilter only supports ARPHRD_ETHER");
169 return;
170 }
171
172 // Capture all:
173 // - ARPs
174 // - DHCPv4 packets
175 // - Router Advertisements & Solicitations
176 // - Neighbor Advertisements & Solicitations
177 //
178 // tcpdump:
179 // arp or
180 // '(ip and udp port 68)' or
181 // '(icmp6 and ip6[40] >= 133 and ip6[40] <= 136)'
182 static sock_filter filter_code[] = {
183 // Load the link layer next payload field.
184 BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kEtherTypeOffset),
185
186 // Accept all ARP.
187 // TODO: Figure out how to better filter ARPs on noisy networks.
188 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_ARP, 16, 0),
189
190 // If IPv4:
191 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IP, 0, 9),
192
193 // Check the protocol is UDP.
194 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol),
195 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 14),
196
197 // Check this is not a fragment.
198 BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset),
199 BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 12, 0),
200
201 // Get the IP header length.
202 BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen),
203
204 // Check the source port.
205 BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPSrcPortIndirectOffset),
206 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 8, 0),
207
208 // Check the destination port.
209 BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset),
210 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 6, 7),
211
212 // IPv6 ...
213 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 6),
214 // ... check IPv6 Next Header is ICMPv6 (ignore fragments), ...
215 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader),
216 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 4),
217 // ... and check the ICMPv6 type is one of RS/RA/NS/NA.
218 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset),
219 BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, ND_ROUTER_SOLICIT, 0, 2),
220 BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0),
221
222 // Accept or reject.
223 BPF_STMT(BPF_RET | BPF_K, 0xffff),
224 BPF_STMT(BPF_RET | BPF_K, 0)
225 };
226 static const sock_fprog filter = {
227 sizeof(filter_code) / sizeof(filter_code[0]),
228 filter_code,
229 };
230
231 int fd = jniGetFDFromFileDescriptor(env, javaFd);
232 if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
233 jniThrowExceptionFmt(env, "java/net/SocketException",
234 "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
235 }
236}
237
238/*
239 * JNI registration.
240 */
241static const JNINativeMethod gNetworkStackUtilsMethods[] = {
242 /* name, signature, funcPtr */
243 { "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_addArpEntry },
244 { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachDhcpFilter },
245 { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) network_stack_utils_attachRaFilter },
246 { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) network_stack_utils_attachControlPacketFilter },
247};
248
249extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
250 JNIEnv *env;
251 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
Lorenzo Colitti88ddd882019-05-01 11:54:33 +0900252 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
paulhue455e2a2019-03-29 17:22:20 +0800253 return JNI_ERR;
254 }
255
256 if (jniRegisterNativeMethods(env, NETWORKSTACKUTILS_PKG_NAME,
257 gNetworkStackUtilsMethods, NELEM(gNetworkStackUtilsMethods)) < 0) {
258 return JNI_ERR;
259 }
260
261 return JNI_VERSION_1_6;
262
263}
Lorenzo Colitti88ddd882019-05-01 11:54:33 +0900264}; // namespace android