ArpPeer.java revision 55b9821dffe5991e554841bf871155a2c4024c56
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 for (int i = 0; i < MAC_ADDR_LENGTH; i++) { 62 mMyMac[i] = (byte) Integer.parseInt(mac.substring( 63 i*3, (i*3) + 2), 16); 64 } 65 66 if (myAddr instanceof Inet6Address || peer instanceof Inet6Address) { 67 throw new IllegalArgumentException("IPv6 unsupported"); 68 } 69 70 mPeer = peer; 71 L2_BROADCAST = new byte[MAC_ADDR_LENGTH]; 72 Arrays.fill(L2_BROADCAST, (byte) 0xFF); 73 74 mSocket = new RawSocket(mInterfaceName, RawSocket.ETH_P_ARP); 75 } 76 77 /** 78 * Returns the MAC address (or null if timeout) for the requested 79 * peer. 80 */ 81 public byte[] doArp(int timeoutMillis) { 82 ByteBuffer buf = ByteBuffer.allocate(MAX_LENGTH); 83 byte[] desiredIp = mPeer.getAddress(); 84 long timeout = SystemClock.elapsedRealtime() + timeoutMillis; 85 86 // construct ARP request packet, using a ByteBuffer as a 87 // convenient container 88 buf.clear(); 89 buf.order(ByteOrder.BIG_ENDIAN); 90 91 buf.putShort((short) ETHERNET_TYPE); // Ethernet type, 16 bits 92 buf.putShort(RawSocket.ETH_P_IP); // Protocol type IP, 16 bits 93 buf.put((byte)MAC_ADDR_LENGTH); // MAC address length, 6 bytes 94 buf.put((byte)IPV4_LENGTH); // IPv4 protocol size 95 buf.putShort((short) 1); // ARP opcode 1: 'request' 96 buf.put(mMyMac); // six bytes: sender MAC 97 buf.put(mMyAddr.getAddress()); // four bytes: sender IP address 98 buf.put(new byte[MAC_ADDR_LENGTH]); // target MAC address: unknown 99 buf.put(desiredIp); // target IP address, 4 bytes 100 buf.flip(); 101 mSocket.write(L2_BROADCAST, buf.array(), 0, buf.limit()); 102 103 byte[] recvBuf = new byte[MAX_LENGTH]; 104 105 while (SystemClock.elapsedRealtime() < timeout) { 106 long duration = (long) timeout - SystemClock.elapsedRealtime(); 107 int readLen = mSocket.read(recvBuf, 0, recvBuf.length, -1, 108 (int) duration); 109 110 // Verify packet details. see RFC 826 111 if ((readLen >= ARP_LENGTH) // trailing bytes at times 112 && (recvBuf[0] == 0) && (recvBuf[1] == ETHERNET_TYPE) // type Ethernet 113 && (recvBuf[2] == 8) && (recvBuf[3] == 0) // protocol IP 114 && (recvBuf[4] == MAC_ADDR_LENGTH) // mac length 115 && (recvBuf[5] == IPV4_LENGTH) // IPv4 protocol size 116 && (recvBuf[6] == 0) && (recvBuf[7] == 2) // ARP reply 117 // verify desired IP address 118 && (recvBuf[14] == desiredIp[0]) && (recvBuf[15] == desiredIp[1]) 119 && (recvBuf[16] == desiredIp[2]) && (recvBuf[17] == desiredIp[3])) 120 { 121 // looks good. copy out the MAC 122 byte[] result = new byte[MAC_ADDR_LENGTH]; 123 System.arraycopy(recvBuf, 8, result, 0, MAC_ADDR_LENGTH); 124 return result; 125 } 126 } 127 128 return null; 129 } 130 131 public static boolean doArp(String myMacAddress, LinkProperties linkProperties, 132 int timeoutMillis, int numArpPings, int minArpResponses) { 133 String interfaceName = linkProperties.getInterfaceName(); 134 InetAddress inetAddress = null; 135 InetAddress gateway = null; 136 boolean success; 137 138 for (LinkAddress la : linkProperties.getLinkAddresses()) { 139 inetAddress = la.getAddress(); 140 break; 141 } 142 143 for (RouteInfo route : linkProperties.getRoutes()) { 144 gateway = route.getGateway(); 145 break; 146 } 147 148 try { 149 ArpPeer peer = new ArpPeer(interfaceName, inetAddress, myMacAddress, gateway); 150 int responses = 0; 151 for (int i=0; i < numArpPings; i++) { 152 if(peer.doArp(timeoutMillis) != null) responses++; 153 } 154 if (DBG) Log.d(TAG, "ARP test result: " + responses + "/" + numArpPings); 155 success = (responses >= minArpResponses); 156 peer.close(); 157 } catch (SocketException se) { 158 //Consider an Arp socket creation issue as a successful Arp 159 //test to avoid any wifi connectivity issues 160 Log.e(TAG, "ARP test initiation failure: " + se); 161 success = true; 162 } 163 return success; 164 } 165 166 public void close() { 167 try { 168 mSocket.close(); 169 } catch (IOException ex) { 170 } 171 } 172} 173