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