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