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