• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 package android.net.cts;
18 
19 import static android.net.cts.PacketUtils.IP4_HDRLEN;
20 import static android.net.cts.PacketUtils.IP6_HDRLEN;
21 import static android.net.cts.PacketUtils.IPPROTO_ESP;
22 import static android.net.cts.PacketUtils.UDP_HDRLEN;
23 import static android.system.OsConstants.IPPROTO_UDP;
24 
25 import static org.junit.Assert.fail;
26 
27 import android.os.ParcelFileDescriptor;
28 
29 import com.android.net.module.util.CollectionUtils;
30 
31 import java.io.FileInputStream;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.nio.ByteBuffer;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.List;
38 import java.util.function.Predicate;
39 
40 public class TunUtils {
41     private static final String TAG = TunUtils.class.getSimpleName();
42 
43     protected static final int IP4_ADDR_OFFSET = 12;
44     protected static final int IP4_ADDR_LEN = 4;
45     protected static final int IP6_ADDR_OFFSET = 8;
46     protected static final int IP6_ADDR_LEN = 16;
47     protected static final int IP4_PROTO_OFFSET = 9;
48     protected static final int IP6_PROTO_OFFSET = 6;
49 
50     private static final int DATA_BUFFER_LEN = 4096;
51     private static final int TIMEOUT = 2000;
52 
53     private final List<byte[]> mPackets = new ArrayList<>();
54     private final ParcelFileDescriptor mTunFd;
55     private final Thread mReaderThread;
56 
TunUtils(ParcelFileDescriptor tunFd)57     public TunUtils(ParcelFileDescriptor tunFd) {
58         mTunFd = tunFd;
59 
60         // Start background reader thread
61         mReaderThread =
62                 new Thread(
63                         () -> {
64                             try {
65                                 // Loop will exit and thread will quit when tunFd is closed.
66                                 // Receiving either EOF or an exception will exit this reader loop.
67                                 // FileInputStream in uninterruptable, so there's no good way to
68                                 // ensure that this thread shuts down except upon FD closure.
69                                 while (true) {
70                                     byte[] intercepted = receiveFromTun();
71                                     if (intercepted == null) {
72                                         // Exit once we've hit EOF
73                                         return;
74                                     } else if (intercepted.length > 0) {
75                                         // Only save packet if we've received any bytes.
76                                         synchronized (mPackets) {
77                                             mPackets.add(intercepted);
78                                             mPackets.notifyAll();
79                                         }
80                                     }
81                                 }
82                             } catch (IOException ignored) {
83                                 // Simply exit this reader thread
84                                 return;
85                             }
86                         });
87         mReaderThread.start();
88     }
89 
receiveFromTun()90     private byte[] receiveFromTun() throws IOException {
91         FileInputStream in = new FileInputStream(mTunFd.getFileDescriptor());
92         byte[] inBytes = new byte[DATA_BUFFER_LEN];
93         int bytesRead = in.read(inBytes);
94 
95         if (bytesRead < 0) {
96             return null; // return null for EOF
97         } else if (bytesRead >= DATA_BUFFER_LEN) {
98             throw new IllegalStateException("Too big packet. Fragmentation unsupported");
99         }
100         return Arrays.copyOf(inBytes, bytesRead);
101     }
102 
getFirstMatchingPacket(Predicate<byte[]> verifier, int startIndex)103     private byte[] getFirstMatchingPacket(Predicate<byte[]> verifier, int startIndex) {
104         synchronized (mPackets) {
105             for (int i = startIndex; i < mPackets.size(); i++) {
106                 byte[] pkt = mPackets.get(i);
107                 if (verifier.test(pkt)) {
108                     return pkt;
109                 }
110             }
111         }
112         return null;
113     }
114 
awaitPacket(Predicate<byte[]> verifier)115     protected byte[] awaitPacket(Predicate<byte[]> verifier) throws Exception {
116         long endTime = System.currentTimeMillis() + TIMEOUT;
117         int startIndex = 0;
118 
119         synchronized (mPackets) {
120             while (System.currentTimeMillis() < endTime) {
121                 final byte[] pkt = getFirstMatchingPacket(verifier, startIndex);
122                 if (pkt != null) {
123                     return pkt; // We've found the packet we're looking for.
124                 }
125 
126                 startIndex = mPackets.size();
127 
128                 // Try to prevent waiting too long. If waitTimeout <= 0, we've already hit timeout
129                 long waitTimeout = endTime - System.currentTimeMillis();
130                 if (waitTimeout > 0) {
131                     mPackets.wait(waitTimeout);
132                 }
133             }
134         }
135 
136         fail("No packet found matching verifier");
137         throw new IllegalStateException("Impossible condition; should have thrown in fail()");
138     }
139 
awaitEspPacketNoPlaintext( int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize)140     public byte[] awaitEspPacketNoPlaintext(
141             int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception {
142         final byte[] espPkt = awaitPacket(
143             (pkt) -> expectedPacketSize == pkt.length
144                     && isEspFailIfSpecifiedPlaintextFound(pkt, spi, useEncap, plaintext));
145 
146         return espPkt; // We've found the packet we're looking for.
147     }
148 
awaitEspPacket(int spi, boolean useEncap)149     public byte[] awaitEspPacket(int spi, boolean useEncap) throws Exception {
150         return awaitPacket((pkt) -> isEsp(pkt, spi, useEncap));
151     }
152 
isSpiEqual(byte[] pkt, int espOffset, int spi)153     private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) {
154         ByteBuffer buffer = ByteBuffer.wrap(pkt);
155         buffer.get(new byte[espOffset]); // Skip IP, UDP header
156         int actualSpi = buffer.getInt();
157 
158         return actualSpi == spi;
159     }
160 
161     /**
162      * Variant of isEsp that also fails the test if the provided plaintext is found
163      *
164      * @param pkt the packet bytes to verify
165      * @param spi the expected SPI to look for
166      * @param encap whether encap was enabled, and the packet has a UDP header
167      * @param plaintext the plaintext packet before outbound encryption, which MUST not appear in
168      *     the provided packet.
169      */
isEspFailIfSpecifiedPlaintextFound( byte[] pkt, int spi, boolean encap, byte[] plaintext)170     private static boolean isEspFailIfSpecifiedPlaintextFound(
171             byte[] pkt, int spi, boolean encap, byte[] plaintext) {
172         if (CollectionUtils.indexOfSubArray(pkt, plaintext) != -1) {
173             fail("Banned plaintext packet found");
174         }
175 
176         return isEsp(pkt, spi, encap);
177     }
178 
isEsp(byte[] pkt, int spi, boolean encap)179     private static boolean isEsp(byte[] pkt, int spi, boolean encap) {
180         if (isIpv6(pkt)) {
181             if (encap) {
182                 return pkt[IP6_PROTO_OFFSET] == IPPROTO_UDP
183                         && isSpiEqual(pkt, IP6_HDRLEN + UDP_HDRLEN, spi);
184             } else {
185                 return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP6_HDRLEN, spi);
186             }
187 
188         } else {
189             // Use default IPv4 header length (assuming no options)
190             if (encap) {
191                 return pkt[IP4_PROTO_OFFSET] == IPPROTO_UDP
192                         && isSpiEqual(pkt, IP4_HDRLEN + UDP_HDRLEN, spi);
193             } else {
194                 return pkt[IP4_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP4_HDRLEN, spi);
195             }
196         }
197     }
198 
isIpv6(byte[] pkt)199     public static boolean isIpv6(byte[] pkt) {
200         // First nibble shows IP version. 0x60 for IPv6
201         return (pkt[0] & (byte) 0xF0) == (byte) 0x60;
202     }
203 
getReflectedPacket(byte[] pkt)204     private static byte[] getReflectedPacket(byte[] pkt) {
205         byte[] reflected = Arrays.copyOf(pkt, pkt.length);
206 
207         if (isIpv6(pkt)) {
208             // Set reflected packet's dst to that of the original's src
209             System.arraycopy(
210                     pkt, // src
211                     IP6_ADDR_OFFSET + IP6_ADDR_LEN, // src offset
212                     reflected, // dst
213                     IP6_ADDR_OFFSET, // dst offset
214                     IP6_ADDR_LEN); // len
215             // Set reflected packet's src IP to that of the original's dst IP
216             System.arraycopy(
217                     pkt, // src
218                     IP6_ADDR_OFFSET, // src offset
219                     reflected, // dst
220                     IP6_ADDR_OFFSET + IP6_ADDR_LEN, // dst offset
221                     IP6_ADDR_LEN); // len
222         } else {
223             // Set reflected packet's dst to that of the original's src
224             System.arraycopy(
225                     pkt, // src
226                     IP4_ADDR_OFFSET + IP4_ADDR_LEN, // src offset
227                     reflected, // dst
228                     IP4_ADDR_OFFSET, // dst offset
229                     IP4_ADDR_LEN); // len
230             // Set reflected packet's src IP to that of the original's dst IP
231             System.arraycopy(
232                     pkt, // src
233                     IP4_ADDR_OFFSET, // src offset
234                     reflected, // dst
235                     IP4_ADDR_OFFSET + IP4_ADDR_LEN, // dst offset
236                     IP4_ADDR_LEN); // len
237         }
238         return reflected;
239     }
240 
241     /** Takes all captured packets, flips the src/dst, and re-injects them. */
reflectPackets()242     public void reflectPackets() throws IOException {
243         synchronized (mPackets) {
244             for (byte[] pkt : mPackets) {
245                 injectPacket(getReflectedPacket(pkt));
246             }
247         }
248     }
249 
injectPacket(byte[] pkt)250     public void injectPacket(byte[] pkt) throws IOException {
251         FileOutputStream out = new FileOutputStream(mTunFd.getFileDescriptor());
252         out.write(pkt);
253         out.flush();
254     }
255 
256     /** Resets the intercepted packets. */
reset()257     public void reset() throws IOException {
258         synchronized (mPackets) {
259             mPackets.clear();
260         }
261     }
262 }
263