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