1/* 2 * Copyright (C) 2012 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 17package android.net.arp; 18 19import android.net.LinkAddress; 20import android.net.LinkProperties; 21import android.net.RouteInfo; 22import android.os.SystemClock; 23import android.util.Log; 24 25import java.io.IOException; 26import java.net.InetAddress; 27import java.net.Inet6Address; 28import java.net.SocketException; 29import java.nio.ByteBuffer; 30import java.nio.ByteOrder; 31import java.util.Arrays; 32 33import libcore.net.RawSocket; 34 35/** 36 * This class allows simple ARP exchanges over an uninitialized network 37 * interface. 38 * 39 * @hide 40 */ 41public class ArpPeer { 42 private static final boolean DBG = false; 43 private static final String TAG = "ArpPeer"; 44 private String mInterfaceName; 45 private final InetAddress mMyAddr; 46 private final byte[] mMyMac = new byte[6]; 47 private final InetAddress mPeer; 48 private final RawSocket mSocket; 49 private final byte[] L2_BROADCAST; // TODO: refactor from DhcpClient.java 50 private static final int MAX_LENGTH = 1500; // refactor from DhcpPacket.java 51 private static final int ETHERNET_TYPE = 1; 52 private static final int ARP_LENGTH = 28; 53 private static final int MAC_ADDR_LENGTH = 6; 54 private static final int IPV4_LENGTH = 4; 55 56 public ArpPeer(String interfaceName, InetAddress myAddr, String mac, 57 InetAddress peer) throws SocketException { 58 mInterfaceName = interfaceName; 59 mMyAddr = myAddr; 60 61 if (mac != null) { 62 for (int i = 0; i < MAC_ADDR_LENGTH; i++) { 63 mMyMac[i] = (byte) Integer.parseInt(mac.substring( 64 i*3, (i*3) + 2), 16); 65 } 66 } 67 68 if (myAddr instanceof Inet6Address || peer instanceof Inet6Address) { 69 throw new IllegalArgumentException("IPv6 unsupported"); 70 } 71 72 mPeer = peer; 73 L2_BROADCAST = new byte[MAC_ADDR_LENGTH]; 74 Arrays.fill(L2_BROADCAST, (byte) 0xFF); 75 76 mSocket = new RawSocket(mInterfaceName, RawSocket.ETH_P_ARP); 77 } 78 79 /** 80 * Returns the MAC address (or null if timeout) for the requested 81 * peer. 82 */ 83 public byte[] doArp(int timeoutMillis) { 84 ByteBuffer buf = ByteBuffer.allocate(MAX_LENGTH); 85 byte[] desiredIp = mPeer.getAddress(); 86 long timeout = SystemClock.elapsedRealtime() + timeoutMillis; 87 88 // construct ARP request packet, using a ByteBuffer as a 89 // convenient container 90 buf.clear(); 91 buf.order(ByteOrder.BIG_ENDIAN); 92 93 buf.putShort((short) ETHERNET_TYPE); // Ethernet type, 16 bits 94 buf.putShort(RawSocket.ETH_P_IP); // Protocol type IP, 16 bits 95 buf.put((byte)MAC_ADDR_LENGTH); // MAC address length, 6 bytes 96 buf.put((byte)IPV4_LENGTH); // IPv4 protocol size 97 buf.putShort((short) 1); // ARP opcode 1: 'request' 98 buf.put(mMyMac); // six bytes: sender MAC 99 buf.put(mMyAddr.getAddress()); // four bytes: sender IP address 100 buf.put(new byte[MAC_ADDR_LENGTH]); // target MAC address: unknown 101 buf.put(desiredIp); // target IP address, 4 bytes 102 buf.flip(); 103 mSocket.write(L2_BROADCAST, buf.array(), 0, buf.limit()); 104 105 byte[] recvBuf = new byte[MAX_LENGTH]; 106 107 while (SystemClock.elapsedRealtime() < timeout) { 108 long duration = (long) timeout - SystemClock.elapsedRealtime(); 109 int readLen = mSocket.read(recvBuf, 0, recvBuf.length, -1, 110 (int) duration); 111 112 // Verify packet details. see RFC 826 113 if ((readLen >= ARP_LENGTH) // trailing bytes at times 114 && (recvBuf[0] == 0) && (recvBuf[1] == ETHERNET_TYPE) // type Ethernet 115 && (recvBuf[2] == 8) && (recvBuf[3] == 0) // protocol IP 116 && (recvBuf[4] == MAC_ADDR_LENGTH) // mac length 117 && (recvBuf[5] == IPV4_LENGTH) // IPv4 protocol size 118 && (recvBuf[6] == 0) && (recvBuf[7] == 2) // ARP reply 119 // verify desired IP address 120 && (recvBuf[14] == desiredIp[0]) && (recvBuf[15] == desiredIp[1]) 121 && (recvBuf[16] == desiredIp[2]) && (recvBuf[17] == desiredIp[3])) 122 { 123 // looks good. copy out the MAC 124 byte[] result = new byte[MAC_ADDR_LENGTH]; 125 System.arraycopy(recvBuf, 8, result, 0, MAC_ADDR_LENGTH); 126 return result; 127 } 128 } 129 130 return null; 131 } 132 133 public static boolean doArp(String myMacAddress, LinkProperties linkProperties, 134 int timeoutMillis, int numArpPings, int minArpResponses) { 135 String interfaceName = linkProperties.getInterfaceName(); 136 InetAddress inetAddress = null; 137 InetAddress gateway = null; 138 boolean success; 139 140 for (LinkAddress la : linkProperties.getLinkAddresses()) { 141 inetAddress = la.getAddress(); 142 break; 143 } 144 145 for (RouteInfo route : linkProperties.getRoutes()) { 146 gateway = route.getGateway(); 147 break; 148 } 149 150 try { 151 ArpPeer peer = new ArpPeer(interfaceName, inetAddress, myMacAddress, gateway); 152 int responses = 0; 153 for (int i=0; i < numArpPings; i++) { 154 if(peer.doArp(timeoutMillis) != null) responses++; 155 } 156 if (DBG) Log.d(TAG, "ARP test result: " + responses + "/" + numArpPings); 157 success = (responses >= minArpResponses); 158 peer.close(); 159 } catch (SocketException se) { 160 //Consider an Arp socket creation issue as a successful Arp 161 //test to avoid any wifi connectivity issues 162 Log.e(TAG, "ARP test initiation failure: " + se); 163 success = true; 164 } 165 return success; 166 } 167 168 public void close() { 169 try { 170 mSocket.close(); 171 } catch (IOException ex) { 172 } 173 } 174} 175