DnsPinger.java revision 79e43f679d6102066ee9eff862912806f53bb0e8
1/* 2 * Copyright (C) 2011 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; 18 19import android.content.Context; 20import android.os.Handler; 21import android.os.Looper; 22import android.os.Message; 23import android.os.SystemClock; 24import android.provider.Settings; 25import android.util.Slog; 26 27import com.android.internal.util.Protocol; 28 29import java.io.IOException; 30import java.net.DatagramPacket; 31import java.net.DatagramSocket; 32import java.net.InetAddress; 33import java.net.NetworkInterface; 34import java.net.SocketTimeoutException; 35import java.util.ArrayList; 36import java.util.Collection; 37import java.util.Iterator; 38import java.util.List; 39import java.util.Random; 40import java.util.concurrent.atomic.AtomicInteger; 41 42/** 43 * Performs a simple DNS "ping" by sending a "server status" query packet to the 44 * DNS server. As long as the server replies, we consider it a success. 45 * <p> 46 * We do not use a simple hostname lookup because that could be cached and the 47 * API may not differentiate between a time out and a failure lookup (which we 48 * really care about). 49 * <p> 50 * 51 * @hide 52 */ 53public final class DnsPinger extends Handler { 54 private static final boolean V = true; 55 56 private static final int RECEIVE_POLL_INTERVAL_MS = 30; 57 private static final int DNS_PORT = 53; 58 59 /** Short socket timeout so we don't block one any 'receive' call */ 60 private static final int SOCKET_TIMEOUT_MS = 1; 61 62 /** Used to generate IDs */ 63 private static final Random sRandom = new Random(); 64 private static final AtomicInteger sCounter = new AtomicInteger(); 65 66 private ConnectivityManager mConnectivityManager = null; 67 private final Context mContext; 68 private final int mConnectionType; 69 private final Handler mTarget; 70 private final ArrayList<InetAddress> mDefaultDns; 71 private String TAG; 72 73 private static final int BASE = Protocol.BASE_DNS_PINGER; 74 75 /** 76 * Async response packet for dns pings. 77 * arg1 is the ID of the ping, also returned by {@link #pingDnsAsync(InetAddress, int, int)} 78 * arg2 is the delay, or is negative on error. 79 */ 80 public static final int DNS_PING_RESULT = BASE; 81 /** An error code for a {@link #DNS_PING_RESULT} packet */ 82 public static final int TIMEOUT = -1; 83 /** An error code for a {@link #DNS_PING_RESULT} packet */ 84 public static final int SOCKET_EXCEPTION = -2; 85 86 /** 87 * Send a new ping via a socket. arg1 is ID, arg2 is timeout, obj is InetAddress to ping 88 */ 89 private static final int ACTION_PING_DNS = BASE + 1; 90 private static final int ACTION_LISTEN_FOR_RESPONSE = BASE + 2; 91 private static final int ACTION_CANCEL_ALL_PINGS = BASE + 3; 92 93 private List<ActivePing> mActivePings = new ArrayList<ActivePing>(); 94 private int mEventCounter; 95 96 private class ActivePing { 97 DatagramSocket socket; 98 int internalId; 99 short packetId; 100 int timeout; 101 Integer result; 102 long start = SystemClock.elapsedRealtime(); 103 } 104 105 public DnsPinger(Context context, String TAG, Looper looper, 106 Handler target, int connectionType) { 107 super(looper); 108 this.TAG = TAG; 109 mContext = context; 110 mTarget = target; 111 mConnectionType = connectionType; 112 if (!ConnectivityManager.isNetworkTypeValid(connectionType)) { 113 throw new IllegalArgumentException("Invalid connectionType in constructor: " 114 + connectionType); 115 } 116 mDefaultDns = new ArrayList<InetAddress>(); 117 mDefaultDns.add(getDefaultDns()); 118 mEventCounter = 0; 119 } 120 121 @Override 122 public void handleMessage(Message msg) { 123 switch (msg.what) { 124 case ACTION_PING_DNS: 125 try { 126 ActivePing newActivePing = new ActivePing(); 127 InetAddress dnsAddress = (InetAddress) msg.obj; 128 newActivePing.internalId = msg.arg1; 129 newActivePing.timeout = msg.arg2; 130 newActivePing.socket = new DatagramSocket(); 131 // Set some socket properties 132 newActivePing.socket.setSoTimeout(SOCKET_TIMEOUT_MS); 133 134 // Try to bind but continue ping if bind fails 135 try { 136 newActivePing.socket.setNetworkInterface(NetworkInterface.getByName( 137 getCurrentLinkProperties().getInterfaceName())); 138 } catch (Exception e) { 139 Slog.w(TAG,"sendDnsPing::Error binding to socket", e); 140 } 141 142 newActivePing.packetId = (short) sRandom.nextInt(); 143 byte[] buf = mDnsQuery.clone(); 144 buf[0] = (byte) (newActivePing.packetId >> 8); 145 buf[1] = (byte) newActivePing.packetId; 146 147 // Send the DNS query 148 DatagramPacket packet = new DatagramPacket(buf, 149 buf.length, dnsAddress, DNS_PORT); 150 if (V) { 151 Slog.v(TAG, "Sending a ping " + newActivePing.internalId + 152 " to " + dnsAddress.getHostAddress() 153 + " with packetId " + newActivePing.packetId + "."); 154 } 155 156 newActivePing.socket.send(packet); 157 mActivePings.add(newActivePing); 158 mEventCounter++; 159 sendMessageDelayed(obtainMessage(ACTION_LISTEN_FOR_RESPONSE, mEventCounter, 0), 160 RECEIVE_POLL_INTERVAL_MS); 161 } catch (IOException e) { 162 sendResponse(msg.arg1, -9999, SOCKET_EXCEPTION); 163 } 164 break; 165 case ACTION_LISTEN_FOR_RESPONSE: 166 if (msg.arg1 != mEventCounter) { 167 break; 168 } 169 for (ActivePing curPing : mActivePings) { 170 try { 171 /** Each socket will block for {@link #SOCKET_TIMEOUT_MS} in receive() */ 172 byte[] responseBuf = new byte[2]; 173 DatagramPacket replyPacket = new DatagramPacket(responseBuf, 2); 174 curPing.socket.receive(replyPacket); 175 // Check that ID field matches (we're throwing out the rest of the packet) 176 if (responseBuf[0] == (byte) (curPing.packetId >> 8) && 177 responseBuf[1] == (byte) curPing.packetId) { 178 curPing.result = 179 (int) (SystemClock.elapsedRealtime() - curPing.start); 180 } else { 181 if (V) { 182 Slog.v(TAG, "response ID didn't match, ignoring packet"); 183 } 184 } 185 } catch (SocketTimeoutException e) { 186 // A timeout here doesn't mean anything - squelsh this exception 187 } catch (Exception e) { 188 if (V) { 189 Slog.v(TAG, "DnsPinger.pingDns got socket exception: ", e); 190 } 191 curPing.result = SOCKET_EXCEPTION; 192 } 193 } 194 Iterator<ActivePing> iter = mActivePings.iterator(); 195 while (iter.hasNext()) { 196 ActivePing curPing = iter.next(); 197 if (curPing.result != null) { 198 sendResponse(curPing.internalId, curPing.packetId, curPing.result); 199 curPing.socket.close(); 200 iter.remove(); 201 } else if (SystemClock.elapsedRealtime() > 202 curPing.start + curPing.timeout) { 203 sendResponse(curPing.internalId, curPing.packetId, TIMEOUT); 204 curPing.socket.close(); 205 iter.remove(); 206 } 207 } 208 if (!mActivePings.isEmpty()) { 209 sendMessageDelayed(obtainMessage(ACTION_LISTEN_FOR_RESPONSE, mEventCounter, 0), 210 RECEIVE_POLL_INTERVAL_MS); 211 } 212 break; 213 case ACTION_CANCEL_ALL_PINGS: 214 for (ActivePing activePing : mActivePings) 215 activePing.socket.close(); 216 mActivePings.clear(); 217 break; 218 } 219 } 220 221 /** 222 * Returns a list of DNS addresses, coming from either the link properties of the 223 * specified connection or the default system DNS if the link properties has no dnses. 224 * @return a non-empty non-null list 225 */ 226 public List<InetAddress> getDnsList() { 227 LinkProperties curLinkProps = getCurrentLinkProperties(); 228 if (curLinkProps == null) { 229 Slog.e(TAG, "getCurLinkProperties:: LP for type" + mConnectionType + " is null!"); 230 return mDefaultDns; 231 } 232 233 Collection<InetAddress> dnses = curLinkProps.getDnses(); 234 if (dnses == null || dnses.size() == 0) { 235 Slog.v(TAG, "getDns::LinkProps has null dns - returning default"); 236 return mDefaultDns; 237 } 238 239 return new ArrayList<InetAddress>(dnses); 240 } 241 242 /** 243 * Send a ping. The response will come via a {@link #DNS_PING_RESULT} to the handler 244 * specified at creation. 245 * @param dns address of dns server to ping 246 * @param timeout timeout for ping 247 * @return an ID field, which will also be included in the {@link #DNS_PING_RESULT} message. 248 */ 249 public int pingDnsAsync(InetAddress dns, int timeout, int delay) { 250 int id = sCounter.incrementAndGet(); 251 sendMessageDelayed(obtainMessage(ACTION_PING_DNS, id, timeout, dns), delay); 252 return id; 253 } 254 255 public void cancelPings() { 256 obtainMessage(ACTION_CANCEL_ALL_PINGS).sendToTarget(); 257 } 258 259 private void sendResponse(int internalId, int externalId, int responseVal) { 260 if(V) { 261 Slog.d(TAG, "Responding to packet " + internalId + 262 " externalId " + externalId + 263 " and val " + responseVal); 264 } 265 mTarget.sendMessage(obtainMessage(DNS_PING_RESULT, internalId, responseVal)); 266 } 267 268 private LinkProperties getCurrentLinkProperties() { 269 if (mConnectivityManager == null) { 270 mConnectivityManager = (ConnectivityManager) mContext.getSystemService( 271 Context.CONNECTIVITY_SERVICE); 272 } 273 274 return mConnectivityManager.getLinkProperties(mConnectionType); 275 } 276 277 private InetAddress getDefaultDns() { 278 String dns = Settings.Secure.getString(mContext.getContentResolver(), 279 Settings.Secure.DEFAULT_DNS_SERVER); 280 if (dns == null || dns.length() == 0) { 281 dns = mContext.getResources().getString( 282 com.android.internal.R.string.config_default_dns_server); 283 } 284 try { 285 return NetworkUtils.numericToInetAddress(dns); 286 } catch (IllegalArgumentException e) { 287 Slog.w(TAG, "getDefaultDns::malformed default dns address"); 288 return null; 289 } 290 } 291 292 private static final byte[] mDnsQuery = new byte[] { 293 0, 0, // [0-1] is for ID (will set each time) 294 1, 0, // [2-3] are flags. Set byte[2] = 1 for recursion desired (RD) on. Currently on. 295 0, 1, // [4-5] bytes are for number of queries (QCOUNT) 296 0, 0, // [6-7] unused count field for dns response packets 297 0, 0, // [8-9] unused count field for dns response packets 298 0, 0, // [10-11] unused count field for dns response packets 299 3, 'w', 'w', 'w', 300 6, 'g', 'o', 'o', 'g', 'l', 'e', 301 3, 'c', 'o', 'm', 302 0, // null terminator of address (also called empty TLD) 303 0, 1, // QTYPE, set to 1 = A (host address) 304 0, 1 // QCLASS, set to 1 = IN (internet) 305 }; 306} 307