1 /* 2 * Copyright (C) 2014 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 com.android.testutils; 18 19 import static android.system.OsConstants.ICMP6_ECHO_REPLY; 20 import static android.system.OsConstants.ICMP6_ECHO_REQUEST; 21 22 import android.annotation.NonNull; 23 import android.net.TestNetworkInterface; 24 import android.system.ErrnoException; 25 import android.system.Os; 26 import android.util.Log; 27 28 import java.io.FileDescriptor; 29 import java.io.IOException; 30 import java.util.Objects; 31 32 /** 33 * A class that echoes packets received on a {@link TestNetworkInterface} back to itself. 34 * 35 * For testing purposes, sometimes a mocked environment to simulate a simple echo from the 36 * server side is needed. This is particularly useful if the test, e.g. VpnTest, is 37 * heavily relying on the outside world. 38 * 39 * This class reads packets from the {@link FileDescriptor} of a {@link TestNetworkInterface}, and: 40 * 1. For TCP and UDP packets, simply swaps the source address and the destination 41 * address, then send it back to the {@link FileDescriptor}. 42 * 2. For ICMP ping packets, composes a ping reply and sends it back to the sender. 43 * 3. Ignore all other packets. 44 */ 45 public class PacketReflector extends Thread { 46 47 static final int IPV4_HEADER_LENGTH = 20; 48 static final int IPV6_HEADER_LENGTH = 40; 49 50 static final int IPV4_ADDR_OFFSET = 12; 51 static final int IPV6_ADDR_OFFSET = 8; 52 static final int IPV4_ADDR_LENGTH = 4; 53 static final int IPV6_ADDR_LENGTH = 16; 54 55 static final int IPV4_PROTO_OFFSET = 9; 56 static final int IPV6_PROTO_OFFSET = 6; 57 58 static final byte IPPROTO_ICMP = 1; 59 static final byte IPPROTO_TCP = 6; 60 static final byte IPPROTO_UDP = 17; 61 private static final byte IPPROTO_ICMPV6 = 58; 62 63 private static final int ICMP_HEADER_LENGTH = 8; 64 static final int TCP_HEADER_LENGTH = 20; 65 static final int UDP_HEADER_LENGTH = 8; 66 67 private static final byte ICMP_ECHO = 8; 68 private static final byte ICMP_ECHOREPLY = 0; 69 70 private static String TAG = "PacketReflector"; 71 72 @NonNull 73 private final FileDescriptor mFd; 74 @NonNull 75 private final byte[] mBuf; 76 77 /** 78 * Construct a {@link PacketReflector} from the given {@code fd} of 79 * a {@link TestNetworkInterface}. 80 * 81 * @param fd {@link FileDescriptor} to read/write packets. 82 * @param mtu MTU of the test network. 83 */ PacketReflector(@onNull FileDescriptor fd, int mtu)84 public PacketReflector(@NonNull FileDescriptor fd, int mtu) { 85 super("PacketReflector"); 86 mFd = Objects.requireNonNull(fd); 87 mBuf = new byte[mtu]; 88 } 89 swapBytes(@onNull byte[] buf, int pos1, int pos2, int len)90 private static void swapBytes(@NonNull byte[] buf, int pos1, int pos2, int len) { 91 for (int i = 0; i < len; i++) { 92 byte b = buf[pos1 + i]; 93 buf[pos1 + i] = buf[pos2 + i]; 94 buf[pos2 + i] = b; 95 } 96 } 97 swapAddresses(@onNull byte[] buf, int version)98 private static void swapAddresses(@NonNull byte[] buf, int version) { 99 int addrPos, addrLen; 100 switch (version) { 101 case 4: 102 addrPos = IPV4_ADDR_OFFSET; 103 addrLen = IPV4_ADDR_LENGTH; 104 break; 105 case 6: 106 addrPos = IPV6_ADDR_OFFSET; 107 addrLen = IPV6_ADDR_LENGTH; 108 break; 109 default: 110 throw new IllegalArgumentException(); 111 } 112 swapBytes(buf, addrPos, addrPos + addrLen, addrLen); 113 } 114 115 // Reflect TCP packets: swap the source and destination addresses, but don't change the ports. 116 // This is used by the test to "connect to itself" through the VPN. processTcpPacket(@onNull byte[] buf, int version, int len, int hdrLen)117 private void processTcpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { 118 if (len < hdrLen + TCP_HEADER_LENGTH) { 119 return; 120 } 121 122 // Swap src and dst IP addresses. 123 swapAddresses(buf, version); 124 125 // Send the packet back. 126 writePacket(buf, len); 127 } 128 129 // Echo UDP packets: swap source and destination addresses, and source and destination ports. 130 // This is used by the test to check that the bytes it sends are echoed back. processUdpPacket(@onNull byte[] buf, int version, int len, int hdrLen)131 private void processUdpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { 132 if (len < hdrLen + UDP_HEADER_LENGTH) { 133 return; 134 } 135 136 // Swap src and dst IP addresses. 137 swapAddresses(buf, version); 138 139 // Swap dst and src ports. 140 int portOffset = hdrLen; 141 swapBytes(buf, portOffset, portOffset + 2, 2); 142 143 // Send the packet back. 144 writePacket(buf, len); 145 } 146 processIcmpPacket(@onNull byte[] buf, int version, int len, int hdrLen)147 private void processIcmpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { 148 if (len < hdrLen + ICMP_HEADER_LENGTH) { 149 return; 150 } 151 152 byte type = buf[hdrLen]; 153 if (!(version == 4 && type == ICMP_ECHO) && 154 !(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) { 155 return; 156 } 157 158 // Save the ping packet we received. 159 byte[] request = buf.clone(); 160 161 // Swap src and dst IP addresses, and send the packet back. 162 // This effectively pings the device to see if it replies. 163 swapAddresses(buf, version); 164 writePacket(buf, len); 165 166 // The device should have replied, and buf should now contain a ping response. 167 int received = PacketReflectorUtil.readPacket(mFd, buf); 168 if (received != len) { 169 Log.i(TAG, "Reflecting ping did not result in ping response: " + 170 "read=" + received + " expected=" + len); 171 return; 172 } 173 174 byte replyType = buf[hdrLen]; 175 if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY) 176 || (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) { 177 Log.i(TAG, "Received unexpected ICMP reply: original " + type 178 + ", reply " + replyType); 179 return; 180 } 181 182 // Compare the response we got with the original packet. 183 // The only thing that should have changed are addresses, type and checksum. 184 // Overwrite them with the received bytes and see if the packet is otherwise identical. 185 request[hdrLen] = buf[hdrLen]; // Type 186 request[hdrLen + 2] = buf[hdrLen + 2]; // Checksum byte 1. 187 request[hdrLen + 3] = buf[hdrLen + 3]; // Checksum byte 2. 188 189 // Since Linux kernel 4.2, net.ipv6.auto_flowlabels is set by default, and therefore 190 // the request and reply may have different IPv6 flow label: ignore that as well. 191 if (version == 6) { 192 request[1] = (byte) (request[1] & 0xf0 | buf[1] & 0x0f); 193 request[2] = buf[2]; 194 request[3] = buf[3]; 195 } 196 197 for (int i = 0; i < len; i++) { 198 if (buf[i] != request[i]) { 199 Log.i(TAG, "Received non-matching packet when expecting ping response."); 200 return; 201 } 202 } 203 204 // Now swap the addresses again and reflect the packet. This sends a ping reply. 205 swapAddresses(buf, version); 206 writePacket(buf, len); 207 } 208 writePacket(@onNull byte[] buf, int len)209 private void writePacket(@NonNull byte[] buf, int len) { 210 try { 211 Os.write(mFd, buf, 0, len); 212 } catch (ErrnoException | IOException e) { 213 Log.e(TAG, "Error writing packet: " + e.getMessage()); 214 } 215 } 216 217 // Reads one packet from our mFd, and possibly writes the packet back. processPacket()218 private void processPacket() { 219 int len = PacketReflectorUtil.readPacket(mFd, mBuf); 220 if (len < 1) { 221 // Usually happens when socket read is being interrupted, e.g. stopping PacketReflector. 222 return; 223 } 224 225 int version = mBuf[0] >> 4; 226 int protoPos, hdrLen; 227 if (version == 4) { 228 hdrLen = IPV4_HEADER_LENGTH; 229 protoPos = IPV4_PROTO_OFFSET; 230 } else if (version == 6) { 231 hdrLen = IPV6_HEADER_LENGTH; 232 protoPos = IPV6_PROTO_OFFSET; 233 } else { 234 throw new IllegalStateException("Unexpected version: " + version); 235 } 236 237 if (len < hdrLen) { 238 throw new IllegalStateException("Unexpected buffer length: " + len); 239 } 240 241 byte proto = mBuf[protoPos]; 242 switch (proto) { 243 case IPPROTO_ICMP: 244 // fall through 245 case IPPROTO_ICMPV6: 246 processIcmpPacket(mBuf, version, len, hdrLen); 247 break; 248 case IPPROTO_TCP: 249 processTcpPacket(mBuf, version, len, hdrLen); 250 break; 251 case IPPROTO_UDP: 252 processUdpPacket(mBuf, version, len, hdrLen); 253 break; 254 } 255 } 256 run()257 public void run() { 258 Log.i(TAG, "starting fd=" + mFd + " valid=" + mFd.valid()); 259 while (!interrupted() && mFd.valid()) { 260 processPacket(); 261 } 262 Log.i(TAG, "exiting fd=" + mFd + " valid=" + mFd.valid()); 263 } 264 } 265