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