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