| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.net.arp; |
| |
| import android.net.LinkAddress; |
| import android.net.LinkProperties; |
| import android.net.RouteInfo; |
| import android.os.SystemClock; |
| import android.util.Log; |
| |
| import java.io.IOException; |
| import java.net.InetAddress; |
| import java.net.Inet6Address; |
| import java.net.SocketException; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.util.Arrays; |
| |
| import libcore.net.RawSocket; |
| |
| /** |
| * This class allows simple ARP exchanges over an uninitialized network |
| * interface. |
| * |
| * @hide |
| */ |
| public class ArpPeer { |
| private static final boolean DBG = false; |
| private static final String TAG = "ArpPeer"; |
| private String mInterfaceName; |
| private final InetAddress mMyAddr; |
| private final byte[] mMyMac = new byte[6]; |
| private final InetAddress mPeer; |
| private final RawSocket mSocket; |
| private final byte[] L2_BROADCAST; // TODO: refactor from DhcpClient.java |
| private static final int MAX_LENGTH = 1500; // refactor from DhcpPacket.java |
| private static final int ETHERNET_TYPE = 1; |
| private static final int ARP_LENGTH = 28; |
| private static final int MAC_ADDR_LENGTH = 6; |
| private static final int IPV4_LENGTH = 4; |
| |
| public ArpPeer(String interfaceName, InetAddress myAddr, String mac, |
| InetAddress peer) throws SocketException { |
| mInterfaceName = interfaceName; |
| mMyAddr = myAddr; |
| |
| for (int i = 0; i < MAC_ADDR_LENGTH; i++) { |
| mMyMac[i] = (byte) Integer.parseInt(mac.substring( |
| i*3, (i*3) + 2), 16); |
| } |
| |
| if (myAddr instanceof Inet6Address || peer instanceof Inet6Address) { |
| throw new IllegalArgumentException("IPv6 unsupported"); |
| } |
| |
| mPeer = peer; |
| L2_BROADCAST = new byte[MAC_ADDR_LENGTH]; |
| Arrays.fill(L2_BROADCAST, (byte) 0xFF); |
| |
| mSocket = new RawSocket(mInterfaceName, RawSocket.ETH_P_ARP); |
| } |
| |
| /** |
| * Returns the MAC address (or null if timeout) for the requested |
| * peer. |
| */ |
| public byte[] doArp(int timeoutMillis) { |
| ByteBuffer buf = ByteBuffer.allocate(MAX_LENGTH); |
| byte[] desiredIp = mPeer.getAddress(); |
| long timeout = SystemClock.elapsedRealtime() + timeoutMillis; |
| |
| // construct ARP request packet, using a ByteBuffer as a |
| // convenient container |
| buf.clear(); |
| buf.order(ByteOrder.BIG_ENDIAN); |
| |
| buf.putShort((short) ETHERNET_TYPE); // Ethernet type, 16 bits |
| buf.putShort(RawSocket.ETH_P_IP); // Protocol type IP, 16 bits |
| buf.put((byte)MAC_ADDR_LENGTH); // MAC address length, 6 bytes |
| buf.put((byte)IPV4_LENGTH); // IPv4 protocol size |
| buf.putShort((short) 1); // ARP opcode 1: 'request' |
| buf.put(mMyMac); // six bytes: sender MAC |
| buf.put(mMyAddr.getAddress()); // four bytes: sender IP address |
| buf.put(new byte[MAC_ADDR_LENGTH]); // target MAC address: unknown |
| buf.put(desiredIp); // target IP address, 4 bytes |
| buf.flip(); |
| mSocket.write(L2_BROADCAST, buf.array(), 0, buf.limit()); |
| |
| byte[] recvBuf = new byte[MAX_LENGTH]; |
| |
| while (SystemClock.elapsedRealtime() < timeout) { |
| long duration = (long) timeout - SystemClock.elapsedRealtime(); |
| int readLen = mSocket.read(recvBuf, 0, recvBuf.length, -1, |
| (int) duration); |
| |
| // Verify packet details. see RFC 826 |
| if ((readLen >= ARP_LENGTH) // trailing bytes at times |
| && (recvBuf[0] == 0) && (recvBuf[1] == ETHERNET_TYPE) // type Ethernet |
| && (recvBuf[2] == 8) && (recvBuf[3] == 0) // protocol IP |
| && (recvBuf[4] == MAC_ADDR_LENGTH) // mac length |
| && (recvBuf[5] == IPV4_LENGTH) // IPv4 protocol size |
| && (recvBuf[6] == 0) && (recvBuf[7] == 2) // ARP reply |
| // verify desired IP address |
| && (recvBuf[14] == desiredIp[0]) && (recvBuf[15] == desiredIp[1]) |
| && (recvBuf[16] == desiredIp[2]) && (recvBuf[17] == desiredIp[3])) |
| { |
| // looks good. copy out the MAC |
| byte[] result = new byte[MAC_ADDR_LENGTH]; |
| System.arraycopy(recvBuf, 8, result, 0, MAC_ADDR_LENGTH); |
| return result; |
| } |
| } |
| |
| return null; |
| } |
| |
| public static boolean doArp(String myMacAddress, LinkProperties linkProperties, |
| int timeoutMillis, int numArpPings, int minArpResponses) { |
| String interfaceName = linkProperties.getInterfaceName(); |
| InetAddress inetAddress = null; |
| InetAddress gateway = null; |
| boolean success; |
| |
| for (LinkAddress la : linkProperties.getLinkAddresses()) { |
| inetAddress = la.getAddress(); |
| break; |
| } |
| |
| for (RouteInfo route : linkProperties.getRoutes()) { |
| gateway = route.getGateway(); |
| break; |
| } |
| |
| try { |
| ArpPeer peer = new ArpPeer(interfaceName, inetAddress, myMacAddress, gateway); |
| int responses = 0; |
| for (int i=0; i < numArpPings; i++) { |
| if(peer.doArp(timeoutMillis) != null) responses++; |
| } |
| if (DBG) Log.d(TAG, "ARP test result: " + responses + "/" + numArpPings); |
| success = (responses >= minArpResponses); |
| peer.close(); |
| } catch (SocketException se) { |
| //Consider an Arp socket creation issue as a successful Arp |
| //test to avoid any wifi connectivity issues |
| Log.e(TAG, "ARP test initiation failure: " + se); |
| success = true; |
| } |
| return success; |
| } |
| |
| public void close() { |
| try { |
| mSocket.close(); |
| } catch (IOException ex) { |
| } |
| } |
| } |