Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 07573b32 authored by Irfan Sheriff's avatar Irfan Sheriff
Browse files

Improve Wi-Fi hand-off

When Wi-fi connects at L2 layer, the beacons reach and the device
can maintain a connection to the access point, but the application
connectivity can be flaky (due to bigger packet size exchange).

We now use Watchdog to monitor the quality of the last hop on
Wi-Fi using signal strength and ARP connectivity as indicators
to decide if the link is good enough to switch to Wi-Fi as the uplink.

ARP pings are useful for link validation but can still get through
when the application traffic fails to go through and thus not best indicator
real packet loss since they are tiny packets (28 bytes) and have
much low chance of packet corruption than the regular data
packets.

Signal strength and ARP used together ends up working well in tests.
The goal is to switch to Wi-Fi after validating ARP transfer
and RSSI and then switching out of Wi-Fi when we hit a low
signal strength threshold and waiting until the signal strength
improves and validating ARP transfer.

Change-Id: Ica593291ec7772da892f03cf45b649635b730c47
parent 6b48f088
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -11807,6 +11807,7 @@ package android.net {
    enum_constant public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR;
    enum_constant public static final android.net.NetworkInfo.DetailedState SCANNING;
    enum_constant public static final android.net.NetworkInfo.DetailedState SUSPENDED;
    enum_constant public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK;
  }
  public static final class NetworkInfo.State extends java.lang.Enum {
+4 −1
Original line number Diff line number Diff line
@@ -77,7 +77,9 @@ public class NetworkInfo implements Parcelable {
        /** Attempt to connect failed. */
        FAILED,
        /** Access to this network is blocked. */
        BLOCKED
        BLOCKED,
        /** Link has poor connectivity. */
        VERIFYING_POOR_LINK
    }

    /**
@@ -94,6 +96,7 @@ public class NetworkInfo implements Parcelable {
        stateMap.put(DetailedState.CONNECTING, State.CONNECTING);
        stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING);
        stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING);
        stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING);
        stateMap.put(DetailedState.CONNECTED, State.CONNECTED);
        stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED);
        stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
+132 −0
Original line number Diff line number Diff line
/*
 * 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.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 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;
    private static final String TAG = "ArpPeer";

    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 void close() {
        try {
            mSocket.close();
        } catch (IOException ex) {
        }
    }
}
+12 −43
Original line number Diff line number Diff line
@@ -3125,15 +3125,8 @@ public final class Settings {
         * ms delay before rechecking an 'online' wifi connection when it is thought to be unstable.
         * @hide
         */
        public static final String WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS =
                "wifi_watchdog_dns_check_short_interval_ms";

        /**
         * ms delay before rechecking an 'online' wifi connection when it is thought to be stable.
         * @hide
         */
        public static final String WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS =
                "wifi_watchdog_dns_check_long_interval_ms";
        public static final String WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS =
                "wifi_watchdog_arp_interval_ms";

        /**
         * ms delay before rechecking a connect SSID for walled garden with a http download.
@@ -3143,44 +3136,28 @@ public final class Settings {
                "wifi_watchdog_walled_garden_interval_ms";

        /**
         * max blacklist calls on an SSID before full dns check failures disable the network.
         * Number of ARP pings per check.
         * @hide
         */
        public static final String WIFI_WATCHDOG_MAX_SSID_BLACKLISTS =
                "wifi_watchdog_max_ssid_blacklists";
        public static final String WIFI_WATCHDOG_NUM_ARP_PINGS = "wifi_watchdog_num_arp_pings";

        /**
         * Number of dns pings per check.
         * Minimum number of responses to the arp pings to consider the test 'successful'.
         * @hide
         */
        public static final String WIFI_WATCHDOG_NUM_DNS_PINGS = "wifi_watchdog_num_dns_pings";
        public static final String WIFI_WATCHDOG_MIN_ARP_RESPONSES =
                "wifi_watchdog_min_arp_responses";

        /**
         * Minimum number of responses to the dns pings to consider the test 'successful'.
         * Timeout on ARP pings
         * @hide
         */
        public static final String WIFI_WATCHDOG_MIN_DNS_RESPONSES =
                "wifi_watchdog_min_dns_responses";
        public static final String WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS =
                "wifi_watchdog_arp_ping_timeout_ms";

        /**
         * Timeout on dns pings
         * @hide
         */
        public static final String WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS =
                "wifi_watchdog_dns_ping_timeout_ms";

        /**
         * We consider action from a 'blacklist' call to have finished by the end of
         * this interval.  If we are connected to the same AP with no network connection,
         * we are likely stuck on an SSID with no external connectivity.
         * @hide
         */
        public static final String WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS =
                "wifi_watchdog_blacklist_followup_interval_ms";

        /**
         * Setting to turn off poor network avoidance on Wi-Fi. Feature is disabled by default and
         * the setting needs to be set to 1 to enable it.
         * Setting to turn off poor network avoidance on Wi-Fi. Feature is enabled by default and
         * the setting needs to be set to 0 to disable it.
         * @hide
         */
        public static final String WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED =
@@ -3203,14 +3180,6 @@ public final class Settings {
        public static final String WIFI_WATCHDOG_WALLED_GARDEN_URL =
                "wifi_watchdog_walled_garden_url";

        /**
         * Boolean to determine whether to notify on disabling a network.  Secure setting used
         * to notify user only once.
         * @hide
         */
        public static final String WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP =
                "wifi_watchdog_show_disabled_network_popup";

        /**
         * The maximum number of times we will retry a connection to an access
         * point for which we have failed in acquiring an IP address from DHCP.
+4 −11
Original line number Diff line number Diff line
@@ -203,7 +203,7 @@ public class NetworkController extends BroadcastReceiver {
        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        Handler handler = new WifiHandler();
        mWifiChannel = new AsyncChannel();
        Messenger wifiMessenger = mWifiManager.getMessenger();
        Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger();
        if (wifiMessenger != null) {
            mWifiChannel.connect(mContext, handler, wifiMessenger);
        }
@@ -767,18 +767,11 @@ public class NetworkController extends BroadcastReceiver {
            } else if (!mWifiConnected) {
                mWifiSsid = null;
            }
            // Apparently the wifi level is not stable at this point even if we've just connected to
            // the network; we need to wait for an RSSI_CHANGED_ACTION for that. So let's just set
            // it to 0 for now
            mWifiLevel = 0;
            mWifiRssi = -200;
        } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
            if (mWifiConnected) {
            mWifiRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
            mWifiLevel = WifiManager.calculateSignalLevel(
                    mWifiRssi, WifiIcons.WIFI_LEVEL_COUNT);
        }
        }

        updateWifiIcons();
    }
Loading