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 90 // Reflect TCP packets: swap the source and destination addresses, but don't change the ports. 91 // This is used by the test to "connect to itself" through the VPN. processTcpPacket(@onNull byte[] buf, int version, int len, int hdrLen)92 private void processTcpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { 93 if (len < hdrLen + TCP_HEADER_LENGTH) { 94 return; 95 } 96 97 // Swap src and dst IP addresses. 98 PacketReflectorUtil.swapAddresses(buf, version); 99 100 // Send the packet back. 101 writePacket(buf, len); 102 } 103 104 // Echo UDP packets: swap source and destination addresses, and source and destination ports. 105 // 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)106 private void processUdpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { 107 if (len < hdrLen + UDP_HEADER_LENGTH) { 108 return; 109 } 110 111 // Swap src and dst IP addresses. 112 PacketReflectorUtil.swapAddresses(buf, version); 113 114 // Swap dst and src ports. 115 int portOffset = hdrLen; 116 PacketReflectorUtil.swapBytes(buf, portOffset, portOffset + 2, 2); 117 118 // Send the packet back. 119 writePacket(buf, len); 120 } 121 processIcmpPacket(@onNull byte[] buf, int version, int len, int hdrLen)122 private void processIcmpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { 123 if (len < hdrLen + ICMP_HEADER_LENGTH) { 124 return; 125 } 126 127 byte type = buf[hdrLen]; 128 if (!(version == 4 && type == ICMP_ECHO) && 129 !(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) { 130 return; 131 } 132 133 // Save the ping packet we received. 134 byte[] request = buf.clone(); 135 136 // Swap src and dst IP addresses, and send the packet back. 137 // This effectively pings the device to see if it replies. 138 PacketReflectorUtil.swapAddresses(buf, version); 139 writePacket(buf, len); 140 141 // The device should have replied, and buf should now contain a ping response. 142 int received = PacketReflectorUtil.readPacket(mFd, buf); 143 if (received != len) { 144 Log.i(TAG, "Reflecting ping did not result in ping response: " + 145 "read=" + received + " expected=" + len); 146 return; 147 } 148 149 byte replyType = buf[hdrLen]; 150 if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY) 151 || (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) { 152 Log.i(TAG, "Received unexpected ICMP reply: original " + type 153 + ", reply " + replyType); 154 return; 155 } 156 157 // Compare the response we got with the original packet. 158 // The only thing that should have changed are addresses, type and checksum. 159 // Overwrite them with the received bytes and see if the packet is otherwise identical. 160 request[hdrLen] = buf[hdrLen]; // Type 161 request[hdrLen + 2] = buf[hdrLen + 2]; // Checksum byte 1. 162 request[hdrLen + 3] = buf[hdrLen + 3]; // Checksum byte 2. 163 164 // Since Linux kernel 4.2, net.ipv6.auto_flowlabels is set by default, and therefore 165 // the request and reply may have different IPv6 flow label: ignore that as well. 166 if (version == 6) { 167 request[1] = (byte) (request[1] & 0xf0 | buf[1] & 0x0f); 168 request[2] = buf[2]; 169 request[3] = buf[3]; 170 } 171 172 for (int i = 0; i < len; i++) { 173 if (buf[i] != request[i]) { 174 Log.i(TAG, "Received non-matching packet when expecting ping response."); 175 return; 176 } 177 } 178 179 // Now swap the addresses again and reflect the packet. This sends a ping reply. 180 PacketReflectorUtil.swapAddresses(buf, version); 181 writePacket(buf, len); 182 } 183 writePacket(@onNull byte[] buf, int len)184 private void writePacket(@NonNull byte[] buf, int len) { 185 try { 186 Os.write(mFd, buf, 0, len); 187 } catch (ErrnoException | IOException e) { 188 Log.e(TAG, "Error writing packet: " + e.getMessage()); 189 } 190 } 191 192 // Reads one packet from our mFd, and possibly writes the packet back. processPacket()193 private void processPacket() { 194 int len = PacketReflectorUtil.readPacket(mFd, mBuf); 195 if (len < 1) { 196 // Usually happens when socket read is being interrupted, e.g. stopping PacketReflector. 197 return; 198 } 199 200 int version = mBuf[0] >> 4; 201 int protoPos, hdrLen; 202 if (version == 4) { 203 hdrLen = IPV4_HEADER_LENGTH; 204 protoPos = IPV4_PROTO_OFFSET; 205 } else if (version == 6) { 206 hdrLen = IPV6_HEADER_LENGTH; 207 protoPos = IPV6_PROTO_OFFSET; 208 } else { 209 throw new IllegalStateException("Unexpected version: " + version); 210 } 211 212 if (len < hdrLen) { 213 throw new IllegalStateException("Unexpected buffer length: " + len); 214 } 215 216 byte proto = mBuf[protoPos]; 217 switch (proto) { 218 case IPPROTO_ICMP: 219 // fall through 220 case IPPROTO_ICMPV6: 221 processIcmpPacket(mBuf, version, len, hdrLen); 222 break; 223 case IPPROTO_TCP: 224 processTcpPacket(mBuf, version, len, hdrLen); 225 break; 226 case IPPROTO_UDP: 227 processUdpPacket(mBuf, version, len, hdrLen); 228 break; 229 } 230 } 231 run()232 public void run() { 233 Log.i(TAG, "starting fd=" + mFd + " valid=" + mFd.valid()); 234 while (!interrupted() && mFd.valid()) { 235 processPacket(); 236 } 237 Log.i(TAG, "exiting fd=" + mFd + " valid=" + mFd.valid()); 238 } 239 } 240