/* * Copyright (C) 2008 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 com.android.server; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.net.NetworkInfo; import android.net.DhcpInfo; import android.net.wifi.ScanResult; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiStateTracker; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.provider.Settings; import android.text.TextUtils; import android.util.Config; import android.util.Slog; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.List; import java.util.Random; /** * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi * network with multiple access points. After the framework successfully * connects to an access point, the watchdog verifies whether the DNS server is * reachable. If not, the watchdog blacklists the current access point, leading * to a connection on another access point within the same network. *
* The watchdog has a few safeguards: *
* The watchdog checks for connectivity on an access point by ICMP pinging the * DNS. There are settings that allow disabling the watchdog, or tweaking the * acceptable packet loss (and other various parameters). *
* The core logic of the watchdog is done on the main watchdog thread. Wi-Fi
* callbacks can come in on other threads, so we must queue messages to the main
* watchdog thread's handler. Most (if not all) state is only written to from
* the main thread.
*
* {@hide}
*/
public class WifiWatchdogService {
private static final String TAG = "WifiWatchdogService";
private static final boolean V = false || Config.LOGV;
private static final boolean D = true || Config.LOGD;
private Context mContext;
private ContentResolver mContentResolver;
private WifiStateTracker mWifiStateTracker;
private WifiManager mWifiManager;
/**
* The main watchdog thread.
*/
private WifiWatchdogThread mThread;
/**
* The handler for the main watchdog thread.
*/
private WifiWatchdogHandler mHandler;
private ContentObserver mContentObserver;
/**
* The current watchdog state. Only written from the main thread!
*/
private WatchdogState mState = WatchdogState.IDLE;
/**
* The SSID of the network that the watchdog is currently monitoring. Only
* touched in the main thread!
*/
private String mSsid;
/**
* The number of access points in the current network ({@link #mSsid}) that
* have been checked. Only touched in the main thread!
*/
private int mNumApsChecked;
/** Whether the current AP check should be canceled. */
private boolean mShouldCancel;
WifiWatchdogService(Context context, WifiStateTracker wifiStateTracker) {
mContext = context;
mContentResolver = context.getContentResolver();
mWifiStateTracker = wifiStateTracker;
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
createThread();
// The content observer to listen needs a handler, which createThread creates
registerForSettingsChanges();
if (isWatchdogEnabled()) {
registerForWifiBroadcasts();
}
if (V) {
myLogV("WifiWatchdogService: Created");
}
}
/**
* Observes the watchdog on/off setting, and takes action when changed.
*/
private void registerForSettingsChanges() {
ContentResolver contentResolver = mContext.getContentResolver();
contentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), false,
mContentObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
if (isWatchdogEnabled()) {
registerForWifiBroadcasts();
} else {
unregisterForWifiBroadcasts();
if (mHandler != null) {
mHandler.disableWatchdog();
}
}
}
});
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
*/
private boolean isWatchdogEnabled() {
return Settings.Secure.getInt(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT
*/
private int getApCount() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_AP_COUNT, 2);
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT
*/
private int getInitialIgnoredPingCount() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT , 2);
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT
*/
private int getPingCount() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_PING_COUNT, 4);
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS
*/
private int getPingTimeoutMs() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS, 500);
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS
*/
private int getPingDelayMs() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS, 250);
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE
*/
private int getAcceptablePacketLossPercentage() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE, 25);
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS
*/
private int getMaxApChecks() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS, 7);
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED
*/
private boolean isBackgroundCheckEnabled() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED, 1) == 1;
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS
*/
private int getBackgroundCheckDelayMs() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS, 60000);
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS
*/
private int getBackgroundCheckTimeoutMs() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS, 1000);
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_WATCH_LIST
* @return the comma-separated list of SSIDs
*/
private String getWatchList() {
return Settings.Secure.getString(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_WATCH_LIST);
}
/**
* Registers to receive the necessary Wi-Fi broadcasts.
*/
private void registerForWifiBroadcasts() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mContext.registerReceiver(mReceiver, intentFilter);
}
/**
* Unregisters from receiving the Wi-Fi broadcasts.
*/
private void unregisterForWifiBroadcasts() {
mContext.unregisterReceiver(mReceiver);
}
/**
* Creates the main watchdog thread, including waiting for the handler to be
* created.
*/
private void createThread() {
mThread = new WifiWatchdogThread();
mThread.start();
waitForHandlerCreation();
}
/**
* Unregister broadcasts and quit the watchdog thread
*/
private void quit() {
unregisterForWifiBroadcasts();
mContext.getContentResolver().unregisterContentObserver(mContentObserver);
mHandler.removeAllActions();
mHandler.getLooper().quit();
}
/**
* Waits for the main watchdog thread to create the handler.
*/
private void waitForHandlerCreation() {
synchronized(this) {
while (mHandler == null) {
try {
// Wait for the handler to be set by the other thread
wait();
} catch (InterruptedException e) {
Slog.e(TAG, "Interrupted while waiting on handler.");
}
}
}
}
// Utility methods
/**
* Logs with the current thread.
*/
private static void myLogV(String message) {
Slog.v(TAG, "(" + Thread.currentThread().getName() + ") " + message);
}
private static void myLogD(String message) {
Slog.d(TAG, "(" + Thread.currentThread().getName() + ") " + message);
}
/**
* Gets the DNS of the current AP.
*
* @return The DNS of the current AP.
*/
private int getDns() {
DhcpInfo addressInfo = mWifiManager.getDhcpInfo();
if (addressInfo != null) {
return addressInfo.dns1;
} else {
return -1;
}
}
/**
* Checks whether the DNS can be reached using multiple attempts according
* to the current setting values.
*
* @return Whether the DNS is reachable
*/
private boolean checkDnsConnectivity() {
int dns = getDns();
if (dns == -1) {
if (V) {
myLogV("checkDnsConnectivity: Invalid DNS, returning false");
}
return false;
}
if (V) {
myLogV("checkDnsConnectivity: Checking 0x" +
Integer.toHexString(Integer.reverseBytes(dns)) + " for connectivity");
}
int numInitialIgnoredPings = getInitialIgnoredPingCount();
int numPings = getPingCount();
int pingDelay = getPingDelayMs();
int acceptableLoss = getAcceptablePacketLossPercentage();
/** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */
int ignoredPingCounter = 0;
int pingCounter = 0;
int successCounter = 0;
// No connectivity check needed
if (numPings == 0) {
return true;
}
// Do the initial pings that we ignore
for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) {
if (shouldCancel()) return false;
boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs());
if (dnsAlive) {
/*
* Successful "ignored" pings are *not* ignored (they count in the total number
* of pings), but failures are really ignored.
*/
pingCounter++;
successCounter++;
}
if (V) {
Slog.v(TAG, (dnsAlive ? " +" : " Ignored: -"));
}
if (shouldCancel()) return false;
try {
Thread.sleep(pingDelay);
} catch (InterruptedException e) {
Slog.w(TAG, "Interrupted while pausing between pings", e);
}
}
// Do the pings that we use to measure packet loss
for (; pingCounter < numPings; pingCounter++) {
if (shouldCancel()) return false;
if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) {
successCounter++;
if (V) {
Slog.v(TAG, " +");
}
} else {
if (V) {
Slog.v(TAG, " -");
}
}
if (shouldCancel()) return false;
try {
Thread.sleep(pingDelay);
} catch (InterruptedException e) {
Slog.w(TAG, "Interrupted while pausing between pings", e);
}
}
int packetLossPercentage = 100 * (numPings - successCounter) / numPings;
if (D) {
Slog.d(TAG, packetLossPercentage
+ "% packet loss (acceptable is " + acceptableLoss + "%)");
}
return !shouldCancel() && (packetLossPercentage <= acceptableLoss);
}
private boolean backgroundCheckDnsConnectivity() {
int dns = getDns();
if (false && V) {
myLogV("backgroundCheckDnsConnectivity: Background checking " + dns +
" for connectivity");
}
if (dns == -1) {
if (V) {
myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false");
}
return false;
}
return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs());
}
/**
* Signals the current action to cancel.
*/
private void cancelCurrentAction() {
mShouldCancel = true;
}
/**
* Helper to check whether to cancel.
*
* @return Whether to cancel processing the action.
*/
private boolean shouldCancel() {
if (V && mShouldCancel) {
myLogV("shouldCancel: Cancelling");
}
return mShouldCancel;
}
// Wi-Fi initiated callbacks (could be executed in another thread)
/**
* Called when connected to an AP (this can be the next AP in line, or
* it can be a completely different network).
*
* @param ssid The SSID of the access point.
* @param bssid The BSSID of the access point.
*/
private void onConnected(String ssid, String bssid) {
if (V) {
myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid);
}
/*
* The current action being processed by the main watchdog thread is now
* stale, so cancel it.
*/
cancelCurrentAction();
if ((mSsid == null) || !mSsid.equals(ssid)) {
/*
* This is a different network than what the main watchdog thread is
* processing, dispatch the network change message on the main thread.
*/
mHandler.dispatchNetworkChanged(ssid);
}
if (requiresWatchdog(ssid, bssid)) {
if (D) {
myLogD(ssid + " (" + bssid + ") requires the watchdog");
}
// This access point requires a watchdog, so queue the check on the main thread
mHandler.checkAp(new AccessPoint(ssid, bssid));
} else {
if (D) {
myLogD(ssid + " (" + bssid + ") does not require the watchdog");
}
// This access point does not require a watchdog, so queue idle on the main thread
mHandler.idle();
}
}
/**
* Called when Wi-Fi is enabled.
*/
private void onEnabled() {
cancelCurrentAction();
// Queue a hard-reset of the state on the main thread
mHandler.reset();
}
/**
* Called when disconnected (or some other event similar to being disconnected).
*/
private void onDisconnected() {
if (V) {
myLogV("onDisconnected");
}
/*
* Disconnected from an access point, the action being processed by the
* watchdog thread is now stale, so cancel it.
*/
cancelCurrentAction();
// Dispatch the disconnected to the main watchdog thread
mHandler.dispatchDisconnected();
// Queue the action to go idle
mHandler.idle();
}
/**
* Checks whether an access point requires watchdog monitoring.
*
* @param ssid The SSID of the access point.
* @param bssid The BSSID of the access point.
* @return Whether the access point/network should be monitored by the
* watchdog.
*/
private boolean requiresWatchdog(String ssid, String bssid) {
if (V) {
myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid);
}
WifiInfo info = null;
if (ssid == null) {
/*
* This is called from a Wi-Fi callback, so assume the WifiInfo does
* not have stale data.
*/
info = mWifiManager.getConnectionInfo();
ssid = info.getSSID();
if (ssid == null) {
// It's still null, give up
if (V) {
Slog.v(TAG, " Invalid SSID, returning false");
}
return false;
}
}
if (TextUtils.isEmpty(bssid)) {
// Similar as above
if (info == null) {
info = mWifiManager.getConnectionInfo();
}
bssid = info.getBSSID();
if (TextUtils.isEmpty(bssid)) {
// It's still null, give up
if (V) {
Slog.v(TAG, " Invalid BSSID, returning false");
}
return false;
}
}
if (!isOnWatchList(ssid)) {
if (V) {
Slog.v(TAG, " SSID not on watch list, returning false");
}
return false;
}
// The watchdog only monitors networks with multiple APs
if (!hasRequiredNumberOfAps(ssid)) {
return false;
}
return true;
}
private boolean isOnWatchList(String ssid) {
String watchList;
if (ssid == null || (watchList = getWatchList()) == null) {
return false;
}
String[] list = watchList.split(" *, *");
for (String name : list) {
if (ssid.equals(name)) {
return true;
}
}
return false;
}
/**
* Checks if the current scan results have multiple access points with an SSID.
*
* @param ssid The SSID to check.
* @return Whether the SSID has multiple access points.
*/
private boolean hasRequiredNumberOfAps(String ssid) {
List
* There is little logic inside this class, instead methods of the form
* "handle___" are called in the main {@link WifiWatchdogService}.
*/
private class WifiWatchdogHandler extends Handler {
/** Check whether the AP is "good". The object will be an {@link AccessPoint}. */
static final int ACTION_CHECK_AP = 1;
/** Go into the idle state. */
static final int ACTION_IDLE = 2;
/**
* Performs a periodic background check whether the AP is still "good".
* The object will be an {@link AccessPoint}.
*/
static final int ACTION_BACKGROUND_CHECK_AP = 3;
/**
* Go to sleep for the current network. We are conservative with making
* this a message rather than action. We want to make sure our main
* thread sees this message, but if it were an action it could be
* removed from the queue and replaced by another action. The main
* thread will ensure when it sees the message that the state is still
* valid for going to sleep.
*
* For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}.
*/
static final int MESSAGE_SLEEP = 101;
/** Disables the watchdog. */
static final int MESSAGE_DISABLE_WATCHDOG = 102;
/** The network has changed. */
static final int MESSAGE_NETWORK_CHANGED = 103;
/** The current access point has disconnected. */
static final int MESSAGE_DISCONNECTED = 104;
/** Performs a hard-reset on the watchdog state. */
static final int MESSAGE_RESET = 105;
void checkAp(AccessPoint ap) {
removeAllActions();
sendMessage(obtainMessage(ACTION_CHECK_AP, ap));
}
void backgroundCheckAp(AccessPoint ap) {
if (!isBackgroundCheckEnabled()) return;
removeAllActions();
sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap),
getBackgroundCheckDelayMs());
}
void idle() {
removeAllActions();
sendMessage(obtainMessage(ACTION_IDLE));
}
void sleep(String ssid) {
removeAllActions();
sendMessage(obtainMessage(MESSAGE_SLEEP, ssid));
}
void disableWatchdog() {
removeAllActions();
sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG));
}
void dispatchNetworkChanged(String ssid) {
removeAllActions();
sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid));
}
void dispatchDisconnected() {
removeAllActions();
sendMessage(obtainMessage(MESSAGE_DISCONNECTED));
}
void reset() {
removeAllActions();
sendMessage(obtainMessage(MESSAGE_RESET));
}
private void removeAllActions() {
removeMessages(ACTION_CHECK_AP);
removeMessages(ACTION_IDLE);
removeMessages(ACTION_BACKGROUND_CHECK_AP);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_NETWORK_CHANGED:
handleNetworkChanged((String) msg.obj);
break;
case ACTION_CHECK_AP:
handleCheckAp((AccessPoint) msg.obj);
break;
case ACTION_BACKGROUND_CHECK_AP:
handleBackgroundCheckAp((AccessPoint) msg.obj);
break;
case MESSAGE_SLEEP:
handleSleep((String) msg.obj);
break;
case ACTION_IDLE:
handleIdle();
break;
case MESSAGE_DISABLE_WATCHDOG:
handleIdle();
break;
case MESSAGE_DISCONNECTED:
handleDisconnected();
break;
case MESSAGE_RESET:
handleReset();
break;
}
}
}
/**
* Receives Wi-Fi broadcasts.
*
* There is little logic in this class, instead methods of the form "on___"
* are called in the {@link WifiWatchdogService}.
*/
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
handleNetworkStateChanged(
(NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
} else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN));
}
}
private void handleNetworkStateChanged(NetworkInfo info) {
if (V) {
myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: "
+ info);
}
switch (info.getState()) {
case CONNECTED:
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
if (V) {
myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: "
+ wifiInfo.getSSID()
+ ", BSSID: "
+ wifiInfo.getBSSID() + ", ignoring event");
}
return;
}
onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID());
break;
case DISCONNECTED:
onDisconnected();
break;
}
}
private void handleWifiStateChanged(int wifiState) {
if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
quit();
} else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
onEnabled();
}
}
};
/**
* Describes an access point by its SSID and BSSID.
*/
private static class AccessPoint {
String ssid;
String bssid;
AccessPoint(String ssid, String bssid) {
this.ssid = ssid;
this.bssid = bssid;
}
private boolean hasNull() {
return ssid == null || bssid == null;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof AccessPoint)) return false;
AccessPoint otherAp = (AccessPoint) o;
boolean iHaveNull = hasNull();
// Either we both have a null, or our SSIDs and BSSIDs are equal
return (iHaveNull && otherAp.hasNull()) ||
(otherAp.bssid != null && ssid.equals(otherAp.ssid)
&& bssid.equals(otherAp.bssid));
}
@Override
public int hashCode() {
if (ssid == null || bssid == null) return 0;
return ssid.hashCode() + bssid.hashCode();
}
@Override
public String toString() {
return ssid + " (" + bssid + ")";
}
}
/**
* Performs a simple DNS "ping" by sending a "server status" query packet to
* the DNS server. As long as the server replies, we consider it a success.
*
* We do not use a simple hostname lookup because that could be cached and
* the API may not differentiate between a time out and a failure lookup
* (which we really care about).
*/
private static class DnsPinger {
/** Number of bytes for the query */
private static final int DNS_QUERY_BASE_SIZE = 33;
/** The DNS port */
private static final int DNS_PORT = 53;
/** Used to generate IDs */
private static Random sRandom = new Random();
static boolean isDnsReachable(int dns, int timeout) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
// Set some socket properties
socket.setSoTimeout(timeout);
byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
fillQuery(buf);
// Send the DNS query
byte parts[] = new byte[4];
parts[0] = (byte)(dns & 0xff);
parts[1] = (byte)((dns >> 8) & 0xff);
parts[2] = (byte)((dns >> 16) & 0xff);
parts[3] = (byte)((dns >> 24) & 0xff);
InetAddress dnsAddress = InetAddress.getByAddress(parts);
DatagramPacket packet = new DatagramPacket(buf,
buf.length, dnsAddress, DNS_PORT);
socket.send(packet);
// Wait for reply (blocks for the above timeout)
DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
socket.receive(replyPacket);
// If a timeout occurred, an exception would have been thrown. We got a reply!
return true;
} catch (SocketException e) {
if (V) {
Slog.v(TAG, "DnsPinger.isReachable received SocketException", e);
}
return false;
} catch (UnknownHostException e) {
if (V) {
Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e);
}
return false;
} catch (SocketTimeoutException e) {
return false;
} catch (IOException e) {
if (V) {
Slog.v(TAG, "DnsPinger.isReachable got an IOException", e);
}
return false;
} catch (Exception e) {
if (V || Config.LOGD) {
Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e);
}
return false;
} finally {
if (socket != null) {
socket.close();
}
}
}
private static void fillQuery(byte[] buf) {
/*
* See RFC2929 (though the bit tables in there are misleading for
* us. For example, the recursion desired bit is the 0th bit for us,
* but looking there it would appear as the 7th bit of the byte
*/
// Make sure it's all zeroed out
for (int i = 0; i < buf.length; i++) buf[i] = 0;
// Form a query for www.android.com
// [0-1] bytes are an ID, generate random ID for this query
buf[0] = (byte) sRandom.nextInt(256);
buf[1] = (byte) sRandom.nextInt(256);
// [2-3] bytes are for flags.
buf[2] = 1; // Recursion desired
// [4-5] bytes are for the query count
buf[5] = 1; // One query
// [6-7] [8-9] [10-11] are all counts of other fields we don't use
// [12-15] for www
writeString(buf, 12, "www");
// [16-23] for android
writeString(buf, 16, "android");
// [24-27] for com
writeString(buf, 24, "com");
// [29-30] bytes are for QTYPE, set to 1
buf[30] = 1;
// [31-32] bytes are for QCLASS, set to 1
buf[32] = 1;
}
private static void writeString(byte[] buf, int startPos, String string) {
int pos = startPos;
// Write the length first
buf[pos++] = (byte) string.length();
for (int i = 0; i < string.length(); i++) {
buf[pos++] = (byte) string.charAt(i);
}
}
}
}