Home | History | Annotate | Download | only in arp
      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 
     17 package android.net.arp;
     18 
     19 import android.net.LinkAddress;
     20 import android.net.LinkProperties;
     21 import android.net.RouteInfo;
     22 import android.os.SystemClock;
     23 import android.util.Log;
     24 
     25 import java.io.IOException;
     26 import java.net.InetAddress;
     27 import java.net.Inet6Address;
     28 import java.net.SocketException;
     29 import java.nio.ByteBuffer;
     30 import java.nio.ByteOrder;
     31 import java.util.Arrays;
     32 
     33 import libcore.net.RawSocket;
     34 
     35 /**
     36  * This class allows simple ARP exchanges over an uninitialized network
     37  * interface.
     38  *
     39  * @hide
     40  */
     41 public 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